Windows Management Instrumentation を簡単に得られるツール wmi2struct

# WMI とは

Windows PC やサーバから色々な情報を得る方法として Microsoft は WMI(Windows Management Instrumentation) を提供しています。

これらの情報は WMI クラスという構造で格納された形式で提供され、数多くのクラスが用意されています。

https://docs.microsoft.com/en-us/windows/desktop/wmisdk/wmi-system-classes

# wmic コマンド

コマンドラインからは wmic というツールを使いこれらの情報に簡単にアクセス出来る様になっています。

![WMIC](https://qiita-image-store.s3.amazonaws.com/0/665/f351d4bb-e042-4c12-160c-cfad503a1607.png)

ちょっと確認するには wmic コマンドは便利です。wmic コマンドには一応、CSV もしくは XML 形式で出力するオプションが付いているのですが、プログラムからは非常に扱い辛い物になっています。また型もないので出力内容を WMI クラスの定義に従って変換しないといけない訳です。そこで便利なのが StackExchange/wmi です。

# StackExchange/wmi パッケージ

https://github.com/StackExchange/wmi

[StackExchange](https://stackexchange.com/) は [StackOverflow](https://stackoverflow.com) と同じ会社が運営している FAQ サイトです。StackOverflow は Web サービスの多くを Windows サーバで運用している珍しい会社として有名ですが、内部のリソース管理に Go 言語で書かれたシステムを使っています。そのシステムで使われているのが StackExchange/wmi です。StackExchange/wmi は内部で COM を使っています。ちなみに Go から COM を扱う方法として [go-ole](https://github.com/go-ole/go-ole) という筆者が開発しているパッケージがあるのですが、wmi はこの go-ole を使ったパッケージになっています。

使い方はとても簡単で、SQL の様な WMI クエリを発行して WMI クラスのレコードを取り出します。

```go
package main

import (
	"fmt"
	"log"

	"github.com/StackExchange/wmi"
)

type Win32ComputerSystemProduct struct {
	Caption           string
	Description       string
	IdentifyingNumber string
	Name              string
	SKUNumber         *string
	UUID              string
	Vendor            string
	Version           string
}

func main() {
	var records []Win32ComputerSystemProduct
	err := wmi.Query("SELECT * FROM Win32_ComputerSystemProduct", &records)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(records[0].Vendor)
}
```

Win32_ComputerSystemProduct はコンピュータの製品情報を得られるクラスです。この例であれば僕のノート PC は HP 製なので `Hewlett-Packard` と表示されます。

さて、この Win32_ComputerSystemProduct  というクラスはブラウザを起動してググって Microsoft のページに辿り着ければ誰でも構造を得られます。

https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-computersystemproduct

```
[Dynamic, Provider("CIMWin32"), UUID("{FAF76B96-798C-11D2-AAD1-006008C78BC7}"), AMENDMENT]
class Win32_ComputerSystemProduct : CIM_Product
{
  string Caption;
  string Description;
  string IdentifyingNumber;
  string Name;
  string SKUNumber;
  string Vendor;
  string Version;
  string UUID;
};
```

なるほど C# で書かれていています。これをチマチマと上記の様に Go に置き換える必要があります。なかなか面倒そうですね。

# wmi2struct

そこで登場するのがこの記事の主役、wmi2struct です。

https://github.com/mattn/wmi2struct

wmi2struct はコマンドラインアプリケーションです。使い方は以下の通り。

```
Usage of wmi2struct:
  -l	list all classes
  -n	output number fields
  -o string
    	output filename
  -p string
    	package (default "main")
```

`-l` オプションを付けて起動すると、その PC で取得可能な WMI クラスの一覧が表示されます。

```
略
Win32_SystemTrace
Win32_ProcessTrace
Win32_ProcessStartTrace
Win32_ProcessStopTrace
Win32_ThreadTrace
Win32_ThreadStartTrace
Win32_ThreadStopTrace
Win32_ModuleTrace
Win32_ModuleLoadTrace
Win32_PowerManagementEvent
Win32_ComputerSystemEvent
Win32_ComputerShutdownEvent
略
```
では例えば上記で使った `Win32_ComputerSystemProduct` を引数に渡してみます。

```
C:\>wmi2struct Win32_ComputerSystemProduct
package main

// Win32_ComputerSystemProduct is struct for WMI
type Win32_ComputerSystemProduct struct {
	Caption           string
	Description       string
	IdentifyingNumber string
	Name              string
	SKUNumber         string
	UUID              string
	Vendor            string
	Version           string
}
```

なんと C# のソースだった Win32_ComputerSystemProduct が Go の形式で出力されているではないですか！便利！

あとはこれを Go のソースとして保存し、上記の様に `wmi.Query` を実行するだけなのです。超便利！

一応、1点だけ注意点があり、Go の string は C# の string の様に nil(null) が格納出来ません。もし WMI クラスの値として nil が格納される可能性があるのであれば、そのフィールドを `*string` として宣言して下さい。wmi パッケージがよろしく変換して実行してくれます。今回の例であれば、SKUNumber が nil を持ち得るフィールドになります。

```go
type Win32_ComputerSystemProduct struct {
	Caption           string
	Description       string
	IdentifyingNumber string
	Name              string
	SKUNumber         *string
	UUID              string
	Vendor            string
	Version           string
}
```

# まとめ

今回は WMI クラスを扱えるパッケージ StackExchange/wmi と、それに渡すべき struct を簡単に生成できるツール wmi2struct を紹介しました。PC に格納されている色々な情報を簡単に得られるのでぜひお使い下さい。