[Blue Prism] アタッチが難しいアプリケーションのプロセスIDを取得するVBO（Virtual Business Object）

はじめに
--------------------

Blue Prism ではアプリケーションを操作する際にまず、Blue Prism からアプリケーションに対して「アタッチ」する必要があります。
自動化対象のアプリケーションによっては、正攻法（ウィンドウタイトルやプロセス名の指定）ではアプリケーションにアタッチできない場面に遭遇します。

そのような場合、対象アプリケーションの Process ID を確認し、その値を使うとアタッチできます。

Process ID を確認するには、こちらの記事 [「Enterprise Blue Ocean：Blue Prism でダイアログをアタッチする際に PID を指定する」](https://www.ebocean.work/entry/2019/12/04/112033) にあるように、tasklist コマンドの実行結果を使う方法があります。先の記事では、コマンドプロンプトのワンライナーで華麗に Process ID を取得しているのですが、ワンライナーの文字列組み立てがエスケープ、文字列結合、バッククオートによる実行結果の展開などでかなり複雑になっています。

本記事では、コマンドプロンプトのワンライナーをできるかぎり単純にして、その他のロジックを Blue Prism のステージに置き換え、任意のアプリケーションの Process ID を取得できる VBO の作りかたについて紹介します。

全体像
--------------------------

VBO の全体像は下記の通りです。それぞれのブロックについて以下で説明します。

<img width="600" alt="flow.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/564576/3cfbd4c9-1510-8408-c452-43f0589fd777.jpeg">


tasklist コマンドについて
----------------------------

実行中のプロセスの一覧を取得する、```tasklist``` というコマンドがあります。今回はこれを利用して、目的のアプリケーションの Process ID を取得します。

コマンドのヘルプは下記の通りです。

```
C:\Users\m-nakamura>tasklist /?

TASKLIST [/S システム [/U ユーザー名 [/P [パスワード]]]]
         [/M [モジュール] | /SVC | /V] [/FI フィルター] [/FO 形式] [/NH]

説明:
    ローカルまたはリモートのシステムで実行されている
    プロセスの一覧を表示します。

パラメーター一覧:
   /S    システム            接続するリモート システムを指定します。

   /U    [ドメイン\]ユーザー コマンドを実行するユーザー コンテキストを
                             指定します。

   /P    [パスワード]        提供されたユーザー コンテキストのパスワードを
                             指定します。省略された場合は、入力を要求します。

   /M    [モジュール]        指定された EXE/DLL 名を使用しているタスクを
                             すべて表示します。モジュール名が指定されない場合
                             は、読み込まれているモジュールすべてを表示します。

   /SVC                      各プロセスをホストしているサービスを表示します。

   /APPS                   Microsoft Store アプリと関連するプロセスを表示します。

   /V                      詳細なタスク情報を表示します。

   /FI    フィルター       フィルターによって指定された、与えられた条件に一致
                           するタスクを表示します。

   /FO    形式             出力の形式を指定します。
                           有効な値: "TABLE", "LIST", "CSV"。

   /NH                     出力するときに、"カラム ヘッダー"を
                           表示しないように指定します。
                           "TABLE" と "CSV" 形式でのみ有効です。

   /?                      このヘルプ メッセージを表示します。

フィルター:
    フィルター名    有効な演算子              有効な値
    -----------     ---------------           --------------------------
    STATUS          eq, ne                    RUNNING | SUSPENDED
                                              NOT RESPONDING | UNKNOWN
    IMAGENAM        eq, ne                    イメージ名
    PID             eq, ne, gt, lt, ge, le    PID 値
    SESSION         eq, ne, gt, lt, ge, le    セッション番号
    SESSIONNAME     eq, ne                    セッション名
    CPUTIME         eq, ne, gt, lt, ge, le    次の形式の CPU 時間
                                              hh:mm:ss
                                              hh - 時間、
                                              mm - 分、ss - 秒
    MEMUSAGE        eq, ne, gt, lt, ge, le    メモリ使用 (KB)
    USERNAME        eq, ne                    [ドメイン\]ユーザーの形式の
                                              ユーザー名
    SERVICES        eq, ne                    サービス名
    WINDOWTITLE     eq, ne                    ウィンドウ タイトル
    MODULES         eq, ne                    DLL 名

注意: リモート コンピューターを照会するときは、"WINDOWTITLE" フィルターと
      "STATUS" フィルターはサポートされません。

Examples:
    TASKLIST
    TASKLIST /M
    TASKLIST /V /FO CSV
    TASKLIST /SVC /FO LIST
    TASKLIST /APPS /FI "STATUS eq RUNNING"
    TASKLIST /M wbem*
    TASKLIST /S システム /FO LIST
    TASKLIST /S システム /U ドメイン\ユーザー名 /FO CSV /NH
    TASKLIST /S システム /U ユーザー名 /P パスワード /FO TABLE /NH
    TASKLIST /FI "USERNAME ne NT AUTHORITY\SYSTEM" /FI "STATUS eq running"
```



/FI オプションによるフィルターで、IMAGENAM（実行ファイル名）を指定してタスクを絞り込み、CSVフォーマットで出力してみます。

以下は、メモ帳を2つ実行している状況で実行した結果の例です。

```
>tasklist /v /fo csv /fi "IMAGENAME eq notepad.exe"
"イメージ名","PID","セッション名","セッション#","メモリ使用量","状態","ユーザー名","CPU 時間","ウィンドウ タイトル"
"notepad.exe","1012","Console","1","26,604 K","Running","DESKTOP-N2L0BGL\m-nakamura","0:00:00","README.md - メモ帳"
"notepad.exe","7004","Console","1","24,540 K","Running","DESKTOP-N2L0BGL\m-nakamura","0:00:00","無題 - メモ帳"
```

この結果を利用すれば、対象アプリケーションの Process ID を取得できます（2番目のカラム"PID"がそれです）。

実行するコマンド文字列の組み立て
-------------------------------------------

先の ```Utility - Environment Start Process Read Stderr and Stdout``` に渡すコマンド文字列を準備する際に、正攻法で文字列を組み立てようとすると、下記のような辛さがあります。

* ```&``` で文字列結合すると、ダブルクオーテーションだらけになって辛い。
* エスケープを間違いなく適用するのが難しい

そこで、文字列置換の関数を利用し、プレースホルダーとなるキーワードが埋め込まれたテンプレートのコマンド文字列を変換して生成します。

まず、置換する元となるデータアイテムを用意します。

<img width="422" alt="arguments.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/564576/49874bcb-b044-92f4-3139-84180364f7bc.jpeg">


下記2点がポイントかと思います。
* 置換対象となるプレースホルダーを ```__image_name__``` としているところ
* ```"```で囲われたコマンド文字列で ```"``` を使う場合のエスケープ文字列が、```^``` である（バックスラッシュではない！）

```
/C "@echo off && tasklist /v /fo csv /fi ^"IMAGENAME eq __image_name__^""
```


Blue Prism にも、[Python の f-string](https://docs.python.org/ja/3/tutorial/inputoutput.html#tut-f-strings) のようなテンプレート機能があるといいですね。。。

外部コマンドを実行する
---------------------------------------------------------------

Blue Prism をインストールすると同時に提供される VBO に、"Utility - Environment" があります。それが持つアクションの一つに "Start Process Read Stderr and Stdout" というものがあります。
コマンドラインのプログラムを同期的に実行し、標準出力・標準エラー出力の内容を取得することができます。[Python の subprocessモジュール](https://docs.python.org/ja/3/library/subprocess.html#popen-constructor) のようなものですね。

コマンドラインのプログラムを実行して結果を取り込めるので、コマンドラインで実行する外部のプログラムやスクリプトを用意すればなんでも実行できますね。多用すると RPA の趣旨から逸脱してしまいますが。。。

このアクションを使って、先ほどの tasklist コマンドを実行し、Stdout に出力される結果を Blue Prism 側に取り込んでゴニョゴニョやれば、目的のアプリケーションの Process ID が取得できます。

<img width="402" alt="environment_start_process_read_stderr_and_stdout.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/564576/f7203f5a-5da0-3b46-7460-7fd106b61e4c.jpeg">


CSV文字列を Collection として取得
---------------------------------------------

先の ```tasklist``` コマンドの出力結果が CSV文字列なので、Blue Prism で扱いやすいように、Collection として解釈します。

```Utility - Strings::Get CSV As Collection``` というアクションが使えます。

<img width="405" alt="get_csv_as_collection.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/564576/fd00ff27-41e1-9c69-49a4-efca8897a33d.jpeg">


```tasklist``` の、``` /NH``` オプションを指定していないので、一行目をヘッダーとしてそのまま使っています。便利ですね！

Collection をループして、条件とマッチさせる
----------------------------------------------
Collection をループして、条件にマッチするプロセスを選ぶことができます。

下の例では ```tasklist``` の結果に、"ウィンドウ タイトル" があるので、それを条件にマッチさせています。
（```Utility - Strings Test Regex Match``` を使い、正規表現で条件をかけています）

<img width="246" alt="match.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/564576/2923e057-7e81-8671-9b17-be7539b6da36.jpeg">



まとめ
-------------------------

* Blue Prism でうまくアタッチできないときは、Process ID を直指定すると解消できる（場合がある）
* Process ID 取得は、ワンライナーを実行する方法もあるが、地道に Blue Prism のロジックを使っても書ける