Click here to Skip to main content
15,884,177 members
Articles / Programming Languages / C#
Tip/Trick

C# Command Prompt class

Rate me:
Please Sign up or sign in to vote.
4.68/5 (13 votes)
13 Jun 2013CPOL3 min read 52.3K   2.6K   45   1
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

C#
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

C#
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

C#
// 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

C#
// 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

C#
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

C#
// 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

C#
// 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

C#
// 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 Smile | <img src=

History

  • November 9, 2012
    • Added the WriteToStandardInput and KillChildProcesses methods
  • October 6, 2012
    • Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
I have been developing software for several years, both personally and professionally. These days, I'm mostly coding in C++, C#, Objective-C and Python. In my spare time, I can usually be found playing video games, reading books, bicycling and hiking.

Comments and Discussions

 
Questiondoesnt quite work Pin
bryce10-Feb-18 0:10
bryce10-Feb-18 0:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.