vb.netreal-timeconsole-redirect

Realtime console redirection issue


First of all, I did spend quite some time trying to find an answer to my issue but so far, nothing.

Here is the issue. I have to run a cli command with arguments and redirect the output (REALTIME) to a textbox. The command will look like this: smcli - n name -f "script.scr"

Since smcli is not in the system PATH variable, I first need to go to the right Dir

So, I end up running the full command that looks like this: cmd /c cd "c:\Program Files(x86)\bla\" && smcli - n name -f "script.scr"

I'll put the code in one sec but basically, the thing is working just fine, I get to the correct directory, run the command, it runs the script, all good.

I tried to redirect the output to a different form but then it doen't work anymore at all; anyway, that would be the bonus question.

The thing is though, when the script runs, it does say step by step what it is doing and sends that info to the output (i.e. restoring the name, restoring the disk config, ...)

As I said, I do get all those outputs....once EVERYTHING is completed (i.e. after 5 minutes of apparent freeze). NOT redirecting the output to the textbox (i.e. leaving the console window open), I get the output real time.

So here is the code now (there is a bit of it, sorry):

Private Sub ReadMDcfgToolStripButton_Click(sender As Object, e As EventArgs) Handles ReadMDcfgToolStripButton.Click
        Dim exitcode As Int32
        smcli = Environment.GetEnvironmentVariable("ProgramFiles") & "\Dell\MD Storage Manager\client\" 'SMcli.exe"
        Dim gotosmclidir As String = "cd """ & smcli & """"
        Dim smclicommand As String
            smclicommand = "smcli -n " & mdname & " -c ""save storagearray configuration file=\""" & saveFileDialog2.FileName & "\"" allconfig"";"
            cmd = gotosmclidir & " && " & smclicommand
            exitcode = RunCommandCom(cmd, "", False)
End Sub

Private Function RunCommandCom(command As String, arguments As String, permanent As Boolean) As Int32
    ' Usage:
    'RunCommandCom("DIR", "/W", true)
    'For the multiple command on one line the key are the & | && and || command connectors
    '•A & B -> execute command A, then execute command B. 
    '•A | B -> execute command A, and redirect all it's output into the input of command B.
    '•A && B -> execute command A, evaluate the errorlevel after running Command A, and if the exit code (errorlevel) is 0, only then execute command B.
    '•A || B -> execute Command A, evalutate the exit code of this command and if it's anything but 0, only then execute command B.

    TextBox1.Text = "Communication in progress..." & vbCrLf
    exitcodelbl.Text = ""
    CmdCloseBtn.Enabled = False
    ToolStrip1.Enabled = False
    CmdOutputPanel.Visible = True

    Dim p As Process = New Process()
    Dim pi As ProcessStartInfo = New ProcessStartInfo()


    pi.FileName = "cmd.exe"
    pi.Arguments = " " + If(permanent = True, "/K", "/C") + " " + command + " " + arguments
    MsgBox(pi.Arguments)
    pi.CreateNoWindow = True
    pi.UseShellExecute = False
    pi.RedirectStandardOutput = True
    pi.RedirectStandardError = True
    pi.CreateNoWindow = True
    p.StartInfo = pi
    AddHandler p.OutputDataReceived, AddressOf GotData
    p.Start()
    p.BeginOutputReadLine()

    Do Until p.HasExited
    Loop

    exitcodelbl.Text = p.ExitCode
    Select Case p.ExitCode
        Case 0
            exitcodelbl.Text += " - Command completed successfully."
        Case 1
            exitcodelbl.Text += " - Could not communicate with the Array."
        Case 9
            exitcodelbl.Text += " - Could not communicate with the Array."
        Case 14
            exitcodelbl.Text += " - Could not communicate with the Array."
        Case Else
            exitcodelbl.Text += " - Unknown code."
    End Select
    Return p.ExitCode
End Function

Sub GotData(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
    UpdateTextBox(e.Data)
End Sub

Private Delegate Sub UpdateTextBoxDelegate(ByVal Text As String)

Private Sub UpdateTextBox(ByVal Tex As String)
    If Me.InvokeRequired Then
        Dim del As New UpdateTextBoxDelegate(AddressOf UpdateTextBox)
        Dim args As Object() = {Tex}
        Me.Invoke(del, args)
    Else
        TextBox1.Text &= Tex & Environment.NewLine
    End If
End Sub

Apart from the command itself, everything else is comming from researches and they are just put together, the way to run the command on one hand and the redirection on the other hand.

I believe the issue is with my command (looks like that part is VERY sensible).

Either I can have it done a different way (again, it works but not real time) or I missed something trivial...?

Thanks for your help.

Following tinstaafl here is the updated code:

Private Sub MainForm_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
    copyrightlbl.Focus()
    wait(1000)
    Await RunCommandCom("", "192.168.219.152", False) '172.16.1.55
    CmdCloseBtn.Enabled = True
End Sub

Private Async Function RunCommandCom(command As String, arguments As String, permanent As Boolean) As Task(Of Int32)
    TextBox1.Text = "Communication in progress..." & vbCrLf
    exitcodelbl.Text = ""
    CmdCloseBtn.Enabled = False
    ToolStrip1.Enabled = False
    CmdOutputPanel.Visible = True

    Dim p As Process = New Process()
    With p.StartInfo
        .WorkingDirectory = Environment.GetEnvironmentVariable("ProgramFiles") & "\Dell\MD Storage Manager\client\" 'Directory for SMcli.exe"
        .FileName = "ping.exe"
        .Arguments = command + " " + arguments
        .CreateNoWindow = True
        .UseShellExecute = False
        .RedirectStandardOutput = True
        .RedirectStandardError = True
    End With

    p.Start()

    Do Until p.HasExited
        TextBox1.Text &= Await p.StandardOutput.ReadLineAsync
    Loop
    exitcodelbl.Text = p.ExitCode
    Return p.ExitCode
End Function

Solution

  • Instead of using a delegate try reading the output directly to the textbox. The StandardOutput property of the Process is a stream that you can use the ReadLineAsync method to get the output. Something like this should work:

    pi.CreateNoWindow = True
    pi.UseShellExecute = False
    pi.RedirectStandardOutput = True
    pi.RedirectStandardError = True
    p.StartInfo = pi
    p.Start()
    
    Do Until p.HasExited
       TextBox1.Text &= Await p.StandardOutput.ReadLineAsync
    Loop
    

    Intellisense will prompt you to make the sub routine Async, but with that you should get the output you want.

    Private Async Function RunCommandCom(command As String, arguments As String, permanent As Boolean) As Task(Of Int32)
    
    
    Await RunCommandCom("", "192.168.219.152", False)