C#(csharp)でコマンドを実行する方法|Process.Startの使い方と実装例
1. C#でコマンドを実行する基本
1-1. C#で外部コマンドを実行する主な方法
C#で外部コマンドやアプリケーションを実行する主な方法は、次の3つです。
1つ目は、実行可能ファイルを直接起動する方法です。
Process.Start("notepad.exe");2つ目は、cmd.exeを経由してWindowsのコマンドを実行する方法です。
Process.Start("cmd.exe", "/c dir");3つ目は、PowerShellを起動してPowerShellコマンドを実行する方法です。
Process.Start("powershell.exe", "-Command \"Get-Process\"");原則として、実行したいプログラムがexeファイルとして存在する場合は、そのexeファイルを直接起動するのがおすすめです。
dirやcopyのようなコマンドプロンプト内蔵コマンド、パイプ、リダイレクトなどを利用する場合に限り、cmd.exeを経由します。
1-2. Process.Startとは
Process.Startは、外部プロセスを開始するためのメソッドです。
次のような処理に利用できます。
exeファイルを起動する
コマンドプロンプトのコマンドを実行する
PowerShellコマンドを実行する
batファイルを実行する
ファイルやURLを関連付けられたアプリケーションで開く
最も単純なコードは次のとおりです。
using System.Diagnostics;Process.Start("notepad.exe");
より細かく実行方法を指定したい場合は、ProcessStartInfoを使用します。
1-3. ProcessクラスとProcessStartInfoの役割
Processクラスは、OS上で動作するプロセスを表します。
プロセスの開始だけでなく、次のような操作が可能です。
プロセスの終了を待つ
標準出力を取得する
標準エラーを取得する
終了コードを確認する
プロセスを強制終了する
プロセスの状態を調べる
一方、ProcessStartInfoは、プロセスをどのような設定で起動するかを指定するクラスです。
代表的なプロパティには、次のものがあります。
var startInfo = new ProcessStartInfo{FileName = "ping.exe",Arguments = "127.0.0.1",WorkingDirectory = @"C:\work",UseShellExecute = false,CreateNoWindow = true,RedirectStandardOutput = true,RedirectStandardError = true};ProcessStartInfoに起動条件を設定し、それをProcessに渡して実行するのが基本的な使い方です。
1-4. 「csharp コマンド」で検索するユーザーが知りたいこと
「csharp コマンド」や「C# コマンド 実行」と検索するユーザーは、主に次のような情報を求めています。
C#からコマンドプロンプトを実行したい
Process.Startの使い方を知りたいコマンドの実行結果を文字列として取得したい
コマンド画面を表示せずに実行したい
コマンドの終了を待ちたい
PowerShellやbatファイルを実行したい
引数にスペースや日本語が含まれる場合の指定方法を知りたい
非同期実行やタイムアウトを実装したい
以降では、これらのケースを順番に解説します。
2. Process.Startでコマンドを実行する最小コード
2-1. System.Diagnosticsを使う準備
ProcessクラスとProcessStartInfoクラスは、System.Diagnostics名前空間に含まれています。
ソースコードの先頭に、次のusingディレクティブを追加します。
using System.Diagnostics;usingディレクティブを追加しない場合は、完全修飾名でも記述できます。
System.Diagnostics.Process.Start("notepad.exe");通常は、using System.Diagnostics;を追加するほうが読みやすくなります。
2-2. exeファイルを起動する基本例
メモ帳を起動する最小コードは次のとおりです。
using System.Diagnostics;Process.Start("notepad.exe");
ProcessStartInfoを使う場合は、次のように記述します。
using System.Diagnostics;var startInfo = new ProcessStartInfo{FileName = "notepad.exe"};
Process.Start(startInfo);
起動したプロセスを操作する場合は、戻り値を変数に保存します。
using Process? process = Process.Start("notepad.exe");if (process is null){Console.WriteLine("プロセスを開始できませんでした。");}
Process.Startは、起動方法や実行環境によってnullを返す可能性があるため、必要に応じて確認します。
2-3. cmd.exe経由でコマンドを実行する例
dirは単独のexeファイルではなく、Windowsのコマンドプロンプトに組み込まれたコマンドです。そのため、cmd.exeを経由して実行します。
using System.Diagnostics;var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/c dir"};
Process.Start(startInfo);
cmd.exeの/cオプションには、「指定したコマンドを実行した後、コマンドプロンプトを終了する」という意味があります。
一方、/kを指定すると、コマンド実行後もコマンドプロンプトが開いたままになります。
var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/k dir"};自動処理では通常、実行後に終了する/cを使用します。
2-4. 引数付きでコマンドを実行する例
引数付きでプログラムを起動する場合は、Argumentsプロパティを指定します。
using System.Diagnostics;var startInfo = new ProcessStartInfo{FileName = "notepad.exe",Arguments = @"C:\work\sample.txt"};
Process.Start(startInfo);
引数にスペースが含まれる場合は、引用符で囲む必要があります。
var filePath = @"C:\My Documents\sample.txt";var startInfo = new ProcessStartInfo{FileName = "notepad.exe",Arguments = $""{filePath}""};
.NET Coreや新しい.NETでは、手動で引用符を組み立てるよりも、ArgumentListを使用する方法が安全です。
var startInfo = new ProcessStartInfo{FileName = "notepad.exe"};startInfo.ArgumentList.Add(@"C:\My Documents\sample.txt");
Process.Start(startInfo);
ArgumentListを使うと、空白や引用符を含む引数のエスケープをランタイムに任せられます。
3. ProcessStartInfoを使った実用的な書き方
3-1. FileNameとArgumentsの指定方法
FileNameには、実行するプログラムを指定します。
Argumentsには、そのプログラムへ渡す引数を指定します。
var startInfo = new ProcessStartInfo{FileName = "ping.exe",Arguments = "127.0.0.1"};次のように、プログラム名と引数をすべてFileNameへ書くことはできません。
// 正しくない指定var startInfo = new ProcessStartInfo{FileName = "ping.exe 127.0.0.1"};プログラム名と引数は、必ず分けて指定します。
より安全に複数の引数を指定する場合は、ArgumentListを利用します。
var startInfo = new ProcessStartInfo{FileName = "ping.exe",UseShellExecute = false};startInfo.ArgumentList.Add("127.0.0.1");startInfo.ArgumentList.Add("-n");startInfo.ArgumentList.Add("2");
ArgumentsとArgumentListを同時に使用することはできません。どちらか一方を選択してください。
3-2. WorkingDirectoryで実行ディレクトリを指定する
コマンドの実行ディレクトリは、WorkingDirectoryで指定できます。
var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/c dir",WorkingDirectory = @"C:\work",UseShellExecute = false};この例では、C:\workをカレントディレクトリとしてdirコマンドが実行されます。
実行対象が相対パスのファイルを参照する場合や、生成ファイルの出力先がカレントディレクトリに依存する場合は、WorkingDirectoryを明示すると動作が安定します。
なお、UseShellExecuteがfalseの場合、WorkingDirectoryは基本的に子プロセスの作業ディレクトリとして使用されます。実行ファイルを探すためのディレクトリとして利用するのではなく、FileNameには実行ファイル名または正しいパスを指定してください。
3-3. UseShellExecuteの設定と注意点
UseShellExecuteは、OSのシェル機能を使ってプロセスを起動するかどうかを指定します。
var startInfo = new ProcessStartInfo{FileName = "notepad.exe",UseShellExecute = false};UseShellExecute = falseにすると、次の機能を利用できます。
標準出力のリダイレクト
標準エラーのリダイレクト
標準入力のリダイレクト
環境変数の個別設定
バックグラウンドでのコマンド実行
標準出力を取得する場合は、必ずfalseに設定します。
var startInfo = new ProcessStartInfo{FileName = "ping.exe",UseShellExecute = false,RedirectStandardOutput = true};一方、ファイルやURLを関連付けられたアプリケーションで開く場合や、Windowsで管理者権限を要求する場合は、UseShellExecute = trueを使用します。
var startInfo = new ProcessStartInfo{FileName = "https://example.com",UseShellExecute = true};Process.Start(startInfo);
.NET Frameworkと新しい.NETでは、UseShellExecuteの既定値が異なる場合があります。意図しない動作を避けるため、必要な値を明示するのがおすすめです。
3-4. CreateNoWindowでコマンド画面を非表示にする
コマンドプロンプトの黒い画面を表示せずに実行する場合は、CreateNoWindowをtrueに設定します。
var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/c dir",UseShellExecute = false,CreateNoWindow = true};CreateNoWindowは、主にWindowsのコンソールアプリケーションを非表示で実行するときに使用します。
一般に、UseShellExecute = trueの場合は期待どおりに機能しないため、次の組み合わせで使用します。
UseShellExecute = false,CreateNoWindow = true3-5. WindowStyleでウィンドウ表示を制御する
WindowStyleを使うと、起動するウィンドウの表示状態を指定できます。
var startInfo = new ProcessStartInfo{FileName = "notepad.exe",UseShellExecute = true,WindowStyle = ProcessWindowStyle.Minimized};指定できる主な値は次のとおりです。
ProcessWindowStyle.NormalProcessWindowStyle.HiddenProcessWindowStyle.MinimizedProcessWindowStyle.Maximized
ただし、実際に指定どおり表示されるかどうかは、OSや起動対象のアプリケーションに依存します。
コマンド画面を確実に表示したくない場合は、WindowStyle = Hiddenだけに頼らず、UseShellExecute = falseとCreateNoWindow = trueを組み合わせます。
4. コマンドの実行結果を取得する方法
4-1. 標準出力を取得する基本
コマンドが出力した文字列をC#で取得するには、標準出力をリダイレクトします。
using System.Diagnostics;var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/c echo Hello",UseShellExecute = false,RedirectStandardOutput = true,CreateNoWindow = true};
using var process = new Process{StartInfo = startInfo};
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
Console.WriteLine(output);
実行結果は、StandardOutput.ReadToEnd()で文字列として取得できます。
4-2. RedirectStandardOutputの使い方
標準出力を取得するには、次の2つの設定が必要です。
UseShellExecute = false,RedirectStandardOutput = trueUseShellExecute = trueのままRedirectStandardOutput = trueを指定すると、例外が発生します。
非同期で読み取る場合は、ReadToEndAsyncを使用できます。
using var process = new Process{StartInfo = new ProcessStartInfo{FileName = "ping.exe",UseShellExecute = false,RedirectStandardOutput = true,CreateNoWindow = true}};process.Start();
string output = await process.StandardOutput.ReadToEndAsync();
await process.WaitForExitAsync();
Console.WriteLine(output);
引数はArgumentListで追加できます。
process.StartInfo.ArgumentList.Add("127.0.0.1");4-3. 標準エラーを取得する方法
コマンドが出力するエラーメッセージは、標準エラーから取得します。
var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/c dir C:\not-found-directory",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true};using var process = new Process{StartInfo = startInfo};
process.Start();
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
string output = await outputTask;string error = await errorTask;
Console.WriteLine($"標準出力: {output}");Console.WriteLine($"標準エラー: {error}");
標準出力と標準エラーを両方リダイレクトする場合は、片方ずつ同期的に読み取ると、出力量によってはデッドロックが発生する可能性があります。
ReadToEndAsyncで両方を並行して読み取るのが安全です。
4-4. 終了コードを確認する方法
プロセスの成功・失敗は、ExitCodeで確認できます。
process.Start();string output = await process.StandardOutput.ReadToEndAsync();string error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
int exitCode = process.ExitCode;
一般的には、終了コード0が成功を表します。
if (process.ExitCode == 0){Console.WriteLine("コマンドは正常に終了しました。");}else{Console.WriteLine($"コマンドが失敗しました。終了コード: {process.ExitCode}");}ただし、終了コードの意味はプログラムごとに異なります。実行対象の仕様を確認して判定してください。
4-5. 日本語文字化けを防ぐエンコーディング設定
日本語を含むコマンド結果が文字化けする場合は、子プロセスが出力する文字コードと、C#側の読み取りエンコーディングを一致させる必要があります。
using System.Text;var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/c dir",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,StandardOutputEncoding = Encoding.UTF8,StandardErrorEncoding = Encoding.UTF8,CreateNoWindow = true};
ただし、C#側だけをUTF-8にしても、コマンド側が別の文字コードで出力していれば正しく表示されません。
Windowsのcmd.exeでUTF-8へ切り替える例は次のとおりです。
var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/d /c "chcp 65001 > nul & dir"",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,StandardOutputEncoding = Encoding.UTF8,StandardErrorEncoding = Encoding.UTF8,CreateNoWindow = true};利用するコマンドによっては、UTF-8への切り替えに対応していない場合があります。文字化けが発生したときは、コマンドが実際に出力している文字コードを確認してください。
5. よく使うコマンド実行の実装例
5-1. dirコマンドを実行する例
dirコマンドを実行して、指定ディレクトリの一覧を取得する例です。
using System.Diagnostics;using System.Text;var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/d /c dir",WorkingDirectory = @"C:\work",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,StandardOutputEncoding = Encoding.UTF8,StandardErrorEncoding = Encoding.UTF8,CreateNoWindow = true};
using var process = new Process{StartInfo = startInfo};
process.Start();
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
string output = await outputTask;string error = await errorTask;
if (process.ExitCode == 0){Console.WriteLine(output);}else{Console.Error.WriteLine(error);}
ファイル一覧を取得するだけであれば、コマンドを実行せず、Directory.GetFilesやDirectory.EnumerateFilesを使う方法もあります。
foreach (string file in Directory.EnumerateFiles(@"C:\work")){Console.WriteLine(file);}C#の標準APIで実現できる処理は、外部コマンドよりも標準APIを優先すると、移植性と安全性が高まります。
5-2. pingコマンドを実行して結果を取得する例
Windowsのping.exeを直接実行する例です。
using System.Diagnostics;var startInfo = new ProcessStartInfo{FileName = "ping.exe",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true};
startInfo.ArgumentList.Add("127.0.0.1");startInfo.ArgumentList.Add("-n");startInfo.ArgumentList.Add("2");
using var process = new Process{StartInfo = startInfo};
process.Start();
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
string output = await outputTask;string error = await errorTask;
Console.WriteLine($"終了コード: {process.ExitCode}");Console.WriteLine(output);
if (!string.IsNullOrWhiteSpace(error)){Console.Error.WriteLine(error);}
ping.exeが実行ファイルとして存在するため、cmd.exeを経由する必要はありません。
なお、pingのオプションはOSによって異なります。Windowsの回数指定は-nですが、LinuxやmacOSでは一般に-cを使用します。
5-3. PowerShellコマンドを実行する例
Windows PowerShellを使って、プロセス一覧を取得する例です。
using System.Diagnostics;var startInfo = new ProcessStartInfo{FileName = "powershell.exe",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true};
startInfo.ArgumentList.Add("-NoProfile");startInfo.ArgumentList.Add("-NonInteractive");startInfo.ArgumentList.Add("-Command");startInfo.ArgumentList.Add("Get-Process | Select-Object -First 5");
using var process = new Process{StartInfo = startInfo};
process.Start();
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
string output = await outputTask;string error = await errorTask;
Console.WriteLine(output);
if (process.ExitCode != 0){Console.Error.WriteLine(error);}
PowerShell 7を使用する場合は、FileNameにpwshを指定します。
FileName = "pwsh"固定されたスクリプトファイルを実行する場合は、長いコマンド文字列を組み立てるよりも、-Fileでps1ファイルを指定したほうが管理しやすくなります。
5-4. batファイルを実行する例
batファイルを実行する場合は、cmd.exeを経由します。
using System.Diagnostics;string batPath = @"C:\scripts\sample.bat";
var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = $"/d /s /c ""{batPath}""",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true};
using var process = new Process{StartInfo = startInfo};
process.Start();
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
Console.WriteLine(await outputTask);Console.Error.WriteLine(await errorTask);Console.WriteLine($"終了コード: {process.ExitCode}");
batファイルのパスにスペースが含まれる可能性があるため、引用符の扱いに注意してください。
実行結果を取得する必要がなければ、シェル関連付けを利用して起動する方法もあります。
var startInfo = new ProcessStartInfo{FileName = @"C:\scripts\sample.bat",UseShellExecute = true};Process.Start(startInfo);
5-5. 外部アプリケーションを起動する例
メモ帳でテキストファイルを開く例です。
var startInfo = new ProcessStartInfo{FileName = "notepad.exe"};startInfo.ArgumentList.Add(@"C:\work\sample.txt");
Process.Start(startInfo);
ファイルに関連付けられた既定のアプリケーションで開く場合は、UseShellExecute = trueを指定します。
var startInfo = new ProcessStartInfo{FileName = @"C:\work\sample.pdf",UseShellExecute = true};Process.Start(startInfo);
WebブラウザでURLを開く場合も同様です。
var startInfo = new ProcessStartInfo{FileName = "https://example.com",UseShellExecute = true};Process.Start(startInfo);
6. 非同期でコマンドを実行する方法
6-1. WaitForExitで完了を待つ方法
プロセスの終了を同期的に待つ場合は、WaitForExitを使用します。
using var process = Process.Start("notepad.exe");if (process is not null){process.WaitForExit();Console.WriteLine("メモ帳が終了しました。");}
WaitForExitを呼び出したスレッドは、プロセスが終了するまで停止します。
コンソールアプリケーションの単純な処理では利用できますが、Windows Forms、WPF、MAUIなどのUIスレッドで実行すると、画面が操作不能になる可能性があります。
6-2. WaitForExitAsyncを使った非同期実行
新しい.NETでは、WaitForExitAsyncを使って非同期に終了を待てます。
using var process = new Process{StartInfo = new ProcessStartInfo{FileName = "ping.exe",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true}};process.StartInfo.ArgumentList.Add("127.0.0.1");process.StartInfo.ArgumentList.Add("-n");process.StartInfo.ArgumentList.Add("2");
process.Start();
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
string output = await outputTask;string error = await errorTask;
WaitForExitAsyncを使えば、待機中に呼び出し元のスレッドを占有しません。
6-3. OutputDataReceivedで標準出力を非同期に読む
実行中の出力を1行ずつ受け取りたい場合は、OutputDataReceivedイベントを使用します。
using System.Diagnostics;using var process = new Process{StartInfo = new ProcessStartInfo{FileName = "ping.exe",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true},EnableRaisingEvents = true};
process.StartInfo.ArgumentList.Add("127.0.0.1");process.StartInfo.ArgumentList.Add("-n");process.StartInfo.ArgumentList.Add("4");
var outputCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var errorCompleted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
process.OutputDataReceived += (_, e) =>{if (e.Data is null){outputCompleted.TrySetResult();}else{Console.WriteLine($"OUT: {e.Data}");}};
process.ErrorDataReceived += (_, e) =>{if (e.Data is null){errorCompleted.TrySetResult();}else{Console.Error.WriteLine($"ERR: {e.Data}");}};
process.Start();process.BeginOutputReadLine();process.BeginErrorReadLine();
await process.WaitForExitAsync();await Task.WhenAll(outputCompleted.Task, errorCompleted.Task);
Console.WriteLine($"終了コード: {process.ExitCode}");
ログをリアルタイム表示したい場合や、長時間実行されるコマンドの進捗を監視したい場合に適しています。
6-4. タイムアウトを設定して強制終了する方法
外部コマンドが応答しなくなる可能性がある場合は、タイムアウトを設定します。
using System.Diagnostics;static async Task<int> RunWithTimeoutAsync(string fileName,IEnumerable<string> arguments,TimeSpan timeout,CancellationToken cancellationToken = default){using var process = new Process{StartInfo = new ProcessStartInfo{FileName = fileName,UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true}};
foreach (string argument in arguments){process.StartInfo.ArgumentList.Add(argument);}process.Start();Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();Task exitTask = process.WaitForExitAsync(cancellationToken);Task timeoutTask = Task.Delay(timeout, cancellationToken);Task completedTask = await Task.WhenAny(exitTask, timeoutTask);if (completedTask == timeoutTask){if (!process.HasExited){process.Kill(entireProcessTree: true);}await process.WaitForExitAsync(CancellationToken.None);throw new TimeoutException($"コマンドが{timeout.TotalSeconds}秒以内に終了しませんでした。");}await exitTask;string output = await outputTask;string error = await errorTask;Console.WriteLine(output);if (!string.IsNullOrWhiteSpace(error)){Console.Error.WriteLine(error);}return process.ExitCode;
}
呼び出し例は次のとおりです。
int exitCode = await RunWithTimeoutAsync("ping.exe",new[] { "127.0.0.1", "-n", "10" },TimeSpan.FromSeconds(5));WaitForExitAsyncへ渡したキャンセルトークンがキャンセルされても、通常は待機処理がキャンセルされるだけで、子プロセス自体が自動的に終了するとは限りません。
プロセスも停止させたい場合は、明示的にKillを呼び出します。
6-5. UIアプリでフリーズを防ぐ実装ポイント
UIアプリケーションでは、次のような同期処理をUIスレッド上で実行しないようにします。
process.WaitForExit();string output = process.StandardOutput.ReadToEnd();代わりに、非同期メソッドを使用します。
private async void ExecuteButton_Click(object sender, EventArgs e){try{ExecuteButton.Enabled = false; string result = await RunCommandAsync();ResultTextBox.Text = result;}catch (Exception ex){MessageBox.Show(ex.Message);}finally{ExecuteButton.Enabled = true;}
}
コマンド実行メソッドでは、WaitForExitAsyncとReadToEndAsyncを使用します。
private static async Task<string> RunCommandAsync(){using var process = new Process{StartInfo = new ProcessStartInfo{FileName = "ping.exe",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true}};process.StartInfo.ArgumentList.Add("127.0.0.1");process.StartInfo.ArgumentList.Add("-n");process.StartInfo.ArgumentList.Add("2");process.Start();Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();await process.WaitForExitAsync();string output = await outputTask;string error = await errorTask;if (process.ExitCode != 0){throw new InvalidOperationException($"コマンドが失敗しました。\n{error}");}return output;
}
.Resultや.Wait()で非同期処理を同期的に待つと、デッドロックやUIフリーズの原因になります。UIイベントからはawaitを使って呼び出してください。
7. Process.Startで発生しやすいエラーと対処法
7-1. 指定したファイルが見つからない
実行ファイルが見つからない場合、Win32Exceptionなどの例外が発生します。
try{Process.Start("not-exists.exe");}catch (System.ComponentModel.Win32Exception ex){Console.Error.WriteLine($"実行ファイルを開始できませんでした: {ex.Message}");}主な原因は次のとおりです。
ファイル名のスペルが間違っている
実行ファイルが存在しない
PATH環境変数に登録されていない
相対パスの基準が想定と異なる
拡張子やファイル名がOSと一致していない
確実に実行する必要がある場合は、絶対パスを指定します。
FileName = @"C:\Tools\MyApp\myapp.exe"実行前に存在確認を行うこともできます。
if (!File.Exists(exePath)){throw new FileNotFoundException("実行ファイルが見つかりません。",exePath);}7-2. アクセス権限がない
実行ファイルや対象ディレクトリへの権限が不足していると、プロセスを開始できないことがあります。
確認するポイントは次のとおりです。
実行ファイルへのアクセス権限
作業ディレクトリへのアクセス権限
出力先ファイルへの書き込み権限
セキュリティソフトによる実行制限
OSの管理者権限が必要な処理か
サービスアカウントやWebアプリの実行ユーザー
単純に管理者権限へ昇格するのではなく、まず必要なフォルダやファイルだけに適切な権限を設定できないか検討してください。
7-3. 引数にスペースや日本語が含まれる
Argumentsを文字列で指定すると、スペース、引用符、末尾のバックスラッシュなどの扱いが複雑になります。
Arguments = ""C:\My Documents\sample.txt""新しい.NETでは、ArgumentListの利用がおすすめです。
var startInfo = new ProcessStartInfo{FileName = "myapp.exe"};startInfo.ArgumentList.Add(@"C:\My Documents\日本語ファイル.txt");startInfo.ArgumentList.Add("--mode");startInfo.ArgumentList.Add("test value");
それぞれの引数を個別に追加するため、手動で引用符を組み立てる必要がありません。
ただし、cmd.exe /cやPowerShellの-Commandへ文字列を渡す場合、その文字列はさらにシェルによって解析されます。シェル用のコマンド文字列には、別途エスケープや入力検証が必要です。
7-4. 標準出力が取得できない
標準出力を取得できない場合は、次の設定を確認します。
UseShellExecute = false,RedirectStandardOutput = trueまた、コマンドが標準出力ではなく、標準エラーへメッセージを出している可能性もあります。
RedirectStandardError = trueGUIアプリケーションなど、標準出力を使用しないプログラムからは、リダイレクトしても文字列を取得できません。
さらに、出力がバッファリングされている場合、プロセスの実行中には結果が届かず、終了時にまとめて出力されることがあります。
7-5. プロセスが終了しない・固まる
プロセスが終了しない主な原因には、次のものがあります。
ユーザー入力を待っている
標準出力または標準エラーのバッファが詰まっている
子プロセスが残っている
ネットワークやファイルI/Oの完了を待っている
アプリケーション側でデッドロックしている
コマンドプロンプトに
/kを指定している
標準出力と標準エラーを両方リダイレクトしている場合は、両方を並行して読み取ります。
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();await process.WaitForExitAsync();
string output = await outputTask;string error = await errorTask;
自動処理ではタイムアウトを設定し、必要に応じてプロセスツリーを終了させる設計が重要です。
7-6. 例外処理の基本パターン
外部コマンドの実行では、プロセスの開始時だけでなく、待機、読み取り、終了処理でも例外が発生する可能性があります。
using System.ComponentModel;using System.Diagnostics;try{using var process = new Process{StartInfo = new ProcessStartInfo{FileName = "myapp.exe",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true}};
process.Start();Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();await process.WaitForExitAsync();string output = await outputTask;string error = await errorTask;if (process.ExitCode != 0){Console.Error.WriteLine($"終了コード: {process.ExitCode}\n{error}");}else{Console.WriteLine(output);}
}catch (Win32Exception ex){Console.Error.WriteLine($"プロセスを開始できませんでした: {ex.Message}");}catch (OperationCanceledException){Console.Error.WriteLine("コマンドの実行がキャンセルされました。");}catch (InvalidOperationException ex){Console.Error.WriteLine($"プロセスの操作に失敗しました: {ex.Message}");}catch (Exception ex){Console.Error.WriteLine($"予期しないエラーが発生しました: {ex.Message}");}
本番環境では、例外を握りつぶさず、必要な情報をログに残したうえで、呼び出し元へ失敗を通知してください。
8. セキュリティ上の注意点
8-1. ユーザー入力をそのままコマンドに渡さない
ユーザーが入力した文字列を、そのままシェルコマンドへ連結してはいけません。
危険な例は次のとおりです。
string userInput = Console.ReadLine() ?? "";var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = $"/c ping {userInput}"};
ユーザーが特殊記号を入力すると、本来想定していない別のコマンドを実行される可能性があります。
ホスト名を受け付ける場合は、形式や長さを検証します。
using System.Text.RegularExpressions;if (!Regex.IsMatch(userInput,@"^[a-zA-Z0-9.-]{1,253}$")){throw new ArgumentException("ホスト名の形式が不正です。");}
入力値の種類に応じて、許可する文字、長さ、値の範囲を限定してください。
8-2. コマンドインジェクションの危険性
コマンドインジェクションとは、入力値にシェルの制御記号を混入させ、意図しないコマンドを実行させる攻撃です。
Windowsのコマンドプロンプトでは、次のような記号が処理に影響します。
& && | || > < ^PowerShellでは、セミコロン、パイプ、変数展開、式、サブ式などにも注意が必要です。
LinuxやmacOSのシェルでは、次のような構文が問題になります。
; && || | > < $() ``引用符で囲むだけでは、すべての入力を安全に処理できるとは限りません。
最も有効な対策は、シェルを介さず、実行ファイルを直接指定することです。
8-3. 安全な引数の組み立て方
安全性を高めるには、ArgumentListを使って引数を個別に指定します。
var startInfo = new ProcessStartInfo{FileName = "ping.exe",UseShellExecute = false};startInfo.ArgumentList.Add(validatedHostName);startInfo.ArgumentList.Add("-n");startInfo.ArgumentList.Add("2");
この方法では、入力値が独立した1つの引数としてプログラムへ渡されます。
ただし、ArgumentListを使えば入力検証が不要になるわけではありません。実行対象のプログラムが特別な形式の引数を解釈する可能性があるため、次の対策を組み合わせます。
許可リストで値を限定する
数値は数値型へ変換して範囲を確認する
ファイルパスは許可されたディレクトリ内か確認する
オプション名をユーザーに自由入力させない
実行ファイル名をユーザーに指定させない
シェルを可能な限り使用しない
8-4. 実行権限と管理者権限の扱い
Windowsで管理者権限を要求する場合は、Verb = "runas"を指定します。
var startInfo = new ProcessStartInfo{FileName = "myapp.exe",UseShellExecute = true,Verb = "runas"};Process.Start(startInfo);
この方法ではUACの確認画面が表示されます。
UseShellExecute = trueが必要になるため、標準出力や標準エラーのリダイレクトとは併用できません。
管理者権限を安易に要求すると、被害範囲が拡大します。アプリケーション全体を昇格させるのではなく、必要な処理だけを分離できないか検討してください。
また、利用者の同意なしにUACを回避する実装は行わないでください。
8-5. ログ出力時に注意すべき情報
実行したコマンドや引数をログに記録すると、障害調査に役立ちます。ただし、次の情報をそのまま出力しないよう注意が必要です。
パスワード
APIキー
アクセストークン
接続文字列
個人情報
セッションID
機密ファイルのパス
ユーザーが入力した秘密情報
たとえば、パスワードを引数として渡している場合、コマンドライン全体をログに出力してはいけません。
logger.LogInformation("コマンドを実行します。FileName={FileName}, User={User}",startInfo.FileName,userName);機密値は省略するか、マスクして記録します。
9. Process.Startを使うときのベストプラクティス
9-1. cmd.exeを使うべきケースと不要なケース
cmd.exeが必要になる代表的なケースは次のとおりです。
dirやcopyなどの内蔵コマンドを使うbatファイルを実行する
パイプを使う
リダイレクトを使う
&&や||で複数コマンドを制御するワイルドカードをシェルに展開させる
一方、次のようなexeファイルは直接実行できます。
FileName = "ping.exe"FileName = "git.exe"FileName = "dotnet"不要なcmd.exeを挟むと、引用符や特殊文字の扱いが複雑になり、コマンドインジェクションの危険性も高まります。
9-2. FileNameとArgumentsを分けて指定する
実行ファイル名と引数は、明確に分けて指定します。
var startInfo = new ProcessStartInfo{FileName = "git.exe",UseShellExecute = false};startInfo.ArgumentList.Add("status");startInfo.ArgumentList.Add("--short");
1つの文字列として連結するよりも、コードの意図が明確になり、引数のエスケープミスも防ぎやすくなります。
9-3. 標準出力と標準エラーを両方処理する
正常時の情報は標準出力、エラーや警告は標準エラーへ出力されるのが一般的です。
RedirectStandardOutput = true,RedirectStandardError = true両方を非同期で読み取ります。
Task<string> outputTask = process.StandardOutput.ReadToEndAsync();Task<string> errorTask = process.StandardError.ReadToEndAsync();await process.WaitForExitAsync();
string output = await outputTask;string error = await errorTask;
標準出力だけを確認すると、失敗原因を取得できない可能性があります。
9-4. 終了コードで成功・失敗を判定する
出力文字列だけで成功を判定せず、終了コードを確認します。
if (process.ExitCode != 0){throw new InvalidOperationException($"コマンドが失敗しました。終了コード: {process.ExitCode}");}正常終了時でも警告が標準エラーへ出力されるプログラムがあります。反対に、エラーメッセージが標準出力へ出る場合もあります。
基本的には終了コードを中心に判定し、標準出力と標準エラーは詳細情報として扱います。
9-5. usingまたはDisposeでリソースを解放する
ProcessはOSのリソースを保持するため、使用後に破棄します。
using var process = new Process{StartInfo = startInfo};または、明示的にDisposeを呼び出します。
var process = new Process();try{process.StartInfo = startInfo;process.Start();process.WaitForExit();}finally{process.Dispose();}
通常は、using宣言を使うと簡潔です。
実務では、次のような共通メソッドにまとめると再利用しやすくなります。
using System.Diagnostics;public sealed record CommandResult(int ExitCode,string StandardOutput,string StandardError);
public static class CommandRunner{public static async Task<CommandResult> RunAsync(string fileName,IEnumerable<string> arguments,string? workingDirectory = null,CancellationToken cancellationToken = default){var startInfo = new ProcessStartInfo{FileName = fileName,UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true};
if (!string.IsNullOrWhiteSpace(workingDirectory)){startInfo.WorkingDirectory = workingDirectory;}foreach (string argument in arguments){startInfo.ArgumentList.Add(argument);}using var process = new Process{StartInfo = startInfo};if (!process.Start()){throw new InvalidOperationException($"プロセスを開始できませんでした: {fileName}");}Task<string> outputTask =process.StandardOutput.ReadToEndAsync();Task<string> errorTask =process.StandardError.ReadToEndAsync();await process.WaitForExitAsync(cancellationToken);return new CommandResult(process.ExitCode,await outputTask,await errorTask);}
}
呼び出し側では、終了コードと出力をまとめて取得できます。
CommandResult result = await CommandRunner.RunAsync("git.exe",new[] { "status", "--short" },@"C:\repository");if (result.ExitCode == 0){Console.WriteLine(result.StandardOutput);}else{Console.Error.WriteLine(result.StandardError);}
10. C#のコマンド実行に関するFAQ
10-1. Process.Startとcmd.exeの違いは?
Process.Startは、外部プロセスを起動するためのC#のAPIです。
cmd.exeは、Windowsのコマンドプロンプト本体です。
exeファイルを直接実行する場合は、Process.Startでその実行ファイルを指定します。
Process.Start("notepad.exe");dirなどのコマンドプロンプト内蔵コマンドを実行する場合は、Process.Startでcmd.exeを起動し、/cの後ろにコマンドを指定します。
Process.Start("cmd.exe", "/c dir");つまり、両者は対立する機能ではなく、Process.Startを使ってcmd.exeを起動する関係です。
10-2. コマンドプロンプトを表示せずに実行できる?
次のように設定すると、コマンド画面を表示せずに実行できます。
var startInfo = new ProcessStartInfo{FileName = "cmd.exe",Arguments = "/c dir",UseShellExecute = false,CreateNoWindow = true};結果を取得する場合は、リダイレクトも設定します。
RedirectStandardOutput = true,RedirectStandardError = true10-3. 管理者権限でコマンドを実行できる?
Windowsでは、次の設定で管理者権限を要求できます。
var startInfo = new ProcessStartInfo{FileName = "myapp.exe",UseShellExecute = true,Verb = "runas"};Process.Start(startInfo);
UACの確認画面が表示されるため、完全な無人実行には向きません。
また、UseShellExecute = trueが必要になるため、標準出力と標準エラーを直接リダイレクトすることはできません。
10-4. LinuxやmacOSでも同じコードで動く?
Processクラス自体はLinuxやmacOSでも利用できます。ただし、実行するコマンド名、パス、オプション、シェルはOSによって異なります。
Windowsのシェルは主に次のとおりです。
cmd.exepowershell.exepwshLinuxやmacOSでは、次のようなシェルが利用されます。
/bin/sh/bin/bash/bin/zshたとえば、LinuxやmacOSでシェルコマンドを実行する場合は、次のように記述できます。
var startInfo = new ProcessStartInfo{FileName = "/bin/sh",UseShellExecute = false,RedirectStandardOutput = true,RedirectStandardError = true,CreateNoWindow = true};startInfo.ArgumentList.Add("-c");startInfo.ArgumentList.Add("ls -la");
ただし、可能であればシェルを介さず、実行ファイルを直接起動するほうが安全です。
クロスプラットフォーム対応では、OSを判定してコマンドや引数を切り替えます。
if (OperatingSystem.IsWindows()){// Windows用の処理}else if (OperatingSystem.IsLinux()){// Linux用の処理}else if (OperatingSystem.IsMacOS()){// macOS用の処理}10-5. 標準出力と標準エラーの違いは?
標準出力は、プログラムの通常の実行結果を出力するためのストリームです。
process.StandardOutput標準エラーは、エラー、警告、診断情報などを出力するためのストリームです。
process.StandardError両方を取得するには、次の設定を行います。
RedirectStandardOutput = true,RedirectStandardError = trueただし、どの情報をどちらへ出力するかは、実行するプログラムの実装によって異なります。標準エラーに何か出力されたという理由だけで、必ず失敗したとは限りません。
成功・失敗は、終了コードもあわせて確認してください。
10-6. Process.Startが推奨されないケースはある?
次のようなケースでは、外部コマンドを実行する前に別の方法を検討してください。
C#の標準APIで同じ処理を実現できる
ユーザー入力をシェルへ渡す必要がある
OSごとの差異を吸収したい
大量の外部プロセスを高頻度で起動する
長時間動作するプロセスを厳密に管理したい
Webアプリやサービスから対話型アプリを起動したい
管理者権限を常時必要とする
実行環境に対象コマンドが存在する保証がない
たとえば、ファイルのコピーにはFile.Copy、ディレクトリ一覧の取得にはDirectory.EnumerateFiles、HTTP通信にはHttpClientを利用できます。
外部コマンドは便利ですが、プロセス起動のコスト、環境依存、セキュリティ、エラー処理の複雑さが増します。C#のAPIで実現できる処理は、まず標準ライブラリの利用を検討してください。
まとめ
C#でコマンドを実行する基本は、System.Diagnostics.ProcessとProcessStartInfoを使用することです。
単純にアプリケーションを起動するだけであれば、Process.Startで実行ファイルを指定できます。実行結果を取得する場合は、UseShellExecute = falseにしたうえで、RedirectStandardOutputとRedirectStandardErrorを有効にします。
実務で特に重要なポイントは、次のとおりです。
exeファイルは可能な限り直接起動する
cmd.exeは内蔵コマンドやシェル機能が必要な場合だけ使う引数は
ArgumentListで個別に指定する標準出力と標準エラーを並行して読み取る
WaitForExitAsyncで非同期に終了を待つ終了コードで成功・失敗を判定する
タイムアウトとキャンセルを考慮する
ユーザー入力をコマンド文字列へ直接連結しない
usingでProcessを確実に破棄するC#の標準APIで代替できる処理は標準APIを優先する
Process.Startは、C#から外部ツールや既存システムを連携させるうえで便利な機能です。一方で、引数の扱い、権限、文字コード、デッドロック、コマンドインジェクションなどに注意し、安全で保守しやすい形に実装することが大切です。

