0

I have asked a similar question elsewhere but perhaps I did not ask it the right way or I was not clear enough so I am asking again.

This is where I want to get:

  1. Open a windows command prompt
  2. Run a DOS application through a dos command
  3. Read the returned text that is shown in the dos box and show it in a text box in my windows form. This needs to be repeated at regular intervals (say, every second) and the dos box should not be closed.

I have been going round in circles trying to use the Process and StartInfo commands, but they only run the application and close the process right away. I need to keep the dos box open and keep reading any new text that is added to it by the dos application. I also came across this thread that seems to answer my problem, but it is to in C# and I could not convert it:

Read Windows Command Prompt STDOUT

I did get to the part where I open the command prompt and get the application started, but I don't know how to read the data that it returns to the dos box console every now and then. I want to constantly check for changes so that I can act on them, perhaps using a timer control.

Please help.

Thanks!

I ran the code that was kindly provided by Stevedog and used it like this:

  Private WithEvents _commandExecutor As New CommandExecutor()

  Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    _commandExecutor.Execute("c:\progra~2\zbar\bin\zbarcam.exe", "")
  End Sub

  Private Sub _commandExecutor_OutputRead(ByVal output As String) Handles _commandExecutor.OutputRead
    txtResult.Text = output
  End Sub

But all I am getting is blank dos box. The zbarcam application runs properly because I can see the camera preview and I can also see it detecting QR Codes, but the text is not showing in the dos box and _commandExecutor_OutputRead sub is not being triggered unless I close the DOS box.

Community
  • 1
  • 1
Osprey
  • 1,523
  • 8
  • 27
  • 44
  • That snippet of code should be pretty easy to convert. If you can't do it, there are some online utilities to help. Google c sharp to vb conversion and there are plenty of options. And if you are a .net programmer, you should be aware that you have the ability to use code from different languages in the same solution. – GrayFox374 Jul 16 '12 at 13:38
  • Thanks GrayFox374. Yes I had already tried a C# to VB.Net converter but I was getting an error in line 1. I also tried converting the code manually (I am slightly familiar with C#) but I could not convert "proc.OutputDataReceived" since that property does not seem to exist (it doesn't even come up with intellisense). – Osprey Jul 16 '12 at 13:56
  • @Osprey see my answer for a conversion to VB.Net – sloth Jul 16 '12 at 14:05
  • Do you need your program to pick up on a console window started elsewhere, or is it permissible for your program to start the console? If the latter and if this needs to happen repeatedly, is it permissible for the console window to close, if your app can just re-open it again? – Joel Coehoorn Jul 16 '12 at 14:07
  • Yes, the console is opened by my application, but it cannot be closed and opened multiple times since the DOS application takes about 20 seconds to start working. The application in question is zbarcam, which after being invoked in the DOS prompt, opens yet another window with a preview of what the webcam is seeing, and then start displaying the decoded text of the QR codes inside the initial command prompt box. I need to read the text in the dos box at regular intervals to check for any new QR codes detected by the web cam. – Osprey Jul 16 '12 at 17:10
  • Please clarify: are you actually talking about a DOS (i.e., 8-bit, 10+ years old) application? Or do you just mean a command-line application? DOS applications run in an emulation environment that may complicate matters. – Harry Johnston Jul 16 '12 at 20:37

2 Answers2

5

That C# example is bad because it doesn't show how to actually read the standard output stream. Create a method like this:

Public Function ExecuteCommand(ByVal filePath As String, ByVal arguments As String) As String
    Dim p As Process
    p = New Process()
    p.StartInfo.FileName = filePath
    p.StartInfo.UseShellExecute = False
    p.StartInfo.RedirectStandardInput = True
    p.StartInfo.RedirectStandardOutput = True
    p.Start()
    p.WaitForExit()
    Return p.StandardOutput.ReadToEnd()
End Function

Then you can call it like this:

Dim output As String = ExecuteCommand("mycommand.exe", "")

Of course this makes a synchronous call. If you want it to call the command line asynchronously and just raise an event when it's complete, that is possible too, but would require a little more coding.

If you want to do it asynchronously and just keep periodically checking for more output on a fixed interval, for instance, here's a simple example:

Public Class CommandExecutor
    Implements IDisposable

    Public Event OutputRead(ByVal output As String)

    Private WithEvents _process As Process

    Public Sub Execute(ByVal filePath As String, ByVal arguments As String)
        If _process IsNot Nothing Then
            Throw New Exception("Already watching process")
        End If
        _process = New Process()
        _process.StartInfo.FileName = filePath
        _process.StartInfo.UseShellExecute = False
        _process.StartInfo.RedirectStandardInput = True
        _process.StartInfo.RedirectStandardOutput = True
        _process.Start()
        _process.BeginOutputReadLine()
    End Sub

    Private Sub _process_OutputDataReceived(ByVal sender As Object, ByVal e As System.Diagnostics.DataReceivedEventArgs) Handles _process.OutputDataReceived
        If _process.HasExited Then
            _process.Dispose()
            _process = Nothing
        End If
        RaiseEvent OutputRead(e.Data)
    End Sub

    Private disposedValue As Boolean = False
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                If _process IsNot Nothing Then
                    _process.Kill()
                    _process.Dispose()
                    _process = Nothing
                End If
            End If
        End If
        Me.disposedValue = True
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
End Class

Then you could use it like this:

Public Class Form1
    Private WithEvents _commandExecutor As New CommandExecutor()

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        _commandExecutor.Execute("D:\Sandbox\SandboxSolution\ConsoleCs\bin\Debug\ConsoleCs.exe", "")
    End Sub

    Private Sub _commandExecutor_OutputRead(ByVal output As String) Handles _commandExecutor.OutputRead
        Me.Invoke(New processCommandOutputDelegate(AddressOf processCommandOutput), output)
    End Sub

    Private Delegate Sub processCommandOutputDelegate(ByVal output As String)
    Private Sub processCommandOutput(ByVal output As String)
        TextBox1.Text = output
    End Sub

    Private Sub Form1_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        _commandExecutor.Dispose()
    End Sub
End Class
Steven Doggart
  • 43,358
  • 8
  • 68
  • 105
  • I'm afraid I need the asynchronous version for what I am after. The dos box needs to remain in the background and I need to read its contents at regular intervals. I've been looking for examples of how to do that in VB.net but had no luck. – Osprey Jul 16 '12 at 17:47
  • Thanks. I tried running the code but unfortunately I got an error of the type `Object reference not set to an instance of an object` on the line `Dim output As String = _output.ReadToEnd()`. And how do I get the output into a textbox in my main form? – Osprey Jul 16 '12 at 19:10
  • Oops. I fixed the bug and added an example of how you could use it. – Steven Doggart Jul 16 '12 at 19:52
  • I posted an update to my original question with a reference to your code. The output event is not being triggered every second. Instead, it is triggered when I close the dos box, but it finds nothing to output and throws an error of type `System.NullReferenceException was unhandled by user code` – Osprey Jul 16 '12 at 20:16
  • 1
    Sorry about that. I should have tested the code first. This time the update definitely works :) – Steven Doggart Jul 17 '12 at 13:14
2

Those converters usually fail when it comes to events.


A direct translation of that code would look like:

Dim proc as Process

Sub RunApp
    proc = new Process()
    proc.StartInfo.FileName = "your_app.exe"
    proc.StartInfo.Arguments = ""
    proc.StartInfo.UseShellExecute = false
    proc.StartInfo.RedirectStandardInput = true
    proc.StartInfo.RedirectStandardOutput = true
    AddHandler proc.OutputDataReceived, AddressOf InterProcOutputHandler
    proc.Start()
    proc.WaitForExit()
End Sub

Sub InterProcOutputHandler(sendingProcess as object, outLine as DataReceivedEventArgs)
    'Read data here

    'Send command if necessary
    proc.StandardInput.WriteLine("your_command")
End Sub

proc.OutputDataReceived is not a property, but an event.

To subscribe to events, in C# you use event += handler, while in VB.NET you use AddHandler event, AddressOf handler.

Community
  • 1
  • 1
sloth
  • 99,095
  • 21
  • 171
  • 219
  • I had to run to a meeting, and then my daily walk, but I was thinking of this while I was afk, your code looks very similar to what I was tinkering with. The only part I was unfamiliar with was the event raising, but I did some research at MSDN, and instead of AddHandler, I used RaiseEvent http://msdn.microsoft.com/en-us/library/fwd3bwed(v=vs.71).aspx. I miss VB! – GrayFox374 Jul 16 '12 at 17:40
  • Thanks for the translation and explanation. So far the most helpful answer! I ran the code and managed to load a dos box and the webcam preview window also loads. However, the dos box is totally black and although I can see that the webcam is reading the QR Codes (it shows a green square around the QR code), the results are not getting to the dos box. Besides, the `InterProcOutputHandler` does not seem to be firing whenever a correct code is detected. I feel I am this close to the solution, but am missing probably a single line of code that makes the difference! – Osprey Jul 16 '12 at 17:41