Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク Delphi Programming / Object Pascal
[掲載 2008年04月26日] [更新 2013年10月17日] Delphi サンプルプログラム集
   
898_WMI イントロダクション
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
[12] Windows 7 U64(SP1) + Delphi XE3 Pro VCL-32
898_WMI_Introduction.zip [7,758 KB] 2013年09月21日版(EXE同梱)



  • 2013年08月27日
  • WmiSet Components の記事を削除
  • WMI Delphi Code Creator と SMBIOS 情報取得の記事を追加
  • 解説的な記事を大幅に追加し,サンプルコードも追加
  • リンクの修正 (マイクロソフトのページは本当によく変わる)
  • 2013年09月08日
  • VBS (VBScript) のコードからの移植の記事を追加




WMI とは


WMI (Windows Management Instrumentation) は,主に,Windows のシステム管理に必要な情報を取得するために用意されたインターフェイスです.システム管理を目的として考案された WBEM(業界団体の DMTF が策定)を,マイクロソフトが拡張したものを WMI といっています.WMI を使用すると,
  • システム関係の情報の取得と設定や操作
  • ハードウェアに関する情報の取得 (BIOS やドライブ等)
  • ソフトウェアに関する情報や操作 (OS の情報やプロセスの管理等)
等を行うことができます.特にシステム関係では管理者権限が必要な操作も可能です.ただし,ミッションクリティカルな,リアルタイム性を必要とする処理には向いないようです.
WMI ではこれらの情報を主に WQL というクエリーを実行します.他の方法についてはリンクの記事参考参考にして下さい.


スクリプトセンターのサンプルは,WMI だけではなく,VBS, WSH 等のコードもありますが,WMI のコードも多くあります.もしこのページが英文で表示されたら,ページの右上のコンボボックスで [日本語] を選択してください.しばらくすると日本語に切り替わります.


図1
スクリプトセンターのサンプル


WMI で扱えると言うか,定義されている数は膨大な量です.目的のクラスを探し出すのは容易ではないでしょう.残念ながら,MSDN オンラインには,全ての情報があるわけではありません.
以下の WUtils.com のサイトに WMI 関係のリファレンス記事があります.当サイトの WMI 関係のサンプルのほとんどは CIMV2 という名前空間を扱っていますが,この名前空間だけでも 1,592 のクラスがあります.

システムで使用可能なこれらの名前空間の一覧と,各々の名前空間で使用可能なクラス名の一覧は,後述の WMI Delphi Code Creator や WMI Code Creator で確認できます.これらのツールでは現在の環境で操作可能なプロパティ等も確認できまから,リファレンスとしても利用できます.






01_タイプライブラリの取り込み  -  WbemScripting_TLB の作成


Delphi で WMI を使用するには,WMI Script という COM オブジェクトを生成して使用します.
本ページのサンプルは,1 部を除いて,[タイプライブラリの取り込み] で
    [Microsoft WMI Scripting V1.2 Library (Version 1.2)]
を取り込んで作成した WbemScripting_TLB というユニットを使用しています.

WMI 関係のオブジェクトを全て OleVariant 型で扱う場合は,この作業は必要ありませんが,ユニットは作成しておいた方が便利です.
このユニットには,WMI の COM API や Script API を使用するためのコード,定数値等の定義関係が含まれています.必要な時に,これらを参照することができます.


図2
タイプライブラリの取り込み




02_プロパティのセット  -  プロパティ名とプロパティの型


最初のサンプルでは,論理ディスクの情報を取得します.各ディスクオブジェクトの名前,ディスクと容量とか空き容量等は,このディスクオブジェクトのプロパティということになります.
WMI で扱うプロパティの型には,下表のようなものがあります.

アプリケーションでは,これらの型に応じて値を取得し,処理に使用することになります.
これらは バリアント型のルーチンの TVariantArg や TVarData でキャストしても,判定できません.コード中で使用しているように,ISWbemProperty の CIMType プロパティで判定します.


定数名 (CIMType の値) 説明 Delphi の型
wbemCimtypeSint16 2 16 ビット整数値 SmallInt
wbemCimtypeSint32 3 32 ビット整数値 Integer, LongInt
wbemCimtypeReal32 4 32 ビット実数値 Single
wbemCimtypeReal64 5 64 ビット実数値 Double
wbemCimtypeString 8 文字列 String
wbemCimtypeBoolean 11 論理値 Boolean
wbemCimtypeObject 13 CIM オブジェクト Get でオブジェクトを取得
wbemCimtypeSint8 16 8 ビット整数値 ShortInt
wbemCimtypeUint8 17 8 ビット符号なし整数値 Byte
wbemCimtypeUint16 18 16 ビット符号なし整数値 WORD
wbemCimtypeUint32 19 32 ビット符号なし整数値 Cardinal, LongWord, DWORD
wbemCimtypeSint64 20 64 ビット整数値 Int64
wbemCimtypeUint64 21 64 ビット符号なし整数値 UInt64
wbemCimtypeDatetime 101 日付時刻 (CIM_DATETIME 型) ISWbemDateTime を使用して変換
wbemCimtypeReference 102 CIM オブジェクトへの参照 文字列として取得可能
[非同期イベントシンク] 参照
wbemCimtypeChar16 103 16 ビットの文字 -


システムの論理ディスクの全てのプロパティを取得するサンプルです.
プロパティの型判定の参考用のサンプルですが,符号なし 64 ビット値だけしか判定していません.後の全てのプロパティは文字列として取得して TMemo に表示します.
このサンプルでは,符号なし 64 ビットのプロパティは,容量と空き容量です.プロパティ名は各々 Size と FreeSpace です.

まず,WMI Script オブジェクトを生成し,WMI サービスに接続します.WMI サービスが起動していないと接続に失敗しますが,WMI サービスは,意図的に操作しない限り,Windows の起動と同時に実行されています.次に,このサービスに対してクエリーを実行して,その結果を取得します.
これはデータベースの選択クエリーと同じですが,* (アスタリスク) で全てのプロパティを取得するようにしています.

以下のリンクは,このサンプルで使用しているインターフェイス関係の,MSDN オンライン記事へのリンクです.


図3
設計時画面
図4
実行結果例
  • 全ての論理ディスクの全てのプロパティを取得

リスト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 Button1Click(Sender: TObject);
  protected
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  全論理ディスクの全てのプロパティを取得して表示
//  プロパティの型を判定して処理する例(ここではUInt64型のみ処理)
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  objWMILocator  : ISWbemLocator;
  objWMIService  : ISWbemServices;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiObject      : ISWbemObject;
  wmiPropSet     : ISWbemPropertySet;
  wmiPropSetEnum : IEnumVariant;
  wmiProperty    : ISWbemProperty;
  ValueUInt64    : UInt64;
  fmtStrStr      : String;
  fmtStrInteger  : String;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

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

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     'Localhost',    //コンピュータ名.ローカルの場合は空白でも可
                     'root\cimv2',   //名前空間
                     '',             //ユーザ名.ローカルの場合は空文字にする
                     '',             //パスワード.ローカルの場合は空文字にする
                     '',             //ロケール識別子
                     '',             //ドメイン名
                     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

    //各オブジェクト(ここでは1つの論理ディスク)のISWbemObjectオブジェクトを取得
    wmiObject := IUnknown(objTemp) as ISWBemObject;
    //プロパティの列挙を取得
    wmiPropSet     := wmiObject.Properties_;
    wmiPropSetEnum := (wmiPropSet._NewEnum) as IEnumVariant;

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

      //プロパティが符号なし64ビット整数の場合
      if wmiProperty.CIMType = wbemCimtypeUint64 then begin
        //値がnullでない場合に書式を整えて表示
        if not VarIsNull(wmiProperty.Get_Value) then begin
          //プロパティの名前
          fmtStrStr := Format('%-30s', [wmiProperty.Name]);

          //プロパティの値
          ValueUInt64  := wmiProperty.Get_Value;
          fmtStrInteger:= FormatFloat('###,##0', ValueUInt64);

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

  Memo1.Lines.EndUpdate;
end;

end.


上のコードでは,プロパティの列挙オブジェクトを取得するのに,3 行を使用しています.
もし,ISWbemObject, ISWbemPropertySet のプロパティやメソッドを使用する予定がなければ,この部分は省略できます.


リスト2
上の 3 行のコードは 1 行にできる
    wmiObject      := IUnknown(objTemp) as ISWBemObject;
    wmiPropSet     := wmiObject.Properties_;
    wmiPropSetEnum := (wmiPropSet._NewEnum) as IEnumVariant;

           

    wmiPropSetEnum := IUnknown(objTemp.Properties_._NewEnum) as IEnumVariant;


[WMI を使用して Windows のバージョンだけを取得] の記事にあるように,プロパティセットのコレクションを取得する部分を別のメソッドにすると使い勝手がいいかも知れません.




03_特定のプロパティだけを取得する場合


上のサンプルは全てのプロパティを取得する例でした.今度は,特定のプロパティだけを取得するサンプルです.

上のサンプルは,2 つの while ループを使用しています.最初の while ループは,実装されている論理ディスクの数だけループします.次の while では,各論理ディスクのプロパティの数だけループします.
特定のプロパティだけが必要な場合,プロパティの数分もループさせる必要はありません.1 つの論理ディスクでは 1 回だけで十分です.したがって,2 回目のループは必要ありません.

そこで,次のコードでは論理ディスクごとに ISWbemPropertySet の Item メソッドで目的のプロパティのオブジェクト,ISWbemProperty を取得します.
後はこの ISWbemProperty のプロパティやメソッドで,論理ディスクのプロパティ名や値を取得して表示します.この時,プロパティの型の判定は特に必要ありません.プロパティの種類によって型は決まっています.それにしたがって処理すればいいことになります.


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

リスト3
論理ディスクの特定のプロパティだけを取得する例
  //-------------------------------------------------------------------------
  //  以下がプロパティの取得コード
  //-------------------------------------------------------------------------

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

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

    //DeviceIDプロパティを取得
    wmiProperty := wmiPropSet.Item('DeviceID', 0);
    if VarIsNull(wmiProperty.Get_Value) then begin
      Memo1.Lines.Add(wmiProperty.Name);
    end else begin
      //プロパティの名前
      fmtStrStr := Format('%-20s', [wmiProperty.Name]);
      //プロパティの名前と実際の値を文字列にして表示
      Memo1.Lines.Add(fmtStrStr + VarToStr(wmiProperty.Get_Value));
    end;

    //Sizeプロパティを取得
    wmiProperty := wmiPropSet.Item('Size', 0);
    if VarIsNull(wmiProperty.Get_Value) then begin
      Memo1.Lines.Add(wmiProperty.Name);
    end else begin
      //プロパティの名前
      fmtStrStr := Format('%-20s', [wmiProperty.Name]);

      //プロパティの値
      ValueUInt64  := wmiProperty.Get_Value;
      fmtStrInteger:= FormatFloat('###,##0', ValueUInt64);

      Memo1.Lines.Add(fmtStrStr + fmtStrInteger);;
    end;

    //FreeSpaceプロパティを取得
    //処理内容は上のSizeプロパティの時と同じ
    wmiProperty := wmiPropSet.Item('FreeSpace', 0);
    if VarIsNull(wmiProperty.Get_Value) then begin
      Memo1.Lines.Add(wmiProperty.Name);
    end else begin
      //プロパティの名前
      fmtStrStr := Format('%-20s', [wmiProperty.Name]);

      //プロパティの値
      ValueUInt64  := wmiProperty.Get_Value;
      fmtStrInteger:= FormatFloat('###,##0', ValueUInt64);

      Memo1.Lines.Add(fmtStrStr + fmtStrInteger);;
    end;
    Memo1.Lines.Add('---------------------------------------');
  end;




04_プロパティを OleVariant 型で取得


プロパティは,ISWbemProperty を取得しなくても,直接 OleVariant 型で操作できます.下のコードはその例です.


リスト4
プロパティの一部だけを取得するコード例
プロパティのオブジェクト (ISWbemProperty) を使用しない場合
  //-------------------------------------------------------------------------
  //  以下がプロパティの取得コード
  //-------------------------------------------------------------------------

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

    strTemp := VarToStrDef(objTemp.DeviceID, '');
    Memo1.Lines.Add('DeviceID            ' + strTemp);

    if VarIsNull(objTemp.Size) then begin
      Memo1.Lines.Add('Size                ');
    end else begin
      ValueUInt64 := objTemp.Size;
      Memo1.Lines.Add('Size                ' + FormatFloat('###,##0', ValueUInt64));
    end;

    if VarIsNull(objTemp.FreeSpace) then begin
      Memo1.Lines.Add('Size                ');
    end else begin
      ValueUInt64 := objTemp.FreeSpace;
      Memo1.Lines.Add('Size                ' + FormatFloat('###,##0', ValueUInt64));
    end;

    Memo1.Lines.Add('---------------------------------------');
  end;




05_プロパティを型に関係なく文字列として取得


プロパティの型に応じて取得するのではなく,単に文字列として表示するだけであれば,以下のようにバリアントの値を文字列に変換する,VarToStrDef 関数や VarToStr 関数で文字列に変換できます.
プロパティの型に応じた変数に値を代入して処理する必要がなければ,これでも十分です.ただし,プロパティの値を文字列に変換する際には注意が必要です.これについては次項で説明します.


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

リスト5
書式なしで,プロパティを文字列として取得して表示する例
ネット上にある VB 関係のサンプルは,この形式のコードが多い
  //-------------------------------------------------------------------------
  //  以下がプロパティの取得コード
  //-------------------------------------------------------------------------

  //順番に取り出す
  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;




06_プロパティを文字列として取得する場合  -  配列型のプロパティ


WMI で扱うプロパティの型は,本ページの最初に表で示しました.この他,配列型のプロパティがあります.この配列型のプロパティは再帰的に格納されている場合もあります.
配列型のプロパティの場合,各要素の型が,表の型のどれかになります.

次のコードは,BIOS の全ての情報を取得する例ですが,VarToStr 関数や VarToStrDef 関数だけではバリアントの型変換ができないというエラーが発生して,文字列として取得できない場合があります.
そこで,このサンプルでは,OleVariant 型の値を String 型の文字列として取得する関数を作成して処理しています.


図7
実行結果例
  • BIOS の情報を取得

リスト6
BIOS の情報を取得
配列型の OleVariant の値を文字列で取得する例
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);
  protected
    { Private 宣言 }
  public
    { Public 宣言 }
    function VarToString(const OleVar: OleVariant): String;
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  BIOSの情報の取得
//  プロパティの値を文字列として取得する場合の処理例
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
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(
                     '',             //コンピュータ名.ローカルの場合は空白で可
                     'root\cimv2',   //名前空間
                     '',             //ユーザ名.ローカルの場合は空文字にする
                     '',             //パスワード.ローカルの場合は空文字にする
                     '',             //ロケール識別子
                     '',             //ドメイン名
                     0,              //応答フラグ
                     nil);

  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  objPropSet := objWMIService.ExecQuery(
                     'SELECT * FROM Win32_BIOS',
                     '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('%-25s', [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 := 'NULL';
end;

end.


後述の THE ROAD TO DELPHI のサイトにバリアント配列型のプロパティを処理するコードがあります.それを使用したサンプルです.
VarStrNull と VarArrayToStr という名前の関数ですが,要素の型によって処理を分岐させています.こちらの方が使いやすいかも知れません.


図8
実行結果例
  • BIOS の情報を取得

リスト7
BIOS の情報の取得
配列型の OleVariantの値を文字列で取得する関数を使用した例
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);
  protected
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//-----------------------------------------------------------------------------
//  配列型のプロパティを文字列で取得する関数の本体
//  WMI Delphi Code Creator の記事のサイトのコード
//-----------------------------------------------------------------------------
function VarArrayToStr(const vArray: variant): string;

    function _VarToStr(const V: variant): string;
    var
    Vt: integer;
    begin
    Vt := VarType(V);
        case Vt of
          varSmallint,
          varInteger  : Result := IntToStr(integer(V));
          varSingle,
          varDouble,
          varCurrency : Result := FloatToStr(Double(V));
          varDate     : Result := VarToStr(V);
          varOleStr   : Result := WideString(V);
          varBoolean  : Result := VarToStr(V);
          varVariant  : Result := VarToStr(Variant(V));
          varByte     : Result := char(byte(V));
          varString   : Result := String(V);
          varArray    : Result := VarArrayToStr(Variant(V));
        end;
    end;

var
i : integer;
begin
    Result := '[';
     if (VarType(vArray) and VarArray)=0 then
       Result := _VarToStr(vArray)
    else
    for i := VarArrayLowBound(vArray, 1) to VarArrayHighBound(vArray, 1) do
     if i=VarArrayLowBound(vArray, 1)  then
      Result := Result + _VarToStr(vArray[i])
     else
      Result := Result + '|' + _VarToStr(vArray[i]);

    Result:=Result + ']';
end;

//-----------------------------------------------------------------------------
//  配列型のプロパティを文字列で取得する関数
//  nullの時は空白文字を返す
//-----------------------------------------------------------------------------
function VarStrNull(const V:OleVariant):string; //avoid problems with null strings
begin
  Result := '';
  if not VarIsNull(V) then
  begin
    if VarIsArray(V) then
       Result := VarArrayToStr(V)
    else
    Result := VarToStr(V);
  end;
end;

//=============================================================================
//  BIOSの情報の取得
//  プロパティの値を文字列として取得する場合の処理例
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
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(
                     '',             //コンピュータ名.ローカルの場合は空白で可
                     'root\cimv2',   //名前空間
                     '',             //ユーザ名.ローカルの場合は空文字にする
                     '',             //パスワード.ローカルの場合は空文字にする
                     '',             //ロケール識別子
                     '',             //ドメイン名
                     0,              //応答フラグ
                     nil);

  //クエリーを実行して,プロパティのセットのオブジェクトを取得
  objPropSet := objWMIService.ExecQuery(
                     'SELECT * FROM Win32_BIOS',
                     '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('%-25s', [wmiProperty.Name]);
      Memo1.Lines.Add(fmtStrStr + VarStrNull(wmiProperty.Get_Value));
    end;
    Memo1.Lines.Add('---------------------------------------');
  end;

  Memo1.Lines.EndUpdate;
end;

end.


プロパティ名とプロパティの値を取得する部分を,OleVariant 型で処理する場合は,次のようになります.プロパティの値は,Get_Value ではなく,Value を使用します.


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

  //順番に取り出す
  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
      //プロパティの名前と実際の値を文字列にして表示
      fmtStrStr := Format('%-25s', [VarToStr(objTemp.Name)]);
      Memo1.Lines.Add(fmtStrStr + VarToString(objTemp.Value));
    end;
    Memo1.Lines.Add('---------------------------------------');
  end;




07_日付時刻型プロパティの処理例  -  TDateTime 型に変換


WMI で扱う日付時刻型は CIM_DATETIME という型です.
インターフェイスオブジェクトは ISWbemDateTime です.Script オブジェクトは SWbemDateTime ですが,Delphi ではどちらでも同じです.

次のサンプルは,特定のファイルのファイル名,ファイルタイプ,作成日時,更新日時を取得するサンプルです.CIM_DATETIME 型のデータを Delphi の TDateTime 型に変換する関数を作成し,それを使用して,書式を設定して表示しています.

WMI では,この他に,32 ビットまたは 64 ビットの符号なし整数値で日付時間の値が格納されているプロパティがあります.この符号なし整数値を扱う例が [903_ WMI 非同期イベントシンク] にいくつかあります.実行結果の図に,日付と時刻が表示されているサンプルで確認してください.


図9
ファイル構成
  • ファイルのプロパティを取得

リスト8
ファイルのプロパティの一部を取得
CIM_DATETIME のデータを TDateTime 型に変換する例
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);
  protected
    { Private 宣言 }
    function WbemToTDateTime(V : OleVariant): TDateTime;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  ファイルの作成日時と更新日時を取得して表示
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  objWMILocator  : ISWbemLocator;
  objWMIService  : ISWbemServices;
  FilePath       : String;
  strWMISQL      : String;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiPropSet     : ISWbemPropertySet;
  wmiProperty    : ISWbemProperty;
  fmtStrStr      : String;
  fmtStrDateTime : String;
  ADateTime      : TDateTime;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

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

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     '',             //コンピュータ名.ローカルの場合は空白で可
                     'root\cimv2',   //名前空間
                     '',             //ユーザ名.ローカルの場合は空文字にする
                     '',             //パスワード.ローカルの場合は空文字にする
                     '',             //ロケール識別子
                     '',             //ドメイン名
                     0,              //応答フラグ
                     nil);

  //クエリーの実行
  FilePath  := Application.ExeName;
  FilePath  := StringReplace(FilePath, '\', '\\', [rfReplaceAll]);
  strWMISQL := 'Select * From CIM_DataFile Where Name ="' + FilePath + '"';

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


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

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

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

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

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

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

    //InstallDate(作成日時)プロパティを取得
    wmiProperty := wmiPropSet.Item('InstallDate', 0);
    fmtStrStr   := Format('%-15s', [wmiProperty.Name]);
    ADateTime   := WbemToTDateTime(wmiProperty.Get_Value);
    if ADateTime <> 0 then begin
      fmtStrDateTime := FormatDateTime('yyyy-mm-dd : hh:nn:ss', ADateTime);
      Memo1.Lines.Add(fmtStrStr + fmtStrDateTime);
    end else begin
      Memo1.Lines.Add(fmtStrStr);
    end;

    //LastModified(更新日時)プロパティを取得
    wmiProperty := wmiPropSet.Item('LastModified', 0);
    fmtStrStr   := Format('%-15s', [wmiProperty.Name]);
    ADateTime   := WbemToTDateTime(wmiProperty.Get_Value);
    if ADateTime <> 0 then begin
      fmtStrDateTime := FormatDateTime('yyyy-mm-dd : hh:nn:ss', ADateTime);
      Memo1.Lines.Add(fmtStrStr + fmtStrDateTime);
    end else begin
      Memo1.Lines.Add(fmtStrStr);
    end;
  end;

  Memo1.Lines.EndUpdate;
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.


参考までに,指定したディレクトリ内の指定拡張子のファイルを取得するには,クエリーの構文を変更することで取得できます.
下のコードはその例です.D:\000 内の拡張子が pas のファイルの一覧を表示します.


//=============================================================================
//  指定ディレクトリ内の指定拡張子のファイルの一覧
//
//  usesにComObj,ActiveX,WbemScripting_TLBが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  objWMILocator  : ISWbemLocator;
  objWMIService  : ISWbemServices;
  strWMISQL      : String;
  objPropSet     : ISWbemObjectSet;
  objPropSetEnum : IEnumVariant;
  objTemp        : OleVariant;
  iValue         : LongWord;
  wmiPropSet     : ISWbemPropertySet;
  wmiProperty    : ISWbemProperty;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

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

  //WMIサービスに接続.引数は必要に応じて設定
  objWMIService := objWMILocator.ConnectServer(
                     '',             //コンピュータ名.ローカルの場合は空白で可
                     'root\cimv2',   //名前空間
                     '',             //ユーザ名.ローカルの場合は空文字にする
                     '',             //パスワード.ローカルの場合は空文字にする
                     '',             //ロケール識別子
                     '',             //ドメイン名
                     0,              //応答フラグ
                     nil);

  //クエリーの実行
  strWMISQL := 'Select * From CIM_DataFile '
               + 'Where Drive="d:" and Path = "\\000\\" and Extension = "pas"';

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


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

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

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

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

    //Nameプロパティを取得
    wmiProperty := wmiPropSet.Item('Name', 0);
    Memo1.Lines.Add(VarToStrDef(wmiProperty.Get_Value, ''));
  end;

  Memo1.Lines.EndUpdate;
end;


WMI とは直接関係ありませんが,ファイルのタイムスタンプ (更新日時) を取得するもっとも簡単な方法は,以下の FileAge 関数を使用した方法ではないかと思います.
ただし,ファイルの更新日時が,どんなアプリケーションで,どんな状況で取得する必要があるかにもよります.したがって,この FileAge 関数の使用が常に最良というわけではありません.


//=============================================================================
//  ファイルのタイムスタンプ(更新日時)の取得
//  System.SysUtils.FileAgeを使用した例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  FilePath : String;
  FileDate : TDateTime;
begin
  FilePath := ExtractFilePath(Application.ExeName) + 'AAA.txt';

  if FileAge(FilePath, FileDate, True) then begin
    ShowMessage(DateTimeToStr(FileDate));
  end;
end;




08_日付時刻型プロパティの処理例  -  ミリ秒まで取得


同じく CIM_DATETIME を扱う例です.
OS が最後に起動した日時を取得して,現在までの経過日数および経過時間を時間,分,秒,ミリ秒まで計算して表示します.つまり,システム稼働時間の確認ということになります.
経過日時は,TDateTime 型の値から直接は算出できません.そこで,TDateTime 型の値を TTimeStamp 型に変換してから,日数とミリ秒に分解します.ここから計算で求めています.


[備考]
ログインの日時等は,Win32_NetworkLoginProfile クラスを使用すると取得できます.



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

リスト9
CIM_DATETIME のデータを扱う例
OS が最後に起動した日時と,起動後の経過日時を表示
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);
  protected
    { Private 宣言 }
    function WbemToTDateTime(V : OleVariant): TDateTime;
    function DateTimeString(V : OleVariant): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  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(
                     '',             //コンピュータ名.ローカルの場合は空白で可
                     'root\cimv2',   //名前空間
                     '',             //ユーザ名.ローカルの場合は空文字にする
                     '',             //パスワード.ローカルの場合は空文字にする
                     '',             //ロケール識別子
                     '',             //ドメイン名
                     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_;

    //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.


ISWbemDateTime を OleVariant 型で処理する場合は,例えば以下のようになります.
オブジェクトを OleVariant 型で扱う場合は,コード補完機能のように,WMIDateTime の Year 等のメンバーの正確なスペルを知ることができません.したがって,一度 ISWbemDateTime 型で動作することを確認してから,このような OleVariant 型のコードに変換しています.


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

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

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

  WMIDateTime := CreateOleObject('WbemScripting.SWbemDateTime');
  WMIDateTime.Value := V;
  Result := WMIDateTime.GetVarDate(True);
end;




09_CreateOleObject による WMI オブジェクトの生成


COM オブジェクトは CreateOleObject でそのインスタンスを生成することができます.
したがって,WMI Script オブジェクトも,ワードやエクセルと同じように,CreateOleObject でそのインスタンスを生成できます.その例です.
このサンプルでは,ISWbemServices,ISWbemObjectSet を OleVariant 型で扱っています.COM オブジェクトを OleVariant 型で扱う場合,デフォルトの値で使用するメソッドの引数を省略できます.

これまでのサンプルと同じ様に,必要に応じて ISWbemProperty や ISWbemDateTime オブジェクト等も利用することができますが,これらも OleVariant 型で扱えます.このサンプルでは使用していませんので,uses には WbemScripting_TLB を追加していません.

他のサンプルで使用している CoSWbemLocator は,下コードのように,CreateComObject で生成したオブジェクトを ISWbemLocator でキャスト(型変換) しているだけです.この CreateComObject の部分は CreateOleObject でも生成できます.
Script オブジェクトを CreateOleObject で生成して OleVariant 型で扱ったり,他の COM オブジェクトも OleVariant 型で扱うかは,プログラマが決めることです.



class function CoSWbemLocator.Create: ISWbemLocator;
begin
  Result := CreateComObject(CLASS_SWbemLocator) as ISWbemLocator;
end;


[備考]
たとえ,CreateOleObject で WMI のインスタンスを生成するにしても,WbemScripting_TLB を作成しておくと便利です.このファイルには,定数等の定義があります.
また,非同期イベントのサンプルがありますが,非同期のイベント部分を OleVariant で扱うには無理があるでしょう.非同期イベント処理では,WbemScripting_TLB を使用した方が無難です.


図11
設計時画面
図12
実行結果例
  • 論理ディスクの特定のプロパティを取得して表示

リスト10
CreateOleObject の使用例
論理ディスクの特定のプロパティを取得して表示
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    ListBox1: TListBox;
    procedure Button1Click(Sender: TObject);
  protected
    { Private 宣言 }
    function VarToString(const OleVar: OleVariant): String;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses ComObj, ActiveX;

{$R *.dfm}

{ TForm1 }

//=============================================================================
//  論理ディスクの一覧を取得して,いくつかのプロパティを取得して表示
//  usesにComObj,ActiveXが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  objWMILocator : OleVariant;
  objWMIService : OleVariant;
  colItems      : OleVariant;
  colItem       : OleVariant;
  colEnum       : IEnumvariant;
  iValue        : LongWord;
begin
  ListBox1.Items.Clear;

  //WMIオブジェクトを生成
  ObjWMILocator := CreateOleObject('WbemScripting.SWbemLocator');

  //WMIサービスに接続
  //第1引数はコンピュータ名(空白なら自分のマシン)
  ObjWMIService := ObjWMILocator.ConnectServer('', 'root\CIMV2');

  //クエリーの実行
  //デフォルトの引数は省略できるので,以下でも可
  //colItems := objWMIService.ExecQuery('SELECT * FROM Win32_LogicalDisk');
  colItems := objWMIService.ExecQuery('SELECT * FROM Win32_LogicalDisk', 'WQL', 0);

  //プロパティのセットのコレクションを取得
  colEnum := IUnknown(colItems._NewEnum) as IEnumVariant;

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

  //それを順番に各々のプロパティを取得
  while colEnum.Next(1, colItem, iValue) = 0 do begin
    ListBox1.Items.Add(Format('Description  %s', [VarToString(colItem.Description)]));
    ListBox1.Items.Add(Format('DeviceID     %s', [VarToString(colItem.DeviceID)]));
    ListBox1.Items.Add(Format('Size         %s', [VarToString(colItem.Size)]));
    ListBox1.Items.Add('');
  end;
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 := 'NULL';
end;

end.


WMI オブジェクトのインスタンスを CreateOleObject で生成し,さらにプロパティも OleVariant 型で処理する場合,Button1 のイベントのコードは,例えば以下のようになります.このコードは,全てのプロパティを列挙するようにしています.
全てを OleVariant 型で扱えば,タイプライブラリの取り込みで作成した WbemScripting_TLB は必要ありません.

このコードは,OleVariant 型で処理する際のひな型として利用できます.後は,プロパティの型に応じた処理や,上のサンプルのように特定のプロパティだけを取り出す処理にすることなります.

VarToString は上のコードでも使用している OleVariantの値をString型に変換する関数です.



procedure TForm1.Button1Click(Sender: TObject);
var
  objWMILocator  : OleVariant;
  objWMIService  : OleVariant;
  colItems       : OleVariant;
  colItem        : OleVariant;
  colEnum        : IEnumvariant;
  iValue         : LongWord;
  wmiPropSetEnum : IEnumVariant;
  fmtStrStr      : String;
begin
  ListBox1.Items.Clear;

  ObjWMILocator := CreateOleObject('WbemScripting.SWbemLocator');
  ObjWMIService := ObjWMILocator.ConnectServer('', 'root\CIMV2');
  colItems := objWMIService.ExecQuery('SELECT * FROM Win32_LogicalDisk');

  colEnum := IUnknown(colItems._NewEnum) as IEnumVariant;

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

  while colEnum.Next(1, colItem, iValue) = 0 do begin

    wmiPropSetEnum := IUnknown(colItem.Properties_._NewEnum) as IEnumVariant;
    while wmiPropSetEnum.Next(1, colItem, iValue) = 0 do begin
      fmtStrStr := Format('%-32s', [VarToStr(colItem.Name)]);
      ListBox1.Items.Add(fmtStrStr + VarToString(colItem.Value));
    end;
    ListBox1.Items.Add('---------------------------------------');
  end;
end;




10_VBS ( VBScript ) の WMI コードを移植


ネット上で,次のような VB や VBS (VBScript) を使用したサンプルを見かけることがあります.
winmgmts: は WMI オブジェクトのライブラリを意味する文字列です.WMI が Windows Management Instrumentation ですから,おそらく Windows Managements ぐらいの意味でしょう.つまり,このコードは WMI オブジェクトを取得して,Win32_LogicalDisk クラスのクエリーを実行します.
結果は,上のコードの colItems に相当することになります.

これは,このままでは利用できません.Delphi では GetObject という関数も InstancesOf という修飾子も使用できません.この VBS のコードは,WMI の Win32_LogicalDisk クラスを使用しています.したがって,上のコードのように書き換えれば動作します.




  GetObject("winmgmts:").InstancesOf ("Win32_LogicalDisk")


このような VBS のコードは,[950_Delphi で VBScript を操作] の説明の方法でも移植できます.
Delphi で VBScript のオブジェクトのインスタンスを生成したら,Eval 関数の引数に,上の VBS のコードをそのまま文字列として渡します.Eval 関数の戻り値は OleVariant 型です.
下がそのコードです.実行結果は,CreateOleObject のサンプルと全く同じになります.

VBS のコードの中に & (アンパサンド) がある場合,これは文字列の結合の意味で,結合する文字列は変数を意味していることがほとんどです.したがって,この & を含めた文字列を Eval 関数の引数にしても動作しません.その場合は,文字列変数を別に用意して処理します.
ですが,書き直してしまった方が早い気もします.Delphi では 3 行となりますが,各々が機能別になっています.



procedure TForm1.Button1Click(Sender: TObject);
var
  VBSpt    : TScriptControl;
  colItems : OleVariant;
  colItem  : OleVariant;
  colEnum  : IEnumvariant;
  iValue   : LongWord;
begin
  ListBox1.Items.Clear;

  //VBScriptオブジェクトを生成
  //VBScriptの使用にはusesにMSScriptControl_TLBが必要
  VBSpt := TScriptControl.Create( Self );
  VBSpt.Language := 'VBScript';

  try
    //VBSの GetObject("winmgmts:").InstancesOf ("Win32_LogicalDisk") 相当のコード
    colItems := VBSpt.Eval('GetObject("winmgmts:").InstancesOf("Win32_LogicalDisk")');

    //プロパティのセットのコレクションを取得
    colEnum := IUnknown(colItems._NewEnum) as IEnumVariant;

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

    //それを順番に各々のプロパティを取得
    while colEnum.Next(1, colItem, iValue) = 0 do begin
      ListBox1.Items.Add(Format('Description  %s', [VarToString(colItem.Description)]));
      ListBox1.Items.Add(Format('DeviceID     %s', [VarToString(colItem.DeviceID)]));
      ListBox1.Items.Add(Format('Size         %s', [VarToString(colItem.Size)]));
      ListBox1.Items.Add('');
    end;
  finally
    FreeAndNil(VBSpt);
  end;
end;


次のように,"winmgmts:" だけのオブジェクトを OleVariant 型で受け取ることもできます.
この VBSpt.Eval で受け取ることができるのは,GetObject が生成するオブジェクトが IUnknown 型インターフェイスのオブジェクトの場合だけです.


procedure TForm1.Button1Click(Sender: TObject);
var
  VBSpt         : TScriptControl;
  objWMIService : OleVariant;
  colItems      : OleVariant;
  colItem       : OleVariant;
  colEnum       : IEnumvariant;
  iValue        : LongWord;
begin
  ListBox1.Items.Clear;

  //VBScriptオブジェクトを生成
  //VBScriptの使用にはusesにMSScriptControl_TLBが必要
  VBSpt := TScriptControl.Create( Self );
  VBSpt.Language := 'VBScript';

  try
    //"winmgmts:" 部分だけのオブジェクトを取得
    objWMIService := VBSpt.Eval('GetObject("winmgmts:")');
    colItems := objWMIService.ExecQuery('SELECT * FROM Win32_LogicalDisk', 'WQL', 0);

    //プロパティのセットのコレクションを取得
    colEnum := IUnknown(colItems._NewEnum) as IEnumVariant;

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

    //それを順番に各々のプロパティを取得
    while colEnum.Next(1, colItem, iValue) = 0 do begin
      ListBox1.Items.Add(Format('Description  %s', [VarToString(colItem.Description)]));
      ListBox1.Items.Add(Format('DeviceID     %s', [VarToString(colItem.DeviceID)]));
      ListBox1.Items.Add(Format('Size         %s', [VarToString(colItem.Size)]));
      ListBox1.Items.Add('');
    end;
  finally
    FreeAndNil(VBSpt);
  end;
end;


InstancesOf を次のように使用することもできます.ただし,InstancesOf の引数はこの場合,文字列です.したがって," (ダブルクォーテーション) ではなく,' (シングルクォーテーション) を使用します.Eval の最初の使用例のコードでは,文字列の中の文字列だったので " を使用しています.
文字列の中の文字列の場合,''' とすることもできます.

対象となる VB や VBScript のコードがどのような動作をするかが分かれば,Delphi 本来のコードに移植しやすいのですが,VB, VBS も変数の型の指定なしで実行できるのが利点となっています.せめて実行時の型が分かれば移植しやすいのですが,中々難しいところです.



procedure TForm1.Button1Click(Sender: TObject);
var
  VBSpt         : OleVariant;
  objWMIService : OleVariant;
  colItems      : OleVariant;
  colItem       : OleVariant;
  colEnum       : IEnumvariant;
  iValue        : LongWord;
begin
  ListBox1.Items.Clear;

  //VBScriptオブジェクトを生成
  VBSpt := CreateOleObject('MSScriptControl.ScriptControl');
  VBSpt.Language := 'VBScript';

  try
    //VBSの GetObject("winmgmts:") 相当のコード
    objWMIService := VBSpt.Eval('GetObject("winmgmts:")');
    colItems := objWMIService.InstancesOf('Win32_LogicalDisk');

    //プロパティのセットのコレクションを取得
    colEnum := IUnknown(colItems._NewEnum) as IEnumVariant;

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

    //それを順番に各々のプロパティを取得
    while colEnum.Next(1, colItem, iValue) = 0 do begin
      ListBox1.Items.Add(Format('Description  %s', [VarToString(colItem.Description)]));
      ListBox1.Items.Add(Format('DeviceID     %s', [VarToString(colItem.DeviceID)]));
      ListBox1.Items.Add(Format('Size         %s', [VarToString(colItem.Size)]));
      ListBox1.Items.Add('');
    end;
  finally
    FreeAndNil(VBSpt);
  end;
end;


更に,VBScript も OleVariant 型で扱うのであれば,上のコードは次のように書けます.
VBScript を OleVariant 型で扱う場合は,MSScriptControl_TLB.pas は必要ありません.


procedure TForm1.Button1Click(Sender: TObject);
var
  VBSpt         : OleVariant;
  objWMIService : OleVariant;
  colItems      : OleVariant;
  colItem       : OleVariant;
  colEnum       : IEnumvariant;
  iValue        : LongWord;
begin
  ListBox1.Items.Clear;

  //VBScriptオブジェクトを生成
  VBSpt := CreateOleObject('MSScriptControl.ScriptControl');
  VBSpt.Language := 'VBScript';

  try
    //VBSの GetObject("winmgmts:") 相当のコード
    objWMIService := VBSpt.Eval('GetObject("winmgmts:")');
    colItems := objWMIService.InstancesOf('Win32_LogicalDisk');

    //プロパティのセットのコレクションを取得
    colEnum := IUnknown(colItems._NewEnum) as IEnumVariant;

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

    //それを順番に各々のプロパティを取得
    while colEnum.Next(1, colItem, iValue) = 0 do begin
      ListBox1.Items.Add(Format('Description  %s', [VarToString(colItem.Description)]));
      ListBox1.Items.Add(Format('DeviceID     %s', [VarToString(colItem.DeviceID)]));
      ListBox1.Items.Add(Format('Size         %s', [VarToString(colItem.Size)]));
      ListBox1.Items.Add('');
    end;
  finally
    VBSpt := Unassigned;
  end;
end;




11_WMI Delphi Code Creator


THE ROAD TO DELPHI のサイトに WDCC.exe というユーティリティがあります.
これは WMI の Delphi の処理コードを生成するアプリケーションです.今回は [Binaries] をダウンロードして使用してみました.
このようなツールが手元にあれば,リファレンス用として活用できます.



  • 図13
  • WDCC.exe を起動してクエリー等を設定

上の図で論理ディスクの全てのプロパティを取得する設定をして,任意のフォルダにプロジェクトを保存します.プロジェクトはコンソールアプリケーションとなっています.コンパイルは上の図の状態からも実行できます.

プロジェクトをコンパイルすると,
     Null型から Integer 型へのバリアント型変換はできません
というエラーが発生して実行できません.



図14
エラー

そこで,下のコードのように,NullStrictConvert := False; としました.また符号なし 64 ビット整数値は,符号なし 64 ビットの変数に代入してから書式を設定します.
Format 関数の引数内でキャストすると実行時にエラーが発生しますが,変数であればエラーは発生しません.
その下の図が実行結果です.コンソールの画面バッファサイズの設定によっては,取得項目数が多いと,最初の部分が表示されない場合があります.

リスト11
修正内容
procedure  GetWin32_LogicalDiskInfo;
const
  WbemUser            ='';
  WbemPassword        ='';
  WbemComputer        ='localhost';
  wbemFlagForwardOnly = $00000020;
var
  //
  ValueInt64    : UInt64; //追加
begin;
    //
    //
    ValueInt64 := FWbemObject.FreeSpace;
    Writeln('FreeSpace                       ' + FormatFloat('####,##0', ValueInt64));
    //
    ValueInt64 := FWbemObject.Size;
    Writeln('Size                            ' + FormatFloat('####,##0', ValueInt64));
    //
    //
  end;
end;


begin
 try
    NullStrictConvert := False;  //追加
    CoInitialize(nil);
    try
      GetWin32_LogicalDiskInfo;
    finally
      CoUninitialize;
      NullStrictConvert := True; //追加
    end;
 except
    on E:EOleException do
        Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode])); 
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;      
end.


図15
実行結果




12_SMBIOS ( System Management BIOS ) 情報の取得


SMBIOS (システム管理 BIOS) は,マザーボード等の製品管理情報の規格のことです.
SMBIOS の情報は WMI からも取得できますが,上の WMI Delphi Code Creator を提供しているサイトで,SMBIOS 関係の情報を取得するためユニットが提供されています.
ユニット名は uSMBIOS です.これを uses に追加して利用します.ソースコードは code google site から SVN でダウンロードできます.


  • 図16
  • SVN でダウンロード

[Samples] フォルダには下図のようなサンプルが収められています.


図17
サンプルプログラムのリスト

サンプルプログラムはコンソールアプリケーションとなっています.ダウンロードしたファイルを解凍したままの状態でコンパイルすることができます.相対パスの設定で [Common] の uSMBIOS にパスが設定されています. 
各サンプルには EXE ファイルも添付されていますが,下図は実際に Delphi XE Pro でコンパイルして実行した結果です.


図18
サンプルの実行結果
  • [Table 0 Bios Info] サンプル




13_コンポーネント  WMI and SMART Component


WMI を 利用しやすいようにしたコンポーネントです.
2013年08月27日現在,Delphi XE4 でのテストも行っているようです.

このコンポーネントには WbemScripting_TLB が含まれています.下図のようなデモログラムも EXE ファイル形式で含まれています.これを使用するどんな WMI のクラス (クエリー) があり,どんなプロパティがあるか,またその結果も確認することができます.
このコンポーネントはインストール (登録) 作業はせず,uses に magwmi と magsubs1 を追加して利用します.
下図は,クエリー Win32_OperatingSystem を実行したところです.結果の左側がプロパティのリストで右がその値となっています.

Online-Admin.com というサイトに WmiSet Components というコンポーネントがありますが,Delphi 7 までの対応となっています.



図19
コンポーネント内のリスト
図20
附属のサンプルプログラム



14_その他のユーティリティ類 - wbemtest.exe ( ウェブエムテスト )


Windows 2000 以上であれば,Windows GUI ツール の 1 つとして,WMI の機能をテストするためのプログラム,wbemtest.exe がインストールされています.[スタート] から [ファイル名を指定して実行] で起動できます.
説明と使用方法は,下のリンクの Microsoft TecNet のスクリプトセンターにあります.


図21
wbemtest.exe 起動
図22
起動して接続




15_その他のユーティリティ類 - WMI Code Creator


マイクロソフトのサイトに WMI Code Creator というユーティリティがあり,これも WMI の各クエリーとそのプロパティ等を確認することができます.
これは,前述の WMI and SMART Component のデモプログラムと違い,全てのプロパティのリストではなく,取得したいプロパティを選択する方式となっています.また,プログラムのソースコードを生成する機能もありますが,Delphiのコードは生成できません.

WMI Delphi Code Creator と同じように,名前空間の一覧と,各々の名前空間で使用可能なクラスの一覧から選択してクエリーを実行することができます.



図23
WMI Code Creator
図24
使用可能な名前空間の一覧
図25
名前空間 CIMV2 で使用可能なクラス名の一覧




16_Delphi XE3 の Winapi.Wbem ユニット


Delphi XE3 で WMI を扱うユニット,Winapi.Wbem が追加されています.このユニットを使用したテストプログラムです.このユニットには IEnumWbemClassObject, IWbemClassObject というインターフェイスが実装されていて,プロパティの処理が便利になっています.
ユニット内のコードは,WbemScripting_TLB とはだいぶ違います.

下のコードは,ローカルコンピュータの全論理ディスクの全てのプロパティを取得します.



図26
実行結果

リスト12
Delphi XE3 の Winapi.Wbem ユニット
全ディスクの全プロパティを取得
unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

uses
  Winapi.ActiveX,
  Winapi.Wbem;

{$R *.dfm}

const
  //偽装レベルの定数
  RPC_C_AUTHN_LEVEL_DEFAULT   = 0;
  RPC_C_IMP_LEVEL_ANONYMOUS   = 1;
  RPC_C_IMP_LEVEL_IDENTIFY    = 2;
  RPC_C_IMP_LEVEL_IMPERSONATE = 3;
  RPC_C_IMP_LEVEL_DELEGATE    = 4;

  //認証サービスの定数
  RPC_C_AUTHN_WINNT      = 10;
  RPC_C_AUTHN_LEVEL_CALL = 3;
  RPC_C_AUTHN_DEFAULT    = Longint($FFFFFFFF);
  EOAC_NONE              = 0;

  //承認定数
  RPC_C_AUTHZ_NONE       = 0;
  RPC_C_AUTHZ_NAME       = 1;
  RPC_C_AUTHZ_DCE        = 2;
  RPC_C_AUTHZ_DEFAULT    = Longint($FFFFFFFF);

//=============================================================================
//  Winapi.Wbemユニット(Delphi XE3以降)を使用したWMIのサンプル
//  ローカルコンピュータの論理ディスクの(ほぼ)全てのプロパティを取得して表示
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
const
  WbemUser      = '';
  WbemPassword  = '';
  WbemComputer  = 'localhost';
  WbemLocale    = '';
var
  LWbemLocator  : IWbemLocator;
  LWbemServices : IWbemServices;
  ppEnum        : IEnumWbemClassObject;
  apObjects     : IWbemClassObject;
  puReturned    : ULONG;
  OpResult      : HRESULT;
  strName       : WideString;
  PropValue     : OleVariant;
  fmtStrStr     : String;
begin
  Memo1.Lines.Clear;
  Memo1.Lines.BeginUpdate;

  OpResult := CoCreateInstance(
                          CLSID_WbemLocator,
                          nil,
                          CLSCTX_INPROC_SERVER,
                          IID_IWbemLocator,
                          LWbemLocator);
  if Failed(OpResult) then exit;

  //WMIサービスへの接続
  OpResult := LWbemLocator.ConnectServer(
                          Format('\\%s\root\CIMV2', [WbemComputer]),
                          WbemUser,
                          WbemPassword,
                          WbemLocale,
                          WBEM_FLAG_CONNECT_USE_MAX_WAIT,
                          '',
                          nil,
                          LWbemServices);

  if Succeeded(OpResult) then begin
    //セキュリティレベルの設定
    if Failed(CoSetProxyBlanket(
                          LWbemServices,
                          RPC_C_AUTHN_WINNT,
                          RPC_C_AUTHZ_NONE,
                          nil,
                          RPC_C_AUTHN_LEVEL_CALL,
                          RPC_C_IMP_LEVEL_IMPERSONATE,
                          nil,
                          EOAC_NONE)) then Exit;

    //クエリーの実行
    LWbemServices.ExecQuery(
                          'WQL',
                          'SELECT * FROM Win32_LogicalDisk',
                          WBEM_FLAG_FORWARD_ONLY,
                          nil,
                          ppEnum);

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

    //プロパティセットの列挙
    while ppEnum.Next(Integer(WBEM_INFINITE), 1, apObjects, puReturned)=0 do begin

      //プロパティの列挙開始
      if (apObjects.BeginEnumeration(
          WBEM_FLAG_NONSYSTEM_ONLY) = WBEM_S_NO_ERROR) then begin

        //各プロパティの取得
        while apObjects.Next(
            0, @strName, @PropValue, nil, nil) = WBEM_S_NO_ERROR do begin

          fmtStrStr := Format('%-32s', [strName]);
          Memo1.Lines.Add(fmtStrStr + VarToStrDef(PropValue, ''));
          VarClear(PropValue);
        end;
      end;
      Memo1.Lines.Add('--------------------------------------');
    end;
  end;
  Memo1.Lines.EndUpdate;
end;

end.


特定のプロパティだけを取得する場合は,例えば次のようになります.
pVal は Variant 型,pType は CIMTYPE 型,plFlavor は PInteger 型の変数です.


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

    //プロパティセットの列挙
    while ppEnum.Next(Integer(WBEM_INFINITE), 1, apObjects, puReturned)=0 do begin
      pType    := 0;
      plFlavor := nil;

      apObjects.Get('Name', 0, pVal, @pType, plFlavor);
      Memo1.Lines.Add(Format('%-32s', ['Name']) + VarToStr(pVal));

      apObjects.Get('Size', 0, pVal, @pType, plFlavor);
      Memo1.Lines.Add(Format('%-32s', ['Size']) + VarToStr(pVal));

      apObjects.Get('FreeSpace', 0, pVal, @pType, plFlavor);
      Memo1.Lines.Add(Format('%-32s', ['FreeSpace']) + VarToStr(pVal));

      Memo1.Lines.Add('--------------------------------------');
      VarClear(pVal);
    end;