From various sources on the web, I have put together the following code for executing a command via CMD.exe
and capturing output from STDOUT
and STDERR
.
public static class Exec
{
public delegate void OutputHandler(String line);
// <summary>
/// Run a command in a subprocess
/// </summary>
/// <param name="path">Directory from which to execute the command</param>
/// <param name="cmd">Command to execute</param>
/// <param name="args">Arguments for command</param>
/// <param name="hndlr">Command output handler (null if none)</param>
/// <param name="noshow">True if no windows is to be shown</param>
/// <returns>Exit code from executed command</returns>
public static int Run(String path, String cmd, String args,
OutputHandler hndlr = null, Boolean noshow = true)
{
// Assume an error
int ret = 1;
// Create a process
using (var p = new Process())
{
// Run command using CMD.EXE
// (this way we can pipe STDERR to STDOUT so they can get handled together)
p.StartInfo.FileName = "cmd.exe";
// Set working directory (if supplied)
if (!String.IsNullOrWhiteSpace(path)) p.StartInfo.WorkingDirectory = path;
// Indicate command and arguments
p.StartInfo.Arguments = "/c \"" + cmd + " " + args + "\" 2>&1";
// Handle noshow argument
p.StartInfo.CreateNoWindow = noshow;
p.StartInfo.UseShellExecute = false;
// See if handler provided
if (hndlr != null)
{
// Redirect STDOUT and STDERR
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
// Use custom event handler to capture output
using (var outputWaitHandle = new AutoResetEvent(false))
{
p.OutputDataReceived += (sender, e) =>
{
// See if there is any data
if (e.Data == null)
{
// Signal output processing complete
outputWaitHandle.Set();
}
else
{
// Pass string to string handler
hndlr(e.Data);
}
};
// Start process
p.Start();
// Begin async read
p.BeginOutputReadLine();
// Wait for process to terminate
p.WaitForExit();
// Wait on output processing complete signal
outputWaitHandle.WaitOne();
}
}
else
{
// Start process
p.Start();
// Wait for process to terminate
p.WaitForExit();
}
// Get exit code
ret = p.ExitCode;
}
// Return result
return ret;
}
// <summary>
/// Run a command in a subprocess and return output in a variable
/// </summary>
/// <param name="path">Directory from which to execute the command</param>
/// <param name="cmd">Command to execute</param>
/// <param name="args">Arguments for command</param>
/// <param name="outp">Variable to contain the output</param>
/// <returns>Exit code from executed command</returns>
public static GetOutputReturn GetOutput(String path, String cmd, String args)
{
GetOutputReturn ret = new GetOutputReturn();
ret.ReturnCode = Run(path, cmd, args, (line) =>
{
ret.Output.AppendLine(line);
});
return ret;
}
}
public class GetOutputReturn
{
public StringBuilder Output = new StringBuilder();
public int ReturnCode = 1;
}
I am able to use this in a console app in three different manners as follows:
static void Main(string[] args)
{
int ret;
Console.WriteLine("Executing dir with no capture and no window");
ret = Exec.Run(@"C:\", "dir", "");
Console.WriteLine("Execute returned " + ret);
Console.WriteLine("Press enter to continue ...");
Console.ReadLine();
Console.WriteLine("Executing dir with no capture and window");
ret = Exec.Run(@"C:\", "dir", "", null, false);
Console.WriteLine("Execute returned " + ret);
Console.WriteLine("Press enter to continue ...");
Console.ReadLine();
Console.WriteLine("Executing dir with capture and no window");
var results = Exec.GetOutput(@"C:\", "dir", "");
Console.WriteLine(results.Output.ToString());
Console.WriteLine("Execute returned " + results.ReturnCode);
Console.ReadLine();
Console.WriteLine("Executing dir with real-time capture and no window");
ret = Exec.Run(@"C:\", "dir", "", ShowString);
Console.WriteLine("Execute returned " + ret);
}
public delegate void StringData(String str);
static void ShowString(String str)
{
Console.WriteLine(str);
}
public delegate void StringData(String str);
static void ShowString(String str)
{
Console.WriteLine(str);
}
The first run does not gather any output and just shows the exit code.
The second run does not gather any output but shows the window.
The effect of this that the output appears in the console window real-time.
The third run uses GetOutput to gather the output.
The effect of this is that the output does not appear until the run is completed.
The last run uses a handler to receive and display the output real-time.
In appearance this looks like the second run but it is very different.
For each line of output that is received ShowString is called.
Show string simply displays the string.
However, it could do anything it needs with the data.
I am trying to adapt the last run such that I can update a text box with the output of the command in real time. The issue that I am having is how to get it in the right context (for lack of a better term). Because OutputHandler is called asynchronously, it has to use the InvokeRequired/BeginInvoke/EndInvoke
mechanism to get in sync with the UI thread. I am having a little problem with how to do this with parameters. In my code the textBox could be one of several in a tab control as several background "Run"'s could be taking place.
So far I have this:
private void btnExecute_Click(object sender, EventArgs e)
{
// Get currently selected tab page
var page = tcExecControl.SelectedTab;
// Get text box (always 3rd control on the page)
var txt = (TextBox)page.Controls[2];
// Create string handler
var prc = new Exec.OutputHandler((String line) =>
{
if (txt.InvokeRequired)
txt.Invoke(new MethodInvoker(() =>
{ txt.Text += line; }));
else txt.Text += line;
});
// Command and arguments are always 1st and 2nd controls on the page
var result = Exec.Run(@"C:\", page.Controls[0].Text, page.Controls[1], prc);
}
But this does not seem to be working. I am not seeing any output to the txtBox.
In fact the program basically hangs in the handler.
If I change the code to use GetOutput and then write the resulting output to the text box everything works. So I know that I have the command set up properly. Using the debugger, I am able to set a break point on the "if (txt.InvokeRequired
)" line and I see the first line of output coming correctly. At this point the code takes the true path of the if statement, but if I set a breakpoint on the txt.Text += line;
line it never gets there.
Can anyone help me out? I'm sure I'm missing something.