Table of Contents

Running Processes

Every invocation is described by a single immutable ProcessRunOptions record. This page covers the core execution options; failure handling and logging have their own pages.

Sync or async

IProcessRunner exposes two methods:

// Blocks the calling thread until the process exits.
ProcessRunResult Run(ProcessRunOptions options);

// Awaits process exit; supports cancellation.
Task<ProcessRunResult> RunAsync(ProcessRunOptions options, CancellationToken cancellationToken = default);

Both behave identically apart from threading: same logging, same capture, same failure handling. Prefer RunAsync in async code paths and anywhere you want cancellation support (see Cancellation).

Passing arguments

Arguments can be supplied as a single pre-joined string or as an array of tokens:

// As a single string
var options = new ProcessRunOptions("git", "status --short");

// As tokens
var options2 = new ProcessRunOptions("git", ["status", "--short"]);

When using the array form, empty and whitespace-only entries are removed before joining. This makes conditional arguments clean — no spurious double spaces:

var verbose = ShouldBeVerbose();

var options = new ProcessRunOptions("git",
[
    "status",
    "--short",
    verbose ? "--verbose" : "",   // dropped entirely when empty
]);
Important

Arguments are ultimately passed to the OS as a single string (ProcessStartInfo.Arguments). If an argument value can contain spaces or special characters (e.g. a user-supplied file path), quote it yourself: $"\"{path}\"".

Working directory

By default the child process inherits the current process's working directory. Set WorkingDirectory to run a tool from a specific location:

var result = processRunner.Run(new ProcessRunOptions("npm", "ci")
{
    WorkingDirectory = "/repo/frontend",
});

Environment variables

EnvironmentVariables lets you add, override, or remove variables for the child process. Variables you don't list are inherited unchanged from the current process:

var result = processRunner.Run(new ProcessRunOptions("npm", "ci")
{
    EnvironmentVariables =
    {
        ["NODE_ENV"] = "production",  // add or override
        ["CI"] = "true",
        ["NPM_TOKEN"] = null,         // a null value removes the inherited variable
    },
});

The invocation log line includes the variables you set, so be careful not to put secrets in values you don't want logged (or lower InvocationLogLevel — see Logging).

Reusing and varying options

ProcessRunOptions is a record, so you can keep a baseline and vary it with with:

var git = new ProcessRunOptions("git", "")
{
    WorkingDirectory = repoRoot,
    InvocationLogLevel = LogLevel.Debug,
};

var status = git with { Args = "status --short" };
var fetch  = git with { Args = "fetch --prune" };

Concurrency

IProcessRunner is stateless and registered as a singleton — it is safe to run multiple processes concurrently from multiple threads.

See also