My goal is to read output from a 7zip command line process in real time. I have coded an asynchronous output reader that uses BeginOutputReadLine. This method should return a new line immediately if it was send to output. Or, like MSDN says: When asynchronous read operations start, the event handler is called each time the associated Process writes a line of text to its StandardOutput stream.
This is my code
Private Sub StartProcess()
Dim Proc As New Process
Proc.StartInfo.FileName = Application.StartupPath & "\7z.exe"
Proc.StartInfo.WorkingDirectory = "D:\temp"
Proc.StartInfo.Arguments = "a -t7z ""D:\temp.7z"" -mx=9 -m0=LZMA -ms=on -mhc=on -mmt=on -mtc=off -mta=off -ptest -mhe=off"
Proc.StartInfo.UseShellExecute = False
Proc.StartInfo.RedirectStandardOutput = True
Proc.StartInfo.CreateNoWindow = True
AddHandler Proc.OutputDataReceived, AddressOf OutputHandler
Proc.Start()
Proc.BeginOutputReadLine()
Proc.WaitForExit()
Proc.Dispose()
Proc = Nothing
End Sub
Private Sub OutputHandler(sendingProcess As Object, outLine As DataReceivedEventArgs)
If Not String.IsNullOrEmpty(outLine.Data) Then
Console.WriteLine(outLine.Data)
End If
End Sub
The problem is that i do not get any line until the process ends. After that all output is returned to the associated stream OutputHandler. The result of my asynchronous code is very much the same as using the synchronous StandardOutput.ReadToEnd method. What am i doing wrong?
[edit]
I have created a batch file for testing my code with a ping command. That seems to work! Does 7zip do something weird with it's output? Because it's looks like the problem has something to do with the output of 7zip rather than the code reading it.
Just remove Proc.WaitForExit() from your code - it causes your application to WAIT until it ends.
If you want to know when the process has exited then use another Addhandler to catch Proc.Exited event
Related
I have created a console app that creates a batch file in code, that will automatically update and re-sign my app manifest file using mage.exe when a new version gets published.
This batch file then gets executed by the same console app after it has created it.
I want to know if there is a way to determine if the mage.exe batch file failed in updating or signing the manifest?
Any help or ideas will be appreciated.
UPDATE
As per TnTinMn's comment, I forced the batch to fail on updating the manifest. This returned a exit code of 1. How is it then possible for me to extract that exit code to do my error handling? Im doing the following:
Dim procInfo As New ProcessStartInfo()
procInfo.UseShellExecute = True
procInfo.FileName = (sDriveLetter & ":\updatemanifest.bat")
procInfo.WorkingDirectory = ""
procInfo.Verb = "runas"
procInfo.WindowStyle = ProcessWindowStyle.Hidden
Dim sval As Object = Process.Start(procInfo) 'I tested the object to see if there is indeed a value that i can use.
While debugging and looking at the sval object's properties, the exit code is set to 1 but i can't seem to extract it from there.
There are two ways (that I know of) that you can wait for the process to exit before retrieving the Process.ExitCode.
The first as is a blocking call: Process.WaitForExit
and the second is to use the Exit event.
Private Sub RunProcess()
Dim psi As New ProcessStartInfo()
psi.UseShellExecute = True
psi.WindowStyle = ProcessWindowStyle.Hidden
psi.FileName = "cmd.exe"
psi.Arguments = "/c Exit 100"
Dim proc As Process = Process.Start(psi)
proc.EnableRaisingEvents = True
AddHandler proc.Exited, AddressOf ProcessExited
End Sub
Private Sub ProcessExited(sender As Object, e As EventArgs)
Dim proc As Process = DirectCast(sender, Process)
proc.Refresh()
Dim code As Int32 = proc.ExitCode
Me.BeginInvoke(Sub() MessageBox.Show(String.Format("Process has exited with code: {0}", code)), Nothing)
proc.Dispose()
End Sub
I am running a powershell script from within my app. I can start it and it completes without problem, but how do I check when its done? I need to do other actions, Only once its complete. Here is what I have, for a bit of reference:
Public Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
If requiredEnd = True And requiredPass = True And requiredPath = True And requiredSheet = True And requiredStart = True And requiredUser = True Then
For I = 0 To 7
objWriter.WriteLine(aryText(I))
Next
objWriter.Close()
Dim p As Process
p.Start("powershell", "-ExecutionPolicy ByPass -windowstyle hidden -file .\\Excel.ps1")
p.WaitForExit()
If p.HasExited = True Then
MsgBox("The Process Has Been Completed!")
Application.Exit()
End If
Else
If requiredEnd = False Or requiredPass = False Or requiredSheet = False Or requiredStart = False Or requiredUser = False Then
MessageBox.Show("You Have Missing Required Fields!")
Else
MessageBox.Show("That Is Not A Valid File!")
End If
End If
End Sub
Thanks for the help! Also, I am a beginner at this, so could you do a bit of an explanation on how it works if it isn't super straight forward? Thanks again.
Edit: I realize I forgot the key point: I can close the script, but I need to know if it exited successfully. So a way to check errors, basically.
Why not use a runspace to execute your powershell code in-process, instead of shelling out to an external application? Most of the code you see for this is in C# but you can of course do it in VB.net as well:
' create Powershell runspace
Dim MyRunSpace As Runspace = RunspaceFactory.CreateRunspace()
' open it
MyRunSpace.Open()
' create a pipeline and feed it the script text
Dim MyPipeline As Pipeline = MyRunSpace.CreatePipeline()
MyPipeline.Commands.AddScript(scriptText)
' add an extra command to transform the script output objects into nicely formatted strings
' remove this line to get the actual objects that the script returns. For example, the script
' "Get-Process" returns a collection of System.Diagnostics.Process instances.
MyPipeline.Commands.Add("Out-String")
' execute the script
Dim results As Collection(Of PSObject) = MyPipeline.Invoke()
' close the runspace
MyRunSpace.Close()
This code is taken from this MSDN blog which has a much more thorough and complete example application, but the basic idea here is to run the code natively in your application and then you can get the output any way you like to test for errors.
I used the VB.Net Shell() command to start a console app that I haven't created. I want to get the lines from that console to a RichTextBox in my form. It should be possible because I have seen many apps that do this. Please provide some information or any program that might help me. I tried to see if the external app creates log files, but it does not.
Here's how I started. What should I add, and where, to return the output?
Try
writer.WriteLine(RichTextBox1.Text)
writer.Close()
Shell(CurDir() & "\yelloeye.exe .\new.demo")
Catch ex As Exception
MsgBox(ex.Message)
End Try
Here's an example using the RedirectStandardOutput property as mentioned by #Plutonix which will ping SO and display the results in a RichTextBox. Just replace the .exe name and arguments (if any) to suit your needs.
Private Sub ShellToRTB()
Dim p As New Process()
p.StartInfo.FileName = "ping.exe"
p.StartInfo.Arguments = "www.stackoverflow.com"
p.StartInfo.UseShellExecute = False
p.StartInfo.RedirectStandardOutput = True
p.Start()
RichTextBox1.Text = p.StandardOutput.ReadToEnd()
p.WaitForExit()
End Sub
Result:
I am trying to get a command line output line by line till the end of the output but I am not able to do so. I am using it in my Form and this code executes on click of a button.
Can you tell me whats wrong with my code?
Dim proc As ProcessStartInfo = New ProcessStartInfo("cmd.exe")
Dim pr As Process
proc.CreateNoWindow = True
proc.UseShellExecute = False
proc.RedirectStandardInput = True
proc.RedirectStandardOutput = True
pr = Process.Start(proc)
pr.StandardInput.WriteLine("cd C:\sdk\platform-tools\")
pr.StandardInput.WriteLine("adb help")
Dim helpArray(20) as String
For i as Integer 1 To 7
helpArray(i) = pr.StandardOutput.ReadLine()
Next
pr.StandardOutput.Close()
The program stops responding when this code is executed.
I've done some research. adb help writes output into STDERR. So you need something like:
Dim proc As ProcessStartInfo = New ProcessStartInfo("cmd.exe")
Dim pr As Process
proc.CreateNoWindow = True
proc.UseShellExecute = False
proc.RedirectStandardInput = True
proc.RedirectStandardOutput = True
pr = Process.Start(proc)
pr.StandardInput.WriteLine("C:\sdk\platform-tools")
pr.StandardInput.WriteLine("adb help 2>&1")
pr.StandardInput.Close()
Console.WriteLine(pr.StandardOutput.ReadToEnd())
pr.StandardOutput.Close()
to catch it.
You need no 2>&1 if you call ipconfig, for example.
Do not interate over the output and do not read it! Normally you don't know how long the output (same goes for error output too) would be, so you need to prepare for an unknown length. Since you are telling the Process class, that you want to handle the standard output and the standard error by yourself, you also need to bind to the events, in this case:
OutputDataReceived
ErrorDataReceived
or to block the current process and read the complete output at once like #Dmitry Kurilo does in his answer. I find the first approach better because I do not need to wait for the process to end to see it's output. The MSDN documentation of the ProcessStartInfo.RedirectstandardError property gives a good explanation of the different possibilities with a lot of examples.
If you want to take a specific line, there are a lot of possibilities. One would be to store each output (line) in the delegate and use it later, using a List(Of String) and output the specific line when the process is done (= all output lines are present).
A possible solution could look like this:
' store error output lines
dim lines = new List(of String)
dim executable = "c:\temp\android\sdk\platform-tools\adb.exe"
dim arguments = " help"
dim process = new Process()
process.StartInfo = createStartInfo(executable, arguments)
process.EnableRaisingEvents = true
addhandler process.Exited, Sub (ByVal sender As Object, ByVal e As System.EventArgs)
Console.WriteLine(process.ExitTime)
Console.WriteLine(". Processing done.")
' output line n when output is ready (= all lines are present)
Console.WriteLine(lines(4))
end sub
' catch standard output
addhandler process.OutputDataReceived, Sub (ByVal sender As Object, ByVal e As System.Diagnostics.DataReceivedEventArgs)
if (not String.IsNullOrEmpty(e.Data))
Console.WriteLine(String.Format("{0}> {1}", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss") ,e.Data))
end if
end sub
' catch errors
addhandler process.ErrorDataReceived, Sub (ByVal sender As Object, ByVal e As System.Diagnostics.DataReceivedEventArgs)
'Console.WriteLine(String.Format("! {0}", e.Data))
' add every output line to the list of strings
lines.Add(e.Data)
end sub
' start process
dim result = process.Start()
' and wait for output
process.BeginOutputReadLine()
' and wait for errors :-)
process.BeginErrorReadLine()
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
Now even if the adb writes to the error output, you will be able to see it. It will also be complete.
The output in this case looks like this:
14.10.2014 12:49:10
. Processing done.
-e - directs command to the only running emulator.
Another possibility would be to put everything into one string and after the process has finished split the single string on line endings (CRLF \r\n) and you will gain the lines you want to filter.
Ok, I'm making a very basic vb.net winforms app, essentially you can drag files into it, and it then uses a batch file to process the files.
It's pretty simple and everything is going to plan so far, it accepts the right files, it uses the batch file to process them and the batch file does what it is supposed to.
The only problem is that I don't know how to hook into the Exited event that can/should be raised by the batch file process when the process completes...
I want the DOS window of the batch file to remain hidden while it is running, so I have used ProcessStartInfo to specify the batch file, then set the WindowStyle property of the process to ProcessWindowStyle.Minimised, then used System.Diagnostics.Process.Start(myBatch) to start the process at the appropriate moment.
This is fine, it works and does what I want. However, the only way to tell when a process ends is to use the Exited event. But the Exited event apparently only works with a Process not a ProcessStartInfo. I could switch to use Process instead but then I couldn't (AFAIK) run the DOS window minimised...
Is there a way around this? I've only been writing .net for a few days. This is how I'm running the batch file:
Dim myBatch As New ProcessStartInfo("C:\\batchFiles\\test.bat")
myBatch.WindowStyle = ProcessWindowStyle.Minimized
system.Diagnostics.Process.Start(myBatch)
Any ideas?
Thanks
Try creating a process object and setting the StartInfo property. You can then call WaitForExit instead of waiting for the event. EG:
using(var process = new Process
{
StartInfo =
new ProcessStartInfo("Foo.exe")
{WindowStyle = ProcessWindowStyle.Minimized}
})
{
process.Start();
process.WaitForExit();
}
Not sure of the syntax in VB but I am almost sure that what you have to do is actually use the WIN API inline with managed code, and then you can use the MainWindowHandle of the Process Object.
[DllImport("User32")]
private static extern int ShowWindow(int hwnd, int nCmdShow);
The commands it takes, I would recommend reference to the win api library for this method. But what you want to do I would think is very feasible with the interop.
Andrew
From the documentation: This event can occur only if the value of the EnableRaisingEvents property is true.
So the following should work:
Dim procStart As New ProcessStartInfo
Dim WithEvents proc As New Process
Private Sub Button21_Click(sender As System.Object, e As System.EventArgs) Handles Button21.Click
procStart.FileName = "C:\PTL\Bin\xxxxxx.exe"
proc.StartInfo = procStart
proc.EnableRaisingEvents = True
proc.Start()
End Sub
Private Sub proc_Exited(sender As Object, e As System.EventArgs) Handles proc.Exited
Debug.WriteLine("Process Ended " + proc.ExitCode.ToString + " " + DateTime.Now.ToString)
End Sub