Meziantou.Framework.ProcessWrapper
1.0.4
Prefix Reserved
dotnet add package Meziantou.Framework.ProcessWrapper --version 1.0.4
NuGet\Install-Package Meziantou.Framework.ProcessWrapper -Version 1.0.4
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="Meziantou.Framework.ProcessWrapper" Version="1.0.4" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Meziantou.Framework.ProcessWrapper" Version="1.0.4" />
<PackageReference Include="Meziantou.Framework.ProcessWrapper" />
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 Meziantou.Framework.ProcessWrapper --version 1.0.4
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: Meziantou.Framework.ProcessWrapper, 1.0.4"
#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 Meziantou.Framework.ProcessWrapper@1.0.4
#: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=Meziantou.Framework.ProcessWrapper&version=1.0.4
#tool nuget:?package=Meziantou.Framework.ProcessWrapper&version=1.0.4
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
Meziantou.Framework.ProcessWrapper
Fluent, immutable API for configuring and running processes.
Basic usage
// Execute and wait for exit (throws if exit code is non-zero by default)
var result = await ProcessWrapper.Create("dotnet")
.WithArguments("--version")
.ExecuteAsync();
int exitCode = result.ExitCode;
Buffered execution
// Capture all output
var result = await ProcessWrapper.Create("dotnet")
.WithArguments("--info")
.ExecuteBufferedAsync();
int exitCode = result.ExitCode;
// Access output after awaiting
foreach (var line in result.Output.StandardOutput)
{
Console.WriteLine(line.Text);
}
Working directory
await ProcessWrapper.Create("git")
.WithArguments("status")
.WithWorkingDirectory("/path/to/repo")
.ExecuteAsync();
Environment variables
// Using callback
await ProcessWrapper.Create("my-app")
.WithEnvironmentVariables(env => env
.Set("MY_VAR", "value")
.Remove("UNWANTED_VAR"))
.ExecuteAsync();
// Using dictionary (null removes the variable)
await ProcessWrapper.Create("my-app")
.WithEnvironmentVariables(new Dictionary<string, string?>
{
["MY_VAR"] = "value",
["UNWANTED_VAR"] = null,
})
.ExecuteAsync();
Resource limits
var result = await ProcessWrapper.Create("my-app")
.WithLimits(new ProcessLimits
{
CpuPercentage = 50, // 50% max CPU
MemoryLimitInBytes = 512L * 1024 * 1024, // 512 MB
ProcessCountLimit = 20,
})
.ExecuteAsync();
Common limits are mapped to Windows Job Objects or Linux cgroups v2 depending on the current OS. If a configured limit cannot be applied on the current platform, execution throws.
Advanced platform-specific configuration
var result = await ProcessWrapper.Create("my-app")
.WithLimits(limits => limits.MemoryLimitInBytes = 512L * 1024 * 1024)
.WithWindowsJobObject(job =>
{
job.SetLimits(new JobObjectLimits
{
Flags = JobObjectLimitFlags.KillOnJobClose,
});
})
.ExecuteAsync();
var result = await ProcessWrapper.Create("my-app")
.WithLinuxControlGroup(cgroup =>
{
cgroup.SetMemoryHigh(256L * 1024 * 1024);
})
.ExecuteAsync();
Output handling
Use With* methods to replace handlers, and Add* methods to append additional handlers.
// Stream output line by line
await ProcessWrapper.Create("dotnet")
.WithArguments("build")
.WithOutputStream(OutputTarget.ToTextDelegate(line => Console.WriteLine($"[OUT] {line}")))
.WithErrorStream(OutputTarget.ToTextDelegate(line => Console.Error.WriteLine($"[ERR] {line}")))
.ExecuteAsync();
// Collect output into a StringBuilder
var sb = new StringBuilder();
await ProcessWrapper.Create("dotnet")
.WithArguments("build")
.WithOutputStream(sb)
.ExecuteAsync();
Console.WriteLine(sb.ToString());
// Stream lines to a TextWriter
using var writer = new StringWriter();
await ProcessWrapper.Create("dotnet")
.WithArguments("--version")
.WithOutputStream(OutputTarget.ToTextWriter(writer))
.ExecuteAsync();
// Collect into a ProcessOutputCollection
var output = new ProcessOutputCollection();
await ProcessWrapper.Create("dotnet")
.WithArguments("build")
.WithOutputStream(output)
.WithErrorStream(output)
.ExecuteAsync();
foreach (var line in output.StandardError)
{
Console.Error.WriteLine(line.Text);
}
// Capture raw bytes from stdout/stderr
await using var stdout = File.Create("stdout.bin");
await using var stderr = File.Create("stderr.bin");
await ProcessWrapper.Create("my-command")
.WithOutputStream(stdout)
.WithErrorStream(stderr)
.ExecuteAsync();
// Text and binary handlers can be combined on the same stream
await using var rawOutput = File.Create("raw-output.bin");
await ProcessWrapper.Create("dotnet")
.WithArguments("--version")
.WithOutputStream(rawOutput)
.AddOutputStream(OutputTarget.ToTextDelegate(line => Console.WriteLine($"Version line: {line}")))
.ExecuteAsync();
// Capture raw bytes using a delegate
await ProcessWrapper.Create("dotnet")
.WithArguments("--version")
.WithOutputStream(OutputTarget.ToBytesDelegate(bytes => Console.WriteLine($"Chunk size: {bytes.Length}")))
.ExecuteAsync();
// Override stdout/stderr decoding when process output is not UTF-8
await ProcessWrapper.Create("my-command")
.WithOutputEncoding(Encoding.Latin1)
.WithErrorEncoding(Encoding.Latin1)
.WithOutputStream(OutputTarget.ToTextDelegate(line => Console.WriteLine(line)))
.WithErrorStream(OutputTarget.ToTextDelegate(line => Console.Error.WriteLine(line)))
.ExecuteAsync();
Input stream
// Pipe a string to stdin
var result = await ProcessWrapper.Create("cat")
.WithInputStream(InputSource.FromText("Hello, World!"))
.ExecuteBufferedAsync();
Console.WriteLine(result.Output.ToString());
// Pipe a file to stdin
var fileResult = await ProcessWrapper.Create("cat")
.WithInputStream(InputSource.FromFile("input.txt"))
.ExecuteBufferedAsync();
Console.WriteLine(fileResult.Output.ToString());
// Pipe from a TextReader
using var reader = new StringReader("Hello from reader");
var readerResult = await ProcessWrapper.Create("cat")
.WithInputStream(InputSource.FromTextReader(reader))
.ExecuteBufferedAsync();
Console.WriteLine(readerResult.Output.ToString());
// Pipe one process output to another process input
var pipe = new ProcessPipe(maxBufferSize: 256 * 1024);
var downstream = ProcessWrapper.Create("process-b")
.WithInputStream(pipe)
.ExecuteAsync();
await ProcessWrapper.Create("process-a")
.WithArguments("--generate-data")
.AddOutputStream(pipe)
.ExecuteAsync();
await downstream;
Validation
// Default: throws ProcessExecutionException if exit code is non-zero
try
{
await ProcessWrapper.Create("false")
.ExecuteAsync();
}
catch (ProcessExecutionException ex)
{
Console.WriteLine($"Process failed with exit code {ex.ExitCode}");
}
// Disable validation
var result = await ProcessWrapper.Create("false")
.WithValidation(ProcessValidationMode.None)
.ExecuteAsync();
int exitCode = result.ExitCode;
// Fail on stderr output as well
await ProcessWrapper.Create("my-command")
.WithValidation(ProcessValidationMode.FailIfNonZeroExitCode | ProcessValidationMode.FailIfStdError)
.ExecuteAsync();
Cancellation
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await ProcessWrapper.Create("long-running-process")
.ExecuteAsync(cts.Token); // throws OperationCanceledException if cancelled
Killing a process
var process = ProcessWrapper.Create("long-running-process")
.WithValidation(ProcessValidationMode.None)
.ExecuteAsync();
process.Kill(); // or process.Kill(entireProcessTree: false)
await process;
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 is compatible. 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. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- Meziantou.Framework.Unix.ControlGroups (>= 1.0.1)
- Meziantou.Framework.Win32.Jobs (>= 3.5.4)
-
net8.0
- Meziantou.Framework.Unix.ControlGroups (>= 1.0.1)
- Meziantou.Framework.Win32.Jobs (>= 3.5.4)
- System.IO.Pipelines (>= 10.0.5)
-
net9.0
- Meziantou.Framework.Unix.ControlGroups (>= 1.0.1)
- Meziantou.Framework.Win32.Jobs (>= 3.5.4)
- System.IO.Pipelines (>= 10.0.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.