[WMI] C++ で 2 つ以上の引数を与えてメソッドを実行するには? 2 / (2)

みなさん御機嫌よう、ういこです。
前回はお題が WMI C++ で 2 つ以上の引数をメソッドに与える方法についてなのに、VBScript やら C# やらで書いていてどうよ?って思われた方もいらっしゃるかと思います。すみません。

【お題 : C++ で WMI - メソッドに二つ以上引数を渡したいの】
前回はこちら。タイトル変えました。
[WMI] C++ で 2 つ以上の引数を与えてメソッドを実行するには? 1 / (2)
https://blogs.technet.com/jpilmblg/archive/2009/10/06/wmi-c-2-1-2.aspx

※ 前回のあらすじ ※
Windows を管理するには最高のパートナーの WMI。しかし、その実態は某大戦中の某国の大型戦車のように威力はすごいが足が遅かった!1 ターンでの移動力が 1 Hex 位しか進まないとまでは行かなくても。そんな WMI を改良すべく、スクリプトだと量がすごいすくないのはわかっていても、あえて C++ で動かそうと考えた熱い魂をもつ漢(おとこ)たち。しかし、MSDN には引数を一個しか指定しない感じのサンプルしかない。C# とかも微妙に参考にならない感じだ。
どうする!?

【はじめに】
早速、ExecQuery を…と思う前に、まあまあはやる心を抑えましょう。
C++ の場合は、自分で認証情報などをつけないといけないのです。C# や、VBScript などでよしなにしてくれた処理も、自分でやらなくてはならないのです。そこが大変な上に、コード量ももりもり増えます。

・IWbemService のプロキシが使用する認証情報を明示的に設定する必要がある。

(特に、リモートでアクセスする場合など)
WMI のプログラムのプロセス実行ユーザーがリモート コンピュータの管理者権限を持たない場合は、ConnectServer() で 管理者権限を持つユーザーの名前とパスワードを指定していても、ExecQuery() でアクセス拒否されてしまいます。
リモート コンピュータ上の情報を取得する際には、リモート コンピュータ上の WMI の実装が DCOM 経由で呼び出されます。この際、リモート コンピュータ上で実行されるスレッドのユーザーは適切な権限を持つユーザーである必要があるのです。この、リモートコンピュータ上で実行されるスレッドのユーザーを指定するには、CoSetProxyBlanket() を使用します。

ちなみに CoSetProxyBlanket() の第 7 引数がポイントです。NULL を指定した場合は「ローカル コンピュータ上で実行しているユーザー」として、リモート コンピュータ上でもスレッドを実行することを意味します。実行ユーザーとは別のリモート コンピュータ上で管理権限を持つユーザーとして、リモート コンピュータ上のスレッドを実行するには、CoSetProxyBlanket() の第 7 引数でユーザーを明示的に指定してあげてください。
それと、ふるーいバージョン Service Pack などがないの Windows XP では CoQueryProxyBlanket() の第 2 引数に DWORD のポインタを渡すように対応してください。Windows Server 2003 では修正されて出荷されているのですが、NULL を渡すと Access Violation が発生する不具合があります。XP のサービスパックなしバージョンなどで動作させようとすることもシステム要件に含む場合は注意してください。

1. ExecQuery() メソッドで、以下の WQL 文を発行します。

例 :
仮想マシン名「Windows Server 2003 (x86)」に対してシャットダウンを行う場合

    1 IEnumWbemClassObject* pEnumerator = NULL;

    2 hres = pSvc->ExecQuery(

    3 bstr_t("WQL"),

    4 bstr_t("SELECT * FROM Msvm_ComputerSystem WHERE ElementName='Windows Server 2003 (x86)'"),

    5     WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,

    6     NULL,

    7     &pEnumerator);

2. IEnumWbemClassObject から仮想マシンの GUID (Name) を列挙し、Get() メソッドで VARIANT 型の変数に格納します。

   10 IWbemClassObject *pclsObj = NULL;

   11 ULONG uReturn = 0;

   12 

   13 VARIANT vtProp;

   14 

   15 while (pEnumerator)

   16 {

   17     HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

   18     if(0 == uReturn)

   19     {

   20         break;

   21     }

   22 

   23     // Get the value of the Name property

   24     hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0);

   25     wcout << " VM's Name : " << vtProp.bstrVal << endl;

   26 

   27     pclsObj->Release();

   28     pclsObj = NULL;

   29 }

3. 上記ステップ 2. で取得した仮想マシン名を用いて、Msvm_ShutdownComponent クラスをクエリします。

   32 IEnumWbemClassObject* pVmshout = NULL;

   33 bstr_t WQL = _T("SELECT * FROM Msvm_ShutdownComponent WHERE SystemName='");

   34     WQL += vtProp.bstrVal;

   35     WQL += _T("'");

   36 

   37     VariantClear(&vtProp);

   38 

   39     hres = pSvc->ExecQuery(bstr_t("WQL"),

   40                         WQL,

   41                         WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,

   42                         NULL,

   43                         &pVmshout);

4. IEnumWbemClassObject を列挙し、Msvm_ShutdownComponent クラスのパスを取得します。

   44     hr = pclsObj->Get(L"__Path", 0, &vtProp, 0, 0);

※ デバッガ上でみたクラスのフルパス = vtProp のデータ例 (実際には改行はありません) 検証端末名 : UIKOUTEST2008

   46     vtProp BSTR = "\\UIKOUTEST2008\root\virtualization: Msvm_ShutdownComponent.CreationClassName="Msvm_ShutdownComponent", DeviceID="Microsoft:9F8233AC-BE49-4C79-8EE3-E7E1985B2077",

5. ConnectServer() の呼び出しの際に指定した IWbemServices オブジェクトを用いて Msvm_ShutdownComponent クラスを取得します。

   50 bstr_t ClassName = _T("Msvm_ShutdownComponent");

   51 

   52 IWbemClassObject* pClass = NULL;

   53 hres = pSvc->GetObject(ClassName, 0, NULL, &pClass, NULL);

 

6. ステップ 5. で取得した Msvm_ShutdownComponent クラスの InitiateShutdown() メソッドを取得します。

 

   56 bstr_t MethodName = _T("InitiateShutdown");

   57 

   58 IWbemClassObject* pInParamsDefinition = NULL;

   59 hres = pClass->GetMethod(MethodName, 0,

   60 &pInParamsDefinition, NULL);

 

7. SpawnInstance() を使用してインスタンスを取得します。

 

   63 IWbemClassObject* pClassInstance = NULL;

   64 hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);

8. InitiateShutdown() メソッドの引数をPut() メソッドを使用して指定します。
以下の例では、InitiateShutdown() メソッドの引数 Force に true を、第二引数 Reason に文字列を指定します。なんと、わかれば単純!この形式でどんどん Put していけばよいのですね。

   67 // Create the values for the in parameters

   68 VARIANT varCommand;

   69 varCommand.vt = VT_BOOL;

   70 varCommand.boolVal = true;

   71 hres = pClassInstance->Put(L"Force", 0,&varCommand, 0);

   72 VariantClear(&varCommand);

   73 

   74 varCommand.vt = VT_BSTR;

   75 varCommand.bstrVal = L"Because I want to do it.";

   76 hres = pClassInstance->Put(L"Reason", 0,&varCommand, 0);

   77 VariantClear(&varCommand);

 

9. ステップ 4. で取得した Msvm_ShutdownComponent クラスのパスを ExecMethod() に指定し、InitiateShutdown() メソッド (第二引数 MethodName) を実行します。

   80 IWbemClassObject* pOutParams = NULL;

   81 hres = pSvc->ExecMethod(vtProp.bstrVal, MethodName, 0,

   82 NULL, pClassInstance, &pOutParams, NULL);

【注意点】
・ドメイン非参加の PC からリモート PC 上の管理者権限ユーザを指定してもアクセス拒否されることがある

ちなみにドメインに参加していない PC (Windows XP など) の既定の設定では、ネットワーク経由でのアクセスの際に認証されたユーザーはリモートコンピュータ上で Guest の権限のみを持ちます。このため、WMI を使ってリモート コンピュータ上の情報を取得する際に、リモート コンピュータ上での管理者権限を持つユーザーを指定してアクセスした場合でも、アクセスを拒否されることがあります。
この設定はローカル セキュリティ ポリシー管理ツールの以下の項目で変更可能です。

[セキュリティの設定]
- [ローカルポリシー]
- [セキュリティ オプション]
- [ネットワーク アクセス:ローカル アカウントの共有とセキュリティ モデル]

この項目を、「クラシック - ローカルユーザーがローカルユーザーとして認証する」に変更することで、ネットワーク アクセスの際に認証されたユーザがリモートコンピュータ上で、認証されたユーザーとしての権限を持つように変更できます。
なお、ドメインに参加している Windows XP 上ではネットワーク アクセスの際に認証されたユーザーは、リモートコンピュータ上でそのユーザーとしての権限を持ちます。ちなみに、Windows Server 2003 のデフォルトの設定では、この設定は「クラシック」に設定されています。

参考 :
エラー メッセージ : Access Denied; Specified User Is Not a Member of TelnetClients Group
https://support.microsoft.com/default.aspx?scid=kb;ja;298060

image 

image

【おまけ : 一連の処理をもっとわかりやすくしてみたです & 行番号がないコピペ対応版】

BSTR methodName = SysAllocString(L"InitiateShutdown");

BSTR className = SysAllocString(L"Msvm_ShutdownComponent");

 

IWbemClassObject *pClass = NULL;

hres = pSvc->GetObject(className, 0, NULL, &pClass, NULL);

 

IWbemClassObject *pInParamsDefinition = NULL;

hres = pClass->GetMethod(methodName, 0, &pInParamsDefinition, NULL);

 

IWbemClassObject *pClassInstance = NULL;

hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);

 

VARIANT varCommand;

varCommand.vt = VT_BOOL;

varCommand.boolVal = true;

VARIANT varCommand2;

varCommand2.vt = VT_BSTR;

varCommand2.bstrVal = L"Because I want to do it.";

 

pClassInstance->Put(L"Force",0,&varCommand,0);

pClassInstance->Put(L"Reason",0,&varCommand2,0);

 

hres = pclsObj->Get(L"__PATH", 0, &vtProp, 0, 0);

 

IWbemClassObject* pOutParams = NULL;

hr = pSvc->ExecMethod(vtProp.bstrVal, methodName,0,NULL,pClassInstance,&pOutParams,NULL);

if (FAILED(hr))

{

wcout << " Could not shutdown" << hex << hr << endl;

}

 

_bstr_t bstrret("ReturnValue");

BSTR ret;

hr = pOutParams->GetObjectText(0, &ret);

cout << "Return Value: " << ret << endl;

 

VariantClear(&varCommand);

VariantClear(&varCommand2);

pclsObj->Release();

pclsObj = NULL;

以上です。

それにしても、なんだかブログへの風当たりが強い今日この頃。わかりやすくて、使えるブログを目指しておりますので、皆さんこれからも応援していただければ幸いです。

ういこう@I Love メカニカルキーボード