Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2013年08月30日] [更新 2013年09月07日] Delphi サンプルプログラム集
   
899_ネットワークコンピュータと WMI のメソッド
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
899_WMI_Introduction_NetPC.zip [1,736 KB] 2013年10月20日版(一部を除きEXEなし)



  • 2013年09月07日
  • IP アドレスの取得と設定のサンプルを追加
  • ネットワークリソースのサンプル (WMI のサンプルではない) を追加




ネットワーク上のコンピュータへの接続と WMI のメソッド


ネットワーク上の他のコンピュータを WMI で操作する際の参考ですが,シャットダウンと IP アドレスの設定のサンプルは,WMI におけるメソッドの使用方法のサンプルとなっています.WMI のプロパティはそのほとんどが読み出し専用です.メソッドはプロパティの書き込み等にも使用されます.

ネット上の WMI 関係の記事では「リモートコンピュータ」という記述になっていますが,リモートコンピュータは,ネットワークの「向こう側」にあるコンピュータのことです,これに対して手元のコンピュータは「ローカルコンピュータ」あるいは「ローカルマシン」と言っています.
本ページのサンプルは,WMI を使用したリモートアクセスのサンプルということになります.

接続先のコンピュータが Windows XP(SP1) までは,コンピュータ名,ユーザ名,パスワードを設定すれば接続でき,WMI でリモート操作ができました.Windows XP(SP2) ではセキュリティが強化され,従来の手順だけでは接続できなくなりました.また,Windows Vista 以降では,UAC の導入により,更にセキュリティが強化されました.
そのため,操作される側である接続先のコンピュータのファイアウォール等の変更が必要です.

以下の記事は,VMware のバーチャルマシン上で動作している 32 ビット版の Windows 7 を接続先としてテストしています.ドメインには参加していません.
設定方法の記事は,参考リンクの記事を書き直しているだけです.Windows XP(SP2) 以降と Windows Vista については,参考リンクの記事を参考にしてください.

本ページのサンプルプロジェクトには EXE は含まれていません.
接続先のコンピュータ名,管理者権限を持つユーザ名,そのパスワード,ドメイン名等を設定してから,コンパイルしてテストしてください.
リモート側のコンピュータが一般ユーザや制限ユーザでログインしていても,必ず管理者権限のユーザ名とそのパスワードを設定します.そうしないと「アクセス拒否」となり,接続できません.





01_Active Directory ( アクティブディレクトリ ) の操作


Active Directory に関係する操作は,LDAP のインスタンスを取得すれば,WMI でも操作できそうな気がしますが,確認はしていません.
アクティブディレクトリサービスにアクセスするには ADSI (Active Directory Service Interfaces) を使用する方法があります.
Delphi で ADSI を使用するには,タイプライブラリの取り込みで [Active DS Type Library] をインポートし,ActiveDs_TLB.pas を作成します.

この ActiveDs_TLB.pas を使用したサンプルがネット上にあります.
下のリンクの Using Delphi and ADSI to ... にはタイプライブラリの取り込みの説明があります.
LDAP.Admin の [Download] のページからは多くのサンプルが個別にダウンロードできます.
Agni Software の WHITE PARERS には Technical papers の項に Active Directory in Delphi のページがあり,ここからサンプルをダウンロードできます.
ADSI Programming というサイトでもサンプルを提供しています.
これらのサンプルには,ActiveDs_TLB.pas が含まれているものもあります.

Delphi 付属の Indy にも LDAP (エルダップ - Lightweight Directory Access Protocol) 操作用のクラス (プリフィックスは TIdLDAP) があります.Synapse TCP/IP and serial library という同期型のネットワークアクセス用のコンポーネントもよく使われているようです.
また,LDAP のインスタンスから ADO (ActiveX Data Object) で操作する方法もあるようです.





02_準備 1  -  ファイアウォールのリモート管理を有効にする


[準備 1] [準備 2] は接続先のコンピュータ側の設定手順です.Windows 7 での設定例です.
まず,ファイアウォールのリモート管理関係の設定です.
[ファイル名を指定して実行] のダイアログを表示して,gpedit.msc を入力して [ローカルグループポリシーエディタ] を起動します.


図1
ファイル名を指定して実行
  • [Windows] + [R] でも表示できる
  • [プログラムとファイルの検索] を使用してもよい
  • gpedit.msc を入力して [OK]

[コンピュータの接続] [管理用テンプレート] [ネットワーク] [ネットワーク接続] [Windows ファイアウォール] [標準プロファイル] の順にツリーを展開していきます.
[標準プロファイル] を選択したら,右のペインの [着信リモート管理の例外を許可する] の項目をダブルクリックします.

  • 図2
  • [Windows 着信リモート管理の例外を許可する] をダブルクリック

現れたダイアログで,
  • [ Windows ファイアウォール: 着信リモート管理の例外を許可する ] を有効に
  • [ 要請されない着信メッセージを許可する IP アドレス: ] を localsubnet
にします.localsubnet は通信元をローカルネットワーク内に制限します.
テストなので,この値にしていますが,IP アドレスを指定すると,特定のコンピュータ以外からは操作できないようにできます.


  • 図3
  • [有効] のラジオボタンをチェックし,[IP アドレス] に localsubnet を入力




03_準備 2  -  管理共有によるアクセスを有効にする


次に管理共有によるアクセスを有効にします.
[ファイル名を指定して実行] のダイアログを表示して,regedit (または regedit.exe) を入力してレジストリエディタを起動します.


図4
ファイル名を指定して実行
  • [Windows] + [R] でも表示できる
  • [プログラムとファイルの検索] を使用してもよい
  • regedit を入力して [OK]

レジストリのツリーを以下の順番で展開します.
  • HKEY_LOCAL_MACHINE
  • SOFTWARE
  • Microsoft
  • Windows
  • CurrentVersion
  • Policies
  • System
[System] を選択したら,メニューの [編集] [新規] で [DWORD (32 ビット) 値] または下図のように,何もないところでマウスの右ボタンクリックでポップアップメニューを表示します.このポップアップメニューで [DWORD (32 ビット) 値] を選択します.
そして,項目の名前を LocalAccountTokenFilterPolicy とします.

[備考]
UIPI の設定,こんなところにあるんですね.

  • 図5
  • 以下の [System] キーに DWORD の項目 LocalAccountTokenFilterPolicy を作成

作成した項目をダブルクリックします.
現れたダイアログの [値のデータ] に 1 を入力して,[OK] ボタンでダイアログを閉じます.
レジストリエディタを閉じれば,接続先のコンピュータ側の設定は終了です.
コンピュータの再起動は必要ないようです.


  • 図6
  • 作成した DWORD 項目 LocalAccountTokenFilterPolicy をダブルクリック
  • [値のデータ] を 1 にする

上のレジストリの操作は,以下のコマンドを,コマンドプロンプトで実行することでも可能です.レジストリの操作ですから,[管理者として実行] で実行します.
レジストリに LocalAccountTokenFilterPolicy 項目を作成して,値を 1 にするコマンドです.
HKLM は HKEY_LOCAL_MACHINE の意味です.


reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f

作成した項目を削除するコマンドです.

reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /f


レジストリをコマンドプロンプトから操作する reg コマンドのパラメータは,コマンドプロンプトで reg /? を実行すると確認できます.各パラメータで使用可能なスイッチは,例えば,add パラメータの場合は reg add /? で確認できます.




04_ネットワークコンピュータの論理ディスク一覧


[準備 1] [準備 2] の作業でネットワーク上のコンピュータに対する操作の準備ができました.
下のコードは,[898_WMI イントロダクション] でテストした論理ディスクのプロパティの一部を取得するサンプルとほとんど同じです.違うのは,接続先のコンピュータ名,ユーザ名,パスワードを設定していることです.


図7
実行結果例
  • 特定のプロパティのみ取得
  • ここではドライブ名,容量,空き容量

リスト1
ネットワーク上のコンピュータの,論理ディスクの特定のプロパティだけを取得する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, WbemScripting_TLB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    ComputerName : String;
    UserName     : String;
    PassWord     : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //これらの文字列を空文字にすればローカルコンピュータが対象となる
  ComputerName := 'VM-WIN7-32';
  UserName     := 'XXXXX';
  PassWord     := 'XXXXX';
end;

//=============================================================================
//  ネットワーク上のコンピュータの論理ディスクのプロパティの一部を取得して表示
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  objWMILocator  : ISWbemLocator;
  objWMIService  : ISWbemServices;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

  //Wbemのオブジェクトを生成
  objWMILocator := CoSWbemLocator.Create;

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     ComputerName,
                     'root\cimv2',
                     UserName,
                     Password,
                     '',
                     '',
                     0,
                     nil);

  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  objPropSet := objWMIService.ExecQuery(
                     'SELECT * FROM Win32_LogicalDisk',
                     'WQL',
                     wbemFlagReturnImmediately,
                     nil);

  //プロパティのセットのコレクションを取得
  objPropSetEnum := objPropSet._NewEnum as IEnumVariant;

  //-------------------------------------------------------------------------
  //  以下がプロパティの取得コード
  //-------------------------------------------------------------------------

    //順番に取り出す
  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin
    Memo1.Lines.Add('DeviceID            ' + VarToStrDef(objTemp.DeviceID, ''));
    Memo1.Lines.Add('Size                ' + VarToStrDef(objTemp.Size, ''));
    Memo1.Lines.Add('FreeSapce           ' + VarToStrDef(objTemp.FreeSpace, ''));
    Memo1.Lines.Add('---------------------------------------');
  end;

  Memo1.Lines.EndUpdate;
end;

end.


接続時のエラーは,except で検出できます.VBS (VB Script) で WMI を使用する場合は,Err オブジェクトでいろいろな情報を調べることができるようです.
ネットワーク上に対象のコンピュータが存在するか確認してから接続した方がいいかも知れません.


図8
リモートコンピュータに接続できない場合
  • コンピュータ名が間違っている場合やリモートコンピュータが起動していな場合等
  • 検出には時間がかかる
図9
ユーザ名かパスワードの間違いの場合
  • リモートコンピュータ側で [準備 1] [準備 2] の設定をしていない場合もこのエラーとなる

リスト2
接続時のエラー検出の例
  try
    objWMIService := objWMILocator.ConnectServer(
                       ComputerName,
                       'root\cimv2',
                       UserName,
                       Password,
                       '',
                       '',
                       0,
                       nil);
  except
    on E:Exception do begin
      if (E is EOleException) or (E is EOleSysError) then begin
        Memo1.Lines.Add(IntToHex((E as EOleException).ErrorCode, 8)
                        + ' (' + IntToStr((E as EOleException).ErrorCode) + ')');
        Memo1.Lines.Add(E.Message);
        Memo1.Lines.EndUpdate;
        exit;
      end;
    end;
  end;




05_ネットワークコンピュータの OS の起動日時と経過日時


次のコードも,[898_WMI イントロダクション] でテストした OS の起動時刻を取得するサンプルとほとんど同じです.
違うのは,コンピュータ名を表示するようにしたことと,接続先のコンピュータ名,ユーザ名,パスワードを設定していることです.


図10
実行結果例
  • OS の起動日時と経過日時
  • ミリ秒単位まで計算して表示

リスト3
ネットワーク上のコンピュータの起動日時と経過日数と時間
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, WbemScripting_TLB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    ComputerName : String;
    UserName     : String;
    PassWord     : String;
    function WbemToTDateTime(V : OleVariant): TDateTime;
    function DateTimeString(V : OleVariant): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //これらの文字列を空文字にすればローカルコンピュータが対象となる
  ComputerName := 'VM-WIN7-32';
  UserName     := 'XXXXX';
  PassWord     := 'XXXXX';
end;

//=============================================================================
//  ネットワークコンピュータのOSの起動日時と経過時間をミリ秒まで取得
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  objWMILocator  : ISWbemLocator;
  objWMIService  : ISWbemServices;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiPropSet     : ISWbemPropertySet;
  wmiProperty    : ISWbemProperty;
  fmtStrStr      : String;
  fmtStrDateTime : String;
  ADateTime      : TDateTime;
  TimeDay        : Integer;
  TimeMili       : Integer;
  IntTemp        : Integer;
  Hour           : Integer;
  Minute         : Integer;
  Second         : Integer;
  MiliSecond     : Integer;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

  //Wbemのオブジェクトを生成
  objWMILocator := CoSWbemLocator.Create;

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     ComputerName,
                     'root\cimv2',
                     UserName,
                     Password,
                     '',
                     '',
                     0,
                     nil);

  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  objPropSet := objWMIService.ExecQuery(
                     'Select * From Win32_OperatingSystem',
                     'WQL',
                     wbemFlagReturnImmediately,
                     nil);

  //プロパティのセットのコレクションを取得
  objPropSetEnum := objPropSet._NewEnum as IEnumVariant;

  //-------------------------------------------------------------------------
  //  以下がプロパティの取得コード
  //-------------------------------------------------------------------------

  //順番に取り出す
  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin

    //プロパティのセットを取得
    wmiPropSet := (IUnknown(objTemp) as ISWBemObject).Properties_;

    //CSName(コンピュータ名)プロパティを取得
    wmiProperty := wmiPropSet.Item('CSName', 0);
    fmtStrStr   := Format('%-17s', [wmiProperty.Name]);
    Memo1.Lines.Add(fmtStrStr + VarToStrDef(wmiProperty.Get_Value, ''));

    //Caption(OS名)プロパティを取得
    wmiProperty := wmiPropSet.Item('Caption', 0);
    fmtStrStr   := Format('%-17s', [wmiProperty.Name]);
    Memo1.Lines.Add(fmtStrStr + VarToStrDef(wmiProperty.Get_Value, ''));


    //最後に起動した日時(LastBootUpTime)を取得
    wmiProperty    := wmiPropSet.Item('LastBootUpTime', 0);
    fmtStrStr      := Format('%-17s', [wmiProperty.Name]);
    fmtStrDateTime := DateTimeString(wmiProperty.Get_Value);
    Memo1.Lines.Add(fmtStrStr + fmtStrDateTime);


    //最後に起動した日時をTDateTime型の値に変換
    ADateTime := WbemToTDateTime(wmiProperty.Get_Value);

    //その日時と現在の日時をTTimeStamp型に変換して,日とミリ秒の差を計算
    TimeDay  := DateTimeToTimeStamp(Now).Date - DateTimeToTimeStamp(ADateTime).Date;
    TimeMili := DateTimeToTimeStamp(Now).Time - DateTimeToTimeStamp(ADateTime).Time;
    if TimeMili < 0 then begin
      TimeDay  := TimeDay - 1;
      TimeMili := TimeMili + 3600 * 24 * 1000;
    end;
    //時,分,秒,ミリセコンドを計算
    Hour       := TimeMili div (3600 * 1000);
    IntTemp    := TimeMili mod (3600 * 1000);
    Minute     := IntTemp  div (60 * 1000);
    IntTemp    := IntTemp  mod (60 * 1000);
    Second     := IntTemp  div 1000;
    MiliSecond := IntTemp  mod 1000;

    Memo1.Lines.Add('経過日時         '
                    + IntToStr(TimeDay)    + '日'
                    + IntToStr(Hour)       + '時間'
                    + IntToStr(Minute)     + '分'
                    + IntToStr(Second)     + '秒'
                    + IntToStr(MiliSecond) + 'ミリ秒');
  end;

  Memo1.Lines.EndUpdate;
end;

//=============================================================================
//  CIM_DATETIMEの日付時刻型の日時を書式付き文字列に変換
//=============================================================================
function TForm1.DateTimeString(V: OleVariant): String;
var
  WMIDateTime : ISWbemDateTime;
begin
  Result := '';
  if VarIsNull(V) then exit;

  WMIDateTime := CoSWbemDateTime.Create;
  WMIDateTime.Value := V;
  Result := Format('%.4d/%.2d/%.2d %.2d:%.2d:%.2d.%.3d',
                   [WMIDateTime.Year,
                    WMIDateTime.Month,
                    WMIDateTime.Day,
                    WMIDateTime.Hours,
                    WMIDateTime.Minutes,
                    WMIDateTime.Seconds,
                    WMIDateTime.MicroSeconds * 1000]);
end;

//=============================================================================
//  CIM_DATETIMEの日付時刻型をDelphiのTDateTime型に変換する関数
//  OleVariantで返して,呼び出し側でTDateTime型に変換した方がいいかも知れない
//=============================================================================
function TForm1.WbemToTDateTime(V: OleVariant): TDateTime;
var
  WMIDateTime : ISWbemDateTime;
begin
  Result := 0;
  if VarIsNull(V) then exit;

  WMIDateTime := CoSWbemDateTime.Create;
  WMIDateTime.Value := V;
  Result := WMIDateTime.GetVarDate(True);
end;

end.




06_ネットワークコンピュータのシャットダウン


上のサンプルで示したように,ネットワークコンピュータに対する操作は,コンピュータ名,ユーザ名等を指定する以外,ローカルなコンピュータに対する操作コードとほとんど変わりません.

次は WMI のメソッドの使用例です.内容は,ネットワークの指定コンピュータをシャットダウンするサンプルです.
Win32_OperatingSystem の Shutdown メソッドを使用しています.メソッドは ExecMethod_ で実行します.シャットダウンの特権を付与していますが,リモートコンピュータはこの特権がなくても動作するようです.


リスト4
ネットワーク上のコンピュータをシャットダウン
Win32_OperatingSystem の Shutdown メソッド (引数がないメソッド)を使用
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, WbemScripting_TLB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    ComputerName : String;
    UserName     : String;
    PassWord     : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //これらの文字列を空文字にすればローカルコンピュータが対象となる
  ComputerName := 'VM-WIN7-32';
  UserName     := 'XXXXX';
  PassWord     := 'XXXXX';
end;

//=============================================================================
//  ネットワークコンピュータをシャットダウン
//  Win32_OperatingSystemのShutdownメソッドを使用する例
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  objWMILocator  : ISWbemLocator;
  objWMISecurity : ISWbemSecurity;
  objWMIService  : ISWbemServices;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiObject      : ISWbemObject;
begin
  //Wbemのオブジェクトを生成
  objWMILocator := CoSWbemLocator.Create;

  //シャットダウン特権を付与
  //ローカルコンピュータの場合に必要
  objWMISecurity := objWMILocator.Security_;
  objWMISecurity.Privileges.Add(wbemPrivilegeShutdown, True);

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     ComputerName,
                     'root\cimv2',
                     UserName,
                     Password,
                     '',
                     '',
                     0,
                     nil);


  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  objPropSet := objWMIService.ExecQuery(
                     'SELECT * FROM Win32_OperatingSystem',
                     'WQL',
                     wbemFlagReturnImmediately,
                     nil);

  //プロパティのセットのコレクションを取得
  objPropSetEnum := objPropSet._NewEnum as IEnumVariant;

  //-------------------------------------------------------------------------
  //  以下がメソッドの実行関係
  //-------------------------------------------------------------------------

  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin
    wmiObject := IUnknown(objTemp) as ISWBemObject;
    wmiObject.ExecMethod_('Shutdown', nil, 0, nil);
  end;
end;

end.




07_引数がないメソッドの使用方法


上のシャットダウンのサンプルでは,Win32_OperatingSystem クラスの Shutdown メソッドを使用しています.このメソッドには引数はありません.引数のないメソッドの使用例となっています.

Delphi の VCL のメソッドとは違い,WMI では上のコードのように,プロパティ取得の際に取得したオブジェクト (ISWbemObject) の ExecMethod_ メソッドの引数にメソッド名を渡して実行する形式となります.
これは,クエリによって,そのクラスの複数のオブジェクトのインスタンスを取得する場合があるからです.例えば,論理ディスク,ディレクトリ,ファイル等は,通常複数あります.
今回の例は,コンピュータシステムですから,オブジェクトのインスタンスは 1 つだけです.したがって,実際には while ループの部分は 1 回しか処理されません.

ただし,どんなメソッドでもこの方法でメソッドを実行するわけではありません.Win32_Process クラスに Create というプロセスを生成,起動するためのメソッドがありますが,これは,Win32_Process クラスのオブジェクトを取得し,それに対して Create メソッドを実行する方式になっています.

メソッドは,以下のように,取得したプロパティセットのオブジェクトである objTemp に対しても実行できます.

リスト5
プロパティセットのオブジェクトに ExecMethod_ を実行する例
  //-------------------------------------------------------------------------
  //  以下がメソッドの実行関係
  //  OleVariant型を使用する場合の例
  //-------------------------------------------------------------------------

  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin
    objTemp.ExecMethod_('Shutdown');
  end;


また,次のように,メソッドを直接実行することもできます.このコードは,VCL のメソッドと同じ様な使い方で,ネット上で見かける VB や VBS のコードとほとんど同じとなっています.


リスト6
プロパティセットのオブジェクトに対して,直接メソッドを実行する例
  //-------------------------------------------------------------------------
  //  以下がメソッドの実行関係
  //  OleVariant型を使用する場合の例
  //-------------------------------------------------------------------------

  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin
    objTemp.Shutdown;
  end;




08_ネットワークコンピュータのシャットダウンと再起動等


同じくネットワーク上のコンピュータをシャットダウンするサンプルですが,再起動,ログオフもできるようにしてみました.
 Win32_OperatingSystem の Win32Shutdown メソッドを使用しています.Win32Shutdown メソッドにはパラメータ (引数) があり,シャットダウン,再起動,ログオフ等の操作を指定できます.





08-01_引数付きメソッドの使用方法


Delphi の VCL のメソッドとは違い,WMI では,最初のプロパティ取得の際に取得したオブジェクト (ISWbemObject) の ExecMethod_ メソッドの引数にメソッド名を渡して実行します.
ExecMethod_ の第 2 引数が,メソッドの引数のオブジェクトです.引数のオブジェクトも,メソッド名から取得します.この引数のオブジェクトのプロパティのセットとして,各引数の値をセットしていきます.この時,引数名と型の情報が必要です.これらは,MSDN の記事等で調べることになります.

引数の型を指定しない方法もあります.下のコードにコメントとして書いておきました.

ただし,どんなメソッドでもこの方法で引数を設定するわけではありません.Win32_Process クラスに Create というプロセスを生成,起動するためのメソッドがありますが,Win32_ProcessStartup というオブジェクトを使用します.


図11
シャットダウン
図12
再起動
図13
ログオフ

リスト7
ネットワーク上のコンピュータをシャットダウン・再起動・ログオフ
Win32_OperatingSystem の Win32Shutdown メソッドを使用
引数付きのメソッドの使用例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, WbemScripting_TLB;

type
  TForm1 = class(TForm)
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
  private
    { Private 宣言 }
    ComputerName : String;
    UserName     : String;
    PassWord     : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //これらの文字列を空文字にすればローカルコンピュータが対象となる
  ComputerName := 'VM-WIN7-32';
  UserName     := 'XXXXX';
  PassWord     := 'XXXXX';
end;

//-----------------------------------------------------------------------------
//  ネットワークコンピュータのシャットダウン等を実行する関数
//  Win32_OperatingSystemのWin32Shutdownメソッドを使用する例
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//-----------------------------------------------------------------------------
function WMIExecute_Win32Shutdown(PCName, UserName, Password: String;
  KindValue: Integer): Boolean;
var
  objWMILocator  : ISWbemLocator;
  objWMISecurity : ISWbemSecurity;
  objWMIService  : ISWbemServices;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiObject      : ISWbemObject;
  wmiParameters  : ISWbemObject;
  wmiShutdown    : ISWbemObject;
  wmiProperty    : ISWbemProperty;
  VariantTemp    : OleVariant;
  A : ISWbemPropertySet
begin
  Result := False;

  //Wbemのオブジェクトを生成
  objWMILocator := CoSWbemLocator.Create;

  //シャットダウン特権を付与
  //ローカルコンピュータの場合に必要
  objWMISecurity := objWMILocator.Security_;
  objWMISecurity.Privileges.Add(wbemPrivilegeShutdown, True);

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     PCName,
                     'root\cimv2',
                     UserName,
                     Password,
                     '',
                     '',
                     0,
                     nil);

  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  objPropSet := objWMIService.ExecQuery(
                     'SELECT * FROM Win32_OperatingSystem',
                     'WQL',
                     wbemFlagReturnImmediately,
                     nil);


  //プロパティのセットのコレクションを取得
  objPropSetEnum := objPropSet._NewEnum as IEnumVariant;

  //-------------------------------------------------------------------------
  //  以下がメソッドとメソッドの引数の設定関係
  //  引数の名前と型はMSDNオンラインの該当メソッドの記事等で確認する
  //-------------------------------------------------------------------------

  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin
    //最初のオブジェクトをISWbemObjectとして取得
    WmiObject := IUnknown(objTemp) as ISWbemObject;

    //メソッド名からパラメータ(引数)のオブジェクトのインスタンスを取得
    //SpawnInstance_で取得する
    wmiParameters := wmiObject.Methods_.Item('Win32Shutdown', 0).InParameters;
    wmiShutdown   := wmiParameters.SpawnInstance_(0);

    //引数Flagsをセット
    VariantTemp := KindValue;
    //wmiProperty := wmiShutdown.Properties_.Item('Flags', 0);でも可
    wmiProperty := wmiShutdown.Properties_.Add('Flags', wbemCimtypeSint32, False, 0);
    wmiProperty.Set_Value(VariantTemp);

    //引数Reservedをセット
    VariantTemp := 0;
    //wmiProperty := wmiShutdown.Properties_.Item('Reserved', 0);でも可
    wmiProperty := wmiShutdown.Properties_.Add('Reserved', wbemCimtypeSint32, False, 0);
    wmiProperty.Set_Value(VariantTemp);

    try
      //メソッドの実行
      wmiObject.ExecMethod_('Win32Shutdown', wmiShutdown, 0, nil);
      Result:= True;
    except
      Break;
    end;
  end;
end;

//=============================================================================
//  シャットダウン
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
begin
  WMIExecute_Win32Shutdown(ComputerName, UserName, PassWord, 12);
end;

//=============================================================================
//  再起動
//=============================================================================
procedure TForm1.Button5Click(Sender: TObject);
begin
  WMIExecute_Win32Shutdown(ComputerName, UserName, PassWord, 6);
end;

//=============================================================================
//  ログオフ
//=============================================================================
procedure TForm1.Button6Click(Sender: TObject);
begin
  WMIExecute_Win32Shutdown(ComputerName, UserName, PassWord, 4);
end;

end.




08-02_引数を OleVariant 型で処理する場合


引数は,OleVarinat 型だけでも設定できます.それが下のコードです.上のコードと違うのは,引数の設定部分だけです.
このコードでは,wmiParameters, wmiShutdown 等,下のコード内の変数は全て OleVariant 型です.

リスト8
引数の設定を OleVariant 型で処理する例
  //-------------------------------------------------------------------------
  //  以下がメソッドとメソッドの引数の設定関係
  //  引数の名前と型はMSDNオンラインの該当メソッドの記事等で確認する
  //  OleVariant型で処理する場合
  //-------------------------------------------------------------------------

  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin

    //メソッド名からパラメータのオブジェクトのインスタンスを取得
    //SpawnInstance_で取得する
    wmiParameters := objTemp.Methods_.Item('Win32Shutdown', 0).InParameters;
    wmiShutdown   := wmiParameters.SpawnInstance_;

    //引数の値を設定
    //以下のようなコードでも同じ
    //wmiShutdown.Properties_.Item('Flags') := KindValue;
    //wmiShutdown.Properties_('Flags') := KindValue;
    wmiShutdown.Flags    := KindValue;
    wmiShutdown.Reserved := 0;

    try
      //メソッドの実行
      objTemp.ExecMethod_('Win32Shutdown', wmiShutdown);
      Result:= True;
    except
      Break;
    end;
  end;




08-03_引数もメソッドも OleVariant 型で処理する場合


Shutdown メソッドのサンプルと同じように,メソッドは,全て OleVariant 型で処理するのであれば,プロパティセットのメソッドとして実行できます.この場合は,引数は順番に,OleVariant 型の変数,あるいは値で指定します.
下のコードでは,Win32Shutdown メソッドの引数名である Flags, Reserved も OleVariant 型の変数としています.これですと,VCL のメソッドの使用方法とほとんど同じで,VB や VBS 等のサンプルからの移植も容易になるかも知れません.


リスト9
引数も OleVariant 型の変数で処理する場合
  //-------------------------------------------------------------------------
  //  以下がメソッドとメソッドの引数の設定関係
  //  引数の名前と型はMSDNオンラインの該当メソッドの記事等で確認する
  //  OleVariant型で処理する場合
  //-------------------------------------------------------------------------

  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin

    //引数の値を設定
    Flags    := KindValue;
    Reserved := 0;

    try
      //メソッドの実行
      //VCLのメソッドと同じような使用方法
      objTemp.Win32Shutdown(Flags, Reserved);
      Result:= True;
    except
      Break;
    end;
  end;




08-04_セキュリティレベルの設定方法


シャットダウンサンプルでは,シャットダウンの特権を付与しました.操作内容によっては,これ以外にもセキュリティ関係の設定が必要な場合があるかも知れません.以下のリンクを参考にしてください.コードはそれらのセキュリティ関係の設定の参考用です.


  //シャットダウン特権を付与
  //ローカルコンピュータの場合に必要
  objWMISecurity := objWMILocator.Security_;
  objWMISecurity.Privileges.Add(wbemPrivilegeShutdown, True); 

  //呼び出し元の資格情報を使用(推奨偽装レベル)
  objWMILocator.Security_.Set_ImpersonationLevel(wbemImpersonationLevelImpersonate);
  //Windowsのデフォルトの認証
  objWMILocator.Security_.Set_AuthenticationLevel(wbemAuthenticationLevelDefault);


コード中の objWMILocator を OleVariant 型とした場合は,設定メソッドではなく,次のようにプロパティの代入で設定します.


  //シャットダウン特権を付与
  //ローカルコンピュータの場合に必要
  objWMISecurity := objWMILocator.Security_;
  objWMISecurity.Privileges.Add(wbemPrivilegeShutdown, True);

  //呼び出し元の資格情報を使用(推奨偽装レベル)
  objWMILocator.Security_.ImpersonationLevel := wbemImpersonationLevelImpersonate;
  //Windowsのデフォルトの認証
  objWMILocator.Security_.AuthenticationLevel := wbemAuthenticationLevelDefault;




09_IP アドレスの取得と設定


WMI のプロパティには配列型のものがあります.WMI のメソッドの引数にも配列型で指定するものがあります.ここでは,IP アドレスの設定,変更のサンプルでそのテストをしています.

WMI のプロパティはほとんどが読出し専用となっています.プロパティの値を設定するには,専用のメソッドが用意されていることがありますが,メソッドの引数名は必ずしも該当するプロパティ名と一致するとは限りません.MDSN オンラインの記事等で確認して使用することになります.
例えば,このサンプルでは,IP アドレスを設定しますが,サブネットのプロパティ名は IPSubnet ですが,メソッドでの引数名は SubnetMask です.

IP アドレスは,ネットワークアダプタの情報用の Win32_NetworkAdapterConfiguration クラスで取得できます.IP アドレスの設定は,このクラスの EnableStatic メソッドを使用します.






09-01_ネットワークアダプタの情報を取得


IP アドレスの設定の前に,IP アドレスを確認しておくことにします.
下図は,対象のリモートコンピュータで [ファイル名を指定して実行] で control ncpa.cpl を実行し,[ローカルエリア接続] のプロパティの [インターネットプロトコルバージョン 4] のプロパティダイアログを表示したものです.

DHCP を使用しているので,[IP アドレスを自動的に取得する] になっていて,IP アドレスの欄は空白になっています.



図14
設定前の内容

下のコードを実行して,対象のリモートコンピュータの IP アドレスを確認したのが下図です.
本当は IP アドレスだけを確認すればいいのですが,このコードでは,ネットワークアダプタのプロパティを全て取得して表示しています.
クエリーの where 句では,対象となるコンピュータ名を指定して,そのコンピュータの情報だけを取得するようにしています.この後の IP アドレスの設定サンプルでもそうしています.


図15
IP アドレスの値
  • コンピュータ名は VM-Win7-32
  • IP アドレスは 192.168.0.30
    これを変更する
  • IP サブネットは 255.255.255.0

リスト10
ネットワークアダプタの情報を取得
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, WbemScripting_TLB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    ComputerName : String;
    UserName     : String;
    PassWord     : String;
    function VarToString(const OleVar: OleVariant): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //これらの文字列を空文字にすればローカルコンピュータが対象となる
  ComputerName := 'VM-WIN7-32';
  UserName     := 'XXXXX';
  PassWord     := 'XXXXX';
end;

//=============================================================================
//  ネットワークアダプタの情報の取得
//  ここでは,クエリーのwhere句で特定のコンピュータだけを指定している
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  コンピュータ名を取得する関数
  //---------------------------------------------------------------------------
  function GetSelfComputerName: String;
  var
    Buff  : array [0..MAX_COMPUTERNAME_LENGTH-1] of Char;
    ASize : Cardinal;
  begin
    ASize := SizeOf(buff);
    if Windows.GetComputerName(Buff, ASize) then begin
      Result := String(Buff);
    end else begin
      Result := '';
    end;
  end;
  //---------------------------------------------------------------------------

var
  objWMILocator  : ISWbemLocator;
  objWMIService  : ISWbemServices;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiPropSetEnum : IEnumVariant;
  wmiProperty    : ISWbemProperty;
  fmtStrStr      : String;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

  //Wbemのオブジェクトを生成
  objWMILocator := CoSWbemLocator.Create;

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     ComputerName,
                     'root\cimv2',
                     UserName,
                     Password,
                     '',
                     '',
                     0,
                     nil);

  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  if Trim(ComputerName) = '' then ComputerName := GetSelfComputerName;
  ShowMessage(ComputerName);
  objPropSet := objWMIService.ExecQuery(
                     'SELECT * FROM Win32_NetworkAdapterConfiguration '
                     + 'where DNSHostName = "' + ComputerName + '"',
                     'WQL',
                     wbemFlagReturnImmediately,
                     nil);

  //プロパティのセットのコレクションを取得
  objPropSetEnum := objPropSet._NewEnum as IEnumVariant;

  //-------------------------------------------------------------------------
  //  以下がプロパティの取得コード
  //-------------------------------------------------------------------------

  //順番に取り出す
  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin

    //各オブジェクトのプロパティの列挙を取得
    wmiPropSetEnum := IUnknown(objTemp.Properties_._NewEnum) as IEnumVariant;

    while wmiPropSetEnum.Next(1, objTemp, iValue) = 0 do begin
      //プロパティを取得
      wmiProperty := IUnknown(objTemp) as ISWBemProperty;

      //プロパティの名前と実際の値を文字列にして表示
      fmtStrStr := Format('%-30s', [wmiProperty.Name]);
      Memo1.Lines.Add(fmtStrStr + VarToString(wmiProperty.Get_Value));
    end;
    Memo1.Lines.Add('---------------------------------------');
  end;

  Memo1.Lines.EndUpdate;
end;

//=============================================================================
//  OleVariantの値をString型に変換(再帰処理)
//  複数の値を持つプロパティがある.そこでこれを[]で括った結果を返すことにする
//  そのためVarArrayも扱えるようにしたVarToStr関数
//=============================================================================
function TForm1.VarToString(const OleVar: OleVariant): String;
var
  LowBound  : Integer;
  HighBound : Integer;
  i         : Integer;
begin
  Result := '';

  //配列の場合
  if VarIsArray(OleVar) then begin
    LowBound  := VarArrayLowBound(OleVar, 1);
    HighBound := VarArrayHighBound(OleVar, 1);

    for i := LowBound to HighBound do begin
      if i = HighBound then begin
        Result := Result + VarToString(OleVar[i]);
      end else begin
        Result := Result + VarToString(OleVar[i]) + ',';
      end;
    end;

    Result:='[' + Result + ']';

  //単一の値を持つプロパティの場合
  end else begin
    Result := VarToStr(OleVar);
  end;
  if Result = '' then  Result := '';
end;

end.




09-02_IP アドレスの設定と配列型の引数の設定方法


IP アドレスの設定には,Win32_NetworkAdapterConfiguration クラスの EnableStatic メソッドを使用します.このメソッドの引数は 2 つあり,どちらも配列型となっています.

そこで,このサンプルでは,配列要素を引数にして,バリアント型の配列を返す自前の関数を作成して使用しています.このサンプルで扱うのは文字列型だけですが,DWORD (32 ビット整数値) 用も作成しておきました.
メソッドの引数の設定方法,メソッドの実行方法は,シャットダウンの時と同じです.
下図は実行結果です.メソッドの実行結果の値が ReturnValaue = 0 となっていて,正常に終了したことが分かります.



図16
実行後の関数の戻り値

リモートコンピュータのプロパティを確認してみると,コード中で指定した値になっています.



[注意]
リモートコンピュータ側で,この下図のダイアログを表示したままの状態では IP アドレスの変更はできません.ダイアログを全て閉じた状態で設定します.



図17
変更の確認

リスト11
IP アドレスの設定変更
Win32_NetworkAdapterConfiguration クラスの EnableStatic メソッドを使用
配列型の引数を持つメソッドの使用例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, WbemScripting_TLB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    ComputerName : String;
    UserName     : String;
    PassWord     : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //これらの文字列を空文字にすればローカルコンピュータが対象となる
  ComputerName := 'VM-WIN7-32';
  UserName     := 'XXXXX';
  PassWord     := 'XXXXX';
end;

//=============================================================================
//  ネットワーク上のコンピュータのIPアドレスを設定
//  ここでは,クエリーのwhere句で特定のコンピュータだけを指定している
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  コンピュータ名を取得する関数
  //---------------------------------------------------------------------------
  function GetSelfComputerName: String;
  var
    Buff  : array [0..MAX_COMPUTERNAME_LENGTH-1] of Char;
    ASize : Cardinal;
  begin
    ASize := SizeOf(buff);
    if Windows.GetComputerName(Buff, ASize) then begin
      Result := String(Buff);
    end else begin
      Result := '';
    end;
  end;

  //---------------------------------------------------------------------------
  //  文字列のバリアント型の配列を作成
  //---------------------------------------------------------------------------
  function ArrayToVarArray(Arr : array of String): OleVariant; overload;
  var
    i : integer;
  begin
    Result := VarArrayCreate([0, High(Arr)], varVariant);
    for i := Low(Arr) to High(Arr) do begin
      Result[i] := Arr[i];
    end;
  end;

  //---------------------------------------------------------------------------
  //  WORD値(32ビット値)のバリアント型の配列を作成
  //  本サンプルでは使用していない.参考用のコード
  //---------------------------------------------------------------------------
  function ArrayToVarArray(Arr : array of WORD): OleVariant; overload;
  var
    i : integer;
  begin
    Result := VarArrayCreate([0, High(Arr)], varVariant);
    for i := Low(Arr) to High(Arr) do begin
      Result[i] := Arr[i];
    end;
  end;
  //---------------------------------------------------------------------------

var
  objWMILocator  : ISWbemLocator;
  objWMIService  : ISWbemServices;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiObject      : ISWbemObject;
  wmiParameters  : ISWbemObject;
  wmiIPAddress   : ISWbemObject;
  wmiProperty    : ISWbemProperty;
  VariantTemp    : OleVariant;
  RetValue       : ISWbemObject;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

  //Wbemのオブジェクトを生成
  objWMILocator := CoSWbemLocator.Create;

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     ComputerName,
                     'root\cimv2',
                     UserName,
                     Password,
                     '',
                     '',
                     0,
                     nil);

  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  if Trim(ComputerName) = '' then ComputerName := GetSelfComputerName;
  objPropSet := objWMIService.ExecQuery(
                     'SELECT * FROM Win32_NetworkAdapterConfiguration '
                     + 'where DNSHostName = "' + ComputerName + '"',
                     'WQL',
                     wbemFlagReturnImmediately,
                     nil);

  //プロパティのセットのコレクションを取得
  objPropSetEnum := objPropSet._NewEnum as IEnumVariant;

  //-------------------------------------------------------------------------
  //  以下がIPアドレスの設定コード
  //  配列型の引数を持つメソッドの実行例
  //  引数の名前と型はMSDNオンラインの該当メソッドの記事等で確認する
  //-------------------------------------------------------------------------

  //順番に取り出す
  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin
    //最初のオブジェクトをISWbemObjectとして取得
    wmiObject := IUnknown(objTemp) as ISWbemObject;

    //メソッド名からパラメータ(引数)のオブジェクトのインスタンスを取得
    //SpawnInstance_で取得する
    wmiParameters := wmiObject.Methods_.Item('EnableStatic', 0).InParameters;
    wmiIPAddress  := wmiParameters.SpawnInstance_(0);

    //引数IPAddressをセット
    VariantTemp := ArrayToVarArray(['192.168.0.50']);
    wmiProperty := wmiIPAddress.Properties_.Item('IPAddress', 0);
    wmiProperty.Set_Value(VariantTemp);

    //引数SubnetMaskをセット
    VariantTemp := ArrayToVarArray(['255.255.255.0']);
    wmiProperty := wmiIPAddress.Properties_.Item('SubnetMask', 0);
    wmiProperty.Set_Value(VariantTemp);

    try
      //メソッドの実行
      RetValue := wmiObject.ExecMethod_('EnableStatic', wmiIPAddress, 0, nil);
    except
    end;

    VarClear(VariantTemp);
    //メソッドの実行結果を表示
    Memo1.Lines.Add(RetValue.GetObjectText_(0));
  end;

  Memo1.Lines.EndUpdate;
end;

end.




09-03_引数を OleVariant 型で処理する場合


シャットダウンのサンプルと同じ様に,引数は,OleVarinat 型だけでも処理できます.それが下のコードです.上のコードと違うのは,引数の設定部分だけです.
このコードでは,wmiParameters, wmiIPAddress 等,コード内の変数は全て OleVariant 型です.


リスト12
引数の設定を OleVariant 型で処理する例
  //-------------------------------------------------------------------------
  //  以下がIPアドレスの設定コード
  //  配列型の引数を持つメソッドの実行例
  //  引数の名前と型はMSDNオンラインの該当メソッドの記事等で確認する
  //  OleVariant型で処理する場合
  //-------------------------------------------------------------------------

  //順番に取り出す
  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin

    //メソッド名からパラメータ(引数)のオブジェクトのインスタンスを取得
    //SpawnInstance_で取得する
    wmiParameters := objTemp.Methods_.Item('EnableStatic', 0).InParameters;
    wmiIPAddress  := wmiParameters.SpawnInstance_;

    //引数の値をセット
    wmiIPAddress.IPAddress  := VarArrayOf(['192.168.0.50']);
    wmiIPAddress.SubnetMask := VarArrayOf(['255.255.255.0']);

    try
      //メソッドの実行
      RetValue := objTemp.ExecMethod_('EnableStatic', wmiIPAddress);
    except
    end;

    VarClear(wmiIPAddress);
    //メソッドの実行結果を表示
    Memo1.Lines.Add(RetValue.GetObjectText_);
  end;




09-04_引数もメソッドも OleVariant 型で処理する場合


さらに,シャットダウンのサンプルでもやったように,全て OleVariant 型で処理するのであれば,メソッドもプロパティセットのメソッドとして実行できます.この場合は,引数は順番に,OleVariant 型の変数で指定します.
下のコードでは,メソッドの引数名である IPAddress, SubnetMask も OleVariant 型の変数としています.これですと,VCL のメソッドの使用方法とほとんど同じで,VB や VBS 等のサンプルからの移植も容易になるかも知れません.

リスト13
引数の変数も OleVariant 型で処理する場合
  //-------------------------------------------------------------------------
  //  以下がIPアドレスの設定コード
  //  配列型の引数を持つメソッドの実行例
  //  引数の名前と型はMSDNオンラインの該当メソッドの記事等で確認する
  //  OleVariant型で処理する場合
  //-------------------------------------------------------------------------

  //順番に取り出す
  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin

    //引数の値をセット
    IPAddress  := VarArrayOf(['192.168.0.50']);
    SubnetMask := VarArrayOf(['255.255.255.0']);

    try
      //メソッドの実行
      //VCLのメソッドと同じような使用方法
      RetValue := objTemp.EnableStatic(IPAddress, SubnetMask);
    except
    end;

    VarClear(IPAddress);
    VarClear(SubnetMask);
    //メソッドの実行結果(戻り値の整数値)を表示
    Memo1.Lines.Add(VarToStr(RetValue));
  end;




09-05_DHCP を有効にする


これでテストは終了です.Win32_NetworkAdapterConfiguration クラスの EnableDHCP メソッを使用して,DHCP を有効にして [IP アドレスを自動的に取得する] に戻しておくことにしました.このメソッドに引数はありません.
使用したのは下のコードです.


リスト14
DHCP を有効にする
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, WbemScripting_TLB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    ComputerName : String;
    UserName     : String;
    PassWord     : String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  //これらの文字列を空文字にすればローカルコンピュータが対象となる
  ComputerName := 'VM-WIN7-32';
  UserName     := 'XXXXX';
  PassWord     := 'XXXXX';
end;

//=============================================================================
//  ネットワーク上のDHCPを有効(IPアドレスを自動的に取得する)にする
//  ここでは,クエリーのwhere句で特定のコンピュータだけを指定している
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);

  //---------------------------------------------------------------------------
  //  コンピュータ名を取得する関数
  //---------------------------------------------------------------------------
  function GetSelfComputerName: String;
  var
    Buff  : array [0..MAX_COMPUTERNAME_LENGTH-1] of Char;
    ASize : Cardinal;
  begin
    ASize := SizeOf(buff);
    if Windows.GetComputerName(Buff, ASize) then begin
      Result := String(Buff);
    end else begin
      Result := '';
    end;

  end;
  //---------------------------------------------------------------------------

var
  objWMILocator  : ISWbemLocator;
  objWMIService  : ISWbemServices;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiObject      : ISWbemObject;
  RetValue       : ISWbemObject;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

  //Wbemのオブジェクトを生成
  objWMILocator := CoSWbemLocator.Create;

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     ComputerName,
                     'root\cimv2',
                     UserName,
                     Password,
                     '',
                     '',
                     0,
                     nil);

  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  if Trim(ComputerName) = '' then ComputerName := GetSelfComputerName;
  objPropSet := objWMIService.ExecQuery(
                     'SELECT * FROM Win32_NetworkAdapterConfiguration '
                     + 'where DNSHostName = "' + ComputerName + '"',
                     'WQL',
                     wbemFlagReturnImmediately,
                     nil);

  //プロパティのセットのコレクションを取得
  objPropSetEnum := objPropSet._NewEnum as IEnumVariant;

  //-------------------------------------------------------------------------
  //  以下がメソッドの実行コード
  //-------------------------------------------------------------------------

  //順番に取り出す
  while objPropSetEnum.Next(1, objTemp, iValue) = 0 do begin
    wmiObject := IUnknown(objTemp) as ISWbemObject;
    try
      RetValue := wmiObject.ExecMethod_('EnableDHCP', nil, 0, nil);
    except
    end;
    //メソッドの実行結果を表示
    Memo1.Lines.Add(RetValue.GetObjectText_(0));
  end;

  Memo1.Lines.EndUpdate;
end;

end.




10_リモートコンピュータが Windows XP ( SP2 以降 ) の場合


リモートコンピュータが Windows XP (SP2 以降) の場合は,
  • [01_準備 1 - ファイアウォールのリモート管理を有効にする] の設定をする
  • [フォルダオプション] の [簡易ファイルの共有を使用する (推奨)] のチェックを外す

という手順で設定します.[準備 2] の設定は必要ありません.
ただし,Windows XP の場合は,[準備 1] で表示されるダイアログのタイトルは [グループポリシー] となっています.また,設定項目は [Windows ファイアウォールリモート管理の例外を許可する] となっています.

[フォルダオプション] [簡易ファイルの共有を使用する (推奨)] の設定は DCOM に関する設定です.
COM はアプリケーション間通信の仕組みですが,DCOM は,その COM をリモートコンピュータ上で実行させる仕組みのことです.



図18
ファイル名を指定して実行
  • [Windows] + [R] でも表示できる
  • control folders を入力して [OK]
図19
フォルダオプションの設定
  • [表示] タブを表示
  • [簡易ファイルの共有を使用する (推奨)] のチェックを外す

上記の設定を行った後,上の [06_ネットワークコンピュータのシャットダウン・再起動・ログオフ] のサンプルを実行した結果です.
リモートコンピュータの OS は Windows XP(SP3) です.


図20
シャットダウン
図21
ログオフ
  • 制限ユーザでログインしているが,ログオフできる

[備考]
上のスナップショット (スクリーンキャプチャの画像) は,合成画像ではありません.
リモートコンピュータの画面を一度適当な範囲でキャプチャし,その画像は廃棄しました.そして,サンプルプログラムのフォームをリモートコンピュータの画面の上に表示しました.ボタンをクリックしたら,適当なタイミングで [前回の矩形範囲を指定してキャプチャ] した画像です.
つまり,実際に動作中の画面をキャプチャしたものです.




11_DCOM を無効にする


WMI は DCOM 上で動作します.その DCOM を リモートコンピュータで無効にする方法です.
ここまでに述べた設定をしなければ,無効と同じですが,設定したとしても,以下の手順で無効にすることができます.

DCOM を無効にしたコンピュータは,他のコンピュータからリモートアクセスされなくなります.つまり,DCOM を無効にしたコンピュータをリモートアクセスすることはできなくなります.
以下は Windows 7 での設定例ですが,多少の違いはあっても,ほぼ同じように設定できます.

[ファイル名を指定して実行] のダイアログを表示して dcomcnfg と入力して [コンポーネントサービス] 設定ダイアログを起動します.



図22
ファイル名を指定して実行
  • [Windows] + [R] でも表示できる
  • dcomcnfg を入力して [OK]

下図のように,[コンポーネントサービス] [コンピュータ] [マイコンピュータ] まで展開し,マウス右ボタンクリックでポップアップメニューを表示し,[プロパティ] を選択します.

  • 図23
  • [マイコンピュータ] のポップアップメニューの [プロパティ] を選択する

現れた [マイコンピュータのプロパティ] のダイアログで [既定のプロパティ] タブを表示します.
[このコンピュータ上で COM を無効にする] のチェックを外します.
これで設定は終了です.コンピュータの再起動は必要ないようです.


  • 図24
  • [既定のプロパティ] タブの [このコンピュータで分散 COM を有効にする] のチェックを外す

[備考]
ちなみに,この上の画像も,赤い枠線以外は合成画像ではありません.拙作のキャプチャアプリで [アプリケーション] を指定してキャプチャしています.[コンポーネントサービス] から起動したダイアログであることを明らかにするために,このような画像としています.




12_ネットワーク上のコンピュータとリソースの列挙


ネットワーク上のコンピュータの名前は,エクスプローラで確認できます.また,IP アドレスの取得のサンプルでも取得できますが,共有フォルダ,共有プリン等のリソースも取得するとなると,結構面倒です.
ネットワークリソースの列挙には,WNetEnumResource 関数が使用できます.下のコードは,Delphi Acid Floor からダウンロードしたプロジェクトのコードを,Delphi XE でも使用できるように筆者が改変したものです.


図25
実行例
  • 業務アプリ開発の時は別として,サンプルのテストの時は,複数のコンピュータを起動し,複数の Delphi を起動していることもある
  • 例えば,このサンプルでは,非ユニコード版とユニコード版の両方の Delphi を起動してテストしている

リスト15
ネットワークリソースの列挙
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, ComCtrls, Buttons, StdCtrls, ImgList;

type
  TForm1 = class(TForm)
    StatusBar1: TStatusBar;
    pnlTop: TPanel;
    pnlBody: TPanel;
    Panel4: TPanel;
    TreeView1: TTreeView;
    btnEnum: TSpeedButton;
    ImageList1: TImageList;
    Memo1: TMemo;
    Splitter1: TSplitter;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnEnumClick(Sender: TObject);
    procedure TreeView1Click(Sender: TObject);
  private
    { Private 宣言 }
    sltNetEnum  : TStringList;  {各リソースのコメント退避用}
    function EnumerateFunc(lpnr: PNetResource; Node: TTreeNode): Boolean;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  初期処理
//  コメント用のリストsltNetEnum(TStringList)を作成
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  sltNetEnum := TStringList.Create;
  Memo1.Lines.Clear;
end;

//=============================================================================
//  終了処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if (Assigned(sltNetEnum)) then sltNetEnum.Free;
end;

//=============================================================================
//  列挙開始
//=============================================================================
procedure TForm1.btnEnumClick(Sender: TObject);
begin
  Screen.Cursor := crHourGlass;

  sltNetEnum.Clear;
  sltNetEnum.BeginUpdate;

  TreeView1.Items.Clear;
  TreeView1.Items.BeginUpdate;

  //列挙関数の呼び出し
  EnumerateFunc(nil, nil);

  TreeView1.Items.EndUpdate;
  sltNetEnum.EndUpdate;
  Statusbar1.Panels[0].Text := '';

  Screen.Cursor :=  crDefault;
end;

//=============================================================================
//  ノードを選択したらコメント表示を表示
//=============================================================================
procedure TForm1.TreeView1Click(Sender: TObject);
var
  SelNode : TTreeNode;
begin
  SelNode := TreeView1.Selected;
  if (SelNode <> nil) then begin
    Memo1.Text := sltNetEnum[SelNode.AbsoluteIndex];
  end;
end;

//-----------------------------------------------------------------------------
//  ネットワーク列挙関数
//
//  オリジナルからの変更点
//  (1) WNetEnumResourceの第2引数をDWORDでキャスト
//  (2) 配列を静的配列に変更
//  (3) RESOURCETYPE_ANYをRESOURCETYPE_DISKに変更
//  (4) TImageListのビットマップをWindows XP用に変更
//  (5) 変数のBufSizeをBuffSizeに変更
//  (6) 関数の最後の列挙ハンドルを閉じる時の処理にResult := True, Falseを追加
//-----------------------------------------------------------------------------
function TForm1.EnumerateFunc(lpnr: PNetResource; Node: TTreeNode): Boolean;
const
  lpBuffLen = 16384;
var
  lpBuffer     : array [0..lpBuffLen div SizeOf(TNetResource)] of TNetResource;
  hEnum        : THandle;
  dwResult     : DWORD;
  cEntries     : Integer;
  BuffSize     : DWORD;
  ix           : Integer;
  NewNode      : TTreeNode;
  strName      : String;
  strName1     : String;
  strName2     : String;
  ContinueFlag : Cardinal;
begin
  //ネットワークリソースの列挙開始
  dwResult := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_DISK, 0, lpnr, hEnum);
  if dwResult <> NO_ERROR  then begin
    WNetCloseEnum(hEnum);
    Result := False;
    exit;
  end;

  //バッファの初期化
  BuffSize := lpBuffLen;
  FillChar(lpBuffer, BuffSize, #0);
  cEntries := -1;

  //リソース情報の取得
  dwResult := WNetEnumResource(hEnum, DWORD(cEntries), @lpBuffer, BuffSize);
  if dwResult <> NO_ERROR then begin
    WNetCloseEnum(hEnum);
    Result := False;
    exit;
  end;

  //取得したリソース情報の処理
  for ix := 0 to cEntries - 1 do begin

    //リソース名を取得
    strName1 := lpBuffer[ix].lpLocalName;
    strName2 := lpBuffer[ix].lpRemoteName;
    strName  := strName1 + strName2;
    //共有ドライブの場合は最後の¥以降の文字列だけとする
    if (Node <> nil) then begin
      if (Node.Level = 2) then begin
        strName :=  ExtractFileName(strName);
      end;
    end;

    //コメントはslNetEnum(StringList)へ格納
    sltNetEnum.Add(lpBuffer[ix].lpComment);

    //ツリーへ追加
    NewNode               :=  TreeView1.items.AddChild(Node,strName);
    NewNode.ImageIndex    :=  NewNode.Level;
    NewNode.SelectedIndex :=  NewNode.Level;

    //ステータスバーにメッセージ
    StatusBar1.Panels[0].Text :=  strName + '....';
    Refresh;

    //さらに下の階層がある場合は再帰的にこの関数を呼び出して処理
    ContinueFlag := lpBuffer[ix].dwUsage and RESOURCEUSAGE_CONTAINER;
    if ContinueFlag = RESOURCEUSAGE_CONTAINER then begin
      EnumerateFunc(@lpBuffer[ix], NewNode);
    end;
  end;

  //列挙ハンドルを閉じる
  dwResult := WNetCloseEnum(hEnum);
  if dwResult <> NO_ERROR then begin
    Result := False;
  end else begin
    Result := True;
  end;
end;

end.