UnityアプリからbatファイルでWindowsのカメラアプリを起動した


**[2018/3/1/21:00]**
ビルドしたプログラムでbatファイルが呼び出されなかった不具合を修正し, 追記しました。

#はじめに
Unityで作成したアプリでカメラアプリも並行して使わないといけなくなったため
カメラアプリの起動と終了を自動化させるbatファイルを作成しました。
今回のカメラアプリは何でも良かったため、
Windowsに搭載されているMicrosoft社のカメラアプリ(Windowsカメラ)を使用しました。
(あの左下の入力するところに「カメラ」と打つと出てくるやつです。)

#開発および実行環境
- Unity 5.6.3f1
- Windows 10 Home
- WindowsCamera.exe

#本システムについて
特殊な環境だと思うので簡単に本システムについて解説します。
結構長いので余計な部分は読み飛ばしていただいて大丈夫です。

##batファイルとは
Windowsのコマンドラインで使われるコマンドをまとめたファイルです。
このファイルを実行すると記述されたコマンドが上から順に実行されていきます。
しかし、普通のコマンドの書き方とは違うものもあるので注意が必要です。

##なぜbatファイルを起用したか
今回、batファイルでの外部アプリの起動をおこなったということで
**「なぜbatファイルでの起動をおこなうことにしたか」**ということを説明します。

Unityにて実行する際にexeファイルを直接指定してやれば、起動できます。
しかし、その際に多くのファイルは**絶対パス**で指定する必要があります。
我らがメモ帳(notepad.exe)でも例外ではありません。
しかし、メモ帳や電卓(calc)等は**標準で環境変数が登録**されており、
**コマンドラインから名前を叩くだけ**で実行できます。

**いろんなアプリを異なる多くの環境で実行させるため**に
コマンドラインを叩くのがいいだろうということで
batファイルを起用しました。

##全体図
本システムの全体図です。

![WindowsCameraBatSystem.png](https://qiita-image-store.s3.amazonaws.com/0/203750/46e73a59-f154-8397-c23f-2ecbf245f78b.png)

カメラアプリの起動
1. Unityで**ProcessManager.cs**の*startCameraAppメソッド*を呼び出すと**StartCameraApp.bat**が起動します。
2. **StartCameraApp.bat**では**Windowsカメラ**を起動し, その*プロセスID*を**pid.txt**に書き出します。

カメラアプリの終了
1. Unityで**ProcessManager.cs**の*endCameraAppメソッド*を呼び出すと**EndCameraApp.bat**が起動します。
2. **EndCameraApp.bat**では**pid.txt**に書かれた*プロセスID*から**カメラアプリ**を特定し, 終了させています。

#実際のコード
以下に実際に作成したコードを載せます。
batファイルは**Assets/StreamingAssets/ProcessScriptsディレクトリ**に置いてあります。
ビルド後のプログラムではStreamingAssets以下に置かれたファイルに対して
プログラムからアクセスできるみたいです。(2018/3/1/21:00追記)

###ProcessManager.cs
Unity側からbatファイルを呼び出すC#プログラム

ここでいう外部プロセスはbatファイルを操作するプロセスのことであり,
**カメラアプリそのもののプロセスではない**ので注意が必要です。

[2018/3/1/21:00]
batファイルが入ったディレクトリのパスを変更


```C#:ProcessManager.cs
using UnityEngine;
using System.Diagnostics;

public class ProcessManager {

    Process process;

    // プロセスを起動させるbatファイルが格納されたディレクトリのパス
    string scriptPath = Application.dataPath + "/StreamingAssets/ProcessScripts/";


    /* カメラアプリを起動 */
    public void startCameraApp()
    {
        callBatFile("StartCameraApp.bat");
    }

    /* カメラアプリを終了 */
    public void endCameraApp()
    {
        callBatFile("EndCameraApp.bat");
    }

    /* アプリを起動/終了させるbatファイルを実行 */
    private void callBatFile(string batFilePath)
    {
        // 他のプロセスが実行しているなら行わない
        if (process != null) return;

        // 新規プロセスを作成し、batファイルのパスを登録
        process = new Process();
        process.StartInfo.FileName = scriptPath + batFilePath;

        // 外部プロセスの終了を検知するための設定
        process.EnableRaisingEvents = true;
        process.Exited += process_Exited;

        // 外部プロセスを実行
        process.Start();
    }

    // 外部プロセスの終了を検知してプロセスを終了
    void process_Exited(object sender, System.EventArgs e)
    {
        process.Dispose();
        process = null;
    }
}

```

###StartCameraApp.bat
カメラアプリを起動させるbatファイル

```bash:StartCameraApp.bat
@echo off

setlocal

set PIDFile="pid.txt"
set AppName="WindowsCamera.exe"
set LogFile="log.txt"

rem 誤作動防止のため0にする
echo 0 > %PIDFile%

rem カメラアプリを起動
start "" "microsoft.windows.camera:"

rem カメラアプリのPIDを検索
for /f "usebackq tokens=2" %%a in (`tasklist ^| find %AppName%`) do (set PID=%%a)
if not defined PID (
  echo 0 > %PIDFile%
  echo could not find camera App Process ID > %LogFile%
)

rem カメラアプリのPIDを外部ファイルに出力
echo %PID% > %PIDFile%

endlocal
```

###EndCameraApp.bat
カメラアプリを終了させるbatファイル

```bash:EndCameraApp.bat
@echo off

setlocal enabledelayedexpansion

set PIDFile="pid.txt"
set ProcessName=
set TargetName=WindowsCamera.exe
set LogFile="log.txt"

rem pidファイルの中身を取得
set /p ProcessID=<%PIDFile%

rem pidファイルの中身が0でなければ
if not %ProcessID%==0 (

  rem プロセスIDからプロセス名を取得
  for /f "usebackq tokens=1" %%i in (`tasklist /FI "PID eq %ProcessID%"`) do (set ProcessName=%%i)

  rem プロセスがカメラアプリであれば
  if !ProcessName!==%TargetName% (
    rem プロセスを終了
    taskkill /pid %ProcessID% /F
  ) else (
    echo Error: Process ID is different > %LogFile%
  )
) else (
  echo Error: Can not get Process ID > %LogFile%
)

rem 誤作動防止のため0にする
echo 0 > %PIDFile%

endlocal
```

現在はカメラアプリのみを使う想定ですが, そのUnityアプリで用いる外部アプリの分だけpid.txtが必要になります。
カメラアプリとメモ帳アプリを用意する場合, 「CameraPid.txt」と「NotepadPid.txt」みたいに用意して
それぞれを起動/終了させるbatファイルのPidFileをそれぞれのpid.txtの名前に書きかえればできます。

#最後に
今回作成したbatファイルは同じ名前のアプリが起動してたら扱えないとかpid.txtがなくなってたら終了できないとか
問題はいろいろあるので直していく必要がありそうです。

#参考
##### C#(Unity)関係
http://neareal.net/index.php?ComputerGraphics%2FUnity%2FTips%2FExternalProgram

##### batファイル関係
全般的に https://qiita.com/kshibamo/items/9368b0b2a4a2f40b61f1
注意とか https://qiita.com/yz2cm/items/8058d503a1b84688af09
tasklist http://wa3.i-3-i.info/word12514.html
for https://qiita.com/sawa_tsuka/items/67be34bab1fdf3fb87f9
kill https://qiita.com/nasum/items/22585cff8745fa61d187

https://social.msdn.microsoft.com/Forums/vstudio/en-US/270f0842-963d-4ed9-b27d-27957628004c/what-is-the-pid-of-the-current-cmdexe?forum=msbuild
https://oshiete.goo.ne.jp/qa/5699529.html