C# Command Prompt class






4.68/5 (13 votes)
This tip describes a class that allows you to run a command and get its output.

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
{
// Process object used to run command.
private Process _process;
// Process start info.
private ProcessStartInfo _startInfo;
// Stores the contents of standard output.
private StringBuilder _standardOutput;
// Stores the contents of standard error.
private StringBuilder _standardError;
/// No timeout.
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
// Value that indicates whether process is currently running.
public bool IsRunning { get; private set; }
// Value that indicates whether process has exited.
public bool HasExited { get; private set; }
// Process ID of the running command.
public int ProcessId { get; private set; }
// Exit code of process. Only set if HasExited is True.
public int ExitCode { get; private set; }
// Standard output of command.
public string StandardOutput
{
get
{
return _standardOutput.ToString();
}
}
// Standard error of command.
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
// Raised when standard output receives data.
public event EventHandler<DataEventArgs> OutputDataReceived = (sender, args) => { };
// Raised when standard error receives data.
public event EventHandler<DataEventArgs> ErrorDataReceived = (sender, args) => { };
// Raised when process has exited.
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
// Run command synchronously.
public void Run(int timeOutInMilliseconds = NoTimeOut)
{
if (!IsRunning && !HasExited)
{
BeginRun();
if (timeOutInMilliseconds == NoTimeOut)
{
_process.WaitForExit();
}
else
{
_process.WaitForExit(timeOutInMilliseconds);
}
}
}
// Run command asynchronously.
public void BeginRun()
{
if (!IsRunning && !HasExited)
{
if (_process.Start())
{
IsRunning = true;
ProcessId = _process.Id;
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
}
}
}
// Write command to standard input.
public void WriteToStandardInput(string command)
{
if (IsRunning && !HasExited)
{
_process.StandardInput.Write(command);
}
}
// Kill process.
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
// Recursively kill child and grand-child processes of parent process.
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)
{
// Ignore; this exception is thrown if the process with the given ID has already exited
}
}
The only private
method here is a method that recursively kills child processes started from the main process.
Event Handlers
// Handler for OutputDataReceived event of process.
private void _process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
_standardOutput.AppendLine(e.Data);
OutputDataReceived(this, new DataEventArgs(e.Data));
}
// Handler for ErrorDataReceived event of process.
private void _process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
_standardError.AppendLine(e.Data);
ErrorDataReceived(this, new DataEventArgs(e.Data));
}
// Handler for Exited event of process.
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
andKillChildProcesses
methods - October 6, 2012
- Initial version