A small, focused .NET library for running external processes with first-class logging, real-time output streaming,
full output capture, and fail-fast error handling. It wraps System.Diagnostics.Process behind a clean, injectable
IProcessRunner abstraction so your build targets, tools, and services can shell out to executables (dotnet, git,
npm, …) without re-implementing redirection, buffering, and exit-code handling every time.
- Sync and async APIs.
RunandRunAsync, the latter withCancellationTokensupport. - Real-time streaming. Stdout and stderr are read asynchronously and logged line-by-line as the process runs, instead of being buffered until exit.
- Full output capture. Complete stdout/stderr are always captured into the result, regardless of log level, so you can inspect them even when logging is suppressed.
- Fail-fast by default. A non-zero exit code throws a descriptive
ProcessException(including command, exit code, working directory, and stderr, plus the full result), or you opt in to inspect failures yourself. - Configurable log levels. Control how the invocation, stdout, and stderr are logged.
- Per-line transforms. Rewrite or suppress individual output/error lines, for example to redact secrets or filter noisy progress output.
- Working directory and environment variables. Run from any directory and inject or remove environment variables per invocation.
- DI-ready. Register once with
AddProcessRunner()and injectIProcessRunneranywhere.
dotnet add package Invex.ProcessThe package targets net10.0. It depends on
Microsoft.Extensions.DependencyInjection.Abstractions and Microsoft.Extensions.Hosting.Abstractions, and logs
through ILogger, so configure logging on your host (it is by default with Host.CreateApplicationBuilder).
Register the process runner with your dependency injection container:
using Invex.Process;
using Microsoft.Extensions.DependencyInjection;
services.AddProcessRunner();Then inject IProcessRunner and run a process:
using Invex.Process;
public sealed class VersionReporter(IProcessRunner processRunner)
{
public async Task<string> GetDotnetVersionAsync(CancellationToken cancellationToken)
{
var result = await processRunner.RunAsync(
new ProcessRunOptions("dotnet", "--version"),
cancellationToken);
return result.Output.Trim();
}
}Arguments can be a single pre-joined string or an array of tokens; in the array form, empty and whitespace-only entries are removed before joining, which makes optional arguments easy. Set a working directory and add, override or remove environment variables per call:
var verbose = true;
var status = new ProcessRunOptions("git", ["status", "--short", verbose ? "--verbose" : ""]);
var result = processRunner.Run(new ProcessRunOptions("npm", "ci")
{
WorkingDirectory = "/repo/frontend",
EnvironmentVariables =
{
["NODE_ENV"] = "production",
["NPM_TOKEN"] = null, // a null value removes the inherited variable
},
});See Running Processes.
By default a non-zero exit code throws with a message that includes the command, exit code, working directory and
stderr. Set AllowFailedResult to inspect the result yourself instead:
var result = processRunner.Run(new ProcessRunOptions("dotnet", "format --verify-no-changes")
{
AllowFailedResult = true,
});
if (result.ExitCode != 0)
{
// Decide what to do with the failure...
}See Handling Failures.
Each invocation can tune how it is logged. The full output is still captured in the result regardless of these settings:
var options = new ProcessRunOptions("dotnet", "test")
{
InvocationLogLevel = LogLevel.Debug, // the "Run: dotnet test" line
OutputLogLevel = LogLevel.Information, // each stdout line
ErrorLogLevel = LogLevel.Error, // each stderr line
};Note
On a non-zero exit, stdout/stderr configured below Information are re-logged in full at Information/Warning
respectively, so the failure is visible in the logs even when per-line logging was quiet.
See Logging.
TransformOutput and TransformError are applied to each line before it is logged and captured. Returning null
suppresses the line entirely:
var options = new ProcessRunOptions("docker", "build .")
{
// Drop noisy progress lines completely
TransformError = line => line.StartsWith("progress:")
? null
: line,
};See Transforming Output.
RunAsync accepts a CancellationToken that cancels the wait for the process to exit and throws
OperationCanceledException.
Important
Cancelling the token aborts the wait but does not kill the underlying OS process.
See Cancellation.
Full documentation: https://invex-games.github.io/process/
| Guide | Contents |
|---|---|
| Introduction | The problem it solves, when to use it, and design principles. |
| Getting Started | Installation, registration, and your first process. |
| Running Processes | Arguments, working directories, environment variables, concurrency. |
| Handling Failures | Fail-fast exceptions, AllowFailedResult, and startup failures. |
| Logging | What is logged, log levels, failure promotion, and secrets. |
| Transforming Output | Redacting, filtering and normalizing output lines. |
| Cancellation | What cancelling RunAsync does and doesn't do. |
| Testing Your Code | Faking IProcessRunner in unit tests. |
| Versioning | The Semantic Versioning policy and what counts as the public API. |
| API Reference | Every public type, with links into the generated reference. |
dotnet build Invex.Process.slnx
dotnet test Invex.Process.slnxThe documentation site is built with DocFX through the
Atom build in _atom/:
atom BuildDocs # generate the site
atom ServeDocs # build and serve it locallyWithout the atom tool installed, run the same targets with dotnet run --project _atom -- BuildDocs.
Licensed under the MIT License.