Console application doesn't want to read standard input - vb.net

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.

Related

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.

Streaming output from CMD console to VB ListBox

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

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

How to redirect Output of console application to Textbox control on a windows form at run time using vb.net 2012

Team,
I have an third party application which actually is a telephonic messaging server and exchange messages between all connected clients and other servers. This messaging server keeps running for several days and even for moths. This is entirely a console application and do not have any GUI. Even to manage the internal operations of this server, there is another tool which is a console based application again. I would like to prepare a GUI to start, stop and restart this server in VB.Net 2012. I have managed to,
Create the process instance of this server
Launch the Server with appropriate parameters and keep it running. Below is some sample code from my application to launch the server,
Private Sub Server_Start_Click(sender As Object, e As EventArgs) Handles Server_Start.Click
Dim parameter, server_admin_path As String
server_admin_path = "D:\Voice_App\DataMessage\MessageServer.exe"
parameter = " -properties " & """" & " D:\Voice_App\Config\message.prop"
Dim proc = New Process()
proc.StartInfo.FileName = server_admin_path
proc.StartInfo.Arguments = parameter
' set up output redirection
proc.StartInfo.RedirectStandardOutput = True
proc.StartInfo.RedirectStandardError = True
proc.EnableRaisingEvents = True
Application.DoEvents()
proc.StartInfo.CreateNoWindow = False
proc.StartInfo.UseShellExecute = False
' see below for output handler
AddHandler proc.ErrorDataReceived, AddressOf proc_OutputDataReceived
AddHandler proc.OutputDataReceived, AddressOf proc_OutputDataReceived
proc.Start()
proc.BeginErrorReadLine()
proc.BeginOutputReadLine()
'proc.WaitForExit()
Server_Logs.Focus()
End sub
This code launches the message server very well. The message server is now started and it is producing log traces on the console after specific time of interval say 30 seconds and this will be continue till message server is not stopped by administration tool. So now what I want is to capture every single line that is being produced by my server on its console and paste that line on to the Textbox I have on my windows form.
I got below code which gives me that every line as and when produced,
Public Sub proc_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
On Error Resume Next
' output will be in string e.Data
' modify TextBox.Text here
'Server_Logs.Text = e.Data ` Does not display anything in textbox
MsgBox(e.Data) 'It works but I want output in text box field
End Sub
P.S = My application will be handling more that one such servers and I don’t want users to have every message server instance open on their taskbar as console window and they are scrolling long log traces. I searched lots of threads here but nothing worked for me in above scenario. Any help would be greatly appreciated as I have been stuck on this since very long time and this is now a showstopper!!!!
Looks like you are trying to make a call from a thread that is different from the thread the form is on. The events raised from the Process class will not be from the same thread.
Delegate Sub UpdateTextBoxDelg(text As String)
Public myDelegate As UpdateTextBoxDelg = New UpdateTextBoxDelg(AddressOf UpdateTextBox)
Public Sub UpdateTextBox(text As String)
Textbox.Text = text
End Sub
Public Sub proc_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
If Me.InvokeRequired = True Then
Me.Invoke(myDelegate, e.Data)
Else
UpdateTextBox(e.Data)
End If
End Sub

Simple software testing tool - VB.NET

ok, please do no laugh at this :x
i'm trying to create a simple software testing tool in VB.NET
i created a simple C program PROG.EXE which scans a number and prints the OUTPUT, and started building my tester, it should execute PROG.EXE output.txt, so PROG.EXE takes input from input.txt and prints the output to output.txt
but i failed, at first i tried Process.start then shell but nothing worked !
so i did this trick, the VB.NET codes generate a batch file with this codes PROG.EXE output.txt, but again i failed, though the VB.NET created the batch file and executes too, but nothing happened ! but when i manually run the batch file i got success !
i tried executing the batchfile then sendkey the VBCR/LF/CRLF still nothing happens !
whats wrong ?
My VB.NET Code, i am using Visual Studio 2010 Professional
Option Explicit On
Option Strict On
Public Class Form1
Dim strFileName As String
Private Sub btnRun_Click() Handles btnRun.Click
Dim strOutput As String
Using P As New Process()
P.StartInfo.FileName = strFileName
P.StartInfo.Arguments = txtInput.Text
P.StartInfo.RedirectStandardOutput = True
P.StartInfo.UseShellExecute = False
P.StartInfo.WindowStyle = ProcessWindowStyle.Hidden ' will this hide the console ?
P.Start()
Using SR = P.StandardOutput
strOutput = SR.ReadToEnd()
End Using
End Using
txtOutput.Text = strOutput
End Sub
Private Sub btnTarget_Click() Handles btnTarget.Click
dlgFile.ShowDialog()
strFileName = dlgFile.FileName
lblFileName.Text = strFileName
End Sub
End Class
And this is my C code
#include<stdio.h>
#include<conio.h>
void main()
{
int x;
scanf("%d",&x);
printf("%d",(x*x));
}
my program runs perfectly when i run prog.exe <input.txt> output.txt in console
Below is a fully working example. You want to use the Process class as you tried but you need to RedirectStandardOutput on the process's StartInfo. Then you can just read the process's StandardOutput. The sample below is written using VB 2010 but works pretty much the same for older versions.
Option Explicit On
Option Strict On
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
''//This will hold the entire output of the command that we are running
Dim T As String
''//Create our process object
Using P As New Process()
''//Pass it the EXE that we want to execute
''//NOTE: you might have to use an absolute path here
P.StartInfo.FileName = "ping.exe"
''//Pass it any arguments needed
''//NOTE: if you pass a file name as an argument you might have to use an absolute path
P.StartInfo.Arguments = "127.0.0.1"
''//Tell the process that we want to handle the commands output stream
''//NOTE: Some programs also write to StandardError so you might want to watch that, too
P.StartInfo.RedirectStandardOutput = True
''//This is needed for the previous line to work
P.StartInfo.UseShellExecute = False
''//Start the process
P.Start()
''//Wrap a StreamReader around the standard output
Using SR = P.StandardOutput
''//Read everything from the stream
T = SR.ReadToEnd()
End Using
End Using
''//At this point T will hold whatever the process with the given arguments kicked out
''//Here we are just dumping it to the screen
MessageBox.Show(T)
End Sub
End Class
EDIT
Here is an updated version that reads from both StandardOutput and StandardError. This time it reads asynchronously. The code calls the CHOICE exe and passes an invalid command line switch which will trigger writing to StandardError instead of StandardOutput. For your program you should probably monitor both. Also, if you're passing a file into the program make sure that you are specifying the absolute path to the file and make sure that if you have spaces in the file path that you are wrapping the path in quotes.
Option Explicit On
Option Strict On
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
''//This will hold the entire output of the command that we are running
Dim T As String
''//Create our process object
Using P As New Process()
''//Pass it the EXE that we want to execute
''//NOTE: you might have to use an absolute path here
P.StartInfo.FileName = "choice"
''//Pass it any arguments needed
''//NOTE: if you pass a file name as an argument you might have to use an absolute path
''//NOTE: I am passing an invalid parameter to show off standard error
P.StartInfo.Arguments = "/G"
''//Tell the process that we want to handle the command output AND error streams
P.StartInfo.RedirectStandardOutput = True
P.StartInfo.RedirectStandardError = True
''//This is needed for the previous line to work
P.StartInfo.UseShellExecute = False
''//Add handlers for both of the data received events
AddHandler P.ErrorDataReceived, AddressOf ErrorDataReceived
AddHandler P.OutputDataReceived, AddressOf OutputDataReceived
''//Start the process
P.Start()
''//Start reading from both error and output
P.BeginErrorReadLine()
P.BeginOutputReadLine()
''//Signal that we want to pause until the program is done running
P.WaitForExit()
Me.Close()
End Using
End Sub
Private Sub ErrorDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
Trace.WriteLine(String.Format("From Error : {0}", e.Data))
End Sub
Private Sub OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
Trace.WriteLine(String.Format("From Output : {0}", e.Data))
End Sub
End Class
Its important that you put your entire file path in quotes if it has spaces in it (in fact, you should always enclose it in quotes just in case.) For instance, this won't work:
P.StartInfo.FileName = "attrib"
P.StartInfo.Arguments = "C:\Program Files\Windows NT\Accessories\wordpad.exe"
But this will:
P.StartInfo.FileName = "attrib"
P.StartInfo.Arguments = """C:\Program Files\Windows NT\Accessories\wordpad.exe"""
EDIT 2
Okay, I'm an idiot. I thought you were just wrapping a filename in angled brackets like <input.txt> or [input.txt], I didn't realize that you were using actual stream redirectors! (A space before and after input.txt would have helped.) Sorry for the confusion.
There are two ways to handle stream redirection with the Process object. The first is to manually read input.txt and write it to StandardInput and then read StandardOutput and write that to output.txt but you don't want to do that. The second way is to use the Windows command interpreter, cmd.exe which has a special argument /C. When passed it executes any string after it for you. All stream redirections work as if you typed them at the command line. Its important that whatever command you pass gets wrapped in quotes so along with the file paths you'll see some double-quoting. So here's a version that does all that:
Option Explicit On
Option Strict On
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
''//Full path to our various files
Dim FullExePath As String = "C:\PROG.exe"
Dim FullInputPath As String = "C:\input.txt"
Dim FullOutputPath As String = "C:\output.txt"
''//This creates our command using quote-escaped paths, all completely wrapped in an extra set of quotes
''//""C:\PROG.exe" < "C:\input.txt" > "C:\output.txt""
Dim FullCommand = String.Format("""""{0}"" < ""{1}"" > ""{2}""""", FullExePath, FullInputPath, FullOutputPath)
''//Create our process object
Using P As New Process()
''//We are going to use the command shell and tell it to process our command for us
P.StartInfo.FileName = "cmd"
''//The /C (capitalized) means "execute whatever else is passed"
P.StartInfo.Arguments = "/C " & FullCommand
''//Start the process
P.Start()
''//Signal to wait until the process is done running
P.WaitForExit()
End Using
Me.Close()
End Sub
End Class
EDIT 3
The entire command argument that you pass to cmd /C needs to be wrapped in a set of quotes. So if you concat it it would be:
Dim FullCommand as String = """""" & FullExePath & """" & " <""" & FullInputPath & """> " & """" & FullOutputPath & """"""
Here's what the actual command that you pass should look like:
cmd /C ""C:\PROG.exe" < "C:\INPUT.txt" > "C:\output.txt""
Here's a full code block. I've added back the error and output readers just in case you're getting a permission error or something. So look at the Immediate Window to see if any errors are kicked out. If this doesn't work I don't know what to tell you.
Option Explicit On
Option Strict On
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
''//Full path to our various files
Dim FullExePath As String = "C:\PROG.exe"
Dim FullInputPath As String = "C:\INPUT.txt"
Dim FullOutputPath As String = "C:\output.txt"
''//This creates our command using quote-escaped paths, all completely wrapped in an extra set of quotes
Dim FullCommand As String = """""" & FullExePath & """" & " <""" & FullInputPath & """> " & """" & FullOutputPath & """"""
Trace.WriteLine("cmd /C " & FullCommand)
''//Create our process object
Using P As New Process()
''//We are going to use the command shell and tell it to process our command for us
P.StartInfo.FileName = "cmd"
''//Tell the process that we want to handle the command output AND error streams
P.StartInfo.RedirectStandardError = True
P.StartInfo.RedirectStandardOutput = True
''//This is needed for the previous line to work
P.StartInfo.UseShellExecute = False
''//Add handlers for both of the data received events
AddHandler P.ErrorDataReceived, AddressOf ErrorDataReceived
AddHandler P.OutputDataReceived, AddressOf OutputDataReceived
''//The /C (capitalized) means "execute whatever else is passed"
P.StartInfo.Arguments = "/C " & FullCommand
''//Start the process
P.Start()
''//Start reading from both error and output
P.BeginErrorReadLine()
P.BeginOutputReadLine()
''//Signal to wait until the process is done running
P.WaitForExit()
End Using
Me.Close()
End Sub
Private Sub ErrorDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
Trace.WriteLine(String.Format("From Error : {0}", e.Data))
End Sub
Private Sub OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
Trace.WriteLine(String.Format("From Output : {0}", e.Data))
End Sub
End Class
Public Class attributeclass
Public index(7) As ctrarray
End Class
Public Class ctrarray
Public nameclass As String
Public ctrlindex(10) As ctrlindexclass
End Class
Public Class ctrlindexclass
Public number As Integer
Public names(10) As String
Public status(10) As Boolean
Sub New()
number = 0
For i As Integer = 0 To 10
names(i) = "N/A"
status(i) = False
Next
End Sub
End Class
Public attr As New attributeclass
Sub Main()
attr.index(1).nameclass = "adfdsfds"
System.Console.Write(attr.index(1).nameclass)
System.Console.Read()
End Sub