Hey, Scripting Guy! WMI を精査する

Microsoft Scripting Guys

目次

名前空間
WMI クラス
プロパティ
メソッド

Scripting Guys の 1 人が若かりしころ、彼には 2 つの趣味がありました。それは、発酵飲料を飲むことと、冬場のキャンプでした。彼はこの 2 つの趣味の間には何の関連もなかったのではないかと思っています。当時の彼の友人は、よく「奥深くまで行く」ことを提案していました。そこで、今月は、この旧友のアドバイスに従い、雑然とした WMI を精査することにしました (ちなみに、WMI は Windows Management Instrumentation の略です)。

ありがたいことに、今回は、大自然の中を長時間歩いたりする必要はありません。ただし、コーヒー メーカーがある場所までは、歩いて行き来する必要があるかもしれません。歩き回る代わりに、スクリプトを使用して WMI の細部を調べます。

Scripting Guys が、実用性を重視するというのは周知のことです。ですから、私たちは、読者の皆さんに対して、(具体的な方法など) 一部の詳細を説明していないような、もったいぶった説明をするのではなく、実際に問題を解決する方法を提示します。ですが、私たちは時流に乗っているわけではありません。今月のコラムには、特定のシステム管理タスクを遂行するという目的はありませんが、実用的な目的があります。第 1 の目的は、皆さんに WMI のインフラストラクチャを学習してもらうことです。そして、第 2 の目的は、便利で詳細なスクリプトを紹介することです。では、メモ帳を起動して、WMI の精査を開始しましょう。

名前空間

WMI リポジトリはデータベースで、Common Information Model (CIM) の格納に使用します。CIM はオブジェクト指向のモデルです。つまり、WMI で管理できるものを表す一連の説明 (WMI クラス) で構成されています。たとえば、WMI クラス Win32_Process はプロセスを表します。WMI クラスは、WMI リポジトリのさまざまなセクションに格納され、このような WMI リポジトリのセクションは、名前空間と呼ばれます。雑然とした WMI で WMI リポジトリに遭遇した場合、まず WMI リポジトリが高レベルな名前空間に分かれていることに気付くでしょう。図 1 のスクリプトは、strComputer 変数で指定されている全コンピュータの WMI リポジトリのすべての名前空間を一覧表示します。

図 1 名前空間の表示

strComputer = "."
Call EnumNameSpaces("root")

Sub EnumNameSpaces(strNameSpace)
    On Error Resume Next
    WScript.Echo strNameSpace
    Set objWMIService=GetObject _
        ("winmgmts:{impersonationLevel=impersonate}\\" & _ 
            strComputer & "\" & strNameSpace)

    Set colNameSpaces = objWMIService.InstancesOf("__NAMESPACE")

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

名前空間には、サブ名前空間が含まれることがあります。この一連の名前空間は、ディレクトリ構造として捉えることができます。名前空間 (トップレベルのルート名前空間) のみが必要な場合は、ルートの直下にある名前空間のみを取得します。そのようにすると、ルートの直下にないサブ名前空間が取得されることはありません。ここでは、再帰処理を使用します。ここでは、名前空間をパラメータとして受け取り、その名前空間の下にあるすべてのサブ名前空間を返す EnumNameSpaces という名前のサブルーチンを作成しました。

まず、EnumNameSpaces サブルーチンを呼び出し、パラメータとしてルートの名前空間を渡します。このサブルーチンからは、指定したルート名前空間の下にあるすべてのサブ名前空間が返されます。次に、再帰処理を行います。EnumNameSpaces サブルーチンでは、実際に、このサブルーチン自体を呼び出し、1 回目の呼び出し時に特定した各サブ名前空間をパラメータとして渡します。コーヒーでも飲んで一息ついたら、少し考えてみてください。この再帰処理により、すべての名前空間が処理され、処理した名前空間にサブ名前空間がある場合は、すべてのサブ名前空間が一覧表示されます。

このサブルーチンの先頭には、On Error Resume Next というステートメントを記述しています。このステートメントは、どの名前空間にもアクセスできないセキュリティ コンテキストでスクリプトを実行した場合に備えて記述したものです。このステートメントがあれば、そのようなセキュリティ コンテキストでも、スクリプトはタイムアウトするまで待機するので処理速度は遅くなりますが、スクリプトは実行されます。

そうですね。このような処理を行う代わりに、単に (WMI がインストールされているすべてのコンピュータで使用できる) wbemtest.exe や Scriptomatic (go.microsoft.com/fwlink/?LinkId=125976) を使うことも可能です。ただし、開始点となるスクリプトと皆さんがお持ちのような一流のスクリプト スキルがあれば、名前空間をフィルタ処理したり、Excel に出力したり、2 台のコンピュータ間で名前空間を比較したりすることもできます。

リポジトリの構成については理解していただけたと思うので、今度は、各セクションに格納されているものを調査するスクリプトを記述しましょう。WMI クラスが、名前空間に格納されていることはわかっているので、WMI クラスについて説明しましょう。

WMI クラス

WMI リポジトリには Common Information Model が格納されるという話をしたことは覚えていますか。このモデルは、CIMV2 名前空間に格納されます (CIMV2 の V2 は、バージョン 2 という意味です)。CIMV2 名前空間を参照すると、このモデルを構成しているすべての WMI クラスが格納されていることを確認できます。この処理を行うのが次のスクリプトです。

strComputer = "."
Set objWMIService=GetObject("winmgmts: _
    {impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in 
    objWMIService.SubclassesOf()
    Wscript.Echo objClass.Path_.Class
Next

では、このスクリプトのしくみを説明しましょう。GetObject メソッドを呼び出すと SWbemServices オブジェクトが返されます。GetObject は、スクリプトで操作できる COM オブジェクトへの参照を返す VBScript 関数です。ここでは、winmgmts:… という文字列を渡しているので、GetObject メソッドからは、WMI Scripting Library のオブジェクトが返されます。GetObject メソッドに渡した文字列には、接続する名前空間 (ここでは、\root\cimv2) が含まれています。ですから、ここで処理する必要がある SWbemServices オブジェクトは、指定した特定の名前空間にバインドされています。SWbemServices オブジェクトに対してスクリプトで行える操作については、このオブジェクトに関するドキュメントを参照してください。

皆さんに馴染みがあるのは ExecQuery メソッドでしょう。このメソッドを使用すると、接続している名前空間に対して WQL (Windows Management Instrumentation Query Language) クエリを実行できます。ですが、SWbemServices オブジェクトを名前空間と関連付けた後に実行できる操作は他にも多数あります。

ここでは接続している名前空間のすべての WMI クラスを特定するために SubClassesOf メソッドを使用しています。このメソッドのドキュメントによると、SWbemObjectSet オブジェクトが返されるとのことですが、これは雑然とした WMI の世界をさまよっているときに知りたい情報ではないですね。ですから、私たちが実用的な情報を提供しましょう。このオブジェクトの最後の 3 文字 (Set) に注目してください。WMI スクリプトを記述したことがある人はご存じだと思いますが、セットというものは、For Each ループを使用して処理することができます。

驚くほどのことではありませんが、SWbemObjectSet オブジェクトの各メンバは SWbemObject オブジェクトです。各 SWbemObject オブジェクトは、CIMV2 名前空間の WMI クラスを表しています。SWbemObject オブジェクトのドキュメントを参照すると、CIMV2 名前空間の WMI クラスに関するあらゆる情報が記載されています。

ここでは、単にクラスの名前を表示することにし、それを行うため Path_ プロパティにアクセスしました。結局のところ、Path_ プロパティ自体もオブジェクトだということがわかりました。このプロパティは、SWbemObjectPath オブジェクトです。そして、オブジェクトであれば、独自のプロパティを多数持っています。ここでは Class クラスを使用しています。

ですから、このスクリプトは、ある名前空間のすべての WMI クラスを表示できるだけでなく、そのクラスに付属する他のさまざまな事項を表示するように簡単に更新することができます。たとえば、WMI クラスは、他の WMI クラスの拡張クラスであることがあります。たとえば、Win32_Car という車種がある場合に、ステーション ワゴンを管理しなければならない状況が発生したとしましょう。この車種に関する特性は、ステーション ワゴンにも当てはまります。

ステーション ワゴンでは、お洒落な木製パネルの有無を示すブール値など、追加の項目が必要です。ですが、Win32_Car クラスの機能をすべて作成し直すようなことはしたくないでしょう。このような場合は、おそらく、Win32_Car クラスを拡張して、新しい特性を追加するメカニズムが必要になるでしょう。WMI には、このようなメカニズムが用意されています。

ある特定の WMI クラスが別の WMI クラスのプロパティを継承しているかどうかは、その WMI クラスと関連付けられている SWbemObject オブジェクトの Derivation_ プロパティで確認できます。図 2 のスクリプトを実行すると、CIMV2 名前空間の WMI クラスと、各クラスの派生元クラスが一覧表示されます。

図 2 CIMV2 名前空間の WMI クラスの派生元クラス

strComputer = "."
Set objWMIService=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
    strComputer & "\root\cimv2")

For Each objclass in objWMIService.SubclassesOf()
    WScript.StdOut.Write objclass.Path_.Class
    arrDerivativeClasses = objClass.Derivation_ 
    For Each strDerivativeClass in arrDerivativeClasses 
       WScript.StdOut.Write " <- " & strDerivativeClass
    Next
    WScript.StdOut.Write vbNewLine
Next

    For Each objNameSpace in colNameSpaces
        Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
    Next
End Sub

このスクリプトの先頭部分は、図 1 のスクリプトと同じです。ここでは、表示した文字列の後に自動で改行文字を追加しないように、WScript.Echo ではなく WScript.StdOut.Write を使用しています (注 : StdOut.Write が機能するには、Wscript.exe ではなく Cscript.exe を使用してスクリプトを実行する必要があります)。

SWbemObject オブジェクトのドキュメントを参照すると、このオブジェクトには Derivation_ プロパティがあるということがわかります。このプロパティは、現在のクラスの派生元クラスの名前が格納された文字列の配列です。ここでは、For Each ループを使用して、この配列を処理して、ASCII 文字の矢印を区切り文字として使用して、すべてのクラスを一覧表示します。GetObject メソッドから返された SWbemServices オブジェクトを処理することに慣れて、WMI Scripting Library のドキュメントに記載されている内容を確認したら、これらの奇妙な名前のオブジェクトのプロパティやメソッドを使って、何ができるのかを探ることができます。

スクリプトのある時点で処理している WMI Scripting Library のオブジェクトを把握できるようになると、WMI スクリプトの次の段階に進むことができます。つまり、ここで紹介したスクリプトを単にそのまま使うのではなく、ExecQuery メソッドを呼び出したり、Properties_ プロパティを参照したりすることができた理由も理解できるようになります。また、さらなる措置を講じることもできるようになります。

Scriptomatic ツールが皆さんの期待値に達していなくても、問題ありません。その場合は、必要な処理を実行するように、このツールを変更すれば良いだけです。もしかすると、皆さんが変更を加えたバージョンに対して、他のユーザーから購入の申し出があるかもしれません。そのような場合は、scripter@microsoft.com (英語のみ) まで変更方法と特許権使用料の受け取り方法の詳細をご連絡ください。

プロパティ

各 WMI クラスは、一連のプロパティとメソッドを使用して管理できるもので構成されています。プロパティは、物の特性です。たとえば、プロセスは ID と優先度を持ち、一定量のメモリを使用します。このようなプロパティは、すべて Win32 _Process という WMI クラスに用意されています。

あるエンティティを管理するクラスを特定したら、そのクラスで使用できるプロパティを参照して、管理する必要のあるものが管理モデルに用意されているかどうかを確認します。SWbemObject クラスには、Properties_ という名前のプロパティがあります。おもしろいですよね。このプロパティの値は、SWbemProperty オブジェクトのコレクションを含む SWbemPropertySet オブジェクトです。このコレクションに含まれている各 SWbemProperty オブジェクトは、SWbemObject オブジェクトと関連付けられている WMI クラスのプロパティに対応しています。このように SWbem という文字列で名前が始まるオブジェクトが多数あるので、物事が非常に複雑であるような印象を与えているかもしれませんが、実は、それほど複雑ではありません。図 3 をご覧ください。

fig03.gif

図 3 SWbemObject オブジェクトではバインドしている WMI クラスのプロパティが公開される (画像をクリックすると拡大表示されます)

SWbem という文字列で名前が始まるクラスは、WMI Scripting Object ライブラリのメンバであるということを覚えておいてください。また、このようなクラスは、WMI を操作できるオブジェクトですが、管理できる WMI モデルの構成要素ではないことに注意してください。

図 3 に示す、SWbemObject オブジェクトは、Property_1、Property_2、および Property_3 というプロパティを持つ Win32_SomeClass という WMI クラスを表しています。SwbemObject オブジェクトでは、そのオブジェクトの Properties_ プロパティ経由で、これらのプロパティを公開しています。もちろん、このクラスが別の WMI クラス (たとえば、Win32_SomeOtherClass) にバインドされていても、プロパティの名前は同じで、Properties_ です。ただし、バインドしているクラスの他のプロパティは名前が異なる可能性が高いです。

基本的に、SWbemObject オブジェクトでは、バインドしている WMI クラスのプロパティを継承しますが、異なるプロパティについても、同じ Properties_ プロパティのメカニズムを使用してアクセスすることができます。おわかりいただけたでしょうか。コーヒーをもう一杯飲んで、図をじっくり眺めてみてください。きっと、はっきりとしてくるでしょうから。

図 4 のスクリプトでは、SWbemObject オブジェクトとその Properties_ プロパティを使用して、Win32_Service という WMI クラスのすべてのプロパティを取得して表示します。このスクリプトの冒頭部分には見覚えがあるでしょう。このスクリプトに加えた主な変更は、名前空間と WMI クラスを別にして、変更しやすくしたことです。たとえば、strClass 変数の値を Win32_BIOS にして、Win32_Service クラスのプロパティではなく、Win32_BIOS クラスのプロパティを確認することができます。また、For Each ループでは、SWbemPropertySet コレクション (objClass.Properties) を処理して、各 SWbemProperty オブジェクトの Name プロパティの値を表示します。

図 4 Win32_Service クラスのプロパティを取得するスクリプト

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Properties"
WScript.Echo "------------------------------"

For Each objClassProperty in objClass.Properties_
    WScript.Echo objClassProperty.Name
Next

メソッド

WMI クラスによっては、管理可能なエンティティのプロパティや特性を構成するにとどまらず、そのエンティティが行える (または、エンティティに対して行える) 動作や操作へのアクセスを提供するメソッドを持っているものもあります。

この WMI クラスのすべてのメソッドを返すスクリプト (図 5 参照) の形式は、プロパティを返すスクリプトと同じです。

図 5 WMI クラスのメソッドを取得するスクリプト

strComputer = "."
strNameSpace = "root\cimv2"
strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _ 
    strComputer & "\" & strNameSpace & ":" & strClass)

WScript.Echo strClass & " Class Methods"
WScript.Echo "---------------------------"

For Each objClassMethod in objClass.Methods_
    WScript.Echo objClassMethod.Name
Next

もちろん、Properties_ プロパティではなく Methods_ プロパティを使うという点は異なります。この時点で、このクラスのメソッドで受け取ることができるパラメータの型を返すことができるのだろうか、実際にメソッドがある WMI クラスのみを表示するにはどうしたら良いのだろうか、という疑問が浮かびました。このような疑問を解消するには、このコラムを読み終えてからご自分でスクリプトを記述して確かめてみてください。

このコラムが、WMI を精査する第一歩としてお役に立てばさいわいです。SWbem という文字列で名前が始まる多数のオブジェクトについては、何とか切り抜けていただく必要がありますが、スクリプトでは、細部を調べるのに適した、難度のあまり高くないメカニズムを見ることができます。冬場のキャンプに連れ立って行った 2 人の友人は、それほど幸運の持ち主ではありませんでした。というのも、彼らは、奥深くまで行けたことがないからです。真冬のさなかに山の奥深いところに必要な量の発酵飲料を十分なだけ運び込むというのは簡単なことではなかったからです。

Scripting Guys は、マイクロソフトの仕事をしています、というよりもマイクロソフトにより雇われています。彼らは、野球の試合や監督や観戦 (または他のさまざまな活動) をしている時以外は、TechNet スクリプト センターを運営しています。詳細については、www.scriptingguys.com を参照してください。