Introduction
This tip describes a simple C# class that can be used to run console commands and get the standard output and standard error text of the command. Most of the functionality
to do this is provided by the Process class. However, it is useful to have the surrounding
code wrapped up in a class that can be easily reused in different projects. The functionality of the CommandPrompt class can be seen in the screenshot
of the form above. It allows you to run a command with optional parameters and a timeout value. The command can be run synchronously or asynchronously.
The code is described in the next section.
Description of the Code
DataEventArgs Class
public class DataEventArgs : EventArgs
{
public string Data { get; private set; }
public DataEventArgs(string data)
{
Data = data;
}
}DataEventArgs is a small class that inherits from EventArgs. It is used in the OutputDataReceived
and ErrorDataReceived events for storing standard output and standard error text.
Class Declaration and Members
internal class CommandPrompt
{
private Process _process;
private ProcessStartInfo _startInfo;
private StringBuilder _standardOutput;
private StringBuilder _standardError;
public const int NoTimeOut = 0;
The class consists of the Process and ProcessStartInfo objects to keep track of the underlying process info. A couple of StringBuilder
objects contain the contents of the standard output and standard error data streams. Also, a public constant is provided for specifying that there should be no timeout
period when running a command synchronously.
Properties
public bool IsRunning { get; private set; }
public bool HasExited { get; private set; }
public int ProcessId { get; private set; }
public int ExitCode { get; private set; }
public string StandardOutput
{
get
{
return _standardOutput.ToString();
}
}
public string StandardError
{
get
{
return _standardError.ToString();
}
}
Several public properties are provided to retrieve the state of the command and the contents of the command output.
Events
public event EventHandler<DataEventArgs> OutputDataReceived = (sender, args) => { };
public event EventHandler<DataEventArgs> ErrorDataReceived = (sender, args) => { };
public event EventHandler Exited = (sender, args) => { };
Events are provided for users of the class to receive notifications when command output is received and when it exits.
The events are hooked up to dummy delegates so that we don't have to check if the event is null when calling it.
Constructor
public CommandPrompt(string exe, string arguments = "", string workingDirectory = "")
{
_standardOutput = new StringBuilder();
_standardError = new StringBuilder();
_startInfo = new ProcessStartInfo()
{
FileName = exe,
Arguments = arguments,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
};
_process = new Process()
{
StartInfo = _startInfo,
EnableRaisingEvents = true,
};
_process.OutputDataReceived += _process_OutputDataReceived;
_process.ErrorDataReceived += _process_ErrorDataReceived;
_process.Exited += _process_Exited;
}
The constructor takes in the EXE to run, parameters to pass in and the working directory that the command should be run in. The constructor initializes
the private members and hooks up event handlers for the Process events. In order to redirect standard output and error to our program,
we need to set UseShellExecute to true.
Public Methods
public void Run(int timeOutInMilliseconds = NoTimeOut)
{
if (!IsRunning && !HasExited)
{
BeginRun();
if (timeOutInMilliseconds == NoTimeOut)
{
_process.WaitForExit();
}
else
{
_process.WaitForExit(timeOutInMilliseconds);
}
}
}
public void BeginRun()
{
if (!IsRunning && !HasExited)
{
if (_process.Start())
{
IsRunning = true;
ProcessId = _process.Id;
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
}
}
}
public void WriteToStandardInput(string command)
{
if (IsRunning && !HasExited)
{
_process.StandardInput.Write(command);
}
}
public void Kill(bool killChildProcesses = false)
{
if (killChildProcesses && ProcessId != 0)
{
KillChildProcesses(ProcessId);
}
else if (IsRunning && !HasExited)
{
_process.Kill();
}
}
The Run and BeginRun methods provide ways for the command to be run both synchronously and asynchronously.
When run synchronously, a timeout can be specified so that the caller is not blocked forever in case the command doesn't die.
When run asynchronously, the caller should subscribe to the Exited event to determine when the command has finished running.
Processes starting using this class can be killed using the Kill method. This can optionally kill any child processes that were started from the main process.
The WriteToStandardInput method allows you to send a command to the standard input of the process.
For example, this can be useful if you started a command prompt using cmd.exe /k and want to run commands on it. By the way, if you want to run built-in shell commands like del,
dir, etc., you have to use cmd.exe as the exe name and '/c del ...' as the arguments. This is because the Process class
doesn't know about the shell commands so it will throw an exception if you try to run them directly.
Private Methods
private void KillChildProcesses(int parentPid)
{
using (var searcher = new ManagementObjectSearcher(
"select ProcessId from Win32_Process where ParentProcessId=" + parentPid))
using (ManagementObjectCollection objCollection = searcher.Get())
{
foreach (ManagementObject obj in objCollection)
{
int pid = Convert.ToInt32(obj["ProcessID"]);
KillChildProcesses(pid);
}
}
try
{
Process.GetProcessById(parentPid).Kill();
}
catch (ArgumentException)
{
}
}
The only private method here is a method that recursively kills child processes started from the main process.
Event Handlers
private void _process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
_standardOutput.AppendLine(e.Data);
OutputDataReceived(this, new DataEventArgs(e.Data));
}
private void _process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
_standardError.AppendLine(e.Data);
ErrorDataReceived(this, new DataEventArgs(e.Data));
}
private void _process_Exited(object sender, EventArgs e)
{
HasExited = true;
IsRunning = false;
ExitCode = _process.ExitCode;
Exited(this, e);
}
Finally, we declare the event handlers for the events of the underlying Process object. In each case, we record the state internally and then forward
the events to the caller of the CommandPrompt class.
Conclusion and Improvements
As you can see, the CommandPrompt class is basically a simple wrapper around Process. I'm sure there is more functionality that can be added
to it to make it even more useful and feature-rich. Is there any other functionality you would like to see in this class? Let me know
in the comments 
History
- November 9, 2012
- Added the
WriteToStandardInput and
KillChildProcesses methods
- October 6, 2012