Tuesday, September 11, 2012

Running an Command Line Program in C# and Getting Output

A simple example. Let's say I want to run ping from command line, but to make this more automated, or maybe user friendly, I would like to run a C# application that pings an IP address, captures the returned result and displays it in a user-friendly format.

Fist thing is to start the command prompt and execute a process. Here's one of the most convenient ways to use it: utilize ProcessStartInfo and Process classes, which are part of System.Diagnostics namespace. ProcessStartInfo takes the program to run, in this case cmd.exe, and parameters, in this case ping, together with its own parameters. Here's how it works:

private void btnPing_Click(object sender, EventArgs e)
{
 string command = "/c ping " + txtIP.Text;

 ProcessStartInfo procStartInfo = new ProcessStartInfo("CMD", command);

 Process proc = new Process();
 proc.StartInfo = procStartInfo;
 proc.Start();
}

Command prompts started from Windows Form

The process starts and the familiar command window appears, then the ping command runs. Now to capture the results of the ping, a few other lines are needed. Firstly, the output of the process needs to be redirected. The following values need to be set:

procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;

Next, to capture the output line by line as it is sent by the process, I'll attach a function that does it asynchronously.

proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();

The function can do anything, but in my case I'm simply redirecting the output to the Windows Form.

void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
 if (e.Data != null)
 {
  txtOutput.Text = txtOutput.Text + e.Data.Trim() + Environment.NewLine;
 }
}

Looks correct, so why am I receiving this exception:

Cross-thread operation not valid: Control 'txtOutput' accessed from a thread other than the thread it was created on.

Well, looks like it's telling me that the process is running from another thread and can not quite access my text box from that thread. Long story short, this is the shortest solution I have found for this issue (there are many options, some as complicated as using a BackgroundWorker).

void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
 if (e.Data != null)
 {
  string newLine = e.Data.Trim() +Environment.NewLine;
  MethodInvoker append = () => txtOutput.Text += newLine;
  txtOutput.BeginInvoke(append);
 }
}

Command prompt output redirected to Windows Form

References:

Having trouble with Process class while redirecting command prompt output to winformCapturing process output via OutputDataReceived event by . Also posted on my website

3 comments:

Jony said...

Is it possible to receive output of a cmd command in a listbox without waiting for the cmd to exit such as adb logcat.

tabish saifullah said...

@JONY:- I think you are asking for this:-

void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
string newLine = e.Data.Trim() + Environment.NewLine;
MethodInvoker append = () => txtOutput.Text += newLine;
txtOutput.BeginInvoke(append);
}

}
private void btnPing_Click(object sender, EventArgs e)
{
string command = "/c ping " + txtPing.Text;

ProcessStartInfo procStartInfo = new ProcessStartInfo("CMD", command);

Process proc = new Process();
proc.StartInfo = procStartInfo;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.Start();
procStartInfo.CreateNoWindow = true;
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start();

proc.BeginOutputReadLine();

procStartInfo.CreateNoWindow = true;
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden;

}

Unknown said...

One suggestion for improvement:

In the proc_OutputDataReceived method, replace this line:

MethodInvoker append = () => pingoutput.Text += newLine;

with this:

MethodInvoker append = () => pingoutput.AppendText(newLine);

AppendText will make the cursor automatically scroll to the bottom of the text.