Paul Selles

Computers and cats

Tag Archives: Process

Running Command Line Executables in C#

The Problem

A co-worker wanted to build a tool in C# which involves running a command line executable. Since an API is not available to them, they ask for a quick way to launch a command line executable with the option of parsing through the standard output.

 

The Solution

My solution for them that will allow them to:

  • Launch a command line executable with arguments
  • Have the option to retrieve Standard Output
  • Have the option to retrieve Standard Error
  • Have the option to throw exception on Standard Error
  • Forward any exceptions (ex: executable does not exist)

The solution is a single static function that uses the System.Diagnostic.Process class to perform the execution [1]. The executable name and the arguments are fed into the Process.StartInfo property as a System.Diagnostic.ProcessStartInfo object [2][3]. We want to set the Process.StartInfo.UseShellExecute to false since we want the option to retrieve both standard output and standard error [4]. Standard output and standard error can be redirected with Process.StartInfo.RedirectStandardOutput and Process.StartInfo.RedirectStandardOutput respectively, which we will turn on or off with flags[5][6]. Finally, in the event that we are reading the Process.StandardOutput or Process.StandardOutput, we need to invoke the method Process.WaitForExit [6][7][8].

 

The Implementation

public static class CommandLine
{
    /// <summary>
    /// Run console command in C#
    /// </summary>
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public static string Execute(string executable,
        string arguments,
        bool standardOutput = false,
        bool standardError = false,
        bool throwOnError = false)
    {
        // This will be out return string
        string standardOutputString = string.Empty;
        string standardErrorString = string.Empty;

        // Use process
        Process process;

        try
        {
            // Setup our process with the executable and it's arguments
            process = new Process();
            process.StartInfo = new ProcessStartInfo(executable, arguments);

            // To get IO streams set use shell to false
            process.StartInfo.UseShellExecute = false;

            // If we want to return the output then redirect standard output
            if (standardOutput) process.StartInfo.RedirectStandardOutput = true;

            // If we std error or to throw on error then redirect error
            if (standardError || throwOnError) process.StartInfo.RedirectStandardError = true;

            // Run the process
            process.Start();

            // Get the standard error
            if (standardError || throwOnError) standardErrorString = process.StandardError.ReadToEnd();

            // If we want to throw on error and there is an error
            if (throwOnError &amp;&amp; !string.IsNullOrEmpty(standardErrorString))
                throw new Exception(
                    string.Format("Error in ConsoleCommand while executing {0} with arguments {1}.",
                    executable, arguments, Environment.NewLine, standardErrorString));

            // If we want to return the output then get it
            if (standardOutput) standardOutputString = process.StandardOutput.ReadToEnd();

            // If we want standard error then append it to our output string
            if (standardError) standardOutputString += standardErrorString;

            // Wait for the process to finish
            process.WaitForExit();
        }
        catch (Exception e)
        {
            // Encapsulate and throw
            throw new Exception(
                string.Format("Error in ConsoleCommand while executing {0} with arguments {1}.", executable, arguments), e);
        }

        // Return the output string
        return standardOutputString;
    }
}

 

The Example

As an example here are the unit tests all centered around git.exe:

[TestClass]
public class CommandLineTests
{
    private const string GIT_PATH = @"C:\Program Files (x86)\Git\bin\git.exe";
    private const string GIT_VALID = "git version 1.9.2.msysgit.0";
    private const string GIT_USAGE = "usage: git [--version] [--help] [-C ] [-c name=value]";
    private const string GIT_ERROR = "git: 'kitten' is not a git command. See 'git --help'.";

    private const string GIT_ERRONIOUS_ARG = "kitten";
    private const string GIT_VALID_ARG = "version";

    [TestMethod]
    public void GitNoAgurments()
    {
        /// Act: Call command with no argument
        string value = CommandLine.Execute(GIT_PATH, "");

        /// Assert: Output and error off expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitValidArgument()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument
        string value = CommandLine.Execute(GIT_PATH, argument);

        /// Assert: Output and error off expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitErroniousArgument()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument
        string value = CommandLine.Execute(GIT_PATH, argument);

        /// Assert: Output and error off expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitNoAgurmentsWithStandardOutputOnly()
    {
        /// Act: Call command with no argument standard output on
        string value = CommandLine.Execute(GIT_PATH, "", true);

        /// Assert: Output expected
        StringAssert.StartsWith(value, GIT_USAGE);
    }

    [TestMethod]
    public void GitValidArgumentWithStandardOutputOnly()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard output on
        string value = CommandLine.Execute(GIT_PATH, argument, true);

        /// Assert: Error expect output expected to be empty
        StringAssert.StartsWith(value, GIT_VALID);
    }

    [TestMethod]
    public void GitErroniousArgumentWithStandardOutputOnly()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard output on
        string value = CommandLine.Execute(GIT_PATH, argument, true);

        /// Assert: Error expect output expected to be empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitNoAgurmentsWithStandardErrorOnly()
    {
        /// Act: Call command no with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, "", standardError: true);

        /// Assert: No error expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitValidArgumentWithStandardErrorOnly()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, standardError: true);

        /// Assert: Expect error expect not empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitErroniousArgumentWithStandardErrorOnly()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, standardError: true);

        /// Assert: Expect error expect not empty
        StringAssert.StartsWith(value, GIT_ERROR);
    }

    [TestMethod]
    public void GitNoAgurmentsWithStandardOutputAndStandardError()
    {
        /// Act: Call command no with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, "", true, true);

        /// Assert: No error expect empty
        StringAssert.StartsWith(value, GIT_USAGE);
    }

    [TestMethod]
    public void GitValidArgumentWithStandardOutputAndStandardError()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, true, true);

        /// Assert: Expect error expect not empty
        StringAssert.StartsWith(value, GIT_VALID);
    }

    [TestMethod]
    public void GitErroniousArgumentWithStandardOutputAndStandardError()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, true, true);

        /// Assert: Expect error expect not empty
        StringAssert.StartsWith(value, GIT_ERROR);
    }

    [TestMethod]
    public void GitNoAgurmentsWithThrowOnError()
    {
        /// Act: Call command no with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, "", throwOnError: true);

        /// Assert: No error expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitValidArgumentWithThrowOnError()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, throwOnError: true);

        /// Assert: No error expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    [ExpectedException(typeof(Exception))]
    public void GitErroniousArgumentWithThrowOnError()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, throwOnError: true);      
    }

    [TestMethod]
    public void GitNoAgurmentsWithStandardOutputAndStandardErrorAndThrowOnError()
    {
        /// Act: Call command no with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, "", true, true, true);

        /// Assert: No error expect empty
        StringAssert.StartsWith(value, GIT_USAGE);
    }

    [TestMethod]
    public void GitValidArgumentWithStandardOutputAndStandardErrorAndThrowOnError()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, true, true, true);

        /// Assert: No error expect empty
        StringAssert.StartsWith(value, GIT_VALID);
    }

    [TestMethod]
    [ExpectedException(typeof(Exception))]
    public void GitErroniousArgumentWithStandardOutputAndStandardErrorAndThrowOnError()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, true, true, true);
    }

    [TestMethod]
    [ExpectedException(typeof(Exception))]
    public void ExecutableNameExeption()
    {
        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH + "a", "");
    }
}

 

Enjoy

 

References

[1] Process Class. MSDN Libarary.

[2] Process.StartInfo Property. MSDN Libarary.

[3] ProcessStartInfo Class. MSDN Libarary.

[4] ProcessStartInfo.UseShellExecute Property. MSDN Libarary.

[5] ProcessStartInfo.RedirectStandardOutput Property. MSDN Libarary.

[6] ProcessStartInfo.RedirectStandardError Property. MSDN Libarary.

[7] Process.StandardOutput Property. MSDN Libarary.

[8] Process.StandardError Property. MSDN Libarary.

[9] Process.WaitForExit Method. MSDN Libarary.

 

Paul

Advertisements
%d bloggers like this: