C#.NETで特定アプリケーションが落ちていたら起動（復旧）させるexe

# 概要

特定のアプリケーションが落ちていたら自動で起動させる。

外部PCから判定や復旧は考えなくていい。
（PCが自分で自分のプロセスを確認するような、セルフチェックのexeでいい）

# なぜC#.NETで作るのか

同じような処理をするbatを作ったことがありました。

https://qiita.com/waokitsune/items/0d6884c85bd5edbde620

ですがbatだと下記のような課題があったため、C#.NETで作ることにしました。

* 細かい要件に対応するのが難しい。
* 起動した時にコマンドプロンプトの画面が一瞬出てきてしまう

# アプリケーションが落ちていることをどう判定するか

起動中プロセスは『イメージパス』『コマンドライン』という2つの値を持っています。

特定の『イメージパス』『コマンドライン』を持つ起動中プロセスが見つからなければ落ちていると判定します。

『イメージパス』『コマンドライン』はタスクマネージャーで確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/0e9776fb-3bec-26ec-5817-b4dcdeccc159.png)

# なぜ『コマンドライン』も使って判定するのか

『同じexeだけど引数が違う』プロセスに対応する為です。

例えば下記のような **引数だけが違う2つのプロセス** があるとします。
```"C:\uxxx\abc.exe" --111```
```"C:\uxxx\abc.exe" --hogeeee```

『イメージパス』だけだとファイルパスの情報しか取得できない為、
```"C:\uxxx\abc.exe" --111``` と ```"C:\uxxx\abc.exe" --hogeeee```
のどちらが起動しているか判別できません。

なので『コマンドライン』も判定に使います。

# だったら『コマンドライン』だけで判定すればよいのでは？

少量ですが下記のようなプロセスもあります。

* 『コマンドライン』が空。（イメージパスだけに値が入っている）
* 『コマンドライン』に入っているのはファイル名のみ。（フルパスではなくファイル名のみ）
* 『コマンドライン』に入っているのは引数のみ。

こういった **ちょっとイレギュラーなプロセス** も扱えるように、『イメージパス』『コマンドライン』の二つを使って判定します。

# 『コマンドライン』の注意点

## 末尾のスペース

『コマンドライン』の末尾に半角スペース付くことがあります。

タスクマネージャーの詳細タブで、行を選択してコピー ⇒ 適当なテキストエディタでペースト すると分かりやすいです。

『末尾の半角スペースの有無』の考慮が抜けていると思わぬバグになるので注意。

## ダブルクオテーション

『ダブルクオテーションの有無』の考慮も抜けないように注意。

# プロジェクト作成まで

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/26db52f0-bf84-fece-550c-4672516baa2c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/4c451745-6943-6295-6811-7246f6bafdc9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/767a84be-a8f6-daf4-7054-18701677f47f.png)

# ソースコード

コードを簡素化する為にtry catchをしていなかったり、ハードコーディングしている部分があります。

```c#
using System.Diagnostics;
using System.Management;

namespace ApplicationDownRecover
{
    internal class Program
    {
        static void Main(string[] args)
        {

            //コードを簡素化する為に固定値。
            //（実際は外部設定ファイルに持たせている）
            const string IMAGE_PATH = "C:\\Program Files (x86)\\yuruyuri\\akkarin.exe";
            const string COMMAND_LINE = "\"C:\\Program Files (x86)\\yuruyuri\\akkarin.exe\" /minimized ";

            //コードを簡素化する為にtry catchは書かない。
            using (ManagementObjectSearcher s_ = new ManagementObjectSearcher())
            {

                //ManagementObjectSearcherでbindする方法が分からなかったのでSQLは固定値。
                //（SQLインジェクションを防止）
                s_.Query.QueryString = "SELECT * FROM win32_process";

                using (ManagementObjectCollection c_ = s_.Get())
                {
                    //起動中プロセス一覧ループ。
                    foreach (ManagementObject o_ in c_)
                    {

                        //まれにExecutablePathやCommandLineが取得できないプロセスがある。
                        //その場合、administrator権限が無いユーザーでこのexeを実行しているのが原因である可能性が高い。
                        //（復旧対象アプリによってはexeを実行するユーザーの設計も必要）

                        //イメージパスはExecutablePathという名前で格納されている。
                        string image_path_ = o_["ExecutablePath"] == null ? "" : o_["ExecutablePath"].ToString();

                        string command_line_ = o_["CommandLine"] == null ? "" : o_["CommandLine"].ToString();

                        //起動中プロセス内にチェック対象が見つかったら（起動していたら）exe終了。
                        if (image_path_.Equals(IMAGE_PATH) && command_line_.Equals(COMMAND_LINE)) return;

                    }

                    //起動中プロセス内に対象が見つからなかったので起動（復旧）。

                    //アプリケーションのパスと引数はコード簡素化する為に固定値。
                    //（実際は外部設定ファイルに持たせている）

                    //コマンドラインからアプリケーションのパスと引数を取り出すこともできる。
                    //でも面倒なので、復旧用の情報はコマンドラインとは別に用意。

                    const string APP_PATH = "C:\\Program Files (x86)\\yuruyuri\\akkarin.exe";

                    //コマンドラインで末尾に半角スペースがあるなら、引数の末尾にも半角スペースを付ける。
                    //そうしないと『復旧によって起動したプロセスのコマンドライン』の末尾に半角スペースが付かず、下記のバグになる。
                    //exeによる復旧 ⇒ exeが再度起動（再チェック）した時に、落ちていると誤判定してしまう。

                    //要は全く同じコマンドラインのプロセスとなるように起動（復旧）させることが大事。
                    const string APP_ARGS = "/minimized ";
                    
                    ProcessStartInfo processStartInfo = new ProcessStartInfo(APP_PATH, APP_ARGS);
                    Process.Start(processStartInfo);

                }

            }
        }
    }
}
```

# デバッグ

下記のようにブレークポイントを張って、F5でデバッグ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706116/8b668d75-f5da-9f43-31f8-3b71bdec5ecd.png)

アプリケーション起動してる ⇒ ブレークポイントに到達せずに終了。
アプリケーション落ちてる ⇒ ブレークポイントに到達する。

って感じで確認すると分かりやすい。

# exeはどこに出来上がるか

VisualStudioなどで特別な設定してなければ、デバッグすれば下記のようなところに出来上がる。

```
C:\Users\ユーザー名\source\repos\ApplicationDownRecover\ApplicationDownRecover\bin\Debug\
```
プロジェクトの ```bin\Debug``` 。

# バージョン

Windows 10 Pro 21H2 OSビルド 19044.2130
Microsoft Visual Studio Community 2022 (64 ビット) - Current Version 17.3.6
Microsoft .NET Framework Version 4.8.04084