WMIがぶっ壊れて沼った話

# 症状
::: note alert
**【症状】WMIコマンドでWin32_LogicalDisk が無効なクラスと言われる**
:::
**背景とか途中経過とかいいから解決方法だけ教えろ！という緊急の方[コチラ](#対処方法まとめ)**
お時間と精神に余裕のある方はぜひお付き合いください。
# なんでこの記事を書くことになったか
とある案件でRaspberryPiPicoを使用することになったのだが、なぜかArduinoIDEでの書き込みが通らない。
IDEのコンソール出力から、ラズパイピコへプログラムを書き込む際にユーザーディレクトリ下の`AppData\Local\Arduino15\packages\rp2040\hardware\rp2040\4.3.1/tools/uf2conv.py`を走らせてるっぽいとこまでは分かったのだが、uf2convの実行中に大量の赤文字エラーメッセージが羅列されて書き込みが通らない。
**しかもこのエラーメッセージ、ほぼすべてが文字化けしている！**（皆さんに見せたかったけどスクショ取るの忘れた orz）
ということで、ArduinoIDEが無事動くように頑張るぞと課題解決のたびに出かけたのですが**最終的に何故かWindows君のシステム治療に落ち着いた**のでネタとして記事にしました。

# エラーが発生している処理の発見
uf2convの実行関数内に様々なデバッグメッセージを仕込んだりコメントアウトして挙動を確認したりすることで色々と格闘していくこと数時間、プログラムを書き込む前にデバイス情報を取得する関数内にてPowerSehllコマンドをサブプロセスでコールするコードを見つけた。ArduinoIDEコンソールでのエラー出力には必ず`Unable to build drive list`を含んでいたのでこのtryが失敗していると見て間違いなさそうだ。
```python:uf2conv.py 252行目
except:
    try:
        nul = open("nul:", "r")
        r = subprocess.check_output(["powershell", "-NonInteractive", "-Command",
                                    "Get-WmiObject -class Win32_LogicalDisk | "
                                    "Format-Table -Property DeviceID, DriveType, Filesystem, VolumeName"],
                                    stdin = nul)
        nul.close()
    except:
        print("Unable to build drive list");
        sys.exit(1)
```
このサブプロセスでコールされている問題のコマンドは以下の通り
```PowerShell:問題のコマンド
powershell -NonInteractive -Command Get-WmiObject -class Win32_LogicalDisk | Format-Table -Property DeviceID, DriveType,Filesystem, VolumeName
```
ArduinoIDEで書き込みが行われる際に、接続されている物理ディスク（ここではターゲットマイコンのこと）を取得して対象ディスクをフォーマットするらしい。
このあとに対象ディスクへビルドしたバイナリデータをアップロードする処理も書かれていたので、RaspberryPiPicoのArduinoIDEでの書き込みでは毎回コントローラのフラッシュをフォーマットする仕様になっているようで間違えなさそうだ。

さて、問題のコマンドについて`powershell -NonInteractive -Command`はPowerShellコマンドをコールするときにCLIを開かず実行する指示なのでこれは素通りして、本命のコマンドはディスク情報取得のコマンドとフォーマットのコマンドだ。
```PowerShell:ディスク情報取得
Get-WmiObject -class Win32_LogicalDisk
```
```PowerShell:フォーマットのコマンド
Format-Table -Property DeviceID, DriveType,Filesystem, VolumeName
```
ArduinoIDEの処理出力画面は前述の通り綺麗に文字化けしていてまったく役に立たないのでPowerSehllを管理者権限で開いてこれらのコマンドを試していこうと思ったところで
```PowerShell:言っていることは分かるが理解したくないメッセージ
> Get-WmiObject -class Win32_LogicalDisk
Get-WmiObject : 無効なクラスです "Win32_LogicalDisk"
発生場所 行:1 文字:1
+ Get-WmiObject -class Win32_LogicalDis
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidType: (:) [Get-WmiObject], ManagementException
    + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
```
**やってまいりました！
Windows君お得意の何もしていないのに壊れている現象が出てきました！**

![a](https://lh4.ggpht.com/-PRQhmp9I_J0/TbGB4l3EJ3I/AAAAAAAF9zc/DV_iPd6Q8Fk/l6772.png)

# Win32_LogicalDiskが無効なクラス?!
さて発見されたエラーメッセージ`Get-WmiObject : 無効なクラスです "Win32_LogicalDisk"`でのWin32_LogicalDiskクラスについてですが、WMIプロパイダ内でのローカルストレージデバイスに関するデータソースをコールするクラスです。WMIについてとLogicalDiskクラスについては下記ページで詳しく解説されています。

https://learn.microsoft.com/ja-jp/windows/win32/cimwin32prov/win32-logicaldisk

https://learn.microsoft.com/ja-jp/windows/win32/wmisdk/wmi-start-page

# WMIの破損
Win32_LogicalDiskのクラスだけピンポイントにパスが壊れたとかそんなことあるか？
と思ったのもつかの間、WMIがWindowsのシステム監視・管理用のインフラだったことを思い出し猛烈に嫌な予感がした僕はそのままPowerShellにsysteminfoをぶち込みました。

**結果**
```PowerShell:WMIぶっ壊れが確定した瞬間
> systeminfo
オペレーティング システム情報を読み込んでいます...                    エラー: 無効なクラスです
```
**はい、確定しました。
WMIがぶっ壊れてます。**

# WMIの復旧
そもそもWMI自体がWindowsの挙動を監視するためのシステムでWindowsの重要インフラになっているはずなのにサイレントにぶっ壊れてるなんてことあるもんですね（白目）。
日本マイクロソフトのサポートチームによる技術ブログとかいう有能すぎる記事があったのでこちらを早速試してみます。

https://jpwinsup.github.io/blog/2022/06/01/UserInterfaceAndApps/wmi/how-to-reset-wmi-repository/

どうやら**PowerSehllで実行できるWMIレポジトリの破損状況確認とその修復を行えるコマンドが存在している**らしくこれを試してみろとのことでした。
この手法はWMIが破損したときに行われる常套手法らしいです。
復旧コマンドの実行でデータベースに矛盾がないって嬉しい出力が返ってきました！
```PowerShell:レポジトリの復旧コマンド実行
 > winmgmt /salvagerepository
 WMI データベースに矛盾はありません
```

みんなこれで治ってるそうだし、これで僕のPCも治るはず！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1092604/9ccb76c9-5e7b-c0bc-2f40-f20240089ff9.png)

・・・まぁ、お約束ですね。人生そんなに楽じゃないわ。

# WMIのレポジトリの手動削除
さて、王道のやり方は失敗したので下記の記事を参考に少々強硬手段で行きます。
**WMIは以下のディレクトリ下にあるマップファイル等を参照して様々なクラス等をコールしてるみたい**です。
`\Windows\system32\wbem\repository`
**このディレクトリのことをWMIレポジトリと呼んでる**みたいで、さっきの修復コマンドはこのディレクトリの修復を行うコマンドだったみたいです。
下記の記事ではWMIサービスを強制的に停止させ、再開する前にrepositoryディレクトリを削除。WMIサービスが立ち上がると再びrepositoryディレクトリが自動修復されるのでWMIレポジトリの修復が完了するという手筈みたいです。

https://future-architect.github.io/articles/20210127/#WMI%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E7%8A%B6%E6%85%8B%E3%81%A8%E6%A7%8B%E6%88%90%E3%81%AE%E7%A2%BA%E8%AA%8D

記事通りに手順を踏んでやったけど...ダメ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1092604/9ccb76c9-5e7b-c0bc-2f40-f20240089ff9.png)

# Autorecover MOFs の発見
ここまで挫折続きでしたが収穫もありました。
前章での作業中にレジストリエディタをいじる工程があったので、同ディレクトリ下のレジストリを片っ端から覗いていたのですが、ふとこんな名前のレジストリを発見...
名前は`Autorecover MOFs`、さっき自動復旧で失敗したので見逃せない名前です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1092604/89521d0f-894a-bbdb-40af-a42649cf02ac.png)

レジストリのキーを読んでみると大量のMOF、MFLファイルのパスが登録されていました。
MFLファイルはシステム設定ファイルで、MOFファイルは[CIMクラス](https://learn.microsoft.com/ja-jp/windows/win32/wmisdk/common-information-model)が記述されているクラスフォーマットファイルらしいです。

**...ん？クラス？
そもそも今回の出発点は、あるはずのクラスが無効だと言われたところからスタートしてるのでは？**

ということでMicrosoftのラーニングページにてMOFファイルについてお勉強しました。

https://learn.microsoft.com/ja-jp/windows/win32/wmisdk/managed-object-format--mof-

上の記事によると
> Managed Object Format (MOF) は、Common Information Model (CIM) クラスを記述するために使用される言語です。
WMI プロバイダーで新しい WMI クラスを実装するには、Mofcomp.exe を使用して "WMI リポジトリ" にコンパイルされる MOF ファイルをお勧めします。 WMI 用 COM API を使用して CIM クラスとインスタンスを作成および操作することもできます。

これはもしかしてWMIサービスが立ち上がるときやWMI復旧コマンドを打ったときに`Autorecover MOFs`に登録されたMOFファイル群がWMIリポジトリにコンパイルされることでWMIリポジトリが構築されているのでは？

ということでsystem32下のwbem内にあるMOFファイルからWin32_LogicalDiskクラスが定義されているMOFファイルを探す旅に出ました。
頑張って探すこと11ファイル目、`cimwin32.mof`の3385行目に
``` :cimwin32.mof 3385行目
class Win32_LogicalDisk : CIM_LogicalDisk
{
  [Read : ToSubclass,Key : ToInstance ToSubclass DisableOverride,Override("DeviceId") : ToSubclass,MappingStrings{"WMI"} : ToSubclass] string DeviceID;
  [read : ToSubclass,MappingStrings{"Win32API|File System Functions|GetVolumeInformation|FS_VOL_IS_COMPRESSED"} : ToSubclass] boolean Compressed;
  [read : ToSubclass,MappingStrings{"Win32API|FileFunctions|GetDriveType"} : ToSubclass] uint32 DriveType;
  [read : ToSubclass,MappingStrings{"Win32API|File System Functions|GetVolumeInformation"} : ToSubclass] string FileSystem;
  [read : ToSubclass,MappingStrings{"Win32API|File System Functions|GetVolumeInformation"} : ToSubclass] uint32 MaximumComponentLength;
  [read : ToSubclass,MappingStrings{"Win32API|Windows Networking Functions|WNetGetConnection"} : ToSubclass] string ProviderName;
  [read : ToSubclass,MappingStrings{"Win32API|File System Functions|GetVolumeInformation|FS_FILE_COMPRESSION"} : ToSubclass] boolean SupportsFileBasedCompression;
  [read : ToSubclass,write : ToSubclass,MappingStrings{"Win32API|File System Functions|GetVolumeInformation"} : ToSubclass] string VolumeName;
  [read : ToSubclass,MappingStrings{"Win32API|File System Functions|GetVolumeInformation"} : ToSubclass] string VolumeSerialNumber;
  [read : ToSubclass,MappingStrings{"Win32API|Device Input and Output Functions|DeviceIoControl"} : ToSubclass] uint32 MediaType;
  [read : ToSubclass] boolean SupportsDiskQuotas;
  [read : ToSubclass] boolean QuotasDisabled;
  [read : ToSubclass] boolean QuotasIncomplete;
  [read : ToSubclass] boolean QuotasRebuilding;
  [Implemented,MappingStrings{"Fmifs.dll | Method ChkDskExRoutine"} : ToSubclass] uint32 Chkdsk([in] boolean FixErrors = FALSE,[in] boolean VigorousIndexCheck = TRUE,[in] boolean SkipFolderCycle = TRUE,[in] boolean ForceDismount = FALSE,[in] boolean RecoverBadSectors = FALSE,[in] boolean OkToRunAtBootUp = FALSE);
  [Read : ToSubclass,MappingStrings{"FSCTL_IS_VOLUME_DIRTY"} : ToSubclass] boolean VolumeDirty;
  [Static,Implemented,MappingStrings{"Chkntfs.exe"} : ToSubclass] uint32 ScheduleAutoChk([in] string LogicalDisk[]);
  [Static,Implemented,MappingStrings{"Chkntfs.exe"} : ToSubclass] uint32 ExcludeFromAutochk([in] string LogicalDisk[]);
};
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1092604/a04d82fd-b4ce-1b7b-3132-4d9f9a866cb6.png)

**しかも`Autorecover MOFs`に`cimwin32.mof`が登録されていないので修復時にコンパイルされてないっぽい！**

ということで、Microsoftによると、Mofcomp.exeを実行するとWMIレポジトリにWMIクラスとしてMOFがコンパイルされるそうなので、WMIサービスを停止した後にコマンドプロンプトを管理者権限で立ち上げて以下を実行。
```powershell:mofcomp実行
Mofcomp C:\windows\system32\wbem\cimwin32.mof
```
再度WMIサービスを立ち上げると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1092604/6c89cb53-443c-d664-7d40-28bc61f07af0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1092604/570b4f6e-e00b-57f5-cc1a-25c25a3c2492.png)

`Get-WmiObject -class Win32_LogicalDis`も無事動作して完全勝利です！
後処理として`Autorecover MOFs`に`cimwin32.mof`を登録したのちにPCを再起動して無事動作を確認しました。
# 対処方法まとめ
ということで対処方法のまとめです。

::: note alert
**【症状】Win32_LogicalDisk が無効なクラスと言われる**
:::
::: note info
**【解決方法】**
:::
1. WMIサービスを停止する
    1. サービスから`Windows Management Instrumentation`を停止
1. 管理者権限でコマンドプロンプトを立ち上げて以下を入力
    1. `Mofcomp C:\windows\system32\wbem\cimwin32.mof`
1. WMIサービスを再開する
1. **【要注意：下の注意事項を読んでから実行するか決めてください】**
レジストリエディタで以下の作業を行う 
    1. `コンピューター\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wbem\CIMOM`へ移動する
    1. `Autorecover MOFs`を右クリックして**修正**を選択
    1. 値のデータに`C:\windows\system32\wbem\cimwin32.mof`を追加
1. 念押しの再起動
::: note warn
**【注意事項】**
- WMIサービスはいろんな処理が依存しているので手順をさっさと進めないと他のプロセスがサービスの立ち上げにかかります。サービスを止めてからは時間勝負なので頑張りましょう。
- **レジストリエディタでの作業は結構危険**を伴います。また、Autorecover MOFsレジストリの修正については未だ検証できていない部分が多く、値を追加することでの影響が未知数です。以上を踏まえて実行するか考えてください。
:::

# おわりに
ところで皆さんなぜこの苦行に挑戦することになったのかを忘れていないだろうか。
その理由は業務で使用するラズパイピコのArduinoIDEでの書き込みができなかったからである。
そう！断じてWMIプロパティでコンピュータの構成情報が見たかったのではなく業務で開発中のマイコンへの書き込み環境が壊れたからこの苦行に飛び込んだのだ！

**そして、この苦行に費やした時間は早８時間...**

この苦行は終わりではなく仕事の前哨戦として消化され、まさにこれから地獄の門が開くのであった。

納期がガガガ...

（おわり）