DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

DOSコマンドを実行し出力データを取得する

ProcessStartInfo.RedirectStandardOutputプロパティをTrueにしてプロセスの出力がProcessインスタンスのStandardOutputプロパティ(StreamReaderクラス)に書き込まれるようにすることで、出力データを取得できるようになります。

下の例ではDOSのDIRコマンドを実行して、その出力結果を取得しています。

VB.NET
コードを隠すコードを選択
'Processオブジェクトを作成
Dim p As New System.Diagnostics.Process()

'ComSpec(cmd.exe)のパスを取得して、FileNameプロパティに指定
p.StartInfo.FileName = System.Environment.GetEnvironmentVariable("ComSpec")
'出力を読み取れるようにする
p.StartInfo.UseShellExecute = False
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.RedirectStandardInput = False
'ウィンドウを表示しないようにする
p.StartInfo.CreateNoWindow = True
'コマンドラインを指定("/c"は実行後閉じるために必要)
p.StartInfo.Arguments = "/c dir c:\ /w"

'起動
p.Start()

'出力を読み取る
Dim results As String = p.StandardOutput.ReadToEnd()

'プロセス終了まで待機する
'WaitForExitはReadToEndの後である必要がある
'(親プロセス、子プロセスでブロック防止のため)
p.WaitForExit()
p.Close()

'出力された結果を表示
Console.WriteLine(results)
C#
コードを隠すコードを選択
//Processオブジェクトを作成
System.Diagnostics.Process p = new System.Diagnostics.Process();

//ComSpec(cmd.exe)のパスを取得して、FileNameプロパティに指定
p.StartInfo.FileName = System.Environment.GetEnvironmentVariable("ComSpec");
//出力を読み取れるようにする
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = false;
//ウィンドウを表示しないようにする
p.StartInfo.CreateNoWindow = true;
//コマンドラインを指定("/c"は実行後閉じるために必要)
p.StartInfo.Arguments = @"/c dir c:\ /w";

//起動
p.Start();

//出力を読み取る
string results = p.StandardOutput.ReadToEnd();

//プロセス終了まで待機する
//WaitForExitはReadToEndの後である必要がある
//(親プロセス、子プロセスでブロック防止のため)
p.WaitForExit();
p.Close();

//出力された結果を表示
Console.WriteLine(results);

ヘルプによると、上の例の様にWaitForExitReadToEndの後にしないと、親プロセスと子プロセスの両方でブロック処理が発生し、デッドロックの原因となるということなので、注意が必要です。

補足:上記のコードは内部コマンドを実行しているため、ProcessStartInfo.FileNameプロパティにComSpec(cmd.exe)を、ProcessStartInfo.Argumentsプロパティに実行するコマンドを指定しています。XCOPY、MOVE、DELTREE、ATTRIB、FORMATなどの外部コマンド(あるいは、バッチファイル)を実行する時は、ProcessStartInfo.FileNameプロパティに外部コマンドの実行ファイル名(XCOPY.EXE、MOVE.EXE、DELTREE.EXE、ATTRIB.EXE、FORMAT.EXEなど)を、ProcessStartInfo.Argumentsプロパティにコマンドライン引数を指定した方がよいでしょう。

通常はcmd.exeでも外部コマンドを実行できますが、コマンドライン引数が複雑になる恐れがあります。例えば、実行ファイル名とその引数にスペース文字が含まれる場合は、スペース文字が含まれる実行ファイル名と引数をダブルコーテーションで囲み、さらに全体(実行ファイル名 + すべての引数)をダブルコーテーションで囲む必要があります。詳しくは、「CMD.exe (Command Shell)」等をご覧ください。
補足:XCOPYを実行する時は、ProcessStartInfo.RedirectStandardInputプロパティをTrueにしないとうまくいかないようです。詳しくは「xcopy help」をご覧ください。

非同期で出力データを取得する

上の例のようにReadToEndメソッドを使って出力データを読み取ると、データをすべて読み取るまでReadToEndメソッドでブロックされ、次の行に進むことがありません。しかし、ReadToEndメソッドの代わりにProcess.BeginOutputReadLineメソッドを使うと、非同期で出力データを読み取ることができるようになり、実行がブロックされることがなくなります。この時、出力データはProcess.OutputDataReceivedイベントハンドラで取得できます。この方法は、.NET Framework 2.0以降でサポートされています。

補足:OutputDataReceivedイベントハンドラで取得できる文字列には行末の改行文字が含まれません。

ちなみに、非同期で読み取り中にProcess.CancelOutputReadメソッドを呼び出すと、読み取り操作をキャンセルすることができます。

以下に、先ほどのコードと同じことを非同期で行うように変更した例を示します。なおこの例は、コンソールアプリケーションになっています。

VB.NET
コードを隠すコードを選択
Class Program
    'エントリポイント
    Public Shared Sub Main(args As String())
        'Processオブジェクトを作成
        Dim p As New System.Diagnostics.Process()

        '出力をストリームに書き込むようにする
        p.StartInfo.UseShellExecute = False
        p.StartInfo.RedirectStandardOutput = True
        'OutputDataReceivedイベントハンドラを追加
        AddHandler p.OutputDataReceived, AddressOf p_OutputDataReceived

        p.StartInfo.FileName = _
            System.Environment.GetEnvironmentVariable("ComSpec")
        p.StartInfo.RedirectStandardInput = False
        p.StartInfo.CreateNoWindow = True
        p.StartInfo.Arguments = "/c dir c:\ /w"

        '起動
        p.Start()

        '非同期で出力の読み取りを開始
        p.BeginOutputReadLine()

        p.WaitForExit()
        p.Close()

        Console.ReadLine()
    End Sub

    'OutputDataReceivedイベントハンドラ
    '行が出力されるたびに呼び出される
    Private Shared Sub p_OutputDataReceived(sender As Object, _
            e As System.Diagnostics.DataReceivedEventArgs)
        '出力された文字列を表示する
        Console.WriteLine(e.Data)
    End Sub
End Class
C#
コードを隠すコードを選択
class Program
{
    //エントリポイント
    static void Main(string[] args)
    {
        //Processオブジェクトを作成
        System.Diagnostics.Process p = new System.Diagnostics.Process();

        //出力をストリームに書き込むようにする
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = true;
        //OutputDataReceivedイベントハンドラを追加
        p.OutputDataReceived += p_OutputDataReceived;

        p.StartInfo.FileName =
            System.Environment.GetEnvironmentVariable("ComSpec");
        p.StartInfo.RedirectStandardInput = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.Arguments = @"/c dir c:\ /w";

        //起動
        p.Start();

        //非同期で出力の読み取りを開始
        p.BeginOutputReadLine();

        p.WaitForExit();
        p.Close();

        Console.ReadLine();
    }

    //OutputDataReceivedイベントハンドラ
    //行が出力されるたびに呼び出される
    static void p_OutputDataReceived(object sender,
        System.Diagnostics.DataReceivedEventArgs e)
    {
        //出力された文字列を表示する
        Console.WriteLine(e.Data);
    }
}

出力とエラーの両方を読み取る

今まで紹介した方法とほぼ同じやり方で、エラー出力を読み取ることもできます。エラー出力を読み取る場合は、、RedirectStandardOutputの代わりにRedirectStandardError、StandardOutputの代わりにStandardError、BeginOutputReadLineの代わりにBeginErrorReadLine、OutputDataReceivedの代わりにErrorDataReceivedを使います。

しかし出力とエラーの両方を読み取る場合は、注意が必要です。両方を同期で読み取るとデッドロックが発生する可能性があります。これを防ぐには、どちらか一方か両方を非同期で読み取るようにします。

以下の例では、出力とエラーの両方を非同期で読み取っています。

VB.NET
コードを隠すコードを選択
Class Program
    'エントリポイント
    Public Shared Sub Main(args As String())
        'Processオブジェクトを作成
        Dim p As New System.Diagnostics.Process()

        '出力とエラーをストリームに書き込むようにする
        p.StartInfo.UseShellExecute = False
        p.StartInfo.RedirectStandardOutput = True
        p.StartInfo.RedirectStandardError = True
        'OutputDataReceivedとErrorDataReceivedイベントハンドラを追加
        AddHandler p.OutputDataReceived, AddressOf p_OutputDataReceived
        AddHandler p.ErrorDataReceived, AddressOf p_ErrorDataReceived

        p.StartInfo.FileName = _
            System.Environment.GetEnvironmentVariable("ComSpec")
        p.StartInfo.RedirectStandardInput = False
        p.StartInfo.CreateNoWindow = True
        p.StartInfo.Arguments = "/c dir c:\ /w"

        '起動
        p.Start()

        '非同期で出力とエラーの読み取りを開始
        p.BeginOutputReadLine()
        p.BeginErrorReadLine()

        p.WaitForExit()
        p.Close()

        Console.ReadLine()
    End Sub

    'OutputDataReceivedイベントハンドラ
    '行が出力されるたびに呼び出される
    Private Shared Sub p_OutputDataReceived(sender As Object, _
            e As System.Diagnostics.DataReceivedEventArgs)
        '出力された文字列を表示する
        Console.WriteLine(e.Data)
    End Sub

    'ErrorDataReceivedイベントハンドラ
    Private Shared Sub p_ErrorDataReceived(sender As Object, _
            e As System.Diagnostics.DataReceivedEventArgs)
        'エラー出力された文字列を表示する
        Console.WriteLine("ERR>{0}", e.Data)
    End Sub
End Class
C#
コードを隠すコードを選択
class Program
{
    //エントリポイント
    static void Main(string[] args)
    {
        //Processオブジェクトを作成
        System.Diagnostics.Process p = new System.Diagnostics.Process();

        //出力とエラーをストリームに書き込むようにする
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        //OutputDataReceivedとErrorDataReceivedイベントハンドラを追加
        p.OutputDataReceived += p_OutputDataReceived;
        p.ErrorDataReceived += p_ErrorDataReceived;

        p.StartInfo.FileName =
            System.Environment.GetEnvironmentVariable("ComSpec");
        p.StartInfo.RedirectStandardInput = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.Arguments = @"/c dir c:\ /w";

        //起動
        p.Start();

        //非同期で出力とエラーの読み取りを開始
        p.BeginOutputReadLine();
        p.BeginErrorReadLine();

        p.WaitForExit();
        p.Close();

        Console.ReadLine();
    }

    //OutputDataReceivedイベントハンドラ
    //行が出力されるたびに呼び出される
    static void p_OutputDataReceived(object sender,
        System.Diagnostics.DataReceivedEventArgs e)
    {
        //出力された文字列を表示する
        Console.WriteLine(e.Data);
    }

    //ErrorDataReceivedイベントハンドラ
    static void p_ErrorDataReceived(object sender,
        System.Diagnostics.DataReceivedEventArgs e)
    {
        //エラー出力された文字列を表示する
        Console.WriteLine("ERR>{0}", e.Data);
    }
}

コマンドプロンプトに文字列を入力することで、複数のコマンドを実行する

プロセスに文字列を入力するには、ProcessStartInfo.RedirectStandardInputプロパティをTrueにして、StandardInputプロパティで取得できるストリームに文字列を書き込みます。これを利用すると、コマンドプロンプトに実行するコマンドを入力することで、複数のコマンドを実行することができます。

以下の例では、「dir c:\ /w」と「dir d:\ /w」を実行しています。出力は、非同期で読み取っています。

VB.NET
コードを隠すコードを選択
Class Program
    'エントリポイント
    Public Shared Sub Main(args As String())
        'Processオブジェクトを作成
        Dim p As New System.Diagnostics.Process()

        '入力できるようにする
        p.StartInfo.UseShellExecute = False
        p.StartInfo.RedirectStandardInput = True

        '非同期で出力を読み取れるようにする
        p.StartInfo.RedirectStandardOutput = True
        AddHandler p.OutputDataReceived, AddressOf p_OutputDataReceived

        p.StartInfo.FileName = _
            System.Environment.GetEnvironmentVariable("ComSpec")
        p.StartInfo.CreateNoWindow = True

        '起動
        p.Start()

        '非同期で出力の読み取りを開始
        p.BeginOutputReadLine()

        '入力のストリームを取得
        Dim sw As System.IO.StreamWriter = p.StandardInput
        If sw.BaseStream.CanWrite Then
            '「dir c:\ /w」を実行する
            sw.WriteLine("dir c:\ /w")
            '「dir d:\ /w」を実行する
            sw.WriteLine("dir d:\ /w")
            '終了する
            sw.WriteLine("exit")
        End If
        sw.Close()

        p.WaitForExit()
        p.Close()

        Console.ReadLine()
    End Sub

    'OutputDataReceivedイベントハンドラ
    '行が出力されるたびに呼び出される
    Private Shared Sub p_OutputDataReceived(sender As Object, _
            e As System.Diagnostics.DataReceivedEventArgs)
        '出力された文字列を表示する
        Console.WriteLine(e.Data)
    End Sub
End Class
C#
コードを隠すコードを選択
class Program
{
    //エントリポイント
    static void Main(string[] args)
    {
        //Processオブジェクトを作成
        System.Diagnostics.Process p = new System.Diagnostics.Process();

        //入力できるようにする
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardInput = true;

        //非同期で出力を読み取れるようにする
        p.StartInfo.RedirectStandardOutput = true;
        p.OutputDataReceived += p_OutputDataReceived;

        p.StartInfo.FileName =
            System.Environment.GetEnvironmentVariable("ComSpec");
        p.StartInfo.CreateNoWindow = true;

        //起動
        p.Start();

        //非同期で出力の読み取りを開始
        p.BeginOutputReadLine();

        //入力のストリームを取得
        System.IO.StreamWriter sw = p.StandardInput;
        if (sw.BaseStream.CanWrite)
        {
            //「dir c:\ /w」を実行する
            sw.WriteLine(@"dir c:\ /w");
            //「dir d:\ /w」を実行する
            sw.WriteLine(@"dir d:\ /w");
            //終了する
            sw.WriteLine("exit");
        }
        sw.Close();
        
        p.WaitForExit();
        p.Close();

        Console.ReadLine();
    }

    //OutputDataReceivedイベントハンドラ
    //行が出力されるたびに呼び出される
    static void p_OutputDataReceived(object sender,
        System.Diagnostics.DataReceivedEventArgs e)
    {
        //出力された文字列を表示する
        Console.WriteLine(e.Data);
    }
}
  • 履歴:
  • 2012/12/16 「非同期で出力データを取得する」、「出力とエラーの両方を読み取る」、「複数のコマンドを実行する」などを追加。
  • 2014/12/8 cmd.exeで外部コマンドを実行する際の注意事項を追加。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • Windows Vista以降でUACが有効になっていると、ファイルへの書き込みに失敗する可能性があります。詳しくは、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。