PowerShellでバッテリ残量を取得して、閾値未満で且つユーザー応答が無ければOSをスリープ状態へ移行する

# 概要

本リポジトリは、次の目的を達成する方法を説明します。

* WindowsモバイルノートPCをバッテリー駆動で利用中に寝落ちした場合、自動的にスリープ状態に移行する

実現に使うツールは以下とします。いずれもWindowsに標準搭載されています。

* タスクスケジューラ
* PowerShell

**実現だけすればよい**場合は、前半のスクリプトの解説を飛ばして、後半の「タスクスケジューラへの設定方法」の節を参照ください。

## 検証環境

「Windows 10 Home」とします。


# 実現方法

次のようにして実現します。

1. タスクスケジューラで定期的に、監視用のPowerShellスクリプトを起動
2. PowerShellスクリプトでバッテリ残量を取得し（監視し）、閾値以下なら「作業を継続するか？」のダイアログを表示
3. ダイアログは一定時間で自動クローズする設定とし、**応答無し**だった場合は「寝落ちしている」と判断してOSをスリープ状態へ移行

## 監視用スクリプトの作成

PowerShellでバッテリー残量を取得するには、WMIが提供するWin32_Batteryクラスを利用して、次のようにします（残量以外にも様々なバッテリー状態を取得できます）。
ここでは、後で表示に使うので「使用可能な残り時間」も合わせて取得しておきます。

```
Get-CimInstance -ClassName Win32_Battery | Select-Object -Property DeviceID, EstimatedChargeRemaining, EstimatedRunTime | Foreach-Object {
  # $DeviceID = $_.DeviceID

  # 残バッテリ量(100-0％) 
  $EstimatedChargeRemaining = $_.EstimatedChargeRemaining

  # 使用可能な残り時間(分)
  $EstimatedRunTime = $_.EstimatedRunTime
}

```

一定の表示時間で自動的に閉じるダイアログをPowerShellで表示するには、次のようにします。
ここではWscriptのポップアップを利用する方法とし、「はい」「いいえ」を表示するようにvbYesNoe (=4) を指定します。

```
  $wsobj = new-object -comobject wscript.shell
  $msgText = "表示するメッセージ"
  $nSecondsToWait = 5 # 表示する秒数
  $titleText = "タイトル文字列"

  $result = $wsobj.popup($msgText, $nSecondsToWait, $titleText, "4")
```

OSをスリープ状態へ移行するには、.Net FrameworkのSetSuspendState()を利用して、次のようにします。
```
Add-Type -Assembly System.Windows.Forms;[System.Windows.Forms.Application]::SetSuspendState(‘Suspend’, $false, $false);
```

以上の内容を用いて作成した、監視用のPowerShellスクリプトのサンプルは
 [message-sleep-battery.ps1](https://github.com/hoshimado/action4threshold-battery/blob/main/message-sleep-battery.ps1) のようになります。
以降は、こちらのスクリプトを用いるものとして説明します。


## タスクスケジューラへの設定方法

**タスクスケジューラ**を起動して、次のようにタスクを設定します。
なお、手順中のスクリプトファイルは、**任意のローカルの場所に格納**した前提とします。

1. 「タスクの作成」を選択
2. タブ「全般」の「名前」に任意（例：message-battery-sleep等）の名称を入力
    * 他の項目はデフォルトのまま
    * ![img1-common.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125588/05075bb3-b45b-5e96-85e7-8847224b22e4.png)
3. タブ「トリガー」で「新規」ボタンを押します
4. 「新しいトリガー」ダイアログで「繰り返し間隔：30分間」と「継続時間：無期限」を設定して「OK」ボタンを押します
    * ![img2-trigger.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125588/cd6ee0b8-5b14-91a6-741a-4f7d9136f39f.png)
5. タブ「操作」で「新規」ボタンを押します
6. 「プログラム／スクリプト」に、PowerShellスクリプト、、、を起動するためのvbsスクリプトのパスを指定します
    * 直接PowerShell.exeを用いてスクリプトを実行しようとすると、「`-WindowStyle Hidden`」を指定しても、「スクリプトをウィンドウ非表示で起動する、ためのPowerShellウィンドウ」が**表示されてしまう**ため、代わりにvbsを経由することで、ウィンドウが表示されることを防ぎます
    * vbsスクリプトは [message-sleep-battery-wrapper.vbs](https://github.com/hoshimado/action4threshold-battery/blob/main/message-sleep-battery-wrapper.vbs) のようにします
7. 「引数の追加（オプション）」に、先に示した監視用のPowerShellスクリプト「[message-sleep-battery.ps1](https://github.com/hoshimado/action4threshold-battery/blob/main/message-sleep-battery.ps1)」へのパスを入力します    * ![img3-command.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125588/b4189a80-39f7-7578-6d66-997e51faf7e3.png)
8. タブ「条件」で「コンピューターをAC電源で試用している場合のみタスクを開始する」のチェックを**外します**
    * ![img4-conditions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125588/24f8f6b4-4d9c-5fc0-3926-ef70700d2910.png)
9. 「タスクの作成」ダイアログの右下にある「OK」ボタンを押してタスクを保存します

動作確認は、作成したタスクを選んで「実行」を押すことできます（スクリプト中の「 `Set-Variable -Name THRESHOLD_BATTERY_CHARGE_REMAINING -Value 30 -Option Constant` 」に指定したバッテリー残量を上回っている場合は、**何も表示されません**）。

以上で、監視タスクの定期実行のための設定は、完了です。

30分おきに監視用のスクリプトが動作し、バッテリーの残量が閾値未満の場合（この例であれば「30％」未満の場合）に、利用者が寝落ちしていないか？を確認するダイアログが表示され、応答が無ければOSをスリープ状態へ移行します。



# （補足）この方法を選んだ背景

「寝落ちした場合に、自動的にスリープ状態へ移行して欲しい」を実現しようと考えた場合に、OS標準の「バッテリーの設定＞電源とスリープ＞次の時間が経過後、PCをスリープ状態にする」の設定で対応可能な場合が多いです。

しかし、上記の設定では「ブラウザゲームを表示中」や「Youtubeを再生中」における「寝落ち」への対処が**出来ません**（スリープ状態へ移行しません）。

なので、上記以外の方法でスリープ状態へ持って行く必要があります。

最初に検討したのはChrome拡張機能「The Marvellous Suspender」で、一定時間の操作が無い場合にChromeブラウザをスリープさせてしまえば「アイドル状態」になるのでは？というアプローチでした。
しかし、残念ながら「The Marvellous Suspender」は「**アクティブでは無い**タブをスリープさせる」ための拡張機能なので、今回の目的には沿いませんでした（アクティブなタブは、表示状態のままとなります）。

https://cravelweb.com/gadget/pc/how-to-setting-the-marvellous-suspender-google-chrome-extension


次に、タスクスケジューラの「トリガー」にある「アイドル状態の時」を利用する方法を
検討しましたが、これも期待した動作を実現できませんでした。「ゲームや動画を再生中」は、たとえ一定時間ユーザー入力が無くとも「Notアイドル状態」と判定されるようです。

https://4thsight.xyz/37398



バッテリー監視系のソフトで、そのような機能を有するものが無いかを検索したところ、
「バッテリー状態をトリガーとしてタスクを実行する」機能を有するソフトはありました。
しかし、そのソフトにとって「トリガー」はあくまでサブの機能であり、今回の目的のためだけに追加ソフト導入するのは、出来れば見送りたい、と考えたため、こちらは保留としました。

https://www.gigafree.net/system/monitor/Battery-Mode.html


ならば、ということで、「バッテリー状態を監視して条件を満たしたら、スリープさせる
アプリ」をいっそのこと作ってしまえば、、、とも考えましたが、ソコまで労力をかけるのは出来れば見送りたい、と考えて保留としました。


以上を踏まえて、「（このくらいの機能なら）スクリプトとタスクスケジューラを組み合わせて何とか実現できないか？」と考えた結果に、今回の実現方法に辿り着いた、というのが背景と成ります。


なお、タスクスケジューラから実行時に、ウィンドウを非表示にするにはvbs経由とする
必要があるので、スクリプト自体をvbsで書くことも考えましたが（とりあえずは作成済み）、OSをスリープ状態にするにはPowerShellが最適である、と言う背景から、スクリプト本体はPowerShellとする、という判断を最終的にはしました。

https://neos21.net/blog/2022/02/23-01.html


PowerShell（だけでなくvbsも）は、今回に初めて書いたので、「ｘｘと書いた方がより適切」などの指摘があれば、コメントいただけると助かります。また、「そもそも、○○と言う方法で、もっと簡単に実現できる」なども歓迎します。