ProcessKit 1.2.0
See the version list below for details.
dotnet add package ProcessKit --version 1.2.0
NuGet\Install-Package ProcessKit -Version 1.2.0
<PackageReference Include="ProcessKit" Version="1.2.0" />
<PackageVersion Include="ProcessKit" Version="1.2.0" />
<PackageReference Include="ProcessKit" />
paket add ProcessKit --version 1.2.0
#r "nuget: ProcessKit, 1.2.0"
#:package ProcessKit@1.2.0
#addin nuget:?package=ProcessKit&version=1.2.0
#tool nuget:?package=ProcessKit&version=1.2.0
ProcessKit
Cross-platform child process management for .NET, with two complementary surfaces:
ProcessGroup— every child started in a group is killed atomically when the group is disposed, even if the parent process crashes. Windows: kernel Job Objects. Unix (Linux / macOS / FreeBSD): POSIX process groups.ProcessRunner/IProcessRunner— async-first runner for external commands. Stream stdout/stderr line-by-line viaIAsyncEnumerable<string>, capture bulk output (ProcessResult<T>with stderr and exit code), or just get the exit code. Pipe stdin from astring/ bytes /Stream/IAsyncEnumerable<string>/ file. Timeouts, cancellation, per-line push handlers, encoding overrides, structuralwith-options, fluentEnsureSuccess/EnsureSuccessAsync, runtime diagnostics (PID, duration, CPU time, peak memory, line counters, timeout flag).
Every spawned process — whether started via ProcessGroup.Start or
ProcessRunner — inherits the kill-on-dispose guarantee. AOT-compatible. Zero
external runtime dependencies.
Requirements
- .NET 10.0 or later
- Windows 8+ / Linux / macOS / FreeBSD
- AOT-compatible
Installation
Available on NuGet.org.
dotnet add package ProcessKit
Verifying the package
Each GitHub Release ships a SHA256SUMS file alongside the .nupkg / .snupkg.
Download all three into the same directory, then:
sha256sum -c SHA256SUMS
Expected:
ProcessKit.<version>.nupkg: OK
ProcessKit.<version>.snupkg: OK
The package on NuGet.org carries a repository signature from nuget.org, which
attributes it to the ProcessKit account. You can inspect it with
dotnet nuget verify ProcessKit.<version>.nupkg --all.
Usage
ProcessGroup — lifetime management
using System.Diagnostics;
using ProcessKit;
// Children are terminated when the group is disposed —
// even if the parent process crashes.
using var group = new ProcessGroup();
var psi = new ProcessStartInfo("myworker", ["--arg"]) { UseShellExecute = false };
var worker = group.Start(psi);
// Kill on cancellation
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var transient = group.Start(psi, cts.Token);
// Add an externally-started process to the group
var external = Process.Start(psi);
group.Add(external!);
// Runtime statistics (CPU time, peak memory, active count)
var stats = group.GetStats();
Console.WriteLine($"active={stats.ActiveProcessCount} cpu={stats.TotalCpuTime} peak={stats.PeakMemoryBytes}");
// Async dispose — non-blocking on Unix where SIGTERM/wait is involved
await using var asyncGroup = new ProcessGroup();
// ...
ProcessRunner — run external commands
ProcessRunner wraps ProcessGroup under the hood, so every spawned process
gets the same kill-on-dispose guarantee. The interface itself has a single
method (Start); everything else is built on top as extension methods.
For casual use without DI, ProcessRunner.Default is a shared singleton.
using ProcessKit;
IProcessRunner runner = new ProcessRunner(); // or use ProcessRunner.Default
// Bulk: full stdout + stderr + exit code in one call
var result = await runner.GetFullOutputAsync("git", ["status", "--porcelain"]);
if (result.IsSuccess)
Console.WriteLine(result.StdOut);
// Fluent error handling
var head = (await runner.GetFullOutputAsync("git", ["rev-parse", "HEAD"])
.EnsureSuccessAsync()).StdOut.Trim();
// Streaming: read stdout line by line as the process produces it
await foreach (var line in runner.GetOutputAsync("docker", ["logs", "-f", "myapp"]))
Console.WriteLine(line);
// Exit-only: no output capture, just the code
var rc = await runner.GetExitCodeAsync("npm", ["install"]);
// First matching line (kills process after match)
var branch = await runner.GetFirstLineOutputAsync(
"git", ["branch", "--show-current"]);
// Binary output (uses raw stdout stream)
var bytes = await runner.GetBytesOutputAsync("git", ["show", "HEAD:logo.png"]);
Stdin can be supplied in several forms via StandardInput:
var options = new ProcessRunOptions
{
StandardInput = StandardInput.FromString("first\nsecond\n"),
// or .FromBytes(...), .FromStream(stream),
// .FromLines(IAsyncEnumerable<string>), .FromEnumerable(IEnumerable<string>),
// .FromFile("path/to/input.txt") // eager existence check
Timeout = TimeSpan.FromSeconds(30), // sets ProcessResult.WasTimedOut on fire
StandardErrorHandler = line => logger.LogWarning("{Line}", line),
};
var result = await runner.GetFullOutputAsync("grep", ["pattern"], options);
Use Start directly when you need the running handle (Pid, line counters,
the Exited cancellation token, simultaneous stdout and stderr enumeration):
await using var p = runner.Start("ffmpeg", ["-i", "in.mp4", "out.webm"]);
// stderr in real-time (ffmpeg writes progress there)
_ = Task.Run(async () =>
{
await foreach (var line in p.StdErr)
progressUi.Update(line);
});
var code = await p.Completion;
Console.WriteLine(
$"pid={p.Pid} duration={p.Duration} cpu={p.CpuTime} peak={p.PeakMemoryBytes} " +
$"stdoutLines={p.StdOutLineCount} timedOut={p.WasTimedOut}");
ProcessRunOptions is a record — derive variants with with:
var fast = new ProcessRunOptions { Timeout = TimeSpan.FromSeconds(5) };
var slow = fast with { Timeout = TimeSpan.FromMinutes(5) };
Changelog
See CHANGELOG.md for the version history.
License
This project is licensed under the MIT License.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
### Added
- `IProcessRunner` interface and `ProcessRunner` default implementation for executing external commands with full lifetime management via `ProcessGroup`.
- `ProcessRunner.Default` static singleton for casual use without DI.
- `IRunningProcess` handle exposing `StdOut`/`StdErr` as `IAsyncEnumerable<string>`, line counters, `Pid`, `StartTime`, `Duration`, `CpuTime`, `PeakMemoryBytes`, `WasTimedOut`, `Exited` cancellation token, and `Completion` task.
- `ProcessResult<T>` record carrying `StdOut`, `StdErr`, `ExitCode`, `WasTimedOut`, `IsSuccess`, and fluent `EnsureSuccess()`.
- `ProcessExitException` raised by `EnsureSuccess()` on non-zero exit, carrying `ExitCode` and the captured `StdErr`.
- `ProcessRunOptions` (record) for stdin, stderr/stdout handlers, shared `ProcessGroup`, timeout, and encoding overrides.
- `StandardInput` closed union with factories `Empty`, `FromString`, `FromBytes`, `FromStream`, `FromLines` (async), `FromEnumerable` (sync), and `FromFile` (eagerly validated path).
- Extension methods on `IProcessRunner`: `Start(exe, args)` convenience overload, `GetOutputAsync`, `GetFirstLineOutputAsync`, `GetFullOutputAsync`, `GetBytesOutputAsync`, `GetExitCodeAsync`, sync `GetOutput`/`GetFirstLineOutput`, and `Task<ProcessResult<T>>.EnsureSuccessAsync()`.
### Changed
- README slimmed for the NuGet package page: contributor-only "Running tests on Linux from Windows" guide moved to `docs/linux-testing.md`.
- README intro and NuGet package description rewritten to reflect both surfaces (`ProcessGroup` lifetime layer + `ProcessRunner` async-first command runner).