Proc 0.22.1
dotnet add package Proc --version 0.22.1
NuGet\Install-Package Proc -Version 0.22.1
<PackageReference Include="Proc" Version="0.22.1" />
<PackageVersion Include="Proc" Version="0.22.1" />
<PackageReference Include="Proc" />
paket add Proc --version 0.22.1
#r "nuget: Proc, 0.22.1"
#:package Proc@0.22.1
#addin nuget:?package=Proc&version=0.22.1
#tool nuget:?package=Proc&version=0.22.1
Proc

A System.Diagnostics.Process supercharger: one-liners that run a program, capture or stream its output, and stop it
with Ctrl+C before killing it.
Install
dotnet add package Proc
Quick start
using ProcNet;
// run a program on the shared console; throws ProcExecException on a non-zero exit code
Proc.Exec("dotnet", "--version");
// capture its output, while still writing it to the console as it arrives
var result = Proc.Start("git", "status", "--short");
foreach (var line in result.ConsoleOut)
Console.WriteLine($"{(line.Error ? "err" : "out")}: {line.Line}");
Console.WriteLine($"exit code {result.ExitCode}");
Proc does four things:
Proc.Exec()for the quick one-liners.Proc.Start()when you want to capture the console output as well as print it in real time. It also lets you script standard input and react to messages.- It wraps
System.Diagnostics.Processas anIObservable:ObservableProcess, a stream based wrapper;EventBasedObservableProcess, an event based wrapper.
- It can send
SIGINTto a process before falling back to a hardSIGKILL(Process.Kill()). SetSendControlCFirst = trueonStartArgumentsto opt in.
Pick the call by what you need back:
| Need | Call | Returns |
|---|---|---|
| run it, fail on a non-zero exit | Proc.Exec("dotnet", "build") |
exit code; throws ProcExecException otherwise |
| the same, async | await Proc.ExecAsync(new ExecArguments("dotnet", "build"), ct) |
exit code |
| its output | Proc.Start("git", "status", "--short") |
ProcessCaptureResult: Completed, ExitCode, ConsoleOut |
| each line as it arrives | Proc.StartRedirected(handler, "dotnet", "test") |
ProcessResult |
| a long-running server you wait on | Proc.StartLongRunning(args, waitForStartedConfirmation) |
a subscription with ProcessId and ExitCode; dispose it to stop |
Pass every argument separately, Proc.Exec("git", "commit", "-m", message), never one string with spaces: each one
reaches the process unchanged. There is no default timeout: without Timeout a call waits for the process to exit.
Proc.Exec
Executes a process and blocks until it exits. It uses the same console session as your program, so the program's
output goes straight to the console. Throws a ProcExecException when the program fails to start, exits with an exit
code that ValidExitCodeClassifier does not accept (by default anything but 0), or runs past its Timeout.
See ExecArguments for more options.
Proc.Exec("ipconfig", "/all");
Proc.Exec(new ExecArguments("npm", "ci")
{
WorkingDirectory = "web",
Environment = new Dictionary<string, string> { ["CI"] = "true" }, // a null value removes a variable
Timeout = TimeSpan.FromMinutes(5), // kills the whole process tree, then throws
ValidExitCodeClassifier = code => code is 0 or 2
});
Proc.Start
Starts a process and blocks until it exits, capturing its output.
var result = Proc.Start("ipconfig", "/all");
Pass StartArguments to control how the process starts, for instance with a timeout and an IConsoleOutWriter that
writes to the console while the call blocks. ConsoleOutColorWriter, the default, writes stderr in red.
var result = Proc.Start(new StartArguments("ipconfig", "/all")
{
WorkingDirectory = "..",
Timeout = TimeSpan.FromSeconds(10),
ConsoleOutWriter = new ConsoleOutColorWriter()
});
Proc.Start does not throw on a non-zero exit code. result has these properties:
Completed: true if the program completed before the timeout.ConsoleOut: the console output as a list ofLineOut, whereErrorsays whether a line was written onstderr.ExitCode.
ConsoleOut is always set, whether or not an IConsoleOutWriter is provided.
Proc.StartLongRunning
Start a process that keeps running, such as a web server or a database, and go on once it says it is ready. Disposing the returned subscription stops the process.
var args = new LongRunningArguments("dotnet", "run", "--project", "api")
{
StartedConfirmationHandler = l => l.Line.Contains("Now listening"),
SendControlCFirst = true
};
using (var api = Proc.StartLongRunning(args, TimeSpan.FromMinutes(1)))
{
// api.ProcessId, api.ExitCode (once it exited on its own), api.WaitForCompletion(timeout)
}
StartLongRunning waits for a line that StartedConfirmationHandler accepts. Without a handler it returns straight away.
When no such line comes in time, it stops the process and throws ProcExecException.
ExitCode stays null while the process runs, and after disposing stopped it.
ObservableProcess
The heart of it all is an IObservable<CharactersOut>. It listens on the output buffers directly and does not wait on
newlines to emit.
To create an observable process manually, follow this pattern:
var args = new StartArguments("dotnet", "--info");
using (var p = new ObservableProcess(args))
{
p.Subscribe(c => Console.Write(c.Characters));
p.WaitForCompletion(TimeSpan.FromSeconds(2));
}
The observable is cold until subscribed, and is not meant to be reused or subscribed to more than once. To share a
subscription, look into Rx's Publish.
The WaitForCompletion() call blocks so that p is not disposed, which would shut down the started process.
By default a shutdown goes through Process.Kill, a hard SIGKILL on the process. Proc also supports SIGINT, so a
process can shut down cleanly:
var args = new StartArguments("elasticsearch.bat")
{
SendControlCFirst = true
};
This sends a Ctrl+C into the running process's console first, then falls back to Process.Kill. On Windows Proc
delivers it through a small helper program it ships. On Linux and macOS it shells out to kill -SIGINT, unless running
on .NET 11+ (see below), where it delivers the signal in-process instead.
Dealing with char[] buffers might not be what you want to program against, so ObservableProcess also emits lines:
using (var p = new ObservableProcess(args))
{
p.SubscribeLines(c => Console.WriteLine(c.Line));
p.WaitForCompletion(TimeSpan.FromSeconds(2));
}
Instead of passing on the buffers as they are received, this buffers and only emits whole lines.
In some cases it is useful to introduce your own line boundaries:
public class MyProcObservable : ObservableProcess
{
public MyProcObservable(string binary, params string[] arguments) : base(binary, arguments) { }
public MyProcObservable(StartArguments startArguments) : base(startArguments) { }
protected override bool BufferBoundary(char[] stdOut, char[] stdErr)
{
return base.BufferBoundary(stdOut, stdErr);
}
}
Returning true from BufferBoundary yields the buffered text to SubscribeLines(). This is useful when your process
prompts without a new line:
Continue [Y/N]: <no newline here>
A more concrete example: a bat file on Windows that gets a SIGINT always prompts
Terminate batch job (Y/N)?
which would not yield to SubscribeLines and would block wait handles unnecessarily. ObservableProcess handles this
case out of the box and replies with Y on stdin.
ObservableProcess also yields whatever is left in the buffer before OnCompleted().
EventBasedObservableProcess
The sibling of ObservableProcess that uses OutputDataReceived and ErrorDataReceived, and so can only emit lines.
On .NET 11+ it reads through Process.ReadAllLinesAsync
instead, which multiplexes stdout and stderr on a single thread without blocking any thread pool threads.
.NET 11
Proc targets net11.0 alongside its other target frameworks, and picks up a few of the
Process API improvements shipped in .NET 11
when running on it. They are opt-in or no-ops elsewhere, so there is nothing to change if you stay on an older target
framework.
SendControlCandSendControlCFirstdeliverSIGINTon Linux and macOS throughSafeProcessHandle.Signalinstead of shelling out to thekillbinary.EventBasedObservableProcessreads throughProcess.ReadAllLinesAsync(see above).Proc.Execwithout aTimeoutcallsProcess.Rundirectly, andProc.ExecAsyncwithout aTimeoutand with a token that cannot be cancelled callsProcess.RunAsyncdirectly. With a timeout or a cancellable token both keep using Proc's own wait loop: on timeout or cancellationProcess.RunandRunAsynckill only the started process, whereas Proc kills its entire process tree and throws.- Two new options on
StartArgumentsandExecArguments, both no-ops on older target frameworks or unsupported platforms:
var args = new StartArguments("some-long-running-tool")
{
// Kills the started process if this process exits, including crashes. Backed by Job objects on Windows
// and PR_SET_PDEATHSIG on Linux. Only takes effect on .NET 11+ on Windows or Linux.
KillOnParentExit = true,
// Restricts which handles the started process inherits, instead of every inheritable handle from this
// process. Only takes effect on .NET 11+.
InheritedHandles = new List<SafeHandle>()
};
Learn more
- F# bindings: Proc.Fs, with
shell { },exec { }and theCmdmodule - Source and issues: https://github.com/nullean/proc
- Releases
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 is compatible. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.6.1
- System.Reactive (>= 6.1.0)
-
.NETStandard 2.0
- System.Reactive (>= 7.0.0)
-
.NETStandard 2.1
- System.Reactive (>= 7.0.0)
-
net10.0
- System.Reactive (>= 7.0.0)
-
net11.0
- System.Reactive (>= 7.0.0)
-
net8.0
- System.Reactive (>= 7.0.0)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on Proc:
| Package | Downloads |
|---|---|
|
Elastic.Elasticsearch.Managed
Provides an observable ElasticsearchNode abstraction that can be used to wrap an elasticsearch process. Also ships with an cluster abstraction that can start one or more ElasticsearchNode's |
|
|
Proc.Fs
Dependency free reactive abstraction around Process, exposes handy static methods for the quick one-liners |
|
|
Nullean.Make
Property-dispatch build target DSL with Argh-quality help and per-target typed CLI arguments. |
|
|
Nullean.Runna
Property-dispatch build target DSL with Argh-quality help and per-target typed CLI arguments. |
|
|
Runna
Property-dispatch build target DSL with Argh-quality help and per-target typed CLI arguments. |
GitHub repositories (4)
Showing the top 4 popular GitHub repositories that depend on Proc:
| Repository | Stars |
|---|---|
|
elastic/elasticsearch-net
This strongly-typed, client library enables working with Elasticsearch. It is the official client maintained and supported by Elastic.
|
|
|
elastic/apm-agent-dotnet
|
|
|
opensearch-project/opensearch-net
OpenSearch .NET Client
|
|
|
editorconfig/editorconfig-core-net
EditorConfig Core library and command line utility written in C# for .NET/Mono http://editorconfig.org
|
| Version | Downloads | Last Updated |
|---|---|---|
| 0.22.1 | 117 | 11/12/2026 |
| 0.22.0 | 399 | 11/12/2026 |
| 0.21.0 | 471 | 11/12/2026 |
| 0.20.0 | 284 | 11/11/2026 |
| 0.18.0 | 417 | 11/11/2026 |
| 0.17.0 | 886 | 11/11/2026 |
| 0.16.0 | 100 | 11/11/2026 |
| 0.15.0 | 126 | 11/11/2026 |
| 0.14.0 | 4,210 | 3/30/2026 |
| 0.13.0 | 93,623 | 1/30/2026 |
| 0.12.0 | 234 | 1/30/2026 |
| 0.11.0 | 21,710 | 11/12/2025 |
| 0.10.0 | 433 | 11/12/2025 |
| 0.9.1 | 70,360 | 12/3/2024 |
| 0.9.0 | 20,408 | 12/2/2024 |
| 0.8.2 | 2,998 | 10/2/2024 |
| 0.8.1 | 6,197 | 1/16/2024 |
| 0.8.0 | 876 | 1/15/2024 |
| 0.7.3 | 727 | 1/8/2024 |
| 0.7.2 | 729 | 1/7/2024 |