ktsu.RunCommand
1.5.4
Prefix Reserved
dotnet add package ktsu.RunCommand --version 1.5.4
NuGet\Install-Package ktsu.RunCommand -Version 1.5.4
<PackageReference Include="ktsu.RunCommand" Version="1.5.4" />
<PackageVersion Include="ktsu.RunCommand" Version="1.5.4" />
<PackageReference Include="ktsu.RunCommand" />
paket add ktsu.RunCommand --version 1.5.4
#r "nuget: ktsu.RunCommand, 1.5.4"
#:package ktsu.RunCommand@1.5.4
#addin nuget:?package=ktsu.RunCommand&version=1.5.4
#tool nuget:?package=ktsu.RunCommand&version=1.5.4
ktsu.RunCommand
A .NET library for executing external commands and handling their output through delegates, with synchronous and asynchronous APIs, cancellation, and control over the spawned process.
Introduction
ktsu.RunCommand runs an external command and hands you its output as it arrives, instead of making you assemble Process, ProcessStartInfo, redirected streams and exit-code plumbing yourself. Output is delivered through delegates — either as raw chunks exactly as the process emits them, or buffered into complete lines — and every method returns the process exit code.
Arguments are passed as a vector rather than as one string, so a path containing spaces needs no manual quoting and cannot be mis-split. The process itself can be shaped through a working directory and an environment variable overlay, run elevated on Windows, and terminated along with its children through a cancellation token.
Features
- Delegate-based output: Receive standard output and standard error through
Action<string>delegates as the process produces them, rather than waiting for it to exit. - Raw or line-buffered:
OutputHandlerdelivers undelimited chunks exactly as they arrive;LineOutputHandlerbuffers across chunks and raises one call per complete line. - Synchronous and asynchronous: Every operation is available as both
ExecuteandExecuteAsync, with the asynchronous implementation as the single source of truth. - Quote-free arguments: Pass the executable and each argument separately, so spaces in paths and arguments are handled by the platform rather than by string concatenation.
- Working directory: Start the process in a specific directory without mutating the process-global current directory.
- Environment variables: Apply an overlay over the inherited environment for a single call, adding, overriding, or removing individual variables.
- Cancellation: A signalled
CancellationTokenterminates the process and always surfaces as anOperationCanceledException, never as a synthetic exit code. - Windows elevation: Launch through the
runasverb for a UAC-elevated process. - Custom encoding: Decode the output streams with any
Encoding; defaults to UTF-8. - Broad target support: .NET Standard 2.0 and 2.1 through .NET 10.
Installation
Package Manager Console
Install-Package ktsu.RunCommand
.NET CLI
dotnet add package ktsu.RunCommand
Package Reference
<PackageReference Include="ktsu.RunCommand" Version="1.5.0" />
Usage Examples
Basic Example
Pass the executable and its arguments separately. All methods return the process exit code:
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute("dotnet", ["--version"]);
if (exitCode == 0)
{
Console.WriteLine("Command executed successfully!");
}
else
{
Console.WriteLine($"Command failed with exit code: {exitCode}");
}
}
}
Custom Output Handling
To handle the output of the command, provide delegates to the OutputHandler class:
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute(
fileName: "dotnet",
arguments: ["--version"],
outputHandler: new(
onStandardOutput: Console.Write,
onStandardError: Console.Write
)
);
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
NOTE: When using the default
OutputHandler, the delegates receive undelimited chunks of output. This gives you exactly what the command produces, including whitespace and non-printable characters, to handle as you see fit.
Line-by-Line Output Handling
To handle the output one line at a time, use the LineOutputHandler class:
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute(
fileName: "dotnet",
arguments: ["--version"],
outputHandler: new LineOutputHandler(
onStandardOutput: line => Console.WriteLine($"Output: {line}"),
onStandardError: line => Console.WriteLine($"Error: {line}")
)
);
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
Asynchronous Execution
All of the above examples can be run asynchronously with ExecuteAsync:
using ktsu.RunCommand;
class Program
{
static async Task Main()
{
int exitCode = await RunCommand.ExecuteAsync("dotnet", ["--version"]);
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
Cancellation
Passing a CancellationToken terminates the process when the token is signalled:
using ktsu.RunCommand;
class Program
{
static async Task Main()
{
using CancellationTokenSource cancellation = new(TimeSpan.FromSeconds(30));
try
{
int exitCode = await RunCommand.ExecuteAsync(
fileName: "dotnet",
arguments: ["build"],
outputHandler: new LineOutputHandler(onStandardOutput: Console.WriteLine),
cancellationToken: cancellation.Token);
Console.WriteLine($"Process exited with code: {exitCode}");
}
catch (OperationCanceledException)
{
Console.WriteLine("The command was cancelled.");
}
}
}
A cancelled call always throws OperationCanceledException — it never returns the killed process's exit code — so cancellation cannot be mistaken for a genuine failure of the command.
On .NET Core 3.0 and later the entire process tree is terminated. On .NET Standard 2.0 and 2.1 only the process itself can be terminated, so any grandchildren it spawned are left running.
Process Options
CommandOptions shapes the process a command runs in. Pass it alongside an executable and its arguments:
using ktsu.RunCommand;
using ktsu.Semantics.Paths;
class Program
{
static async Task Main()
{
int exitCode = await RunCommand.ExecuteAsync(
fileName: "git",
arguments: ["status", "--short"],
outputHandler: new LineOutputHandler(onStandardOutput: Console.WriteLine),
options: new()
{
WorkingDirectory = AbsoluteDirectoryPath.Create(@"C:\repos\my project"),
EnvironmentVariables = new Dictionary<string, string?>
{
["GIT_TERMINAL_PROMPT"] = "0",
["LC_ALL"] = "C",
},
});
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
CommandOptions.Elevation carries the privilege level too, so a single options object replaces the separate Elevation argument.
Working Directory
Without a WorkingDirectory the process inherits the current directory of the calling process, which is what commands did before this option existed.
The type is AbsoluteDirectoryPath rather than a string on purpose. A relative directory would have to be resolved against the caller's current directory — the process-global state this option exists to avoid depending on, since it is shared by every thread and races with concurrent calls.
Environment Variables
EnvironmentVariables is an overlay on the inherited environment, not a replacement: a name you do not list keeps whatever the calling process had. A null value removes a variable, which is how you unset something the parent had set:
EnvironmentVariables = new Dictionary<string, string?>
{
["GIT_DIR"] = null,
}
Environment variables are the only control surface some tools expose, so this covers behaviour with no command-line equivalent — GIT_TERMINAL_PROMPT=0 to make an authenticating git fetch fail rather than block forever on a prompt no terminal will answer, GIT_ASKPASS/SSH_ASKPASS to supply credentials without putting them on a command line where any process listing can read them, and LC_ALL=C to force stable, machine-parseable output rather than whatever the host locale produces.
NOTE:
EnvironmentVariablescannot be combined withElevation.Elevatedon Windows. Elevation requiresUseShellExecute, which offers nowhere to pass an environment, so the call throwsArgumentExceptionrather than silently dropping the variables.
Elevation (Windows)
To run a command with elevated privileges, set Elevation.Elevated. On Windows this launches the process with the runas verb, which triggers a UAC prompt:
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute(
fileName: "powershell",
arguments: ["-Command", "Get-Service"],
outputHandler: new(),
options: new() { Elevation = Elevation.Elevated });
Console.WriteLine($"Process exited with code: {exitCode}");
}
}
NOTE: Output redirection is incompatible with
runas, so anOutputHandlerpassed alongsideElevation.Elevatedwill not be invoked. You still get the process exit code.
On non-Windows platforms Elevation.Elevated is a no-op — prefix your command with sudo yourself if you need elevation there.
Encoding
By default the library decodes the output streams as UTF-8. To use a different encoding, specify it in the OutputHandler or LineOutputHandler constructor:
using System.Text;
using ktsu.RunCommand;
class Program
{
static void Main()
{
int exitCode = RunCommand.Execute(
fileName: "dotnet",
arguments: ["--version"],
outputHandler: new(
onStandardOutput: Console.Write,
onStandardError: Console.Write,
encoding: Encoding.ASCII
)
);
}
}
Deprecated: Single Command Strings
The overloads taking one command string are obsolete. They separate the executable from its arguments by splitting on the first space, which cannot represent an executable path that itself contains a space — on Windows that includes anything under C:\Program Files\:
// Obsolete, and broken: splits into "C:\Program" plus "Files\Git\bin\git.exe --version"
await RunCommand.ExecuteAsync(@"C:\Program Files\Git\bin\git.exe --version");
// Correct
await RunCommand.ExecuteAsync(@"C:\Program Files\Git\bin\git.exe", ["--version"]);
Quoting does not rescue it, because the split happens before any quote handling. The string form is inherently ambiguous — no parse handles every combination of spaces and quotes without adopting a shell's full grammar — so rather than grow a half-grammar that moves the surprise elsewhere, these overloads are deprecated in favour of the argument-list ones, which have no such ambiguity because the executable is passed separately.
Migration is mechanical: split the string yourself at the boundaries you meant.
| Obsolete | Replacement |
|---|---|
Execute(command) |
Execute(fileName, arguments) |
Execute(command, outputHandler) |
Execute(fileName, arguments, outputHandler) |
Execute(command, elevation) |
Execute(fileName, arguments, outputHandler, options) |
ExecuteAsync(command) |
ExecuteAsync(fileName, arguments) |
ExecuteAsync(command, outputHandler) |
ExecuteAsync(fileName, arguments, outputHandler) |
ExecuteAsync(command, cancellationToken) |
ExecuteAsync(fileName, arguments, outputHandler, cancellationToken) |
ExecuteAsync(command, outputHandler, elevation, cancellationToken) |
ExecuteAsync(fileName, arguments, outputHandler, options, cancellationToken) |
API Reference
RunCommand
Static class providing the command execution API. Every method returns the process exit code.
Methods
| Name | Return Type | Description |
|---|---|---|
Execute(string fileName, IEnumerable<string> arguments) |
int |
Executes a command synchronously. |
Execute(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler) |
int |
Executes a command synchronously with custom output handling. |
Execute(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, CommandOptions options) |
int |
Executes a command synchronously with the given process options. |
ExecuteAsync(string fileName, IEnumerable<string> arguments) |
Task<int> |
Executes a command asynchronously. |
ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler) |
Task<int> |
Executes a command asynchronously with custom output handling. |
ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, CancellationToken cancellationToken) |
Task<int> |
As above, terminating the process and its children if the token is signalled. |
ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, Elevation elevation, CancellationToken cancellationToken) |
Task<int> |
As above, at the given elevation level. |
ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, CommandOptions options) |
Task<int> |
Executes a command asynchronously with the given process options. |
ExecuteAsync(string fileName, IEnumerable<string> arguments, OutputHandler outputHandler, CommandOptions options, CancellationToken cancellationToken) |
Task<int> |
As above, terminating the process and its children if the token is signalled. |
The overloads taking a single command string — four Execute and seven ExecuteAsync — are obsolete. See Deprecated: Single Command Strings for the migration table.
CommandOptions
Record describing how to shape the process a command runs in. Every member defaults to the behaviour commands had before the type existed, so an instance with nothing set is equivalent to not passing one at all.
Properties
| Name | Type | Description |
|---|---|---|
WorkingDirectory |
AbsoluteDirectoryPath? |
The directory the process starts in, or null to inherit the caller's current directory. |
EnvironmentVariables |
IReadOnlyDictionary<string, string?>? |
Variables applied over the inherited environment, or null to inherit it unchanged. A null value removes a variable. |
Elevation |
Elevation |
The privilege level under which to run the command. Defaults to Elevation.Default. |
OutputHandler
Processes output in raw, undelimited chunks as they arrive from the process.
Constructor
| Name | Description |
|---|---|
OutputHandler(Action<string>? onStandardOutput = null, Action<string>? onStandardError = null, Encoding? encoding = null) |
Creates a handler with delegates for the output and error streams. encoding defaults to UTF-8. |
Properties
| Name | Type | Description |
|---|---|---|
Encoding |
Encoding |
The encoding used to decode the process's output streams. |
LineOutputHandler
Inherits from OutputHandler and buffers incoming chunks, invoking the delegates once per complete line. Incomplete trailing data is held until the rest of the line arrives.
Constructor
| Name | Description |
|---|---|
LineOutputHandler(Action<string>? onStandardOutput = null, Action<string>? onStandardError = null, Encoding? encoding = null) |
Creates a line-buffering handler with delegates for the output and error streams. |
Elevation
Enum specifying the privilege level under which a command runs.
| Name | Description |
|---|---|
Default |
Run with the current process's privileges. Standard output and standard error are captured. |
Elevated |
On Windows, launch through the runas verb, prompting for UAC consent; output is not captured. No effect on non-Windows platforms. |
Contributing
Contributions are welcome! Feel free to open issues or submit pull requests.
License
This project is licensed under the MIT License. See the LICENSE.md file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 is compatible. net5.0-windows was computed. net6.0 is compatible. 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 is compatible. 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 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. |
| .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 was computed. 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. |
-
.NETStandard 2.0
- ktsu.Semantics.Paths (>= 3.1.2)
- ktsu.Semantics.Strings (>= 3.1.2)
- System.Memory (>= 4.6.3)
- System.Threading.Tasks.Extensions (>= 4.6.3)
-
.NETStandard 2.1
- ktsu.Semantics.Paths (>= 3.1.2)
- ktsu.Semantics.Strings (>= 3.1.2)
-
net10.0
- ktsu.Semantics.Paths (>= 3.1.2)
- ktsu.Semantics.Strings (>= 3.1.2)
-
net5.0
- ktsu.Semantics.Paths (>= 3.1.2)
- ktsu.Semantics.Strings (>= 3.1.2)
-
net6.0
- ktsu.Semantics.Paths (>= 3.1.2)
- ktsu.Semantics.Strings (>= 3.1.2)
-
net7.0
- ktsu.Semantics.Paths (>= 3.1.2)
- ktsu.Semantics.Strings (>= 3.1.2)
-
net8.0
- ktsu.Semantics.Paths (>= 3.1.2)
- ktsu.Semantics.Strings (>= 3.1.2)
-
net9.0
- ktsu.Semantics.Paths (>= 3.1.2)
- ktsu.Semantics.Strings (>= 3.1.2)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on ktsu.RunCommand:
| Package | Downloads |
|---|---|
|
ktsu.GitIntegration
A .NET library that wraps the git command-line binary behind a fluent, strongly-typed interface for reading repository state — status, log, diff, branches, remotes, and revision resolution — and for mutating it — init, clone, staging, committing, branch creation and deletion, checkout, remote management, and remote sync via fetch, pull, and push — executed via ktsu.RunCommand with reproducible, copy-pasteable failures, locale-safe parsing, and a machine-readable per-reference account of every fetch and push, and that also unifies access to hosted Git providers behind a pluggable GitProvider abstraction with a GitHub implementation built on Octokit and credential resolution through ktsu.CredentialCache. Includes a set of semantic string types that replace stringly-typed Git identifiers — branch names, commit SHAs, ref names, remote names, author names and emails, repository names, and web URIs — with validated, compile-time-safe wrappers. |
|
|
ktsu.SvnToGit.Core
A guided .NET command-line tool that migrates a Subversion repository to Git by wrapping git svn in an interactive Spectre.Console front-end. Walks through cloning with standard layout, optional authors-file mapping and empty-directory preservation, converting remote git-svn branches into local Git branches, and aggressive garbage collection, with validation and progress reporting at every step. Ships the migration logic as a reusable library alongside the console app. |
|
|
ktsu.KtsuTools.Core
KtsuTools is a unified developer tools suite that consolidates multiple ktsu-dev utilities into a single CLI application with consistent UX powered by Spectre.Console. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.5.4 | 0 | 8/24/2026 |
| 1.5.3 | 65 | 8/21/2026 |
| 1.5.2 | 114 | 8/20/2026 |
| 1.5.1 | 130 | 8/19/2026 |
| 1.5.0 | 251 | 8/19/2026 |
| 1.4.29 | 80 | 8/19/2026 |
| 1.4.28 | 155 | 8/18/2026 |
| 1.4.27 | 82 | 8/18/2026 |
| 1.4.26 | 522 | 8/11/2026 |
| 1.4.25 | 167 | 8/6/2026 |
| 1.4.24 | 89 | 8/6/2026 |
| 1.4.23 | 119 | 8/5/2026 |
| 1.4.22 | 146 | 7/28/2026 |
| 1.4.21 | 149 | 7/21/2026 |
| 1.4.20 | 158 | 7/15/2026 |
| 1.4.19 | 145 | 7/14/2026 |
| 1.4.18 | 139 | 7/13/2026 |
| 1.4.17 | 161 | 7/8/2026 |
| 1.4.16 | 264 | 7/1/2026 |
| 1.4.15 | 213 | 6/30/2026 |
## v1.5.4 (patch)
No significant changes detected since v1.5.3.