CliInvoke offers three distinct patterns for invoking external processes. Each pattern targets a different audience and trade-off space. This guide helps you pick the right pattern for your scenario and understand how to migrate between them as your needs grow.
If you need the full API-level detail for each pattern, see
DESIGN_PATTERNS.md.
For the internal data-flow, see
Architecture.
Upgrading from v2? See the Migrating to 3.0.0 guide for breaking changes, before/after examples, and the full walkthrough of v2-style code replacements.
| Pattern | Best for | Boilerplate | DI required | Lifecycle control |
|---|---|---|---|---|
CliRun |
Scripts, CI/CD, quick prototypes | Minimal | No | No |
IProcessInvoker |
DI-centric apps, testable code | Moderate | Yes | No |
IExternalProcess |
Granular process control, long-running processes | Significant | Optional | Yes |
Use the flowchart below to find your starting pattern. Each question narrows the choice until one pattern remains.
flowchart TD
START([Need to run an external process]) --> Q1{Do you need to\ninteract with the\nprocess while it runs?\n\(e.g. send input,\nmonitor progress\)}
Q1 -->|Yes| EXTERNAL[IExternalProcess\n& IExternalProcessFactory]
Q1 -->|No| Q2{Do you need to\nunit-test the\ncalling code or\nswap invoker\nimplementations?}
Q2 -->|Yes| Q3{Do you already\nuse dependency\ninjection?}
Q2 -->|No| CLIRUN[CliRun\n— zero boilerplate]
Q3 -->|Yes| INVOKER[IProcessInvoker\n— DI-friendly]
Q3 -->|No| Q4{Are you comfortable\nsetting up a\nDI container?}
Q4 -->|Yes| INVOKER
Q4 -->|No| CLIRUN
Quick rules of thumb:
CliRunIProcessInvokerIExternalProcessCliRun — Quickstart & scripting// Run a simple command and wait for completion.
using CliInvoke;
BufferedProcessResult result = await CliRun.RunBufferedAsync(
"dotnet", "--version");
Console.WriteLine(result.StandardOutput);
CliRun is a static façade. It builds a ProcessConfiguration internally,
applies sensible defaults, and delegates to the default IProcessInvoker.
You get results with a single line of code — no DI container, no factories.
IProcessInvoker — DI-centric applicationsIProcessInvoker.ProcessConfiguration or ProcessExitConfiguration
per invocation.// Register in DI
using CliInvoke.Core;
using CliInvoke;
using CliInvoke.Extensions;
services.AddCliInvoke();
// IProcessInvoker is registered automatically.
// Later in your code
IProcessInvoker invoker = provider.GetRequiredService<IProcessInvoker>();
ProcessConfiguration config = new("dotnet", "--version");
BufferedProcessResult result = await invoker.ExecuteBufferedAsync(
config, ProcessExitConfiguration.CreateGraceful());
Console.WriteLine(result.StandardOutput);
IProcessInvoker is an interface that consumes a ProcessConfiguration
and returns a typed result. The default implementation (ProcessInvoker)
wires together configuration, exit behaviour, cancellation, and piping.
IExternalProcess — Power-user lifecycle controlSystem.Diagnostics.Process but with a
richer, safer surface.CliRun or
IProcessInvoker are simpler.using CliInvoke;
using CliInvoke.Core;
using CliInvoke.Core.Factories;
using CliInvoke.Factories;
IExternalProcessFactory factory = new ExternalProcessFactory();
ProcessConfiguration config = new("dotnet", "--version");
using IExternalProcess process = factory.CreateExternalProcess(config);
await process.StartAsync();
// You can interact with the process here — pipe input,
// check status, etc.
BufferedProcessResult result = await process.CaptureBufferedResultAsync(
CancellationToken.None);
Console.WriteLine(result.StandardOutput);
services.AddCliInvoke();
// IExternalProcessFactory is registered automatically.
IExternalProcessFactory factory =
provider.GetRequiredService<IExternalProcessFactory>();
ProcessConfiguration config = new("dotnet", "--version");
using IExternalProcess process = factory.CreateExternalProcess(config);
await process.StartAsync();
BufferedProcessResult result = await process.CaptureBufferedResultAsync(
CancellationToken.None);
IExternalProcess encapsulates a process instance and exposes asynchronous
start, capture, and kill methods. IExternalProcessFactory creates
configured instances, optionally with a custom IFilePathResolver.
As your application grows, you may need to move from a simpler pattern to a more capable one. The patterns are designed to be composable — you can upgrade without rewriting your configuration.
CliRun → IProcessInvokerThe ProcessConfiguration you built implicitly in CliRun can be
constructed explicitly and passed to IProcessInvoker:
// Before: CliRun
BufferedProcessResult result = await CliRun.RunBufferedAsync(
"dotnet", "--version");
// After: IProcessInvoker
IProcessInvoker invoker = provider.GetRequiredService<IProcessInvoker>();
ProcessConfiguration config = new("dotnet", "--version");
BufferedProcessResult result = await invoker.ExecuteBufferedAsync(config);
What changes:
services.AddCliInvoke()).ProcessConfiguration explicitly.IProcessInvoker from the container.IProcessInvoker → IExternalProcessThe ProcessConfiguration stays the same. Instead of passing it to the
invoker, you pass it to the factory and manage the lifecycle yourself:
// Before: IProcessInvoker
BufferedProcessResult result = await invoker.ExecuteBufferedAsync(config);
// After: IExternalProcess
IExternalProcessFactory factory =
provider.GetRequiredService<IExternalProcessFactory>();
using IExternalProcess process = factory.CreateExternalProcess(config);
await process.StartAsync();
// Now you can interact with the process before capturing results.
BufferedProcessResult result = await process.CaptureBufferedResultAsync(
CancellationToken.None);
What changes:
IExternalProcessFactory instead of IProcessInvoker.StartAsync() and CaptureBufferedResultAsync() separately.IExternalProcess.If you are upgrading from v2, the Migrating to 3.0.0 guide covers every breaking change including:
CliRun.UseExternalProcessFactory / CliRun.UseFilePathResolver removalExitConfiguration becoming read-only at constructionProcessConfigurationFactory removal (use init construction)ArgumentsList → ArgumentList renameExternalProcess sealed with constructor C onlySee the Shared Construction walkthrough for the init-first construction story that replaces v2 builder-default patterns.
| Pattern | Beginner friendly | Handles resource disposal | Testable | Lifecycle control | Boilerplate |
|---|---|---|---|---|---|
CliRun |
✔ | ✔ | ✖ | ✖ | Minimal |
IProcessInvoker |
✖ | Requires using |
✔ | ✖ | Moderate |
IExternalProcess / IExternalProcessFactory |
✖ | Requires using |
✔ | ✔ | Significant |
Choose CliRun for scripting or basic command execution where you
don't need DI or advanced configuration.
Choose IProcessInvoker for DI-centric applications where testability
and per-invocation configuration matter.
Choose IExternalProcess when you need process-level APIs similar to
System.Diagnostics.Process — interacting with the process while it runs,
controlling start/stop sequences, or building process-aware libraries.
DESIGN_PATTERNS.md — full API reference for each pattern.