Skip to content

Creating a Command with CommandBuilder

Dustin Horne edited this page May 14, 2024 · 3 revisions

Installation

Install the ParentElement.ReProcess package from NuGet. This package has no additional dependencies aside from .NET.

Creating a Command

Commands are designed to be immutable. A single command can be executed more than once, however, its properties cannot be changed once constructed. If a command needs to be executed multiple times in parallel, a new command instance must be created for each parallel process. Commands are constructed using the CommandBuilder class. To construct a command in its simplest form, do the following:

var command = CommandBuilder.Create("docker")
  .Build();

To Create Multiple Command Instances

var builder = CommandBuilder.Create("docker");

//First command instance
var cmd1 = builder.Build();

//Second command instance
var cmd2 = builder.Build();

CommandBuilder Options

By default, a command will have the following properties:

  • Execution happens in the background
  • Execution is silent (StdOut and StdErr are not redirected)
  • If capturing output, messages processing is delayed 50ms per iteration
  • Working Directory is the path or current working directory of the process invoking the command.

These options can all be modified when constructing the command.

Adding arguments

Arguments can be added as a string, a collection, or independently. The end result is the same.

Adding an argument string

var command = CommandBuilder.Create("docker")
  .WithArgument("pull postgres")
  .Build();

Adding arguments individually

var command = CommandBuilder.Create("docker")
  .WithArgument("pull")
  .WithArgument("postgres")
  .Build();

Adding arguments as a collection

var command = CommandBuilder.Create("docker")
  .WithArguments(["pull", "postgres"])
  .Build();

Capturing Output

If you want the process to redirect StdErr and StdOut, you must construct the command using the WithOutput option.

var command = CommandBuilder.Create("docker")
  .WithArgument("pull postgres")
  .WithOutput()
  .Build();

Output redirection is backed by Channels when the .WithOutput() option is supplied. By default, an unbounded channel is used. This means that the buffer can grow indefinitely. If you're immediately processing the messages, this isn't an issue, but for a long running process with a lot of output, this could cause an issue with memory consumption. For that reason, .WithOutput has an optional parameter called maxMessageCount. When this value is supplied, the channel retains only the the last [maxMessageCount] number of messages and the oldest are discarded. Example usage:

var command = CommandBuilder.Create("docker")
 .WithArgument("pull postgres")
 .WithOutput(5)
 .Build();

The preceding example will execute the command and only retain the last 5 messages sent to either StdErr or StdOut.

Setting the Working Directory

The working directory is supplied using the .WithWorkingDirectory(...) option. This method checks to see whether the supplied directory exists and will throw a DirectoryNotFoundException if it does not.

var command = CommandBuilder.Create("docker")
 .WithArgument("build -f Dockerfile")
 .WithWorkingDirectory("/usr/images/custom")
 .Build();

Unleashing Output Processing Frequency

Message processing is done via IAsyncEnumerable. By default, while iterating over the enumerable, there is a 50ms delay applied to save on unnecessary CPU cycles by continuously iterating when no new messages are available. This can be overridden to remove the delay with the .WithAggressiveOutputProcessing() option.

var command = CommandBuilder.Create("docker")
  .WithArgument("pull postgres")
  .WithOutput()
  .WithAggressiveOutputProcessing()
  .Build();