[C++/Windows] Windowsの電源プランの中の各種設定値を取ってくる

もくじ
https://tera1707.com/entry/2022/02/06/144447

# やりたいこと

Windowsの電源プランに含まれる各種設定を、プログラムから取得したい。

### 電源プランとは？

電源プランとは、下記の画面に出てくるもので、ノートPCやタブレットなどの電源の持ちにかかわる設定のこと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/e4dad020-f2ac-2ab8-df7c-3395929d849e.png)

「プラン」の中には、いろいろな設定がある。WindowsのUIで見ると、
こういうのとか、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/ee3eccac-dbad-9771-27f7-5c68b6d5d057.png)
こういうのとか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/94a36d30-173e-12c6-524b-ea6b5dc3e824.png)

### 電源プランの構成

電源プランの中身は、ざっくり下記のような階層的な作りになっている。

- 電源プラン(スキーマ(schema)とも言う)
    - サブグループ
        - 個別の設定

それぞれ、スキーマ、サブグループ、個別の設定には**GUID**が振られていて、それを使ってコマンドやWind32APIでどれを設定するか/取得するか指定して、いろいろな操作を行う。

### 電源プランの設定値にはどういうものがあるか？

どういう設定があるかは、`powercfg`というコマンドをコマンドプロンプトを開いてたたけば見ることができる。
https://docs.microsoft.com/ja-jp/windows-hardware/design/device-experiences/powercfg-command-line-options

`powercfg -qh` と入力すると、隠し設定も含めて、**現在選択している**プランに、どんな設定があるかと、その設定値を見ることができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/a2f2f331-f25c-6bae-035b-599cb5923f25.png)
(それぞれの設定には、AC電源のときの設定値と、DC電源の時の設定値がある)

ここで見える**「電源設定のGUID ・・・ (バランス)」**というのが、コントロールパネルの電源オプションの画面で見た「電源プラン」のこと。

その下に、**「サブグループ」**というグループで設定値が分けられている。
(上図の「サブグループの・・・」というのがそれ)

で、そのサブグループの中に、具体的な**個別の設定値**がある、というイメージ。

### 電源プランとそのGUIDの一覧の味方

現在選択しているプランがなにか、ということと、どんなプランがあるか、は、`powercfg -l`で見ることができる。(※がついてるのが、現在選択中の電源プラン)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/48fdaf55-8a47-aeb0-19e3-573b85f23c53.png)

### 電源プランの中の設定値をC++で取得する

`powrprof.h`にある`PowerEnumerate`関数を使って設定値を取得する。
https://docs.microsoft.com/en-us/windows/win32/api/powrprof/nf-powrprof-powerenumerate

`PowerEnumerate()`は、上記MS公式解説ページを見ると下記のように定義されている。

```cpp
DWORD PowerEnumerate(
  HKEY                RootPowerKey,
  const GUID          *SchemeGuid,
  const GUID          *SubGroupOfPowerSettingsGuid,
  POWER_DATA_ACCESSOR AccessFlags,
  ULONG               Index,
  UCHAR               *Buffer,
  DWORD               *BufferSize
);
```

公式ページによると、

- `SchemeGuid`をNULLにすると、power policiesの列挙が返される。  
(power policiesというのは、スキーマ(=電源プラン)のGUIDのことっぽい)
- `SubGroupOfPowerSettingsGuid`をNULLにすると、個別の設定の一覧が返される。
- `Index`を++しながら何度も読んでやると、お望みの一覧が取れる

とのこと。
なので、上の階層から順番に、indexを増やしながら読んでいくと、スキーマ(電源プラン)の中のサブグループ、そのまた中に含まれる個別の設定値をぐるぐる全部取っていける様子。

それを試したのが下記。

### サンプル

```PowerEnumerateTest.cpp
// include必要
#include <Windows.h>
#include <powrprof.h>

// GUIDを文字列に変換する（取得したGUIDの確認のためのデバッグ出力用）
// https://www.wabiapp.com/WabiSampleSource/windows/uuid_to_string.html
std::wstring GuidToString
(
    GUID* pGuid
)
{
    std::wstring oGuidString;
    RPC_WSTR waString;

    // GUIDを文字列へ変換する
    if (RPC_S_OK == ::UuidToString(pGuid, &waString)) {
        // GUIDを結果にセット
        oGuidString = (WCHAR*)waString;
        // GUID文字列の破棄
        RpcStringFree(&waString);
    }
    // GUIDを返す
    return(oGuidString);
}

void PowerEnumerateTest()
{
    ULONG index1 = 0;
    ULONG index2 = 0;
    ULONG index3 = 0;

    while (1)
    {
        UCHAR buf[256] = { 0 };
        DWORD size = sizeof(buf);
        GUID sheme = { 0 };
        GUID subgr = { 0 };
        GUID indv = { 0 };

        // 電源プランのGUIDを取得
        if (PowerEnumerate(NULL, NULL, NULL, ACCESS_SCHEME, index1++, buf, &size))
            break;
        memcpy_s(&sheme, sizeof(sheme), buf, sizeof(sheme));
        OutputDebugString((GuidToString(&sheme) + L"\r\n").c_str());

        index2 = 0;
        while (1)
        {
            // サブグループのGUIDを取得
            if (PowerEnumerate(NULL, &sheme, NULL, ACCESS_SUBGROUP, index2++, buf, &size))
                break;
            memcpy_s(&subgr, sizeof(subgr), buf, sizeof(subgr));
            OutputDebugString((L" " + GuidToString(&subgr) + L"\r\n").c_str());

            index3 = 0;
            while (1)
            {
                // 個別の設定のGUIDを取得
                if (PowerEnumerate(NULL, &sheme, &subgr, ACCESS_INDIVIDUAL_SETTING, index3++, buf, &size))
                    break;
                memcpy_s(&indv, sizeof(indv), buf, sizeof(indv));
                OutputDebugString((L"  " + GuidToString(&indv)).c_str());

                // AC/DCの設定値を取得
                DWORD ac, dc = 0;
                PowerReadACValueIndex(NULL, &sheme, &subgr, &indv, &ac);
                PowerReadDCValueIndex(NULL, &sheme, &subgr, &indv, &dc);

                OutputDebugString((L"  ac:" + std::to_wstring(ac) + L" dc:" + std::to_wstring(dc) + L"\r\n").c_str());
            }
        }
    }
}
```

取得した結果はこんな感じ。

```
381b4222-f694-41f0-9685-ff5bb260df2e
 0012ee47-9041-4b5d-9b77-535fba8b1442
  0b2d69d7-a2a1-449c-9680-f91c70521c60  ac:1 dc:1
  51dea550-bb38-4bc4-991b-eacf37be5ec8  ac:100 dc:100
  6738e2c4-e8a5-4a42-b16a-e040e769756e  ac:1200 dc:600
  80e3c60e-bb94-4ad8-bbe0-0d3195efc663  ac:0 dc:30
  d3d55efd-c1ff-424e-9dc3-441be7833010  ac:2000 dc:1000
  d639518a-e56d-4345-8af2-b9f32fb26109  ac:200 dc:100
　　　　　・
　　　　　・
　　　　　・
```

取れる情報には、下記のようなものが含まれている。(他にもたくさんある)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/fd903cc3-3386-516f-9df2-bc107f8ed1b1.png)

例えば「ディスプレイの明るさ」の設定は、Windowsの設定画面の下記の値を取ってきてることになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352601/7a7030d3-165b-2f6c-c303-810580e85a61.png)

色々と使えそう。

### メモ

何気にはまった点。
PowerEnumerateの最後の引数`BufferSize`は、PowerEnumerateを呼ぶ時点で、Bufferに必要なサイズ(GUIDなのでおそらく16)を入れておかないと、確かエラーコード234?243?が返ってきて失敗してしまうので注意。

`GUID`は構造体。

# 参考

PowerEnumerate function (powrprof.h)
https://docs.microsoft.com/en-us/windows/win32/api/powrprof/nf-powrprof-powerenumerate

powercfgコマンド
https://docs.microsoft.com/ja-jp/windows-hardware/design/device-experiences/powercfg-command-line-options

pinvoke.net ※C#から呼ぶときのためのページ。
http://pinvoke.net/default.aspx/powrprof/PowerEnumerate.html?diff=y