🕌

[C#] 外部プロセスの実行管理画面を作った時の記録

2024/02/04に公開

背景

  • 実行するにあたり環境変数やコマンドライン引数を設定する必要があった
  • 環境変数がコロコロ変わっていちいち変更するのがめんどくさい
  • バッチファイルなどでログを出力するとき「>exec.log」みたいにするのがめんどくさい
  • コマンドプロンプトみたいなのを一回作ってみたかった

環境変数に関してはコマンドプロンプト上でsetするのもめんどくさい、かつOSの環境変数に余計なものを追加したくない、またコマンドライン引数もショートカットを作成すれば解決できるかもしれないが、数が多いと作成するのがめんどくさいなど思いました。
ただ、一番の理由は時間が取れそうだったので作ってみたいという気持ちがあったからです!

※初投稿で誤字脱字、意味不明なコーディングが含まれているかもしれません。ご了承ください。

環境

  • Windows10
  • Visual Studio 2019
  • .NET Framework4.7

作成過程

要件

  1. 画面から外部プロセスを実行しリアルタイムで結果を画面に表示する
  2. 環境変数とコマンドライン引数の設定ができる
  3. 外部プロセスの実行結果をテキストファイルにエクスポートできる
  4. 画面から外部プロセスを中断できる

完成イメージ

  • 使用コントロール
    • コマンドライン引数:DataGridView
    • 環境変数:DataGridView
    • 外部プロセス実行結果:RichTextBox
    • 他:Button/TextBox

1.外部プロセスの実行→画面に表示

  • 外部プロセスを実行する
  • 実行した外部プロセスの出力内容を画面にリアルタイムで表示する

実装

↓Form1.cs

private void execute_Click(object sender, EventArgs e)
{
    // 画面初期化
    dos.Clear();
    // 指定したバッチファイルを実行する
    string batch = batch_file.Text;
    try
    {
        ExecuteBatch(batch);
    }
    catch (Exception ex)
    {
        DisplayText(ex.Message);
    }
}

private void ExecuteBatch(string batch)
{
    // 指定したバッチファイルを実行する
    try
    {
        using (Process process = new Process())
        {
            process.StartInfo.FileName = batch;
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.CreateNoWindow = true;
            process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
            process.Start();
            process.BeginOutputReadLine();
        }
    }
    catch (Exception)
    {
        throw;
    }
}

private void OutputHandler(object sender, DataReceivedEventArgs e)
{
    if (!string.IsNullOrEmpty(e.Data))
    {
        Invoke(new MethodInvoker(delegate {
            DisplayText(e.Data);
        }));
    }
}

private void DisplayText(string text)
{
    dos.AppendText(text + Environment.NewLine);
}
  • メソッド:execute_Click
    • 「実行」ボタンのClickイベント
  • メソッド:ExecuteBatch
    • 外部プロセスの実行処理
      • 外部プロセス起動時に別画面が表示されないように設定
      • (重要)Process.StartInfo.RedirectStandardOutputプロパティで外部プロセスの出力内容をStandardOutputへ書き込むよう設定
      • (重要)Process.OutputDataReceivedイベントを定義し、StandardOutputへの書き込みが行われたときのイベントを設定
  • メソッド:OutputHandler
    • Process.OutputDataReceivedイベントの処理

2.実行時に環境変数とコマンドライン引数を設定

  • 環境変数を設定し、コマンドライン引数を設定した外部プロセスを実行する
  • 環境変数は同じプロセス内でのみ有効としてOSの設定は変更しないようにする

実装

  1. 外部プロセス実行時のコマンドライン引数を追加する

↓Form.cs

private void ExecuteBatch(string batch)
{
    // 指定したバッチファイルを実行する
    try
    {
        using (Process process = new Process())
        {
            process.StartInfo.FileName = batch;
            process.StartInfo.Arguments = SetCommandOption();
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.CreateNoWindow = true;
            process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
            process.Start();
            process.BeginOutputReadLine();
        }
    }
    catch (Exception)
    {
        throw;
    }
}
private string SetCommandOption()
{
    List<string> options = new List<string>();
    for (int i = 0; i < option_list.Rows.Count; i++)
    {
        var optionValue = option_list.Rows[i].Cells[0].Value;
        if (optionValue != null)
        {
            options.Add(optionValue.ToString());
        }
    }
    return string.Join(" ", options);
}
  • メソッド:SetCommandOption
    • DataGridViewに設定したコマンドライン引数を取得
  • メソッド:ExecuteBatch
    • プロパティ:Process.StartInfo.Argumentsでコマンドライン引数を設定する
  1. 外部プロセスの実行前に環境変数を設定する

↓Form.cs

private void execute_Click(object sender, EventArgs e)
{
    // 画面初期化
    dos.Clear();
    // 指定したバッチファイルを実行する
    string batch = batch_file.Text;
    try
    {
        ExecuteBatch(batch);
    }
    catch (Exception ex)
    {
        DisplayText(ex.Message);
    }
}

private void SetEnviromentValue()
{
    DisplayText("環境変数を設定します。");
    for (int i = 0; i < env_list.Rows.Count; i++)
    {
        // [1]DataGridViewに追加した環境変数を取得し設定する
        var envKey = env_list.Rows[i].Cells[0].Value;
        var envValue = env_list.Rows[i].Cells[1].Value;
        if (envKey != null && envValue != null)
        {
            // [2]環境変数を設定する
            Environment.SetEnvironmentVariable(envKey.ToString(), envValue.ToString());
            DisplayText("KEY=" + envKey + " VALUE=" + Environment.GetEnvironmentVariable(envKey.ToString()));
        }
    }
}
  • メソッド:SetEnviromentValue
    • DataGridViewに設定した環境変数を取得
    • Environment.SetEnvironmentVariableで環境変数を設定
  • イベント:execute_Click
    • 外部プロセス実行前にSetEnviromentValueを呼び出す

3.外部プロセスの実行結果をテキストファイルにエクスポート

  • 結果のエクスポート時に出力先を指定する
  • 出力するファイルの拡張子はtxtとする

実装

↓Form.cs

private void export_Click(object sender, EventArgs e)
{
    try
    {
        using (SaveFileDialog dialog = new SaveFileDialog())
        {
            dialog.Filter = "テキストファイル|*.txt";
            if (dialog.ShowDialog(this) == DialogResult.OK)
            {
                using (StreamWriter writer = new StreamWriter(dialog.FileName, false, Encoding.GetEncoding("SHIFT_JIS")))
                {
                    writer.Write(dos.Text);
                }
            }
        }
    }
    catch (Exception ex)
    {
        DisplayText(ex.Message);
    }
}
  • イベント:export_Click
    • ボタン:実行結果エクスポートのClickイベント
    • SaveFileDialogクラスを利用して保存先を指定
    • プロパティ:SaveFileDialog.Filterで保存ファイルの拡張子を指定
    • 文字コード:shift-jisで出力

4.外部プロセスの実行を中断

  • 実行中の外部プロセスを中断する

実装

↓Form.cs

private int ProcessId = 0;

private void ExecuteBatch(string batch)
{
    // 指定したバッチファイルを実行する
    try
    {
        using (Process process = new Process())
        {
            process.StartInfo.FileName = batch;
            process.StartInfo.Arguments = SetCommandOption();
            process.StartInfo.UseShellExecute = false;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.CreateNoWindow = true;
            process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
            process.Start();
            ProcessId = process.Id;
            process.BeginOutputReadLine();
        }
    }
    catch (Exception)
    {
        throw;
    }
}

private void cancel_Click(object sender, EventArgs e)
{
    using (Process cancelProcess = Process.GetProcessById(ProcessId))
    {
        cancelProcess.Kill();
    }
    MessageBox.Show("外部プロセスを中断しました。");
}
  • メンバー変数:ProcessId
    • 実行中の外部プロセスIDを管理
  • メソッド:ExecuteBatch
    • 外部プロセス実行後にメンバー変数:ProcessIdに設定
  • イベント:cancel_Click
    • ボタン:中断のClickイベント
    • メンバー変数:ProcessIdから実行中の外部プロセスを取得
    • Process.Killで外部プロセスを強制終了

完了

実際に作成した画面

感想

思いのほか簡単にできました。
#画面と外部プロセスのやり取りってできるのか不安でしたが、便利なイベントもありかなり簡単にできましたー
現状まだ、外部プロセスの実行しかできないのでもう少し継続してみようと思います!
#実行結果のコントロールでせっかくRichTextBoxを使っているのでエラーで文字色を変えたりなど使い勝手を良くしていきたい・・・

Discussion