Proc 0.22.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Proc --version 0.22.0
                    
NuGet\Install-Package Proc -Version 0.22.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Proc" Version="0.22.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Proc" Version="0.22.0" />
                    
Directory.Packages.props
<PackageReference Include="Proc" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Proc --version 0.22.0
                    
#r "nuget: Proc, 0.22.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Proc@0.22.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Proc&version=0.22.0
                    
Install as a Cake Addin
#tool nuget:?package=Proc&version=0.22.0
                    
Install as a Cake Tool

Proc

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.

NuGet

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:

  1. Proc.Exec() for the quick one-liners.
  2. 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.
  3. It wraps System.Diagnostics.Process as an IObservable:
    • ObservableProcess, a stream based wrapper;
    • EventBasedObservableProcess, an event based wrapper.
  4. It can send SIGINT to a process before falling back to a hard SIGKILL (Process.Kill()). Set SendControlCFirst = true on StartArguments to 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 of LineOut, where Error says whether a line was written on stderr.
  • 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.

  • SendControlC and SendControlCFirst deliver SIGINT on Linux and macOS through SafeProcessHandle.Signal instead of shelling out to the kill binary.
  • EventBasedObservableProcess reads through Process.ReadAllLinesAsync (see above).
  • Proc.Exec without a Timeout calls Process.Run directly, and Proc.ExecAsync without a Timeout and with a token that cannot be cancelled calls Process.RunAsync directly. With a timeout or a cancellable token both keep using Proc's own wait loop: on timeout or cancellation Process.Run and RunAsync kill only the started process, whereas Proc kills its entire process tree and throws.
  • Two new options on StartArguments and ExecArguments, 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

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed