Vb.NET Start Process and return output - vb.net

I'm trying to execute some command line commands (putty). Now it may be that the command line waits for an input (simplest example: password maybe wrong). All the output should be written into Console.
My code displays a non-ending command line that does not write anything to the console.
Imports System.Text
Public Class Form1
Private Shared processOutput As StringBuilder = Nothing
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim line As String = "/c plink -ssh chef#192.168.178.35 -pw 12345678 -m C:\putty\pw\putty.txt"
processOutput = New StringBuilder()
Dim objP As New System.Diagnostics.Process()
Dim objPi As ProcessStartInfo = New ProcessStartInfo()
With objPi
.FileName = "cmd.exe"
.Arguments = line
.RedirectStandardOutput = True
.RedirectStandardError = True
.RedirectStandardInput = True
.UseShellExecute = False
.WindowStyle = ProcessWindowStyle.Hidden
.CreateNoWindow = False
End With
objP.StartInfo = objPi
AddHandler objP.OutputDataReceived, AddressOf OutputHandler
objP.Start()
objP.BeginOutputReadLine()
objP.WaitForExit(1000)
Debug.WriteLine(processOutput.ToString())
End Sub
Private Shared Sub OutputHandler(sendingProcess As Object, outLine As DataReceivedEventArgs)
If Not String.IsNullOrEmpty(outLine.Data) Then
processOutput.AppendLine(outLine.Data)
End If
End Sub
End Class

As Stephen mentioned, your are not printing anything to the console, only writing to the StringBuilder object. So you have to print the content of StringBuilder object to the console to actually see a result:
Console.WriteLine(processOutput)
MSDN example see:
https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.outputdatareceived?view=netframework-4.7.2

it's because you're not writing anything to console or the UI. Debug.write is not the same as console.write
Additionally, your delegate function may be returning after 1 second. A better paradigm would use await.
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Call the method that runs asynchronously.
Dim result As String = Await WaitAsynchronouslyAsync()
' Call the method that runs synchronously.
'Dim result As String = Await WaitSynchronously()
' Display the result.
TextBox1.Text &= result
End Sub
' The following method runs asynchronously. The UI thread is not
' blocked during the delay. You can move or resize the Form1 window
' while Task.Delay is running.
Public Async Function WaitAsynchronouslyAsync() As Task(Of String)
Await Task.Delay(10000)
Return "Finished"
End Function
' The following method runs synchronously, despite the use of Async.
' You cannot move or resize the Form1 window while Thread.Sleep
' is running because the UI thread is blocked.
Public Async Function WaitSynchronously() As Task(Of String)
' Import System.Threading for the Sleep method.
Thread.Sleep(10000)
Return "Finished"
End Function
https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/operators/await-operator

Related

Wait until download is complete before starting other tasks

I am trying to download a file, and then run some adb commands. However when downloading, i could not get it to update the progress bar using
downloadFile(url,filename)` command.
A bit of searching it said that the command was blocking the UI thread, so i decided to use Task.Run() (a solution to my previous post to run ADB Commands when it blocked the UIThread).
This made no difference. Another solution that i found is to use
downloadFileAsync(url, filename)
The progress bar is updating!
But, the ADB commands are running before the file is downloaded! They are declared after, but they are still being run before the file is downloaded, which I don't want.
Here is the code:
Private Sub btnFlashRecovery_Click(sender As Object, e As EventArgs) Handles btnFlashRecovery.Click
'Insert ommited Code here (removed to simplify question)
'id is variable obtained from a previous code that was ommited here
Dim fileName As String = "downloads/twrp-" & id & ".img"
DownloadFile(url, fileName)
'run the right commands
LabelToOutput = txtBoxRecovery
Dim commands(3, 3) As String
commands = {{"adb", "reboot bootloader", "Rebooting to bootloader"},
{"fastboot", "flash recovery" & "downloads/twrp-3.1.1-0.img", "Flashing recovery: (make sure device is plugged, otherwise it will not output anything)"},
{"fastboot", "reboot", "Rebooting device"}
}
'Task to run after
Task.Run(Sub() runComands(commands))
End Sub
Private Sub UpdateProgressBar(ByVal a As Integer)
If Me.InvokeRequired Then
Dim args() As String = {a}
Me.Invoke(New Action(Of String)(AddressOf UpdateProgressBar), args)
Return
End If
ProgressBar1.Value = CInt(a)
End Sub
Public Sub DownloadFile(urlAddress As String, location As String)
Using webClient = New WebClient()
AddHandler webClient.DownloadFileCompleted, AddressOf Completed
AddHandler webClient.DownloadProgressChanged, AddressOf ProgressChanged
Try
' Start downloading the file
webClient.DownloadFileAsync(New Uri(urlAddress), location)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Using
End Sub
' The event that will fire whenever the progress of the WebClient is changed
Private Sub ProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs)
'Console.WriteLine(e.ProgressPercentage)
' Update the progressbar percentage only when the value is not the same.
UpdateProgressBar(e.ProgressPercentage)
End Sub
' The event that will trigger when the WebClient is completed
Private Sub Completed(sender As Object, e As AsyncCompletedEventArgs)
If e.Cancelled = True Then
MessageBox.Show("Download has been canceled.")
Else
MessageBox.Show("Download completed!")
End If
End Sub
Do it like this:
'Need Async keyword with the method
Private Async Sub btnFlashRecovery_Click(sender As Object, e As EventArgs) Handles btnFlashRecovery.Click
'Insert ommited Code here (removed to simplify question)
'id is variable obtained from a previous code that was ommited here
Dim fileName As String = "downloads/twrp-" & id & ".img"
'You need to AWAIT the result of the task
Await Task.Run(Sub() DownloadFile(url, fileName))
'run the right commands
LabelToOutput = txtBoxRecovery
Dim commands(3, 3) As String
commands = { {"adb", "reboot bootloader", "Rebooting to bootloader"},
{"fastboot", "flash recovery" & "downloads/twrp-3.1.1-0.img", "Flashing recovery: (make sure device is plugged, otherwise it will not output anything)"},
{"fastboot", "reboot", "Rebooting device"}
}
'Task to run after
'Await here, too, to allow the UI to remain responsive
Await Task.Run(Sub() runComands(commands))
End Sub

Cannot update textbox vb.net -crossthreading

I am trying to make an application to run multiple (adb specially) commands and get the output and display in a label.
First of all, i need to start the process to execute the commands. Thanks to stackoverflow and #pasty I found this (second reply): How to get Output of a Command Prompt Window line by line in Visual Basic?
Well, i thought that because it outputted to the console, it would be simple to just write it to the label. BIG MISTAKE! It gives me a cross threading error!
A little bit of googling and stack overflow I found this: vb.net accessed from a thread other than the thread it was created on
Well, i tried to implement that, but the program just crashes freezes.
Here is the code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' store error output lines
Dim executable As String() = {"adb", "adb"}
Dim arguments As String() = {"help", "reboot"}
For i As Integer = 0 To 0
Dim process = New Process()
process.StartInfo = createStartInfo(executable(i), arguments(i))
process.EnableRaisingEvents = True
AddHandler process.Exited, Sub(ByVal sendera As Object, ByVal ea As System.EventArgs)
Console.WriteLine(process.ExitTime)
Console.WriteLine(". Processing done.")
'UpdateTextBox(ea)
End Sub
' catch standard output
AddHandler process.OutputDataReceived, Sub(ByVal senderb As Object, ByVal eb As DataReceivedEventArgs)
If (Not String.IsNullOrEmpty(eb.Data)) Then
Console.WriteLine(String.Format("{0}> {1}", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"), eb.Data))
'UpdateTextBox(eb.Data)
End If
End Sub
' catch errors
AddHandler process.ErrorDataReceived, Sub(ByVal senderc As Object, ByVal ec As DataReceivedEventArgs)
Console.WriteLine(String.Format("! {0}", ec.Data))
Dim a As String = String.Format("! {0}", ec.Data)
'UpdateTextBox(a)
End Sub
' start process
Dim result = process.Start()
' and wait for output
process.BeginOutputReadLine()
' and wait for errors :-)
process.BeginErrorReadLine()
process.WaitForExit()
Next
End Sub
Private Sub UpdateTextBox(ByVal a As String)
If Me.InvokeRequired Then
Dim args() As String = {a}
Me.Invoke(New Action(Of String)(AddressOf UpdateTextBox), args)
Return
End If
Label1.Text += "a"
End Sub
Private Function createStartInfo(ByVal executable As String, ByVal arguments As String) As ProcessStartInfo
Dim processStartInfo = New ProcessStartInfo(executable, arguments)
processStartInfo.WorkingDirectory = Path.GetDirectoryName(executable)
' we want to read standard output
processStartInfo.RedirectStandardOutput = True
' we want to read the standard error
processStartInfo.RedirectStandardError = True
processStartInfo.UseShellExecute = False
processStartInfo.ErrorDialog = False
processStartInfo.CreateNoWindow = True
Return processStartInfo
End Function
And the source code: https://github.com/iAmAOpenSource/SyncfusionWindowsFormsApplication3
The call to process.WaitForExit() will block the UI thread until the spawned process exits, but while processing the output in the process.OutputDataReceived event you are calling Me.Invoke which tries to run the code on the UI thread, which is blocked, so the program freezes. You could move the logic in Button1_Click onto another thread, e.g.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Task.Run(
Sub()
... current logic
End Sub
)
End Sub
That way the UI thread won't be blocked and the Me.Invoke call won't cause a deadlock.

Reading Output from cmd asynchronously

I'm using this code:
Shared sb_OutputData As New StringBuilder()
Shared sb_ErrorData As New StringBuilder()
Shared proc As Process = Nothing
Private Sub cmd()
If proc IsNot Nothing Then
Exit Sub
End If
Dim info As New ProcessStartInfo("cmd.exe")
' Redirect the standard output of the process.
info.RedirectStandardOutput = True
info.RedirectStandardInput = True
info.RedirectStandardError = True
' Set UseShellExecute to false for redirection
info.UseShellExecute = False
proc = New Process
proc.StartInfo = info
' Set our event handler to asynchronously read the sort output.
AddHandler proc.OutputDataReceived, AddressOf proc_OutputDataReceived
AddHandler proc.ErrorDataReceived, AddressOf proc_ErrorDataReceived
proc.Start()
' Start the asynchronous read of the sort output stream. Note this line!
proc.BeginOutputReadLine()
proc.BeginErrorReadLine()
End Sub
Private Shared Sub proc_ErrorDataReceived(sender As Object, e As DataReceivedEventArgs)
'Console.WriteLine("Error data: {0}", e.Data)
sb_ErrorData.AppendLine(e.Data)
End Sub
Private Shared Sub proc_OutputDataReceived(sender As Object, e As DataReceivedEventArgs)
' Console.WriteLine("Output data: {0}", e.Data)
sb_OutputData.AppendLine(e.Data)
End Sub
Sub CmdWrite(arguments As String)
Dim writeStream As StreamWriter = proc.StandardInput
writeStream.WriteLine(arguments)
End Sub
It works exactly as I want, be able to retrieve cmd output and error data without closing it (and asynchronously), however, I'm not able to know when the command is finished executing. I'd like to know when it reaches the end of the stream for me to grab all the output and do something with it...
I've been searching for quite long, and can't find an answer to this.
Help please?
The stream is open is long as the command window is open. You can't tell when they stop or start. If the command you're running doesn't indicate its end with a unique/detectable pattern, you're stuck, unless you can edit the script you're running and insert a string - which you can't guarantee won't show in the normal output - in between the command calls.

User input on command prompt

I need to automate usage of a command line utility in VB.net. here is an example.
From the code, I need to decrypt a file using command line utility. Here is the command line procedure.
You start utility using this line
C:\gnupg>gpg --decrypt c:\temp\File_Encr.xml
Once executed, then it shows this
You need a passphrase to unlock the secret key for
user: "xxxx <abc#def.com>"
1024-bit ELG-E key, ID ABCD, created 2013-10-25 (main key ID DEF)
Enter passphrase:
and when you enter the passphrase, it do the job.
I need start this process from the code (VB.NET) and input passphrase as well so that it doesn't require any user interaction. My code will be used in Windows Services as well as Web application.
Can someone help on this please?
Thank you.
Sameers
Here is a code snippet I use.
All output code is written to the Visual Studio debug window for convenience. All program output all redirected to output handlers. This allows you to inspect the output coming out of the program in "real time". If you needed to watch the output lines and scan for a certain phrase and then perform an action, you could easily do this in Sub OutputReceivedHandler().
I tried to make it generic so you can see how it works:
Imports Microsoft.VisualBasic
Imports System.Diagnostics
Imports System
Public Class ExternalUtilities
Private myprocess As Process
Private SW As System.IO.StreamWriter
Private myprocess_HasError As Boolean = False
Private myprocess_ErrorMsg As String = ""
Private myprocess_Output As String = ""
Public Sub New()
' do init stuff here.
' maybe pass the executable path, or command line args.
End Sub
Public Sub launchUtilityProcess1()
Dim executeableFullPath As String = "C:\Path\To\file.exe"
' define the process
myprocess = New Process
myprocess.StartInfo.FileName = executeableFullPath
myprocess.StartInfo.RedirectStandardInput = True
myprocess.StartInfo.RedirectStandardOutput = True
myprocess.StartInfo.RedirectStandardError = True
myprocess.StartInfo.UseShellExecute = False
myprocess.StartInfo.CreateNoWindow = True
myprocess.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(executeableFullPath)
myprocess.StartInfo.Arguments = "--decrypt c:\temp\File_Encr.xml"
' add handlers to monitor various conditions
myprocess.EnableRaisingEvents = True
AddHandler myprocess.OutputDataReceived, AddressOf OutputReceivedHandler
AddHandler myprocess.ErrorDataReceived, AddressOf ErrorReceivedHandler
AddHandler myprocess.Exited, AddressOf ExitedHandler
' launch
Try
myprocess.Start()
' redirect this processes IO
SW = myprocess.StandardInput
SW.AutoFlush = True
' use asynchronous reading so buffers dont fill up
myprocess.BeginOutputReadLine()
myprocess.BeginErrorReadLine()
' wait for program to end
myprocess.WaitForExit()
myprocess.Close()
SW.Close()
myprocess.Dispose()
' check for errors
If myprocess_ErrorMsg "" Then
' something bad happened, handle it.
End If
Catch ex As Exception
' something bad happened, handle it.
End Try
End Sub
Private Sub OutputReceivedHandler(ByVal sendingProcess As Object, ByVal line As DataReceivedEventArgs)
If Not line.Data Is Nothing Then
If line.Data = "string to search for..." Then
' when this string is detected, send more output
SW.Write("helloworld" & System.Environment.NewLine)
ElseIf line.Data = "something else..." Then
' when this string is detected, send more output
SW.Write("goodbyeworld" & System.Environment.NewLine)
End If
Debug.WriteLine(line.Data)
End If
End Sub
Private Sub ErrorReceivedHandler(ByVal sendingProcess As Object, ByVal line As DataReceivedEventArgs)
Debug.WriteLine(line.Data)
' These executables send newlines on the STDERR path, ignore newlines
If line.Data <> "" Then
myprocess_HasError = True
myprocess_ErrorMsg = line.Data
End If
End Sub
Private Sub ExitedHandler(ByVal sender As Object, ByVal e As System.EventArgs)
Debug.WriteLine("Process Exited")
End Sub
End Class

Console application doesn't want to read standard input

I am writing an application to manage other console application(game server - jampded.exe)
When it's running in console it writes data and reads commands with no problem.
In my application I redirected standard I/O to StreamWriter and StreamReader
Public out As StreamReader
Public input As StreamWriter
Dim p As New Process()
p.StartInfo.FileName = My.Application.Info.DirectoryPath & "\" &
TextBox6.Text 'PATH TO JAMPDED.EXE
p.StartInfo.Arguments = TextBox1.Text 'EXTRA PARAMETERS
p.StartInfo.CreateNoWindow = True
p.StartInfo.RedirectStandardInput = True
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.UseShellExecute = False
p.Start()
input = p.StandardInput
out = p.StandardOutput
Dim thr As Thread = New Thread(AddressOf updatetextbox)
thr.IsBackground = True
thr.Start()
Sub updatetextbox()
While True
While Not out.EndOfStream
RichTextBox1.AppendText(out.ReadLine())
RichTextBox1.AppendText(vbNewLine)
End While
End While
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) _
Handles Button2.Click
input.WriteLine(TextBox4.Text)
TextBox4.Text = ""
input.Flush()
End Sub
When I am pressing Button2 that should write to STD/I text from my textbox, jampded.exe acts like it wasn't written. Also Output works well at startup, after that new lines are added rarely when there is a lot data in buffer.
Am I doing something wrong, or is it the application's fault?
For the standard input question:
Are you certain that the application you're starting is reading data from standard input (and not trapping keyboard events or something)? To test this, put some text that you're trying to send to the application in a text file (named, for example, commands.txt). Then send it to the application from a command prompt like so:
type commands.txt | jampded.exe
If that application reads those commands, then it is indeed reading from standard input. If it isn't, then redirecting standard input isn't going to help you get data to that application.
For the standard output question:
Instead of launching your own thread to handle the data coming from the other application, I would suggest doing something like this:
AddHandler p.OutputDataReceived, AddressOf OutputData
p.Start()
p.BeginOutputReadLine()
Private Sub AddLineToTextBox(ByVal line As String)
RichTextBox1.AppendText(e.Data)
RichTextBox1.AppendText(vbNewLine)
End Sub
Private Delegate Sub AddLineDelegate(ByVal line As String)
Private Sub OutputData(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
If IsNothing(e.Data) Then Exit Sub
Dim d As AddLineDelegate
d = AddressOf AddLineToTextBox
Invoke(d, e.Data)
End Sub
The Invoke call is required because OutputData may get called on a different thread, and UI updates all have to happen on the UI thread.
I've seen the same issue with data coming in batches when reading from the StandardOutput stream directly. The asynchronous read + event handler combo fixed it.