UiPathのPicture in Pictureセッションを自動終了させる

# この記事の要約
* UiPathのプロセス終了時に、PiPセッションを自動終了する方法の説明
* アイデア
  * `Invoke PowerShell`アクティビティで`shutdown.exe /l`を実行 -> PiPセッションからサインアウト
  * カレントセッションがPiPセッションか否かを`quser.exe`で判定

# Picture in Picture機能とは
Picture in Picture(以降PiP）は、UiPath v2020.4で追加されたAttended Robot用の機能で、
Robotを別ウィンドウ上の別セッション(PiPセッション)上で実行させることができる便利な機能です。
>ピクチャ イン ピクチャ
>https://docs.uipath.com/robot/lang-ja/docs/picture-in-picture

PiPセッションはユーザーの画面・セッションとは独立しているため、以下のようにRobotを安定して実行させることができるようになります。

* ユーザーのマウス・キーボード操作によるRobotの妨害・誤動作を防げる
  * Robot実行中でも他の作業をできる
* ユーザーがスクリーンロックしてもRobotが実行を継続できる
  * マウスクリックなどの一部のアクティビティは、スクリーンロック中は動かない

ただし、PiPにも[いくつかの制限事項](https://docs.uipath.com/robot/lang-ja/docs/picture-in-picture#known-issues-and-limitations)があり、ちょっと厄介なのが下記2点。
> * PiP セッションを事前に閉じる必要があるため、PiP セッションが開いている間はマシンを再起動またはシャットダウンできません。
> * 一度に開始できるピクチャ イン ピクチャ セッションは 1 つのみです。

Attended Robotを共有PCで運用している場合、あるユーザーが実行してPiPセッションを開いたまま放置していると、他のユーザーが使うことができなくなってしまいます。また、PCの再起動・シャットダウンもできなくなると、Windows Updateも滞ってしまい、共有PCの運用に支障が出てしまいます。

# PiPセッションを自動終了させる方法
UiPathのプロセス終了時にPiPセッションも自動終了してくれると嬉しいのですが、残念なことに v2020.10 時点ではそのような機能はありません。
**なので、作ってしまいましょう**。

## 基本的なアイデア
PiPセッションは、PiPセッションからサインアウトすることで終了させることができます(PiPウィンドウも自動で閉じます)。
サインアウトはスタートボタンからだけでなく、`shutdown.exe /l`をコマンドライン実行することでも行えるので、基本的なアイデアは`Invoke PowerShell`アクティビティで`shutdown.exe /l`を実行することになります。
ただし、UiPathのプロセス内でサインアウト処理を行ってしまうと、Robotが外部から強制終了されて異常終了の扱いになってしまうため、`shutdown.exe`をRobotとは別プロセスで実行するようにします。やり方は下記の通り。

```powershell:別プロセスでshutdown.exeを実行
Start-Process -FilePath shutdown.exe -ArgumentList '/l'
```
Robot終了直前にいきなりサインアウトが走るのは少し怖いので、例えば5秒待ってからサインアウトするときは下記のように`Start-Sleep 5`を挟みます。

```powershell:5秒待ってからサインアウト
Start-Process -FilePath powershell.exe -WindowStyle Hidden -ArgumentList '-Command Start-Sleep 5; shutdown.exe /l'
```

ワークフローやシーケンスの最後で上記PowerShellスクリプトを`Invoke PowerShell`アクティビティで実行するだけで、「PiPセッションを自動終了させる」という当初の目的は概ね達成できます。
しかし、開発・テストの都合でPiP機能を使わずにプロセスを実行した場合にもサインアウトが走ってしまうという問題があります。

## カレントセッションがPiPセッションか否かを判定する
PiPセッションの場合だけ`shutdown.exe /l`を実行させるようにするために、カレントセッションがPiPセッションか否かを判別する方法を考えます。
幸い、環境変数`%SESSIONNAME%`の値によって、カレントセッションがPiPセッションか否かをある程度判断できます。
`%SESSIONNAME%`を取得するには`quser.exe`を使います[^1]。

[^1]: `Get Environment Variable`アクティビティとかで`%SESSIONNAME%`を直接取得できると楽なんですが、UiPathのRobotは仕組み上[一部の環境変数を取得できない](https://forum.uipath.com/t/environment-variable-clientname-not-visible-by-the-robot/286869)模様。

```:quser.exe
PS C:\> quser.exe
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 takeruko              console             1  Active          .  3/4/2021 09:31
 takeruko              rdp-tcp#16          8  Active          .  3/4/2021 09:47
>takeruko              7a78855482a0...    14  Active          .  3/4/2021 12:56
```

`quser.exe`を実行すると、PC上の全てのセッションが一覧表示されます。
行の先頭に`>`が付いている`ID:14`がカレントセッションです。
`SESSIONNAME`の値はセッションの接続方法によって以下のような値を取るようです。

|SESSIONNAMEの値|セッションの種類/接続方法|
|:----------|:-------------------|
|console    |ローカルからサインイン    |
|rdp-tcp#...|リモートデスクトップ接続   |
|上記以外     |**PiPセッションなど**   | 

今回はカレントセッションがユーザーセッション(ローカルからのサインインやリモートデスクトップ接続)ではない事が判別できればいいので、以下のようなPowershellスクリプトを実行します。

```powershell:カレントセッションがPiPセッションの場合にだけshutdown.exeを実行する
# quser.exeは%windir%¥Sysnative 配下
$quser = $env:windir + '\Sysnative\quser.exe'
$csv = . $quser |  ForEach-Object -Process { $_ -replace '\s{2,}',',' } | ConvertFrom-Csv
foreach ($row in $csv) {
  if ( $row.USERNAME.StartsWith('>') -and $row.SESSIONNAME -notmatch '(console|rdp-tcp#.+)' ) {
    Start-Process -FilePath powershell.exe -WindowStyle Hidden -ArgumentList '-Command Start-Sleep 5; shutdown.exe /l'
  }
}
```

後は、上記スクリプトを`Invoke PowerShell`アクティビティの**コマンドテキスト**に貼り付けて、**TypeArgument**を`String`、**スクリプト入力**にチェックを入れればOKです。
![7c11aeb3-792a-468d-8d21-8ad844a5c9f0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81556/7d0bce6f-bcc7-2ced-be9f-182621125f5b.jpeg)
![fa917211-1083-4520-9581-2ba8f0dc1929.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81556/3858fdc6-8e63-4f1a-844e-b14019f84ea1.jpeg)
![44a7f745-fef8-46d5-9f93-35f45ea90527.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81556/2aeb1cc0-ab3b-0281-0561-32dfe01b4982.jpeg)

以上