This page is the canonical reference for how CliInvoke is configured. It documents the configuration models, the relationship between the builders and the models they produce, and the role of the invoker that consumes them.
If you only read one section, read Construction (the default) and the Reference Appendix.
This page exists to answer three questions precisely:
A Configuration Model is an immutable value-bearing object that describes one aspect of how a process should be run. Models in this library are POCOs (plain old CLR objects): they hold data, expose read-only properties, and implement value equality.
A Builder is a fluent, mutable object used to assemble a
configuration model. Each builder is a short-lived staging area whose
only job is to produce exactly one model via Build(). The produced
model is independent of the builder — they do not share lifetime.
An Invoker is the abstraction that turns a configuration model
into a running process. The invoker owns nothing about the
configuration; it reads the model, spawns a System.Diagnostics.Process,
runs it, captures the result, and disposes the OS resources it
allocated.
In v3, init construction is the default. Every configuration model has a public constructor that accepts its required parameters positionally. The builder is reserved for advanced scenarios that the simple constructors cannot express.
ProcessConfiguration — direct constructionProcessConfiguration is the only required model. TargetFilePath is
a required init property — the constructor throws ArgumentException
if it is null or empty. WorkingDirectoryPath defaults to
Directory.GetCurrentDirectory(), so the caller's current directory is
the configured starting point when the property is not explicitly set.
When set to a non-empty value, it validates the directory exists at init
time and throws DirectoryNotFoundException if it does not.
Convenience constructor — for the common case of target file path plus arguments:
ProcessConfiguration config = new ProcessConfiguration("dotnet", "--version");
This sets TargetFilePath, Arguments, and OutputRedirection (which
defaults to true); all other properties take their documented
defaults.
Parameterless constructor with an object initializer — when you need to set additional init-only properties:
ProcessConfiguration config = new ProcessConfiguration
{
TargetFilePath = "dotnet",
Arguments = "--version",
OutputRedirection = true,
WorkingDirectoryPath = @"C:\my\project"
};
ArgumentList — replaces the v2 ArgumentsList property. When
non-empty, the control adapter emits entries via
ProcessStartInfo.ArgumentList instead of the single Arguments
string, so the OS command-line parser passes each entry unmodified:
ProcessConfiguration config = new ProcessConfiguration("dotnet")
{
ArgumentList = ["--list-sdks", "--verbosity", "minimal"]
};
The model is mostly immutable: most properties have only a getter.
TargetFilePath, Arguments, and OutputRedirection are mutable on
the public surface for back-compat reasons — see the
Reference Appendix for the exact mutability of
each property.
ProcessExitConfiguration — direct constructionProcessExitConfiguration exitConfig = new ProcessExitConfiguration(
ProcessTimeoutPolicy.FromTimeSpan(TimeSpan.FromSeconds(30)));
Or use the static factory for the common graceful case:
ProcessExitConfiguration exitConfig = ProcessExitConfiguration.CreateGraceful();
UserCredential — direct constructionUserCredential credential = new UserCredential
{
Domain = "CONTOSO",
UserName = "admin",
Password = securePassword
};
UserCredential implements IDisposable — see the
Resource Disposal guide for ownership rules.
ProcessResourcePolicy — direct constructionProcessResourcePolicy policy = ProcessResourcePolicy.Default;
Or construct a custom policy:
ProcessResourcePolicy policy = new ProcessResourcePolicy
{
PriorityClass = ProcessPriorityClass.High
};
The builder is not required and is positioned as an advanced construction path. Use the builder when you need features that the simple constructors cannot express:
ConfigureArguments(Action<ArgumentsSpec>)
wraps and validates individual arguments, applying character escaping
(quotes, backslashes, control characters) before joining.ConfigureUserCredential(Action<UserCredentialSpec>) for Windows-only
credential injection with SecureString password staging.ConfigureProcessResourcePolicy(Action<ProcessResourcePolicySpec>)
for processor affinity and resource settings with internal pairing
logic (e.g., SetMinWorkingSet without a prior SetMaxWorkingSet
fabricates Max = Min + 1).using CliInvoke.Builders;
IProcessConfigurationBuilder builder = new ProcessConfigurationBuilder("dotnet")
.SetArguments(["--info"])
.SetOutputRedirection(true);
ProcessConfiguration config = builder.Build();
The builder delegates to the same init-only properties under the hood; it adds escaping, credential spec, and resource policy callbacks on top.
The builder and the direct constructor do not always produce the same model for the same input. Concretely:
OutputRedirection default differs. The model's public
constructor defaults outputRedirection to true; the builder
defaults it to false. new ProcessConfiguration("git") and
new ProcessConfigurationBuilder("git").Build() produce
configurations with different OutputRedirection values.SetWorkingDirectory throws DirectoryNotFoundException
if the directory does not exist. The model's constructor also
validates via the WorkingDirectoryPath init setter.Add and
AddRange on ArgumentsSpec apply character escaping before
joining. The model's Arguments string is stored verbatim.ProcessResourcePolicySpec. Calling
SetMinWorkingSet without a prior SetMaxWorkingSet fabricates
Max = Min + 1 so the resulting policy is internally consistent.
The model allows Min and Max to be set independently.| Builder method | Init property | Notes |
|---|---|---|
SetTargetFilePath(string) |
TargetFilePath |
Required in both paths |
SetArguments(string) |
Arguments |
Verbatim string |
SetArguments(IEnumerable<string>) |
ArgumentList |
Init-only |
ConfigureArguments(Action<ArgumentsSpec>) |
(no direct init) | Escaping, validation |
SetOutputRedirection(bool) |
OutputRedirection |
Default differs: true (init) vs false (builder) |
SetWorkingDirectory(string) |
WorkingDirectoryPath |
Builder validates existence |
SetWindowCreation(bool) |
WindowCreation |
|
ConfigureShellExecution() |
UseShellExecution |
|
ConfigureEnvironmentVariables(Action<EnvironmentVariablesSpec>) |
EnvironmentVariables |
|
ConfigureUserCredential(Action<UserCredentialSpec>) |
Credential |
Advanced: SecureString staging |
ConfigureProcessResourcePolicy(Action<ProcessResourcePolicySpec>) |
ResourcePolicy |
Advanced: pairing logic |
The builder is the right choice when:
\, ", or
control characters).UserCredentialSpec for Windows-domain credential staging
with SecureString.ProcessResourcePolicySpec for resource-policy callback
flows with internal pairing logic.AddCliInvoke (namespace CliInvoke.Extensions, ships in the
CliInvoke package) registers all core services. Call it once at
startup:
services.AddCliInvoke();
You can configure the middleware pipeline when registering:
services.AddCliInvoke(builder => builder.UseMiddleware<LoggingMiddleware>());
If you use the CliInvoke.Specializations package's
UsePowerShell()/UseCmd()middleware, also callAddCliInvokeSpecializations()(same namespace) with the sameServiceLifetimeso those middleware types resolve from the container.
Every CliInvoke invocation moves through three stages.
┌──────────────┐ init / Build() ┌──────────────────┐ ExecuteAsync ┌──────────┐
│ Construction │ ──────────────────► │ Configuration │ ────────────────► │ Invoker │
│ (init or build)│ │ Model │ │ (executes)│
└──────────────┘ │ (immutable) │ └──────────┘
└──────────────────┘
ProcessConfiguration.IProcessInvoker.ExecuteAsync,
IExternalProcess.StartAsync (followed by a separate capture
call), or the static CliRun.RunAsync family. Each consumer
reads the model and runs the process; the result is then obtained
from the returned task or, for IExternalProcess, by calling
WaitForExitOrTimeoutAsync / CaptureBufferedResultAsync
after the process has started.The same model can be reused across multiple invocations.
The library has four top-level configuration models. Three of them are
optional; only ProcessConfiguration is required.
| # | Model | Required? | Purpose |
|---|---|---|---|
| 1 | ProcessConfiguration |
Yes | Describes what to run and how to start it. |
| 2 | ProcessExitConfiguration |
No | Describes timeout, exception, and cancellation behaviour. |
| 3 | UserCredential |
No | Windows-domain credentials for the spawned process. |
| 4 | ProcessResourcePolicy |
No | Processor affinity, priority class, and working-set sizes. |
ProcessExitConfiguration is the only one passed as a separate
parameter to the invoker; the others are referenced from
ProcessConfiguration.
ProcessConfigurationDefined in src/CliInvoke.Core/Primitives/ProcessConfiguration.cs.
public class ProcessConfiguration : IEquatable<ProcessConfiguration>
The only required model. It describes the executable to run, the arguments to pass, and the OS-level knobs that affect how the process is spawned (working directory, environment, redirection, credentials, resource policy, encodings).
Required init property: TargetFilePath. The constructor
throws ArgumentException if it is null or empty.
Relationship to other models: A ProcessConfiguration may hold
references to a UserCredential (via Credential) and a
ProcessResourcePolicy (via ResourcePolicy). It does not hold a
reference to a ProcessExitConfiguration; the exit configuration is
passed alongside it to the invoker. This separation is intentional:
many invocations share the same ProcessConfiguration but differ in
their ProcessExitConfiguration (e.g., one has a timeout, another
does not).
Disposal: ProcessConfiguration does not implement IDisposable. It is a plain
immutable value object. The StandardInput (StreamWriter) and UserCredential you supply
remain your responsibility to dispose — see the
Resource Disposal guide for ownership rules.
ProcessExitConfigurationDefined in src/CliInvoke.Core/Primitives/ProcessExitConfiguration.cs.
public class ProcessExitConfiguration : IEquatable<ProcessExitConfiguration>
Describes how the invoker should behave while the process is
running and after it exits. It is not stored on the
ProcessConfiguration; it is passed as a separate parameter to
IProcessInvoker.ExecuteAsync, ExecuteBufferedAsync. If the caller
does not pass one, the invoker uses
its own internal default.
Owns: a ProcessTimeoutPolicy (via TimeoutPolicy), a
ProcessExitBehaviour (via RequestedCancellationExitBehaviour), a
ProcessExceptionBehaviour (via ExceptionBehaviour), and a bool
(via CancellationThrowsException).
Relationship to the lifecycle: The exit configuration is
constructed in the same Construction → Model stage as the rest, but it is
the only model that can vary between two invocations of the same
ProcessConfiguration without changing the spawned process itself.
UserCredentialDefined in src/CliInvoke.Core/Primitives/UserCredential.cs.
public class UserCredential : IEquatable<UserCredential>, IDisposable
Represents the Windows-domain credentials under which the child
process should run. On non-Windows platforms the credential is
constructed but not applied; the property is [SupportedOSPlatform("windows")]
on Domain, Password, and LoadUserProfile.
UserCredential.Null is a static singleton representing "no
credential". This is the default assigned by
ProcessConfiguration's constructor.
Disposal: UserCredential owns its SecureString password and
implements IDisposable. A UserCredential assigned to a
ProcessConfiguration.Credential is not disposed by the configuration —
the caller must dispose it. See the
Resource Disposal guide.
ProcessResourcePolicyDefined in src/CliInvoke.Core/Primitives/Policies/ProcessResourcePolicy.cs.
public class ProcessResourcePolicy : IEquatable<ProcessResourcePolicy>
Describes OS-level resource constraints applied to the spawned
process: processor affinity, priority class, priority boost, and
working-set sizes. The default value (ProcessResourcePolicy.Default)
assigns affinity to all available logical processors; everything else
is left at the OS default.
Platform notes:
ProcessorAffinity is supported on Windows and Linux only.MinWorkingSet and MaxWorkingSet are not supported on Linux
or Android.The model is value-equal and immutable.
The third stage of the lifecycle has three consumption paths.
They all consume the same ProcessConfiguration (and, optionally,
the same ProcessExitConfiguration) but differ in lifetime,
ergonomics, and level of control.
| Consumer | Defined in | Lifetime | Use when |
|---|---|---|---|
IProcessInvoker |
src/CliInvoke.Core/IProcessInvoker.cs |
Fire-and-forget; runs to completion. | You want to run a process and get a result. |
IExternalProcess |
src/CliInvoke.Core/Processes/IExternalProcess.cs |
Long-lived handle to a running process. | You need to observe Started/Exited events, stream output, or interact with the process while it runs. |
CliRun (static) |
src/CliInvoke/CliRun.cs |
Fire-and-forget; builds the configuration for you. | You want the shortest possible call and don't need to reuse the configuration. |
IProcessInvokerpublic interface IProcessInvoker
{
Task<ProcessResult> ExecuteAsync(
ProcessConfiguration processConfiguration,
ProcessExitConfiguration? processExitConfiguration = null,
CancellationToken cancellationToken = default);
Task<BufferedProcessResult> ExecuteBufferedAsync(
ProcessConfiguration processConfiguration,
ProcessExitConfiguration? processExitConfiguration = null,
CancellationToken cancellationToken = default);
}
The invoker is the only stage of the lifecycle that performs side-effects. It is also the only stage that does not retain references to the configuration after the call returns — see the Resource Disposal guide.
The invoker does not validate the configuration. The configuration
models validate themselves in their constructors (e.g.,
ProcessConfiguration rejects a null TargetFilePath). The invoker
trusts the model it was given.
The invoker is also not the place to configure process behaviour —
that is the job of the configuration model. If you find yourself
wanting to pass a flag to ExecuteAsync that is not on
ProcessConfiguration or ProcessExitConfiguration, the right answer
is to add it to the appropriate model, not to overload the invoker.
IExternalProcesspublic interface IExternalProcess : IDisposable
{
ProcessConfiguration Configuration { get; init; }
ProcessExitConfiguration ExitConfiguration { get; }
bool HasExited { get; }
bool HasStarted { get; }
event EventHandler Started;
event EventHandler Exited;
Task StartAsync(CancellationToken cancellationToken);
Task StartAsync(ProcessConfiguration configuration, CancellationToken cancellationToken);
Task<ProcessResult> WaitForExitOrTimeoutAsync(CancellationToken cancellationToken);
Task<BufferedProcessResult> CaptureBufferedResultAsync(CancellationToken cancellationToken);
Task Kill();
}
IExternalProcess is a long-lived wrapper around the running
System.Diagnostics.Process. Unlike IProcessInvoker, which spawns
and joins in a single call, IExternalProcess exposes the process's
lifecycle as a sequence of steps you orchestrate yourself:
StartAsync(...). This returns once the OS
process has been launched and the redirected pipes are attached.
StartAsync returns a plain Task; it does not return the
process result. The result is obtained separately, by calling
one of the capture methods below.Started and Exited events, or
poll HasStarted / HasExited.WaitForExitOrTimeoutAsync for a plain
ProcessResult, or CaptureBufferedResultAsync to read the buffered
stdout/stderr into memory. These methods can be called at any point
during execution, not only at exit.Kill() to forcibly stop a runaway process.Fire-and-forget launching is not a member of
IExternalProcess. If you only need the OS process id and do not care about the result, use the staticCliRun.FireAndForget(...)API instead:// Returns the OS process id; the process runs detached from the caller. static int CliRun.FireAndForget(ProcessConfiguration configuration); static int CliRun.FireAndForget(string targetFilePath, string arguments = "", string? workingDirectory = null);
IExternalProcess is constructed by IExternalProcessFactory
(typically obtained via AddCliInvoke dependency injection, namespace
CliInvoke.Extensions) or by the CliRun static API below. The caller owns the
returned IExternalProcess and is responsible for disposing it —
see the Resource Disposal guide.
IProcessInvoker and IExternalProcess are not competing APIs.
IProcessInvoker is the right choice when you want a one-shot run;
IExternalProcess is the right choice when you need ongoing control.
Internally, the invoker factory constructs an IExternalProcess to
do its work.
CliRunpublic static class CliRun
{
public static Task<ProcessResult> RunAsync(
string targetFilePath,
string arguments = "",
string? workingDirectory = null,
TimeSpan? timeoutTimeSpan = null,
CancellationToken cancellationToken = default);
public static Task<ProcessResult> RunAsync(
ProcessConfiguration configuration,
ProcessExitConfiguration? exitConfiguration = null,
CancellationToken cancellationToken = default);
// RunBufferedAsync follows the same shape.
}
CliRun is a static façade that hides the configuration model
entirely. It is the right choice when you have a single command to
run, do not need to reuse the configuration, and do not want to
import an IProcessInvoker from DI.
Internally, CliRun constructs an IExternalProcess via a
default IExternalProcessFactory, calls StartAsync on it, then
calls one of the capture methods (WaitForExitOrTimeoutAsync for
RunAsync or CaptureBufferedResultAsync for RunBufferedAsync) to
obtain the result,
and disposes the IExternalProcess. The configuration is built for
you from the positional parameters; the timeout defaults to
ProcessTimeoutPolicy.Default.TimeoutThreshold (3 minutes); and
the exit configuration defaults to a graceful one. The factory and
file-path resolver are fixed defaults — CliRun keeps no process-wide
mutable state — so callers that need a custom factory or resolver
should construct an IProcessInvoker (or resolve one from the DI
container) instead.
CliRun is the most concise entry point and the most opinionated.
It trades the explicitness of the configuration model for
readability. Callers that need to configure anything beyond
targetFilePath, arguments, workingDirectory, and
timeoutTimeSpan should drop down to a constructed
ProcessConfiguration and use one of the other consumers.
| Scenario | Recommended construction |
|---|---|
| One-off command with a small fixed set of arguments | CliRun.RunAsync(...) (no model), or direct constructor on ProcessConfiguration |
| Process with many optional properties set conditionally | IProcessConfigurationBuilder (advanced) |
| Need a per-invocation timeout but a shared process configuration | Direct constructor on ProcessExitConfiguration passed alongside |
| Running as a different Windows user | Configure a UserCredentialSpec through the builder or construct a UserCredential directly |
| Constraining CPU or memory | Construct a ProcessResourcePolicy directly or use ProcessResourcePolicySpec (advanced) |
Need to observe Started/Exited events or stream output while the process runs |
IExternalProcess (via IExternalProcessFactory) |
| Run from a static context without DI | CliRun |
This appendix lists every property on every configuration model in CliInvoke, with its type, its default value, and where it lives in the source.
ProcessConfigurationDefined in src/CliInvoke.Core/Primitives/ProcessConfiguration.cs.
| Property | Type | Default | Mutability | Source line |
|---|---|---|---|---|
TargetFilePath |
string |
(required, no default) | Read-only | 111 |
Arguments |
string |
"" |
Read-only | 121 |
RequiresAdministrator |
bool |
false |
Read-only | 106 |
WorkingDirectoryPath |
string |
Directory.GetCurrentDirectory() |
Read-only | 116 |
WindowCreation |
bool |
false |
Read-only | 126 |
UseShellExecution |
bool |
false |
Read-only | 147 |
EnvironmentVariables |
IReadOnlyDictionary<string, string> |
new Dictionary<string, string>() |
Read-only | 131 |
Credential |
UserCredential |
UserCredential.Null |
Read-only | 136 |
OutputRedirection |
bool |
true |
Read-only | 168 |
RedirectStandardInput |
bool |
false |
Read-only | 163 |
StandardInput |
StreamWriter? |
StreamWriter.Null |
Read-only | 158 |
ResourcePolicy |
ProcessResourcePolicy |
ProcessResourcePolicy.Default |
Read-only | 181 |
StandardInputEncoding |
Encoding |
Encoding.Default |
Read-only | 186 |
StandardOutputEncoding |
Encoding |
Encoding.Default |
Read-only | 191 |
StandardErrorEncoding |
Encoding |
Encoding.Default |
Read-only | 196 |
Note on encodings:
Encoding.Defaultis UTF-8 on .NET 10 and later. Each encoding property is applied by the control adapter only when its corresponding stream is redirected:StandardInputEncodingonly when bothRedirectStandardInputistrueandStandardInputis non-null, andStandardOutputEncoding/StandardErrorEncodingwhenOutputRedirectionistrue. Callers can override per-stream via the init properties without affecting the other streams.
Note on
OutputRedirection: This is the master switch for stdout/stderr redirection. Whenfalse, neither stream is captured and the invoker's buffered/piped result types cannot be used.
ProcessExitConfigurationDefined in src/CliInvoke.Core/Primitives/ProcessExitConfiguration.cs.
| Property | Type | Default | Source line |
|---|---|---|---|
TimeoutPolicy |
ProcessTimeoutPolicy |
ProcessTimeoutPolicy.Default |
64 |
RequestedCancellationExitBehaviour |
ProcessExitBehaviour |
ProcessExitBehaviour.GracefulExit |
74 |
ExceptionBehaviour |
ProcessExceptionBehaviour |
ProcessExceptionBehaviour.AllowExceptionsIfUnexpected |
82 |
CancellationThrowsException |
bool |
false |
88 |
ProcessTimeoutPolicyDefined in src/CliInvoke.Core/Primitives/Policies/ProcessTimeoutPolicy.cs.
The parameterless constructor sets TimeoutThreshold to 2 minutes;
the static Default instance used by ProcessExitConfiguration sets
it to 3 minutes. Code that constructs its own
ProcessTimeoutPolicy() gets the 2-minute value; code that relies on
ProcessExitConfiguration()'s default gets the 3-minute value via
Default.
| Property | Type | new ProcessTimeoutPolicy() |
ProcessTimeoutPolicy.Default |
ProcessTimeoutPolicy.None |
Source line |
|---|---|---|---|---|---|
Enabled |
bool |
true |
true |
false |
74 |
TimeoutThreshold |
TimeSpan |
TimeSpan.FromMinutes(2) |
TimeSpan.FromMinutes(3) |
TimeSpan.FromSeconds(0) |
69 |
TimeoutExitBehaviour |
ProcessExitBehaviour |
GracefulExit |
GracefulExit |
WaitForExit |
64 |
ProcessResourcePolicyDefined in src/CliInvoke.Core/Primitives/Policies/ProcessResourcePolicy.cs.
| Property | Type | Default | Platform | Source line |
|---|---|---|---|---|
ProcessorAffinity |
IntPtr? |
2 * Environment.ProcessorCount - 1 (all logical processors) |
Windows, Linux | 87 |
PriorityClass |
ProcessPriorityClass |
ProcessPriorityClass.Normal |
All | 92 |
EnablePriorityBoost |
bool |
false |
All | 97 |
MinWorkingSet |
nint? |
null |
Windows, macOS | 105 |
MaxWorkingSet |
nint? |
null |
Windows, macOS | 113 |
ProcessResourcePolicy.Default is a static instance that
initializes ProcessorAffinity to all logical processors and leaves
the other properties at their constructor defaults.
UserCredentialDefined in src/CliInvoke.Core/Primitives/UserCredential.cs.
| Property | Type | new UserCredential() |
UserCredential.Null |
Platform | Source line |
|---|---|---|---|---|---|
Domain |
string? |
null |
null |
Windows | 65 |
UserName |
string? |
null |
null |
All | 70 |
Password |
SecureString? |
null |
null |
Windows | 76 |
LoadUserProfile |
bool? |
false |
null |
Windows | 82 |
UserCredential.Null is a static singleton with all four fields
null. It is the value ProcessConfiguration assigns to
Credential by default.
ProcessExitBehaviourDefined in src/CliInvoke.Core/Primitives/ProcessExitBehaviour.cs.
| Value | Numeric | Meaning |
|---|---|---|
WaitForExit |
0 |
Run until the process exits on its own. |
GracefulExit |
1 |
(default) Cancel via SIGTERM/SIGINT, fall back to a CancellationTokenSource. |
ForcefulExit |
2 |
Forcefully terminate the process and attempt to terminate all child processes. |
Note on
ForcefulExit: The tree-kill is best-effort, matching .NET's ownKill(entireProcessTree: true)semantics. Descendants spawned while the tree is being killed may survive. CliInvoke does not add a post-kill delay or a second kill pass — this mirrors the documented behavior ofProcess.Kill(entireProcessTree: true).
ProcessExceptionBehaviourDefined in src/CliInvoke.Core/Primitives/ProcessExceptionBehaviour.cs.
| Value | Numeric | Meaning |
|---|---|---|
SuppressExceptions |
0 |
Suppress all exceptions thrown during execution. |
AllowExceptions |
1 |
Allow .NET to throw the exception if expected. |
AllowExceptionsIfUnexpected |
2 |
(default) Allow the exception only if it was unexpected. |
ProcessConfiguration and UserCredential.src/CliInvoke.Core/Primitives/ProcessConfiguration.cssrc/CliInvoke.Core/Primitives/ProcessExitConfiguration.cssrc/CliInvoke.Core/Primitives/ProcessExitBehaviour.cssrc/CliInvoke.Core/Primitives/ProcessExceptionBehaviour.cssrc/CliInvoke.Core/Primitives/UserCredential.cssrc/CliInvoke.Core/Primitives/Policies/ProcessTimeoutPolicy.cssrc/CliInvoke.Core/Primitives/Policies/ProcessResourcePolicy.cssrc/CliInvoke.Core/Builders/IProcessConfigurationBuilder.cssrc/CliInvoke.Core/IProcessInvoker.cs