C#のProcessクラス完全ガイド|外部プロセスの起動・終了・標準出力取得まで実例で解説

はじめに

C#で外部のexe、コマンド、バッチファイル、PowerShell、Python、Node.jsなどを実行したいときに使う代表的なクラスがProcessクラスです。たとえば、C#アプリからpingコマンドを実行して結果を取得したり、別アプリを起動したり、実行中のプロセスを終了したり、標準出力・標準エラーをログに保存したりできます。

一方で、Processクラスは便利な反面、UseShellExecuteRedirectStandardOutputWaitForExitKillなどの設定を誤ると、標準出力が取得できない、プロセスが固まる、コマンドインジェクションの危険がある、といった問題が起きやすい機能でもあります。

この記事では、C#のProcessクラスを使った外部プロセスの起動、終了待ち、強制終了、標準出力取得、標準エラー取得、標準入力、非同期実行、実践サンプル、よくあるエラー、安全な使い方まで実例付きで解説します。

1. C#のProcessクラスとは

Processクラスは、C#からOS上のプロセスを操作するためのクラスです。System.Diagnostics名前空間に用意されており、外部プログラムの起動、実行中プロセスの情報取得、終了待ち、終了、標準入出力のリダイレクトなどに利用できます。Microsoftのドキュメントでも、Processはプロセスリソースの開始や関連付けに使われるクラスとして説明されています。Microsoft Learn

1-1. Processクラスでできること

C#のProcessクラスでは、主に次のような処理ができます。

  • 外部exeを起動する

  • コマンドプロンプトやPowerShellのコマンドを実行する

  • URL、ファイル、フォルダを開く

  • 外部プロセスの終了を待つ

  • 終了コードを取得する

  • 標準出力を取得する

  • 標準エラー出力を取得する

  • 標準入力に値を渡す

  • 実行中のプロセスを終了・強制終了する

  • 非同期でプロセスの終了や出力を監視する

たとえば、C#からメモ帳を起動するだけなら、次のように書けます。

C#
using System.Diagnostics;

Process.Start("notepad.exe");

このように、単純な外部アプリの起動であれば非常に少ないコードで実装できます。

1-2. 外部プロセスを扱う代表的な用途

Processクラスは、業務アプリやツール開発でよく使われます。代表的な用途は次の通りです。

用途
外部ツールの実行ffmpeg、git、7zip、PythonなどをC#から呼び出す
OSコマンドの実行ping、ipconfig、dir、tasklistなど
バッチ処理.bat.cmdファイルを起動する
自動化PowerShellスクリプトをC#から実行する
ログ取得標準出力・標準エラーを取得して保存する
アプリ連携外部アプリを起動して処理結果を受け取る

特に、既存のコマンドラインツールとC#アプリを連携させる場合、Processクラスは非常に強力です。

1-3. Processクラスを使う前に知っておきたい基本用語

Processクラスを理解するには、いくつかの用語を押さえておくとスムーズです。

用語意味
プロセス実行中のプログラムの単位
実行ファイル.exeなど、起動できるファイル
引数コマンドに渡す追加情報
標準出力通常の実行結果を出力するストリーム
標準エラーエラー内容を出力するストリーム
標準入力外部プロセスへ入力を渡すストリーム
終了コードプロセス終了時の結果を表す数値
シェルWindowsシェルなど、OS経由でファイルやURLを開く仕組み

たとえば、コマンドプロンプトで次のように実行する場合、

cmd
ping example.com

pingが実行ファイル名、example.comが引数です。

1-4. System.Diagnostics名前空間の役割

ProcessクラスはSystem.Diagnostics名前空間に含まれています。そのため、C#で使う場合は基本的に次のusingを追加します。

C#
using System.Diagnostics;

System.Diagnosticsには、プロセス操作だけでなく、ログ出力、トレース、デバッグ支援など、診断に関するクラスが含まれています。Processクラスもその一部で、外部プロセスの状態確認や実行結果の取得に使われます。

2. C#で外部プロセスを起動する基本

2-1. Process.Startでexeやコマンドを実行する方法

外部プロセスを起動する基本はProcess.Startです。もっともシンプルな例は次の通りです。

C#
using System.Diagnostics;

Process.Start("notepad.exe");

コマンドを実行する場合は、実行ファイル名と引数を分けて指定します。

C#
using System.Diagnostics;

Process.Start("ping", "example.com");

ただし、標準出力を取得したい場合や、作業ディレクトリ、文字コード、ウィンドウ表示などを細かく制御したい場合は、ProcessStartInfoを使うのが基本です。

2-2. ProcessStartInfoを使った起動設定の基本

ProcessStartInfoは、プロセス起動時の設定をまとめるクラスです。Microsoftのドキュメントでは、ProcessStartInfoはプロセス起動時に使われるファイル名やコマンドライン引数などの情報を指定するクラスとして説明されています。Microsoft Learn

基本形は次の通りです。

C#
using System.Diagnostics;

var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false
};

using var process = Process.Start(startInfo);
process?.WaitForExit();

FileNameに実行するプログラム、Argumentsに引数、UseShellExecuteにシェルを使うかどうかを指定します。

2-3. FileNameとArgumentsの指定方法

FileNameには実行するファイル名を指定します。Argumentsにはコマンドライン引数を文字列で指定します。

C#
var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "-n 4 example.com",
UseShellExecute = false
};

Windowsのcmd.exeでコマンドを実行する場合は、次のように/cを使います。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c dir",
UseShellExecute = false
};

/cは「コマンドを実行したら終了する」という意味です。対話的にコマンドプロンプトを開いたままにしたい場合は/kを使います。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/k dir",
UseShellExecute = true
};

ただし、ユーザー入力をそのままArgumentsに連結するのは危険です。安全性を高めるには、可能であればArgumentListを使います。ArgumentListに追加した文字列は、事前にエスケープする必要がない引数コレクションとして扱われます。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "ping",
UseShellExecute = false
};

startInfo.ArgumentList.Add("example.com");

2-4. 作業ディレクトリを指定する方法

外部プロセスのカレントディレクトリを指定したい場合は、WorkingDirectoryを使います。

C#
var startInfo = new ProcessStartInfo
{
FileName = "mytool.exe",
Arguments = "input.txt",
WorkingDirectory = @"C:\Tools\MyTool",
UseShellExecute = false
};

using var process = Process.Start(startInfo);
process?.WaitForExit();

WorkingDirectoryは、相対パスでファイルを読む外部ツールを実行するときに重要です。

C#
// C:\Tools\MyTool\input.txt を参照する想定
Arguments = "input.txt"

なお、WorkingDirectoryの解釈はUseShellExecuteの値によって変わります。UseShellExecute=falseでは起動後プロセスの作業ディレクトリとして使われ、UseShellExecute=trueでは実行ファイルを探す場所として扱われます。Microsoft Learn

2-5. URL・ファイル・フォルダを開く方法

URL、ファイル、フォルダを既定のアプリで開くには、UseShellExecute=trueを指定します。

C#
using System.Diagnostics;

Process.Start(new ProcessStartInfo
{
FileName = "https://example.com",
UseShellExecute = true
});

フォルダを開く例です。

C#
Process.Start(new ProcessStartInfo
{
FileName = @"C:\Temp",
UseShellExecute = true
});

PDFやテキストファイルを既定のアプリで開く例です。

C#
Process.Start(new ProcessStartInfo
{
FileName = @"C:\Temp\sample.pdf",
UseShellExecute = true
});

UseShellExecute=falseの場合、Processで起動できるのは基本的に実行ファイルです。ドキュメントやURLを既定のアプリで開く場合はUseShellExecute=trueを使います。Microsoft Learn

3. ProcessStartInfoの主要プロパティ

3-1. UseShellExecuteの意味とtrue・falseの違い

UseShellExecuteは、OSのシェルを使ってプロセスを起動するかどうかを指定する重要なプロパティです。Microsoftのドキュメントでは、UseShellExecute=trueはシェルを使ってプロセスを開始し、falseは実行ファイルから直接プロセスを作成する設定と説明されています。既定値は.NETではfalse、.NET Frameworkではtrueです。Microsoft Learn

UseShellExecute特徴
trueURL、ファイル、フォルダを既定のアプリで開ける
false標準出力、標準エラー、標準入力をリダイレクトできる

標準出力や標準エラーを取得する場合は、原則としてUseShellExecute=falseにします。

C#
var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true
};

3-2. CreateNoWindowで黒い画面を非表示にする方法

コンソールアプリを実行するとき、黒い画面を表示したくない場合はCreateNoWindow=trueを指定します。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c dir",
UseShellExecute = false,
CreateNoWindow = true
};

標準出力を裏側で取得する処理では、次のように組み合わせることが多いです。

C#
var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};

3-3. WindowStyleでウィンドウ表示を制御する方法

WindowStyleを使うと、起動するウィンドウの表示状態を制御できます。

C#
var startInfo = new ProcessStartInfo
{
FileName = "notepad.exe",
WindowStyle = ProcessWindowStyle.Minimized
};

Process.Start(startInfo);

指定できる代表的な値は次の通りです。

意味
Normal通常表示
Hidden非表示
Minimized最小化
Maximized最大化

ただし、すべてのアプリが指定通りに表示されるとは限りません。アプリ側の実装やOSの制約によって動作が異なる場合があります。

3-4. Verbを使って管理者権限で実行する方法

Windowsで管理者権限として実行したい場合は、Verb = "runas"を指定します。Verbは、FileNameで指定したアプリケーションやドキュメントを開くときに使う動詞を指定するプロパティです。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c net session",
UseShellExecute = true,
Verb = "runas"
};

Process.Start(startInfo);

管理者権限で起動する場合、通常はUACの確認ダイアログが表示されます。また、Verb="runas"を使うには基本的にUseShellExecute=trueが必要です。

3-5. EnvironmentVariablesで環境変数を渡す方法

外部プロセスに環境変数を渡したい場合は、EnvironmentVariablesまたはEnvironmentを使います。ProcessStartInfoには、起動するプロセスや子プロセスに適用される環境変数を設定するプロパティが用意されています。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c echo %MY_VALUE%",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};

startInfo.EnvironmentVariables["MY_VALUE"] = "Hello Process";

using var process = Process.Start(startInfo);
string output = process!.StandardOutput.ReadToEnd();
process.WaitForExit();

Console.WriteLine(output);

環境変数は、外部ツールに設定値や認証情報の一部を渡すときに使えます。ただし、機密情報を環境変数で渡す場合は、ログや子プロセスへの伝播に注意が必要です。

4. 外部プロセスの終了を待つ方法

4-1. WaitForExitの基本的な使い方

外部プロセスが終了するまで待つにはWaitForExitを使います。

C#
using var process = Process.Start("notepad.exe");

process?.WaitForExit();

Console.WriteLine("メモ帳が終了しました。");

コマンド実行結果を取得する場合も、通常は読み取り後または非同期読み取りの完了確認としてWaitForExitを使います。

C#
var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true
};

using var process = Process.Start(startInfo);
string output = process!.StandardOutput.ReadToEnd();
process.WaitForExit();

Console.WriteLine(output);

4-2. タイムアウト付きで終了を待つ方法

外部プロセスが固まる可能性がある場合は、タイムアウトを設定します。

C#
using var process = Process.Start("notepad.exe");

bool exited = process!.WaitForExit(5000);

if (exited)
{
Console.WriteLine("5秒以内に終了しました。");
}
else
{
Console.WriteLine("5秒以内に終了しませんでした。");
}

タイムアウト後に強制終了する例です。

C#
using var process = Process.Start("notepad.exe");

if (!process!.WaitForExit(5000))
{
process.Kill();
process.WaitForExit();
}

外部コマンドやユーザー操作待ちのアプリをC#から実行する場合、無期限に待つWaitForExit()だけに頼るとアプリ全体が止まる原因になります。

4-3. ExitCodeで実行結果を判定する方法

プロセスが終了した後は、ExitCodeで終了コードを取得できます。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c exit 1",
UseShellExecute = false,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);
process!.WaitForExit();

Console.WriteLine($"ExitCode: {process.ExitCode}");

一般的には、終了コード0が成功、0以外がエラーを表すことが多いです。ただし、終了コードの意味は実行する外部プログラムによって異なります。

C#
if (process.ExitCode == 0)
{
Console.WriteLine("成功");
}
else
{
Console.WriteLine("失敗");
}

4-4. HasExitedで終了状態を確認する方法

HasExitedを使うと、プロセスがすでに終了しているかを確認できます。

C#
using var process = Process.Start("notepad.exe");

Thread.Sleep(3000);

if (process!.HasExited)
{
Console.WriteLine("終了済みです。");
}
else
{
Console.WriteLine("まだ実行中です。");
}

KillCloseMainWindowを呼び出した後に終了確認する場合にも使います。

C#
if (!process.HasExited)
{
process.Kill();
}

4-5. Exitedイベントで非同期に終了を検知する方法

プロセス終了をイベントで検知したい場合は、Exitedイベントを使います。Exitedイベントを使うにはEnableRaisingEvents=trueを設定します。

C#
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "notepad.exe"
},
EnableRaisingEvents = true
};

process.Exited += (sender, e) =>
{
Console.WriteLine("プロセスが終了しました。");
process.Dispose();
};

process.Start();

画面をブロックせずに終了を検知したいGUIアプリや常駐ツールでは、Exitedイベントが便利です。

5. 外部プロセスを終了・強制終了する方法

5-1. CloseMainWindowで正常終了を依頼する方法

GUIアプリを通常終了させたい場合は、CloseMainWindowを使います。これは、対象プロセスのメインウィンドウへ閉じるメッセージを送るメソッドです。戻り値は、閉じるメッセージを送信できた場合にtrue、メインウィンドウがない場合や無効な場合にfalseになります。Microsoft Learn

C#
using var process = Process.Start("notepad.exe");

Thread.Sleep(3000);

if (!process!.HasExited)
{
bool requested = process.CloseMainWindow();

if (requested)
{
process.WaitForExit(5000);
}
}

CloseMainWindowは「終了してください」と依頼する方法です。未保存ファイルがある場合、アプリが確認ダイアログを表示し、終了しないこともあります。

5-2. Killでプロセスを強制終了する方法

プロセスを強制終了するにはKillを使います。Microsoftのドキュメントでも、Killは関連付けられたプロセスを強制終了するメソッドとして説明されています。Microsoft Learn

C#
using var process = Process.Start("notepad.exe");

Thread.Sleep(3000);

if (!process!.HasExited)
{
process.Kill();
process.WaitForExit();
}

Killは強力ですが、編集中データや処理中リソースが失われる可能性があります。そのため、まずCloseMainWindowで正常終了を依頼し、終了しない場合だけKillする流れが安全です。

5-3. KillとCloseMainWindowの使い分け

KillCloseMainWindowの違いは次の通りです。

メソッド特徴主な用途
CloseMainWindow正常終了を依頼するGUIアプリを安全に閉じる
Kill強制終了する応答しないプロセスを終了する

ドキュメントでも、CloseMainWindowは終了を要求するだけで強制終了ではなく、アプリが終了を拒否する可能性があるため、強制終了が必要な場合はKillを使うと説明されています。Microsoft Learn

推奨パターンは次のようになります。

C#
if (!process.HasExited)
{
if (process.CloseMainWindow())
{
if (!process.WaitForExit(5000))
{
process.Kill();
}
}
else
{
process.Kill();
}
}

5-4. 子プロセスごと終了させる方法

.NETでは、Kill(true)を使うことで、関連プロセスと子孫プロセスをまとめて終了できます。Kill(bool entireProcessTree)の引数にtrueを指定すると、関連プロセスとその子孫プロセスを終了対象にできます。Microsoft Learn

C#
if (!process.HasExited)
{
process.Kill(entireProcessTree: true);
process.WaitForExit();
}

ただし、Kill(entireProcessTree: true)を使っても、権限やOSの状態によってすべての子孫プロセスを終了できるとは限りません。また、WaitForExitHasExitedは指定した親プロセスの終了状態を反映するもので、すべての子孫プロセス終了を完全に保証するものではありません。Microsoft Learn

5-5. 終了処理で注意すべきリソース解放

ProcessはOSリソースを扱うため、使い終わったら破棄することが重要です。基本的にはusingを使います。

C#
using var process = Process.Start("notepad.exe");
process?.WaitForExit();

Processオブジェクトを長時間保持する場合は、不要になったタイミングでDisposeを呼び出します。

C#
process.Dispose();

また、強制終了後もすぐに終了状態が反映されるとは限らないため、Killの後にWaitForExitを呼ぶのが安全です。Killは非同期的に実行されるため、呼び出し後にWaitForExitまたはHasExitedで終了を確認する必要があります。Microsoft Learn

6. 標準出力を取得する方法

6-1. RedirectStandardOutputの基本

C#で外部プロセスの標準出力を取得するには、RedirectStandardOutput=trueを指定します。RedirectStandardOutputは、アプリケーションのテキスト出力をStandardOutputストリームに書き込むかどうかを指定するプロパティです。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};

この設定により、通常コンソール画面に表示される結果をC#側で文字列として取得できます。

6-2. StandardOutput.ReadToEndで出力を取得する方法

標準出力をまとめて取得するには、StandardOutput.ReadToEnd()を使います。

C#
using System.Diagnostics;
using System.Text;

var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8
};

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
process.WaitForExit();

Console.WriteLine(output);

ReadToEndは、プロセスが標準出力を閉じるまで読み取りを続けます。そのため、プロセスが終了しないコマンドでは処理が戻ってこない可能性があります。

6-3. 標準出力取得時に必要なUseShellExecute=false

標準出力を取得する場合、UseShellExecute=falseが必要です。Microsoftのドキュメントでも、RedirectStandardOutput=trueにするにはUseShellExecute=falseを設定する必要があり、そうしないとStandardOutputの読み取りで例外が発生すると説明されています。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c echo Hello",
UseShellExecute = false,
RedirectStandardOutput = true
};

標準出力が取得できない場合、まずUseShellExecute=falseになっているかを確認しましょう。

6-4. 文字化けを防ぐエンコーディング指定

外部コマンドの出力が文字化けする場合は、StandardOutputEncodingを指定します。

C#
using System.Diagnostics;
using System.Text;

var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c echo こんにちは",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8
};

Windowsの古いコマンドでは、Shift_JIS相当のコードページを使うこともあります。その場合は次のように指定します。

C#
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c echo こんにちは",
UseShellExecute = false,
RedirectStandardOutput = true,
StandardOutputEncoding = Encoding.GetEncoding(932)
};

外部プログラムがUTF-8で出力しているならEncoding.UTF8、日本語Windowsのレガシーコマンド出力ならEncoding.GetEncoding(932)を検討します。

6-5. 標準出力をリアルタイムに取得する方法

リアルタイムに標準出力を取得するには、OutputDataReceivedイベントとBeginOutputReadLineを使います。非同期読み取りを行うには、UseShellExecute=falseRedirectStandardOutput=trueを設定したうえでBeginOutputReadLineを呼び出します。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};

using var process = new Process
{
StartInfo = startInfo,
EnableRaisingEvents = true
};

process.OutputDataReceived += (sender, e) =>
{
if (e.Data != null)
{
Console.WriteLine($"OUT: {e.Data}");
}
};

process.Start();
process.BeginOutputReadLine();
process.WaitForExit();

長時間実行されるコマンドや進捗を逐次表示したい処理では、ReadToEndよりリアルタイム取得の方が向いています。

7. 標準エラー出力を取得する方法

7-1. RedirectStandardErrorの基本

標準エラー出力を取得するには、RedirectStandardError=trueを指定します。RedirectStandardErrorは、アプリケーションのエラー出力をStandardErrorストリームに書き込むかどうかを指定するプロパティです。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c dir C:\\not_exists",
UseShellExecute = false,
RedirectStandardError = true,
CreateNoWindow = true
};

標準出力と同様に、標準エラーをリダイレクトする場合もUseShellExecute=falseが必要です。Microsoft Learn

7-2. StandardError.ReadToEndでエラー内容を取得する方法

標準エラーをまとめて取得するには、StandardError.ReadToEnd()を使います。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c dir C:\\not_exists",
UseShellExecute = false,
RedirectStandardError = true,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);

string error = process!.StandardError.ReadToEnd();
process.WaitForExit();

Console.WriteLine(error);

外部コマンドが失敗した理由をログに残したい場合、標準エラーの取得は非常に重要です。

7-3. 標準出力と標準エラーを同時に扱う方法

実務では、標準出力と標準エラーの両方を取得することが多いです。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c dir C:\\not_exists",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

Console.WriteLine("Output:");
Console.WriteLine(output);

Console.WriteLine("Error:");
Console.WriteLine(error);

ただし、標準出力と標準エラーを同期的に両方読む場合、出力量が多いとデッドロックが発生する可能性があります。安全性を高めるには、少なくとも片方を非同期で読み取る、または両方を非同期で読み取る方法を使います。

7-4. エラー内容をログに保存する実装例

標準エラーをログファイルに保存する例です。

C#
using System.Diagnostics;
using System.Text;

var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c dir C:\\not_exists",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

File.WriteAllText("output.log", output, Encoding.UTF8);
File.WriteAllText("error.log", error, Encoding.UTF8);

Console.WriteLine($"ExitCode: {process.ExitCode}");

エラーが発生したときだけログに保存するなら、ExitCodeを見て分岐します。

C#
if (process.ExitCode != 0)
{
File.WriteAllText("error.log", error, Encoding.UTF8);
}

7-5. デッドロックを避ける読み取り順序

標準出力や標準エラーのリダイレクトでは、読み取り順序に注意が必要です。Microsoftのドキュメントでも、親プロセスが読み取り待ち、子プロセスが満杯のストリームへの書き込み待ちになることでデッドロックが起きる可能性が説明されています。Microsoft Learn

危険な例です。

C#
// 出力量が多い場合、デッドロックの可能性がある
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();

安全なパターンの一つは、標準エラーを非同期で読み取り、標準出力を同期で読む方法です。

C#
var errorBuilder = new StringBuilder();

using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c your-command",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};

process.ErrorDataReceived += (sender, e) =>
{
if (e.Data != null)
{
errorBuilder.AppendLine(e.Data);
}
};

process.Start();
process.BeginErrorReadLine();

string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

string error = errorBuilder.ToString();

より安全にするなら、標準出力と標準エラーの両方を非同期で読み取る設計にします。

8. 標準入力に値を渡す方法

8-1. RedirectStandardInputの基本

外部プロセスにC#から入力を渡したい場合は、RedirectStandardInput=trueを指定します。RedirectStandardInputは、アプリケーションの入力をStandardInputストリームから読み取るかどうかを指定するプロパティです。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "sort.exe",
UseShellExecute = false,
RedirectStandardInput = true
};

sortのように標準入力から文字列を受け取るコマンドに対して有効です。

8-2. StandardInput.WriteLineで入力を送る方法

StandardInput.WriteLineを使うと、外部プロセスに1行ずつ入力を送れます。

C#
var startInfo = new ProcessStartInfo
{
FileName = "sort.exe",
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);

process!.StandardInput.WriteLine("orange");
process.StandardInput.WriteLine("apple");
process.StandardInput.WriteLine("banana");
process.StandardInput.Close();

string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

Console.WriteLine(output);

この例では、sort.exeに複数行の入力を渡し、並び替え結果を標準出力から取得しています。

8-3. 入力後にCloseする理由

標準入力に値を渡した後は、StandardInput.Close()を呼び出します。

C#
process.StandardInput.Close();

これを呼ばないと、外部プロセス側は「まだ入力が続く」と判断して待ち続けることがあります。Microsoftのサンプルでも、標準入力の書き込み後にストリームを閉じることで、コマンドが入力終了を認識して処理を進める流れが示されています。Microsoft Learn

8-4. 対話型コマンドを操作する実装例

対話型コマンドを操作する場合も、標準入力・標準出力・標準エラーをリダイレクトします。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c set /p name=Name: & echo Hello %name%",
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);

process!.StandardInput.WriteLine("Taro");
process.StandardInput.Close();

string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

Console.WriteLine(output);
Console.WriteLine(error);

ただし、複雑な対話型プログラムは、単純な標準入力だけでは制御が難しい場合があります。プロンプトの表示タイミング、バッファリング、エコーバック、文字コードなどの影響を受けるため、可能であれば非対話モードや引数指定で実行できるツールを選ぶ方が安定します。

9. 非同期でProcessを扱う方法

9-1. WaitForExitAsyncの使い方

.NETでは、WaitForExitAsyncを使ってプロセス終了を非同期に待てます。WaitForExitAsyncは、関連付けられたプロセスが終了するか、キャンセルトークンがキャンセルされるまで待機するタスクを返します。Microsoft Learn

C#
var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);

await process!.WaitForExitAsync();

Console.WriteLine("終了しました。");

GUIアプリ、ASP.NET、バックグラウンドサービスなど、スレッドをブロックしたくない処理ではWaitForExitAsyncが便利です。

9-2. OutputDataReceivedで標準出力を非同期取得する方法

標準出力を非同期に取得するには、OutputDataReceivedイベントを使います。

C#
var outputLines = new List<string>();

using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};

process.OutputDataReceived += (sender, e) =>
{
if (e.Data != null)
{
outputLines.Add(e.Data);
Console.WriteLine(e.Data);
}
};

process.Start();
process.BeginOutputReadLine();

await process.WaitForExitAsync();

BeginOutputReadLineを呼ばないと、イベントによる読み取りは開始されません。

9-3. ErrorDataReceivedで標準エラーを非同期取得する方法

標準エラーを非同期に取得するには、ErrorDataReceivedイベントを使います。

C#
var errorLines = new List<string>();

using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c dir C:\\not_exists",
UseShellExecute = false,
RedirectStandardError = true,
CreateNoWindow = true
}
};

process.ErrorDataReceived += (sender, e) =>
{
if (e.Data != null)
{
errorLines.Add(e.Data);
Console.Error.WriteLine(e.Data);
}
};

process.Start();
process.BeginErrorReadLine();

await process.WaitForExitAsync();

標準出力と標準エラーの両方を扱う場合は、OutputDataReceivedErrorDataReceivedを両方使うのが安全です。

9-4. BeginOutputReadLineとBeginErrorReadLineの使い方

BeginOutputReadLineは標準出力の非同期読み取りを開始し、BeginErrorReadLineは標準エラーの非同期読み取りを開始します。

C#
process.Start();

process.BeginOutputReadLine();
process.BeginErrorReadLine();

await process.WaitForExitAsync();

呼び出し順序は、基本的にStart()の後です。

C#
process.Start();              // プロセス開始
process.BeginOutputReadLine(); // 標準出力の読み取り開始
process.BeginErrorReadLine(); // 標準エラーの読み取り開始

標準出力・標準エラーのデッドロックを避けたい場合、この非同期読み取りパターンが実務ではよく使われます。

9-5. async/awaitで外部コマンドを安全に実行する実装例

標準出力・標準エラーを非同期で取得し、タイムアウトにも対応した実装例です。

C#
using System.Diagnostics;
using System.Text;

public record ProcessResult(
int ExitCode,
string StandardOutput,
string StandardError
);

public static async Task<ProcessResult> RunProcessAsync(
string fileName,
IEnumerable<string> arguments,
int timeoutMilliseconds = 30000,
CancellationToken cancellationToken = default)
{
var outputBuilder = new StringBuilder();
var errorBuilder = new StringBuilder();

var startInfo = new ProcessStartInfo
{
FileName = fileName,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};

foreach (var arg in arguments)
{
startInfo.ArgumentList.Add(arg);
}

using var process = new Process
{
StartInfo = startInfo,
EnableRaisingEvents = true
};

process.OutputDataReceived += (_, e) =>
{
if (e.Data != null)
{
outputBuilder.AppendLine(e.Data);
}
};

process.ErrorDataReceived += (_, e) =>
{
if (e.Data != null)
{
errorBuilder.AppendLine(e.Data);
}
};

process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();

using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeoutCts.CancelAfter(timeoutMilliseconds);

try
{
await process.WaitForExitAsync(timeoutCts.Token);
}
catch (OperationCanceledException)
{
if (!process.HasExited)
{
process.Kill(entireProcessTree: true);
}

throw new TimeoutException($"Process timed out: {fileName}");
}

return new ProcessResult(
process.ExitCode,
outputBuilder.ToString(),
errorBuilder.ToString()
);
}

呼び出し例です。

C#
var result = await RunProcessAsync(
"ping",
new[] { "example.com" },
timeoutMilliseconds: 10000
);

Console.WriteLine(result.StandardOutput);
Console.WriteLine(result.StandardError);
Console.WriteLine(result.ExitCode);

このように共通メソッド化しておくと、外部プロセス実行を安全かつ再利用しやすく管理できます。

10. よく使う実践サンプル

10-1. pingコマンドを実行して結果を取得する

C#
using System.Diagnostics;
using System.Text;

var startInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

Console.WriteLine(output);

if (process.ExitCode != 0)
{
Console.Error.WriteLine(error);
}

Windowsで回数を指定する場合は-nを使います。

C#
Arguments = "-n 4 example.com";

LinuxやmacOSでは-cを使います。

C#
Arguments = "-c 4 example.com";

OSによってコマンド引数が異なる点に注意しましょう。

10-2. PowerShellコマンドをC#から実行する

PowerShellを実行する例です。

C#
var startInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = "-NoProfile -ExecutionPolicy Bypass -Command \"Get-Date\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

Console.WriteLine(output);
Console.WriteLine(error);

PowerShell 7以降のpwshを使う場合は、FileNameを変更します。

C#
FileName = "pwsh";

引数にユーザー入力を含める場合は、文字列連結ではなくArgumentListを使う方が安全です。

C#
var startInfo = new ProcessStartInfo
{
FileName = "pwsh",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};

startInfo.ArgumentList.Add("-NoProfile");
startInfo.ArgumentList.Add("-Command");
startInfo.ArgumentList.Add("Get-Date");

10-3. バッチファイルを起動する

バッチファイルを起動する例です。

C#
var startInfo = new ProcessStartInfo
{
FileName = @"C:\Scripts\sample.bat",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WorkingDirectory = @"C:\Scripts"
};

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

Console.WriteLine(output);
Console.WriteLine(error);

cmd.exe経由で実行する場合は次のようにします。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c \"C:\\Scripts\\sample.bat\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

パスにスペースがある場合は引用符で囲む必要があります。

10-4. PythonやNode.jsなど外部プログラムを実行する

Pythonスクリプトを実行する例です。

C#
var startInfo = new ProcessStartInfo
{
FileName = "python",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

startInfo.ArgumentList.Add("script.py");
startInfo.ArgumentList.Add("arg1");

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

Console.WriteLine(output);
Console.WriteLine(error);

Node.jsを実行する例です。

C#
var startInfo = new ProcessStartInfo
{
FileName = "node",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

startInfo.ArgumentList.Add("app.js");
startInfo.ArgumentList.Add("--mode");
startInfo.ArgumentList.Add("production");

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

Console.WriteLine(output);
Console.WriteLine(error);

PythonやNode.jsがPATHに通っていない場合は、FileNameにフルパスを指定します。

C#
FileName = @"C:\Python312\python.exe";

10-5. 実行結果をファイルに保存する

外部コマンドの結果をファイルに保存する例です。

C#
using System.Diagnostics;
using System.Text;

var startInfo = new ProcessStartInfo
{
FileName = "ipconfig",
Arguments = "/all",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.GetEncoding(932),
StandardErrorEncoding = Encoding.GetEncoding(932)
};

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

File.WriteAllText("result.txt", output, Encoding.UTF8);

if (!string.IsNullOrWhiteSpace(error))
{
File.WriteAllText("error.txt", error, Encoding.UTF8);
}

実行結果をログとして残す場合は、終了コード、実行コマンド、開始時刻、終了時刻も一緒に記録するとトラブルシューティングしやすくなります。

11. Processクラスでよくあるエラーと対処法

11-1. 「指定されたファイルが見つかりません」の原因

このエラーは、FileNameに指定した実行ファイルが見つからないときに発生します。

主な原因は次の通りです。

  • 実行ファイル名のスペルミス

  • PATHが通っていない

  • 作業ディレクトリの勘違い

  • 相対パスの基準が想定と違う

  • 32bit/64bit環境の違い

  • UseShellExecute=falseでファイルやURLを開こうとしている

対処法として、まずフルパスで指定します。

C#
var startInfo = new ProcessStartInfo
{
FileName = @"C:\Windows\System32\notepad.exe",
UseShellExecute = false
};

URLやフォルダを開く場合はUseShellExecute=trueを使います。

C#
Process.Start(new ProcessStartInfo
{
FileName = "https://example.com",
UseShellExecute = true
});

11-2. 標準出力が取得できない原因

標準出力が取得できない主な原因は次の通りです。

  • RedirectStandardOutput=trueになっていない

  • UseShellExecute=falseになっていない

  • 外部プロセスが標準出力ではなく標準エラーに出している

  • プロセスが終了していない

  • 文字コードが合っていない

  • 出力量が多くデッドロックしている

最小構成は次の通りです。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c echo Hello",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};

RedirectStandardOutput=trueにするにはUseShellExecute=falseが必要です。Microsoft Learn

11-3. 外部プロセスが固まる原因

外部プロセスが固まる原因として多いのは、次のようなケースです。

  • 標準入力待ちになっている

  • ReadToEndがプロセス終了を待ち続けている

  • 標準出力・標準エラーのバッファが詰まっている

  • WaitForExitを先に呼んでデッドロックしている

  • 外部コマンド自体が応答していない

  • 対話型コマンドを自動実行しようとしている

対策として、タイムアウトを設定します。

C#
if (!process.WaitForExit(30000))
{
process.Kill(entireProcessTree: true);
}

また、標準出力と標準エラーは非同期で読み取ると安全です。

11-4. 管理者権限が必要な処理で失敗する原因

管理者権限が必要なコマンドを通常権限で実行すると失敗します。たとえば、サービス操作、システム設定変更、一部のネットワーク設定変更などです。

管理者権限で起動するには、WindowsではVerb="runas"を使います。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c your-admin-command",
UseShellExecute = true,
Verb = "runas"
};

Process.Start(startInfo);

ただし、管理者権限で実行するとUAC確認が表示され、標準出力のリダイレクトとは相性が悪くなります。管理者権限が必要な処理を自動化する場合は、設計段階で権限管理を検討しましょう。

11-5. パスや引数にスペースがある場合の対処法

パスにスペースがある場合、引数全体を正しく引用符で囲む必要があります。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c \"C:\\Program Files\\MyApp\\tool.exe\" \"C:\\Temp\\input file.txt\"",
UseShellExecute = false
};

ただし、文字列で手動エスケープするとミスが起きやすいため、可能であればArgumentListを使います。

C#
var startInfo = new ProcessStartInfo
{
FileName = @"C:\Program Files\MyApp\tool.exe",
UseShellExecute = false
};

startInfo.ArgumentList.Add(@"C:\Temp\input file.txt");

ArgumentListを使うと、スペースを含む引数も個別の引数として扱いやすくなります。

12. 安全にProcessクラスを使うための注意点

12-1. コマンドインジェクションを防ぐ

Processクラスで最も注意すべきリスクの一つがコマンドインジェクションです。ユーザー入力をそのままcmd.exe /cやPowerShellのコマンド文字列に連結すると、意図しないコマンドを実行される可能性があります。

危険な例です。

C#
string userInput = Console.ReadLine()!;
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c ping " + userInput,
UseShellExecute = false
};

安全性を高めるには、次の対策を行います。

  • cmd.exe /cをなるべく使わない

  • ArgumentListで引数を分ける

  • 入力値をホワイトリストで検証する

  • 許可した文字だけ受け付ける

  • 管理者権限で実行しない

  • 実行可能なコマンドを固定する

C#
string host = "example.com";

var startInfo = new ProcessStartInfo
{
FileName = "ping",
UseShellExecute = false
};

startInfo.ArgumentList.Add(host);

12-2. ユーザー入力をArgumentsに渡すときの注意

ユーザー入力をArgumentsに渡す場合は、入力値の検証が必須です。

たとえば、ホスト名だけを受け付けたい場合は、許可する文字を制限します。

C#
using System.Text.RegularExpressions;

string host = Console.ReadLine()!;

if (!Regex.IsMatch(host, @"^[a-zA-Z0-9.-]+$"))
{
throw new InvalidOperationException("不正なホスト名です。");
}

var startInfo = new ProcessStartInfo
{
FileName = "ping",
UseShellExecute = false
};

startInfo.ArgumentList.Add(host);

特に、&|;><、バッククォート、引用符などは、シェルで特殊な意味を持つことがあります。ユーザー入力をシェルに渡す設計はできるだけ避けましょう。

12-3. usingでProcessを破棄する理由

Processは外部プロセスに関連するハンドルなどのリソースを扱います。そのため、使い終わったらDisposeする必要があります。

C#
using var process = Process.Start(startInfo);
process?.WaitForExit();

usingを使うことで、例外が発生しても適切に破棄されます。

C#
try
{
using var process = Process.Start(startInfo);
process?.WaitForExit();
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
}

ただし、Processオブジェクトを破棄しても、起動した外部プロセス自体が必ず終了するわけではありません。外部プロセスを終了させたい場合は、必要に応じてCloseMainWindowKillを使います。

12-4. タイムアウトを必ず設定すべきケース

次のようなケースでは、タイムアウトを設定するべきです。

  • 外部コマンドがネットワークに依存する

  • ユーザー入力や外部ファイルに依存する

  • CI/CDやサーバー上で実行する

  • Webアプリから外部コマンドを呼び出す

  • 大量データを処理する

  • 失敗時に自動復旧したい

例です。

C#
using var process = Process.Start(startInfo);

if (!process!.WaitForExit(60000))
{
process.Kill(entireProcessTree: true);
throw new TimeoutException("外部プロセスがタイムアウトしました。");
}

タイムアウトなしで外部プロセスを実行すると、アプリ全体が停止したり、サーバーリソースを消費し続けたりする原因になります。

12-5. OS依存コマンドを使うときの注意

cmd.exediripconfigpowershell.exeは主にWindows向けです。一方、LinuxやmacOSではbashlsifconfigping -cなどを使います。

OSごとに処理を分ける例です。

C#
using System.Runtime.InteropServices;

string fileName;
string arguments;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
fileName = "cmd.exe";
arguments = "/c dir";
}
else
{
fileName = "/bin/bash";
arguments = "-c \"ls\"";
}

var startInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true
};

クロスプラットフォーム対応が必要なC#アプリでは、OS依存コマンドを直接書く前に、.NET標準APIで代替できないか検討しましょう。

13. Processクラスのベストプラクティス

13-1. 同期処理と非同期処理の使い分け

短時間で終わる単純なコマンドなら、WaitForExitReadToEndを使った同期処理でも十分です。

C#
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

一方、次のような場合は非同期処理を使うべきです。

  • 実行時間が長い

  • 出力量が多い

  • GUIを固めたくない

  • Webサーバー上で実行する

  • 標準出力をリアルタイム表示したい

  • タイムアウトやキャンセルに対応したい

C#
await process.WaitForExitAsync();

WaitForExitAsyncは、プロセス終了を待つ処理をTaskとして扱えるため、async/awaitと相性がよいメソッドです。Microsoft Learn

13-2. 標準出力・標準エラーを両方取得する推奨パターン

実務でおすすめなのは、標準出力と標準エラーの両方を非同期で読むパターンです。

C#
var output = new StringBuilder();
var error = new StringBuilder();

using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "your-command",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};

process.OutputDataReceived += (_, e) =>
{
if (e.Data != null)
{
output.AppendLine(e.Data);
}
};

process.ErrorDataReceived += (_, e) =>
{
if (e.Data != null)
{
error.AppendLine(e.Data);
}
};

process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();

このパターンなら、出力量が多いコマンドでもデッドロックのリスクを下げられます。

13-3. 例外処理とログ出力の設計

Process.Startでは、ファイルが見つからない、アクセス権限がない、パスが不正、作業ディレクトリが存在しないなどの理由で例外が発生します。

C#
try
{
using var process = Process.Start(startInfo);
process!.WaitForExit();

if (process.ExitCode != 0)
{
Console.Error.WriteLine($"外部プロセスが失敗しました: {process.ExitCode}");
}
}
catch (System.ComponentModel.Win32Exception ex)
{
Console.Error.WriteLine($"起動に失敗しました: {ex.Message}");
}
catch (Exception ex)
{
Console.Error.WriteLine($"予期しないエラー: {ex}");
}

ログには次の情報を残すと原因調査がしやすくなります。

  • 実行ファイル名

  • 引数

  • 作業ディレクトリ

  • 開始時刻

  • 終了時刻

  • 終了コード

  • 標準出力

  • 標準エラー

  • 例外内容

ただし、パスワードやトークンなどの機密情報をログに出さないよう注意してください。

13-4. 再利用しやすいProcess実行メソッドの作り方

毎回ProcessStartInfoを書くと冗長になるため、共通メソッド化すると便利です。

C#
public static string RunCommand(string fileName, params string[] args)
{
var startInfo = new ProcessStartInfo
{
FileName = fileName,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

foreach (var arg in args)
{
startInfo.ArgumentList.Add(arg);
}

using var process = Process.Start(startInfo);

string output = process!.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();

process.WaitForExit();

if (process.ExitCode != 0)
{
throw new InvalidOperationException(error);
}

return output;
}

呼び出し例です。

C#
string result = RunCommand("ping", "example.com");
Console.WriteLine(result);

実務では、戻り値に終了コードや標準エラーも含めたProcessResult型を作ると扱いやすくなります。

13-5. .NET Frameworkと.NETの違いに注意するポイント

Processクラスは.NET Frameworkでも.NETでも使えますが、いくつか違いがあります。

特に注意したいのは、UseShellExecuteの既定値です。.NETでは既定値がfalse、.NET Frameworkではtrueです。Microsoft Learn

そのため、同じコードでもターゲットフレームワークによって動作が変わる可能性があります。標準出力や標準エラーを扱うコードでは、必ず明示的に指定しましょう。

C#
UseShellExecute = false

また、文字コードや対応APIにも違いがあります。古い.NET Framework向けのサンプルを.NET 8や.NET 10のプロジェクトに流用する場合は、WaitForExitAsyncArgumentList、エンコーディング、OS対応などを確認しましょう。

14. C#のProcessクラスに関するよくある質問

14-1. Process.Startでcmdコマンドを実行するには?

cmd.exe/cを付けて実行します。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c dir",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};

using var process = Process.Start(startInfo);
string output = process!.StandardOutput.ReadToEnd();
process.WaitForExit();

Console.WriteLine(output);

/cはコマンド実行後に終了、/kは実行後もウィンドウを開いたままにします。

14-2. Processで標準出力をリアルタイム表示するには?

OutputDataReceivedBeginOutputReadLineを使います。

C#
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "ping",
Arguments = "example.com",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};

process.OutputDataReceived += (_, e) =>
{
if (e.Data != null)
{
Console.WriteLine(e.Data);
}
};

process.Start();
process.BeginOutputReadLine();
process.WaitForExit();

リアルタイムにログ表示したい場合や、長時間実行されるコマンドの進捗を表示したい場合に有効です。

14-3. Processで起動したアプリを閉じるには?

GUIアプリなら、まずCloseMainWindowを使います。

C#
using var process = Process.Start("notepad.exe");

Thread.Sleep(3000);

if (!process!.HasExited)
{
process.CloseMainWindow();
}

終了しない場合は、最終手段としてKillを使います。

C#
if (!process.HasExited)
{
process.Kill();
}

安全性を考えるなら、CloseMainWindowで正常終了を依頼し、一定時間待っても終了しない場合にKillする流れがよいです。

14-4. Processで管理者権限実行するには?

WindowsではVerb="runas"を指定します。

C#
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c your-command",
UseShellExecute = true,
Verb = "runas"
};

Process.Start(startInfo);

この方法ではUAC確認が表示されます。標準出力を取得したい処理とは相性が悪い場合があるため、管理者権限が必要な処理は設計に注意しましょう。

14-5. Processが終了しないときはどうすればよい?

まず、なぜ終了しないのかを確認します。

  • 標準入力待ちになっていないか

  • StandardInput.Close()を呼んでいるか

  • 標準出力・標準エラーの読み取りで詰まっていないか

  • 外部コマンドが対話入力を要求していないか

  • 無限ループやネットワーク待ちになっていないか

対策として、タイムアウトを設定します。

C#
if (!process.WaitForExit(30000))
{
process.Kill(entireProcessTree: true);
process.WaitForExit();
}

標準出力と標準エラーを扱う場合は、非同期読み取りを使ってデッドロックを避けましょう。

C#
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync();

まとめ

C#のProcessクラスを使うと、外部プロセスの起動、終了待ち、強制終了、標準出力取得、標準エラー取得、標準入力への書き込み、非同期実行などを柔軟に実装できます。

基本的なポイントは、Process.Startで起動し、細かな設定はProcessStartInfoで行うことです。標準出力や標準エラーを取得する場合は、UseShellExecute=falseRedirectStandardOutput=trueRedirectStandardError=trueを適切に設定します。

また、外部プロセスは固まる可能性があるため、実務ではタイムアウトを設定し、必要に応じてKill(entireProcessTree: true)で子プロセスごと終了できるようにしておくと安全です。ただし、強制終了はデータ損失の可能性があるため、GUIアプリではまずCloseMainWindowで正常終了を依頼するのが基本です。

標準出力と標準エラーを両方取得する場合は、デッドロックを避けるために非同期読み取りを使うのがおすすめです。さらに、ユーザー入力をコマンド引数に渡す場合は、コマンドインジェクションを防ぐため、ArgumentListの利用や入力値の検証を徹底しましょう。

Processクラスは、C#アプリと外部ツールをつなぐ強力な仕組みです。正しい設定と安全対策を理解して使えば、コマンド実行、自動化、ログ取得、外部プログラム連携を効率よく実装できます。