How to get Output of a Command Prompt Window line by line in Visual Basic? - vb.net

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.

Related

Saving a Structure to a Binary File

I am creating an application using a windows forms application in visual studio in the vb.net language. I need help converting a structure that I coded into a binary file that is essentially a save in user results. I'm not a very good coder so excuse the poor code.
The code below shows that I have created a structure called saveresults and by clicking button1, it should get the contents of the binary file and edit them to be the new result. When I run the code the problem seems to be in the line FileOpen(1, "/bin/debug/1.txt", OpenMode.Binary) in the saveres subroutine.
Structure saveresults 'Structure for saving results
Dim numright As Integer
Dim numwrong As Integer
Dim totalnum As Integer
End Structure
'Subroutine aimed at getting stats saved to a text file to eventually be displayed to the user
Sub saveres(saveresults As saveresults, correct As Boolean)
saveresults.totalnum = saveresults.totalnum + 1
'Determining the contents to be saved to the binary file
If correct = True Then
saveresults.numright = saveresults.numright + 1
ElseIf correct = False Then
saveresults.numwrong = saveresults.numwrong + 1
End If
FileOpen(1, "/bin/debug/1.txt", OpenMode.Binary)
FilePut(1, saveresults)
FileClose(1)
End Sub
'attempt at saving results to the binary file
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim correct = True
Dim results As saveresults
FileOpen(1, "/bin/debug/1.txt", OpenMode.Binary)
FileGet(1, results)
saveres(results, correct)
FileClose(1)
End Sub
Any help would be appreciated. Thank you.
Use this instead
FileOpen(1, "1.txt", OpenMode.Binary)
Using the above opens the file in your project's debug folder.
You are referring to text files and binary files as if they are the same thing. They are not. Text files are human readable in Notepad; binary files are not.
I have not used the methods you are attempting since VB 6. Use the .Net System.IO methods. To use these you need to add Imports System.IO at the very top of your code file.
I have broken your code into Subs and Functions that have a single purpose. Reading the file, writing the file, updating the data, and displaying the data. This makes code more maintainable. If your code misbehaves it is easier to find the error and easier to fix if a method has only one thing to do.
The file location in the example is in the same directory as your .exe file. Probably
/bin/Degug.
'A Form level variable to hold the data
Private results As New saveresults
Structure saveresults 'Structure for saving results
Dim numright As Integer
Dim numwrong As Integer
Dim totalnum As Integer
End Structure
'Update the data
Private Sub UpdateResults(Correct As Boolean)
'It is not necessary to include = True when testing a Boolean
If Correct Then
'this is a shortcut method of writin results.numright = results.numright + 1
results.numright += 1
'A Boolean can only be True or False so if it is not True
'it must be False so, we can just use an Else
'No need to check the condition again
Else
results.numwrong += 1
End If
results.totalnum += 1
End Sub
'Write text file
Private Sub SaveResultsFile(results As saveresults, correct As Boolean)
Dim sb As New StringBuilder
sb.AppendLine(results.numright.ToString)
sb.AppendLine(results.numwrong.ToString)
sb.AppendLine(results.totalnum.ToString)
File.WriteAllText("1.txt", sb.ToString)
End Sub
'Read the text file
Private Function ReadResultsFile() As saveresults
Dim ResultsFiLe() = File.ReadAllLines("1.txt")
Dim results As New saveresults With
{
.numright = CInt(ResultsFiLe(0)),
.numwrong = CInt(ResultsFiLe(1)),
.totalnum = CInt(ResultsFiLe(2))
}
Return results
End Function
'Display
Private Sub DisplayResults()
Dim ResultsToDisplay As saveresults = ReadResultsFile()
'The $ indicates an interpolated string, the same thing can be accomplished with String.Format
Label1.Text = $"The correct number is {ResultsToDisplay.numright}. The wrong number is {ResultsToDisplay.numwrong}. The total is {ResultsToDisplay.totalnum}."
End Sub

Check if Mage.exe batch manifest update was successful or not - ClickOnce

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

Unsure on proper use of Serial Port Data Received Event

I'm working on a VSTO add-in for Excel 2013 in VB.NET that will help me interface with an instrument via a serial connection. I currently have the COM connection set up correctly and it will allow me to send and receive one command at a time. I'd like to set it up so that I can push one button and have it collect two separate readings in different worksheet cells. Using the code below, the tools work great to collect a single reading, but when I enable the code to send a second command to the instrument the Data Received event stops working entirely until I send another single read command. I know that the instrument received and processed the second command, but it never appears in excel. Could anyone help with a way to modify this code?
Private Sub mySerialPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
'Handles serial port data received events
UpdateFormDeligate1 = New UpdateFormDeligate(AddressOf UpdateDisplay)
Dim n As Integer = mySerialPort.BytesToRead 'find number of bytes in buff
comBuffer = New Byte(n - 1) {} 're-dimension storage buffer (n - 1)
mySerialPort.Read(comBuffer, 0, n) 'read data from the buffer
comBuffer2 = mySerialPort.ReadTo(vbCr)
Me.Invoke(UpdateFormDeligate1) 'call the deligate
mySerialPort.Close()
End Sub
Private Sub Invoke(updateFormDeligate1 As UpdateFormDeligate)
lblReading.Label = processReading() 'write to a Current Reading lable on the ribbon
Dim myApp As Excel.Application = Globals.ThisAddIn.Application
Dim currentCell = myApp.ActiveCell
currentCell.Value = processReading() 'write data in the excel active cell
Try
advanceCell()
Catch ex As Exception
System.Windows.Forms.MessageBox.Show(ex.Message)
End Try
If measureNo = 2 Then 'this case is selected when I want to read 2 measurements with a single button push
cmdSent = 2
sendCommand(measureCmd)
End If
End Sub
Private Sub UpdateDisplay()
End Sub
Note that I did not include my sendCommand sub because this is a simple .write command to the instrument that appears to be working correctly in all cases. I'd much appreciate any help anyone could provide as I'm pretty new to using data received events.
OK, I tried to isolate only the relevant the part of the script that was having an issue and I created a completely new toolbar for testing. Below is the full code for this new toolbar that contains one connect/measure button and a label that displays the status/result. I tried to comment the code to make it readable, hopefully this helps.
This new toolbar does appear to be working correctly. I'm still a little unsure on my correct usage of the DataReceived event handler in conjunction with the Invoke method (which Visual Studio slightly changed for use with Excel2013). Could anyone please provide comment as to whether I'm still using these events in an unclear way and provide a suggestion on how I may make it better?
Thanks again in advance for any help. I really appreciate it.
Imports Microsoft.Office.Tools.Ribbon
Imports System.IO.Ports
Public Class Measure2x_COM
Dim mySerialPort As New SerialPort
Dim CMD As String = "M" & vbCr 'statement telling instrument to measure
Dim measureNo As Integer = 0 'counts the number of measure commands sent to the instrument
Private Delegate Sub UpdateFormDeligate()
Private UpdateFormDeligate1 As UpdateFormDeligate
Dim sngReading As Single 'this is the reading received from the instrument as a single data type
Private Sub setupConnectCOM()
'Open COM and send measure command - this part works correctly
'first, check if serial port is open
If mySerialPort.IsOpen Then 'send measure command
mySerialPort.Write(CMD) 'the instrument will generally take 15.1 sec to perform a measurement before sending the result back
Else
'if serial port is not open, set it up, then open, then send command
'Setup COM --this part works correctly
With mySerialPort
.PortName = "COM3"
.BaudRate = 1200
.DataBits = 7
.Parity = Parity.None
.StopBits = StopBits.Two
.Handshake = Handshake.None
.ReadTimeout = 16000
End With
Try
mySerialPort.Open()
Catch ex As Exception
System.Windows.Forms.MessageBox.Show(ex.Message)
Exit Sub 'exit sub if the connection fails
End Try
Threading.Thread.Sleep(200) 'wait 0.2 sec for port to open
mySerialPort.Write(CMD) 'send measure command after serial port is open
End If
measureNo = 1
lblResult.Label = "Measuring"
End Sub
Private Sub Measure2x_COM_Load(ByVal sender As System.Object, ByVal e As RibbonUIEventArgs) Handles MyBase.Load
AddHandler mySerialPort.DataReceived, AddressOf mySerialPort_DataReceived
End Sub
Private Sub mySerialPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
'Handles serial port data received events
UpdateFormDeligate1 = New UpdateFormDeligate(AddressOf UpdateDisplay)
'Read data as it comes back from serial port
'I had to do this in two steps because it, for some reason needs to read
'the +/- symbol as a Byte, then needs to read the ASCII measurement number
'the third part concatenates the data and converts it to a single type
'part 1 - read +/- symbol
Dim comBuffer As Byte()
Dim n As Integer = mySerialPort.BytesToRead 'find number of bytes in buff
comBuffer = New Byte(n - 1) {} 're-dimension storage buffer (n - 1)
mySerialPort.Read(comBuffer, 0, n) 'read data from the buffer
'part 2 - read ASCII measurement number
Dim comBuffer2 As String
comBuffer2 = mySerialPort.ReadTo(vbCr)
'part 3 - concatenate read data and convert to single type
Dim txtReading As String = Nothing
txtReading = System.Text.ASCIIEncoding.ASCII.GetString(comBuffer) & CStr(CInt(comBuffer2) / 10)
sngReading = CSng(txtReading)
'Call the update form deligate
'Visual Studio slightly changed this from the example on Microsoft's website that used a Windows Form
'I tried the code in a windows form and I get the same results
Me.Invoke(UpdateFormDeligate1) 'call the deligate
End Sub
Private Sub Invoke(updateFormDeligate1 As UpdateFormDeligate)
lblResult.Label = sngReading 'set the Result label in the ribbon to equal the received data value
'now place the data received in the active cell in the worksheet
Dim myApp As Excel.Application = Globals.ThisAddIn.Application
Dim currentCell = myApp.ActiveCell
currentCell.Value = sngReading
'advance cell to the next cell
Dim newCell = currentCell
newCell = myApp.ActiveCell.Offset(1, 0)
newCell.Select()
currentCell = newCell
'check if this was the first reading from the instrument
'if it was the first reading, then send a second read command
If measureNo = 1 Then
measureNo = 2 'make sure to change measurement number to 2 to avoid infinite loop
mySerialPort.Write(CMD) 'send command to measure to instrument
End If
End Sub
'the usage of this section changed from the Microsoft Windows Form example
'in function, the mySerialPort_DataREceived(), Invoke(), and UpdateDisplay() functions do appear to be
'working with the same results and same hangups
Private Sub UpdateDisplay()
End Sub
Private Sub btnMeasure_Click(sender As Object, e As RibbonControlEventArgs) Handles btnMeasure.Click
setupConnectCOM() 'connect to COM and send first measure command
End Sub
End Class

How to check when a powershell script is done running with Visual Studio

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.

code that doesn't work...Kill-process , search-for-file , streamwrite on file searched file

can you help me with that code ?
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim x As String = "C:\Users\Andy\Documents\Visual Studio 2008\Projects\minecraft srv\"
For Each app As Process In Process.GetProcesses
If app.ProcessName = "notepad" Then
app.Kill()
End If
Next
Dim result As String
Dim servprop() As String
servprop = System.IO.Directory.GetFiles(x, "server.*")
For Each file In servprop
result = Path.GetFileName(file)
Next
Dim z As String = "C:\Users\Andy\Documents\Visual Studio 2008\Projects\minecraft srv\" & result.ToString
Dim t As StreamWriter = New StreamWriter(z)
t.WriteLine(TextBox1.Text.ToString)
t.Close()
End Sub
so... I got a button (button1) that finds if notepad is opened and kills it.
Then it searches for "server.Properties" in "x" location
if server.properties is found , then "result" will get his name (server)
"z" is the file location where streamwriter must write the text from textbox1 .
And it doesn't work... streamwirter is not writing on server.properties ... why ?
mention : I'm just a kid :D and i'm trying to learn by myself visual basic .
If you have only one file called "server.properties" then you could remove all the code that search for this file and write it directly.
Dim z As String
z = System.IO.Path.Combine(x, "server.properties")
Using t = New StreamWriter(z)
t.WriteLine(TextBox1.Text.ToString)
t.Flush()
End Using
Regarding the error, encapsulating the writing code with a proper using statement ensures a complete clean-up. Also adding a call to Flush() is probably not necessary, but doesn't hurt.