Paul Selles
Computers and cats
Tag Archives: Process
Running Command Line Executables in C#
June 4, 2014
Posted by on 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 && !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
Recent Comments