Streaming output from CMD console to VB ListBox - vb.net

I am trying to pass text from console to the Listbox1 line by line so hidden console will stream its output to my ListBox. But I'm getting Cross-thread operation not valid
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim start_info As New ProcessStartInfo()
start_info.FileName = ("cmd.exe")
start_info.UseShellExecute = False
start_info.CreateNoWindow = False
start_info.RedirectStandardOutput = True
start_info.WindowStyle = ProcessWindowStyle.Hidden
start_info.Arguments = ("ipconfig")
Dim proc As New Process
proc.StartInfo = start_info
proc.Start()
Dim std_out As System.IO.StreamReader
std_out = proc.StandardOutput
Do
ListBox1.Items.Add(std_out.ReadLine)
Loop While proc.HasExited = False
End Sub

The DoWork event of the BackgroundWorker runs in a different thread than the UI. You'll have to make sure the Items are being added in the UI thread. Use for example Invoke to accomplish this.
Change your Do ... Loop While to the following:
Do
Dim line As String = std_out.ReadLine()
ListBox1.Invoke(Sub() ListBox1.Items.Add(line))
Loop While proc.HasExited = False
More information also on MSDN:
How to: Make Thread-Safe Calls to Windows Forms Controls
Control.Invoke Method

Related

Vb.NET Start Process and return output

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

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.

Notify Icon appears to be disposed after running batch file

I have a WinForms application in VB.net with a NotifyIcon with ContextMenu attached. For some reason when I click the menu item to set the main form as Me.TopMost = false, then copy the batch file to the PC and run it, the NotifyIcon seems to get disposed (no longer appears in system tray). This only appears to happen intermittently.... I have no idea what could be causing this.
The reason that I am thinking that the NotifyIcon is disposed is because if I add a readerNotify.Visible = True after the operation the icon still does not re-appear. See below for the code
Private Sub ResetWindowsUpdateComponentsToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles ResetWindowsUpdateComponentsToolStripMenuItem.Click
'Adds Windows Update Components reset batch file to ProgramData and runs the file
Dim fileContent As String = My.Resources.WindowsUpdate_Components_Reset
Dim filename As String = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\WUCR.bat"
Me.TopMost = False
clearWUCache()
My.Computer.FileSystem.WriteAllText(filename, fileContent, False, System.Text.Encoding.ASCII)
'**WHERE NOTIFYICON SEEMS TO DIE**
Dim objProcess As Process = New Process
objProcess.StartInfo.FileName = filename
objProcess.Start()
objProcess.WaitForExit()
prgBarTemps.Value = 100
lblStatus.Text = "Windows Update components reset successfully!"
Me.TopMost = True
End Sub
Private Sub clearWUCache()
Dim wuCacheFolder As String = Environment.GetFolderPath(Environment.SpecialFolder.Windows) & "\SoftwareDistribution\Download"
Try
WebFixProcesses.deleteFolders(wuCacheFolder)
WebFixProcesses.deleteFiles(wuCacheFolder)
Catch ex As Exception
MsgBox(ex.Message)
Dim wuCacheDirect As String = "C:\Windows\SoftwareDistribution\Download"
WebFixProcesses.deleteFolders(wuCacheDirect)
WebFixProcesses.deleteFiles(wuCacheDirect)
lblStatus.Text = "Press a button to fix problems"
End Try
End Sub
I must be doing something wrong here in the way I am setting up the batch file in conjunction with setting Me.TopMost = False or something like that. Any help on this is greatly appreciated!
EDIT:
Here is some additional info from my Form.Designer InitializeComponent() method on the NotifyIcon.
Private Sub InitializeComponent()
'Have omitted other controls
Me.readerNotify = New System.Windows.Forms.NotifyIcon(Me.components)
'readerNotify
'
Me.readerNotify.ContextMenuStrip = Me.ContextMenu
Me.readerNotify.Icon = CType(resources.GetObject("readerNotify.Icon"), System.Drawing.Icon)
Me.readerNotify.Text = "WebFix"
Me.readerNotify.Visible = True
End Sub
Public WithEvents readerNotify As System.Windows.Forms.NotifyIcon

after using background worker also my application get stuck,,how i can resolve

I am working on windows form application :
in my form am filling my datagrid view in some interval.so some time my application getting stuck..so i used back ground worker and timer
in back ground worker i am calling my function to fill the my data grid view.i set Timer Interval as 10000. in background worker i given code like this:
Private Sub BackgroundWorker1_DoWork
Call Fetch_Info()
End Sub
in Timer click event i given code like thise:
If Not BackgroundWorker1.IsBusy Then
BackgroundWorker1.RunWorkerAsync()
End If
my Fetch_Info() function like this:
Dim cnt As Integer
Dim tbarcodedgv As String
Dim totaltbarcode As String
cnt = DGVall.RowCount
Dim tbar As String
Dim locTable As New DataTable
locTable.Columns.Add("carid", GetType(String))
If cnt > 0 Then
For i = 0 To cnt - 2
tbarcodedgv = DGVall.Rows(i).Cells(0).Value
locTable.Rows.Add(tbarcodedgv)
Next
End If
Dim flag As Boolean = False
Dim dcnt As Integer = DGVall.RowCount
Dim trid As Integer
Dim tbarcode As String
Dim keyloc As String
Dim cmd23 As New SqlCommand("IBS_fetchrequested", con.connect)
cmd23.CommandType = CommandType.StoredProcedure
cmd23.Parameters.Add("#tid", SqlDbType.Int).Value = tid
If cnt > 1 Then
Dim tvp1 As SqlParameter = cmd23.Parameters.AddWithValue("#Tbaroced", locTable)
tvp1.SqlDbType = SqlDbType.Structured
tvp1.TypeName = "dbo.TBarcode"
End If
dr = cmd23.ExecuteReader
While dr.Read
flag = False
tbarcode = dr("TBarcode")
If flag = False Then
If dr("keyloc") Is DBNull.Value Then
keyloc = ""
Else
keyloc = dr("keyloc")
End If
Dim row0 As String() = {tbarcode, keyloc, "", "Release"}
DGVall.Rows.Add(row0)
AxWindowsMediaPlayer1.URL = "C:\Beep.mp3"
End If
End While
dr.Close()
con.disconnect()
While your background worker runs in another thread than your GUI you are manipulating the Datagridview that's running in the GUI's thread. This should usually not work at all but it is probably the reason, why your GUI hangs while the BGW is running.
Try splitting the work: The time consuming fetching of data from the database is carried out in the Backgroundworker's DoWork event handler and you set the results as the e.Result value of the EventArgs variable in the DoWork function.
Then you handle the Backgroundworker's RunWorkerCompleted event and there you quickly update your datagridview with the results you set in the DoWork method. That way your GUI has nothing to do with the actual time consuming task and will only be affected by the quick update of your datagridview.
The code example for this is:
Public Class Form1
Private WithEvents LazyBGW As New System.ComponentModel.BackgroundWorker
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'This code runs in the UI-Thread
LazyBGW.RunWorkerAsync()
End Sub
Private Sub LazyBGW_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles LazyBGW.DoWork
'This code runs in the BGW-Thread
Dim a As Integer = 0
For i = 1 To 5
a += 1
'I'm a lazy worker, so after this hard work I need to...
Threading.Thread.Sleep(1000) 'This locks up the BGW-Thread, not the UI-thread
Next
'Work is done, put results in the eventargs-variable for further processing
e.Result = a
End Sub
Private Sub LazyBGW_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles LazyBGW.RunWorkerCompleted
'This code runs in the UI-Thread
Dim results As Integer = CInt(e.Result) 'e.Result contains whatever you put into it in the DoWork() method
MessageBox.Show("Finally the worker is done and our result is: " & results.ToString)
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.