.NET C# Programming

Handling External Processes in C#

Pinterest LinkedIn Tumblr

Image by rawpixel.com on Freepik

 The code is available on GitHub here

When working with C# applications, there may be times when you need to call an external process and wait for its completion before continuing with your application. The Process class in System.Diagnostics allows you to launch a new process. In this post, we will explore how to call a process, wait for its completion, and get the result in C#.

Calling a Process

There is one way to initiate a process, but it can be done with different arguments. The Process.Start() method is used to start a process, and it comes with multiple overloads, allowing you to specify the necessary arguments based on your requirements.

The most straightforward way to start a process using Process.Start() is to provide a file name. For instance, if you want to launch the notepad application, you can use the following code:

// It starts the notepad application.
Process.Start("notepad.exe");

// You can also specify a file name with arguments.
Process.Start("notepad.exe", "example.txt");

// When a filename is specified, Windows will launch the program that is registered. to handle that file type's extension.
Process.Start("e:\\file.txt")

If you need more control over the process’s behavior, you can use the ProcessStartInfo class to specify additional parameters. For instance, you can set the working directory, arguments, and other properties using the ProcessStartInfo object, as shown in the following example:

var startInfo = new ProcessStartInfo
{
   FileName = "the file name of the application"
   UseShellExecute = false,
   WorkingDirectory = @"C:\Temp",
   WindowStyle = ProcessWindowStyle.Hidden,
   CreateNoWindow = true
}

var process = Process.Start(startInfo);

The ProcessStartInfo class offers a range of properties that can be used to configure how a process is started. Here are some details on the most commonly used properties:

UseShellExecute – This property should be set to false if you wish to capture and redirect the input, output, and error output of the launched process. If the property is set to true, output from the external process will be written in the host process console.

Arguments – The arguments to be passed to the process when it is started.

CreateNoWindow– This property should be set to true if you want the process to start without creating a new window to contain it.

RedirectStandardOutput – Set this property to true if you want the output to be written to the StandardOutput. If this property is set to true, the corresponding streams can be accessed using the StandardInput, StandardOutput, and StandardError properties. For more details on accessing these streams, see the Getting the Result section.

RedirectStandardInput – Set this property to true if you want to redirect the standard input of the process. This property works in a similar way to RedirectStandardOutput.

RedirectStandardError – Set this property to true if you want to redirect the standard error of the process. This property also works in a similar way to RedirectStandardOutput.

Waiting for Completion

In order to wait for a process to complete before continuing with the application, the WaitForExit() method is used as shown below:

process.WaitForExit();

This method blocks the current thread until the process is completed.

Getting the Result

To get the result of the process, such as the output or return value, the StandardOutput property is used as shown below:

var startInfo = new ProcessStartInfo
{ 
   FileName = @"app.exe",
   UseShellExecute = false,
   CreateNoWindow = false,
   RedirectStandardOutput = true // this must be set to true.
};

var process = _Process.Start(startInfo);
// output variable holds whatever the external process is writing to Console.
var output = process.StandardOutput.ReadToEnd();

Additionally, the exit code of the external process can be easily obtained.

Passing and getting arguments

To pass arguments to the process, the Arguments property is used as shown below:

var startInfo = new ProcessStartInfo
{ 
   FileName = @"app.exe",
   Arguments = "-x 1 -y 2"
};

var process = _Process.Start(startInfo);

To obtain the arguments passed to the process, we can use the following code:

static void Main(string[] args)
{
   if (args.Length == 2)
   {
      if (!int.TryParse(args[0], out int x)) return;
      if (!int.TryParse(args[0], out int y)) return;
      Console.WriteLine(x + y);
   }
}

Another way to obtain arguments is to use a library like McMaster.Extensions.CommandLineUtils. This library provides Attributes that can be used to define separate properties for each argument as shown below:

class Program
{
   [Option(ShortName = "x", Description = "the first number to add.")]
   public int X { get; set; }

   [Option(ShortName = "y", Description = "the second number to add.")]
   public int Y { get; set; }

   static void Main(string[] args)
      => CommandLineApplication.Execute<Program>(args);

   private void OnExecute()
   {
      Console.Write(X + Y);
   }
}

Handle Processes with a ProcessController

A ProcessController class can be created to handle the complexity of starting a process, getting its result, and deserializing the result to a specific object. The class takes the filename where the application is located and a dictionary of arguments as constructor arguments. The RunAndGetResult method is used to get a result of a specific custom type or a primitive or a string as a result. The deserialization of the result is done using the Newtonsoft library.

public class ProcessController
{
   private readonly Dictionary<string, string> _args;
   private readonly string _fileName;

   public ProcessController(string fileName, Dictionary<string, string> args = null)
   {
      _fileName = fileName;
      _args = args ?? new Dictionary<string, string>();
   }

   public ProcessResult<string> RunAndGetResult()
      => RunAndGetResult<string>();

   public ProcessResult<T> RunAndGetResult<T>() 
   {
      var startInfo = new ProcessStartInfo
      {
         FileName = _fileName,
         UseShellExecute = false,
         CreateNoWindow = false,
         RedirectStandardOutput = true,
      };

      if (_args.Any())
         startInfo.Arguments = string.Join(" ", _args.Select(x => $"-{x.Key} {x.Value}"));

      var process = _Process.Start(startInfo);

      process.WaitForExit();
      var output = process.StandardOutput.ReadToEnd();

      var exitCode = process.ExitCode;

      T result = default;
      if (exitCode == 0 )
      {
         if (typeof(T) != typeof(string) && !typeof(T).IsPrimitive)
         {
            result = JsonConvert.DeserializeObject<T>(output);
         }
         else
            result = (T)(object)output;
      }
      return new ProcessResult<T>(result, exitCode);
   }
}

public class ProcessResult<T>
{
   public T Result { get; }
   public int ExitCode { get; }
   public bool HasError => ExitCode != 0;

   public ProcessResult(T result, int exitCode)
   {
      Result = result;
      ExitCode = exitCode;
   }
}

The code of the ProcessController class can be found on Github.

Below is an example of how to use the ProcessController to call various processes and obtain their results:

class Program
{
   static void Main(string[] args)
   {
      var processWithStringResult = new ProcessController(@"..\..\..\..\Process.StringResult\bin\Debug\netcoreapp3.1\Process.StringResult.exe");
      var res = processWithStringResult.RunAndGetResult();
      Console.WriteLine("Result from Process.StringResult:");
      Console.WriteLine(JsonConvert.SerializeObject(res));

      var processWithExitCode2 = new ProcessController(@"..\..\..\..\Process.WithExitCode2\bin\Debug\netcoreapp3.1\Process.WithExitCode2.exe");
      res = processWithExitCode2.RunAndGetResult();
      Console.WriteLine("Result from Process.WithExitCode2:");
      Console.WriteLine(JsonConvert.SerializeObject(res));

      var processWithArgs = new ProcessController(@"..\..\..\..\Process.WithArgs\bin\Debug\netcoreapp3.1\Process.WithArgs.exe",
         new Dictionary<string, string>()
         {
            {"x", "1"}, {"y", "2"}
         });
      res = processWithArgs.RunAndGetResult();
      Console.WriteLine("Result from Process.WithArgs:");
      Console.WriteLine(JsonConvert.SerializeObject(res));

      var processWithJsonResult = new ProcessController(@"..\..\..\..\Process.WithJsonResult\bin\Debug\netcoreapp3.1\Process.WithJsonResult.exe");
      var res2 = processWithJsonResult.RunAndGetResult<User>();
      Console.WriteLine("Result from Process.WithJsonResult:");
      Console.WriteLine(JsonConvert.SerializeObject(res2));
   }
}

class User
{
   public string Email { get; set; }
   public string Username { get; set; }
}

The following would be the result:

Result from Process.StringResult:
{"Result":"Hello World!","ExitCode":0,"HasError":false}
Result from Process.WithExitCode2:
{"Result":null,"ExitCode":2,"HasError":true}
Result from Process.WithArgs:
{"Result":"3","ExitCode":0,"HasError":false}
Result from Process.WithJsonResult:
{"Result":{"Email":"user@email.com","Username":"username"},"ExitCode":0,"HasError":false}

Write A Comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.