VBAからWMIでシステム情報を取得する実践ガイド

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

VBAからWMIでシステム情報を取得する実践ガイド

背景と要件

Windows Management Instrumentation (WMI) は、Windowsオペレーティングシステムとそのコンポーネントに関する管理データにアクセスするためのMicrosoftのインターフェースです。システムの状態監視、構成変更、トラブルシューティングなど、多岐にわたるタスクで利用されます。VBA(Visual Basic for Applications)からWMIを利用することで、ExcelやAccessといったOfficeアプリケーション内から、外部ツールを使うことなく、PCのOS情報、CPU、メモリ、ネットワーク設定といった詳細なシステム情報を取得し、レポート作成や資産管理の自動化が可能になります。 、VBAからWMIを介してシステム情報を取得する具体的な方法について、以下の要件を満たす形で解説します。

  • 外部ライブラリは使用せず、標準のVBA機能とWMI(COMオブジェクト)のみで実装します。

  • ExcelおよびAccessを対象とし、実務で再現可能なVBAコードを少なくとも2本提供します。

  • 処理性能を最大化するためのチューニング手法(配列バッファ、ScreenUpdatingCalculationなど)を数値的な観点から解説します。

  • 処理フローをMermaid形式の図で視覚化します。

  • 実行手順、ロールバック方法、および運用上の注意点についても言及します。

設計

アーキテクチャ概要

VBAからWMIを利用する際の基本的なアーキテクチャは以下の通りです。

  1. VBAアプリケーション: ExcelやAccessのVBAプロジェクト内でコードを実行します。

  2. COMオブジェクト: VBAはGetObject関数を通じてWMIのCOMインターフェース(WbemScripting.SWbemLocator)に接続します。

  3. WMIサービス: WMIサービスはOS内部のさまざまなプロバイダー(WMIクラスの実体)と通信し、要求されたシステム情報を収集します。

  4. OS/ハードウェア: WMIプロバイダーは、Windowsカーネル、ドライバー、またはレジストリから直接、具体的なシステム情報を受け取ります。

主要なWMIクラスの選定とデータモデル

システム情報取得のために主に使用するWMIクラスと、取得するプロパティは以下の通りです。

  • Win32_OperatingSystem:

    • Caption: OSの名称 (例: Microsoft Windows 10 Pro)

    • Version: OSのバージョン

    • OSArchitecture: OSのアーキテクチャ (例: 64-bit)

    • InstallDate: OSのインストール日時 (UTC)

  • Win32_ComputerSystem:

    • Name: コンピューター名

    • Manufacturer: 製造元

    • Model: モデル名

    • TotalPhysicalMemory: 搭載されている合計物理メモリ (バイト単位)

  • Win32_Processor:

    • Name: プロセッサの名称

    • NumberOfCores: 物理コア数

    • NumberOfLogicalProcessors: 論理プロセッサ数 (ハイパースレッディング有効時)

  • Win32_NetworkAdapterConfiguration: (IPアドレスが有効なアダプタのみ)

    • Description: ネットワークアダプタの名称

    • MACAddress: MACアドレス

    • IPAddress: 割り当てられたIPアドレスの配列

    • DefaultIPGateway: デフォルトゲートウェイの配列

    • DNSHostName: DNSホスト名

性能最適化の戦略

VBAから大量のデータを処理したり、頻繁にシート(またはAccessのフォーム)を更新したりする場合、性能が著しく低下することがあります。以下の戦略で最適化を図ります。

  1. Application.ScreenUpdating = False (Excel):

    • 画面の再描画を一時停止し、処理完了後にまとめて表示することで、UI更新によるオーバーヘッドを削減します。体感で数倍から数十倍の速度向上が見込めます。
  2. Application.Calculation = xlCalculationManual (Excel):

    • Excelの自動計算機能を手動モードに切り替えることで、セルへのデータ書き込み時の再計算を抑制します。データ量や計算式の複雑さにもよりますが、こちらも数倍から数十倍の速度向上に寄与します。
  3. 配列バッファリング:

    • WMIから取得したデータを直接セルに書き込むのではなく、一旦VBA内部の配列に格納します。全てのデータが配列に揃った後、Range.Value = Arrayのように一括でシートに書き込みます。これにより、セルへのアクセス回数を大幅に削減し、I/Oオーバーヘッドを最小限に抑えます。数百行から数千行のデータでは、セル単位での書き込みに比べて100倍以上の速度差が出ることも珍しくありません。

処理フロー図 (Mermaid)

VBAからWMIでシステム情報を取得し、Excelシートに出力する一般的な処理フローを示します。

graph TD
    A["VBAスクリプト開始"] --> B{"Application設定最適化"};
    B --> C["WMIサービス接続|GetObject(\"winmgmts:\\.\root\cimv2\")"];
    C -- 成功の場合 --> D{"WMIクエリ実行|ExecQuery(\"SELECT ... FROM ...\")"};
    D -- クエリ成功 --> E["結果セットの取得と反復処理"];
    E --> F["取得データの配列への格納"];
    F --> G["配列データをシート/テーブルに出力"];
    G --> H["Application設定復元"];
    H --> I["VBAスクリプト終了"];
    C -- 失敗の場合 --> J["エラー処理|WMIサービス接続失敗"];
    D -- クエリ失敗 --> K["エラー処理|WMIクエリ失敗"];
    E -- データ処理中にエラー --> L["エラー処理|データ処理エラー"];

実装

WMIを使用するためには、VBAエディタで「ツール」->「参照設定」を開き、Microsoft Scripting RuntimeMicrosoft WMI Scripting Library (または Microsoft VBScript Regular Expressions) を参照設定する必要がある場合があります。しかし、GetObject関数を使用する場合、動的バインディングにより参照設定なしでも動作することが多いため、ここでは参照設定は必須とはしません。ただし、IntelliSenseを効かせたい場合は参照設定してください。

コード例1: 基本的なシステム情報の取得とExcelシートへの出力

この例では、OS、CPU、メモリ、コンピューター名などの基本情報を取得し、Excelシートに整理して出力します。

実行手順 (Excel):

  1. 新しいExcelブックを開きます。

  2. Alt + F11 を押してVBAエディタを開きます。

  3. 「挿入」->「標準モジュール」を選択し、新しいモジュールに以下のコードを貼り付けます。

  4. Excelシートに戻り、開発タブ(リボンにない場合はファイル->オプション->リボンのユーザー設定で表示)からマクロを選択し、GetBasicSystemInfoを実行します。または、任意の図形にマクロを割り当てて実行します。

ロールバック方法:

  • シートに出力されたデータを手動で削除します。

  • モジュールからコードを削除します。

Option Explicit

' WMIから基本的なシステム情報を取得し、Excelシートに出力する
Public Sub GetBasicSystemInfo()
    Dim objWMIService As Object
    Dim colItems As Object
    Dim objItem As Object
    Dim ws As Worksheet
    Dim lastRow As Long
    Dim varData(1 To 100, 1 To 2) As Variant ' 配列バッファ (最大100行x2列)
    Dim rowIdx As Long
    Dim originalScreenUpdating As Boolean
    Dim originalCalculation As Long

    ' --- 性能最適化設定 ---
    ' 画面描画の停止
    originalScreenUpdating = Application.ScreenUpdating
    Application.ScreenUpdating = False
    ' 自動計算の停止
    originalCalculation = Application.Calculation
    Application.Calculation = xlCalculationManual

    On Error GoTo ErrorHandler

    Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
    ws.Name = "SystemInfo_" & Format(Now, "yyyyMMdd_HHmmss")
    rowIdx = 1

    ' ヘッダーの書き込み
    varData(rowIdx, 1) = "項目": varData(rowIdx, 2) = "値"
    rowIdx = rowIdx + 1

    ' WMIサービスへの接続
    ' 外部ライブラリ不使用のためGetObjectを使用し、動的バインディングを行う
    Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")

    ' --- OS情報の取得 ---
    Set colItems = objWMIService.ExecQuery("SELECT Caption, Version, OSArchitecture, InstallDate FROM Win32_OperatingSystem")
    For Each objItem In colItems
        varData(rowIdx, 1) = "OS名": varData(rowIdx, 2) = objItem.Caption
        rowIdx = rowIdx + 1
        varData(rowIdx, 1) = "バージョン": varData(rowIdx, 2) = objItem.Version
        rowIdx = rowIdx + 1
        varData(rowIdx, 1) = "アーキテクチャ": varData(rowIdx, 2) = objItem.OSArchitecture
        rowIdx = rowIdx + 1
        If Not IsNull(objItem.InstallDate) Then
            ' WMIのInstallDateはUTCで"YYYYMMDDHHMMSS.mmmmmm+UUU"形式なので変換
            varData(rowIdx, 1) = "インストール日時": varData(rowIdx, 2) = CDate(Left(objItem.InstallDate, 14))
        Else
            varData(rowIdx, 1) = "インストール日時": varData(rowIdx, 2) = "N/A"
        End If
        rowIdx = rowIdx + 1
    Next

    ' --- コンピューター情報の取得 ---
    Set colItems = objWMIService.ExecQuery("SELECT Name, Manufacturer, Model, TotalPhysicalMemory FROM Win32_ComputerSystem")
    For Each objItem In colItems
        varData(rowIdx, 1) = "コンピューター名": varData(rowIdx, 2) = objItem.Name
        rowIdx = rowIdx + 1
        varData(rowIdx, 1) = "製造元": varData(rowIdx, 2) = objItem.Manufacturer
        rowIdx = rowIdx + 1
        varData(rowIdx, 1) = "モデル": varData(rowIdx, 2) = objItem.Model
        rowIdx = rowIdx + 1
        ' TotalPhysicalMemoryはバイト単位なのでGBに変換
        varData(rowIdx, 1) = "物理メモリ総量": varData(rowIdx, 2) = Format((objItem.TotalPhysicalMemory / (1024 ^ 3)), "0.00") & " GB"
        rowIdx = rowIdx + 1
    Next

    ' --- プロセッサ情報の取得 ---
    Set colItems = objWMIService.ExecQuery("SELECT Name, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor")
    For Each objItem In colItems
        varData(rowIdx, 1) = "プロセッサ名": varData(rowIdx, 2) = objItem.Name
        rowIdx = rowIdx + 1
        varData(rowIdx, 1) = "物理コア数": varData(rowIdx, 2) = objItem.NumberOfCores
        rowIdx = rowIdx + 1
        varData(rowIdx, 1) = "論理プロセッサ数": varData(rowIdx, 2) = objItem.NumberOfLogicalProcessors
        rowIdx = rowIdx + 1
    Next

    ' 配列の内容をシートに一括書き込み
    ws.Range("A1").Resize(rowIdx - 1, UBound(varData, 2)).Value = varData
    ws.Columns("A:B").AutoFit ' 列幅の自動調整

Finalize:
    ' --- 性能最適化設定の復元 ---
    Application.ScreenUpdating = originalScreenUpdating
    Application.Calculation = originalCalculation

    Set objWMIService = Nothing
    Set colItems = Nothing
    Set objItem = Nothing
    Set ws = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description & vbCrLf & "エラー番号: " & Err.Number, vbCritical
    GoTo Finalize
End Sub

コード例2: ネットワークアダプタ情報の取得とAccessテーブルへの出力

この例では、IPアドレスが有効なネットワークアダプタの情報を取得し、Accessの新規テーブルに出力します。

実行手順 (Access):

  1. 新しいAccessデータベースを開きます。

  2. Alt + F11 を押してVBAエディタを開きます。

  3. 「挿入」->「標準モジュール」を選択し、新しいモジュールに以下のコードを貼り付けます。

  4. VBAエディタで F5 キーを押して GetNetworkAdapterInfo を実行します。または、Accessのナビゲーションウィンドウからモジュールをダブルクリックし、GetNetworkAdapterInfo を選択して実行します。

ロールバック方法:

  • 作成されたテーブル (NetworkAdapters_YYYYMMDD_HHMMSS) を手動で削除します。

  • モジュールからコードを削除します。

Option Explicit

' WMIからネットワークアダプタ情報を取得し、Accessテーブルに出力する
Public Sub GetNetworkAdapterInfo()
    Dim objWMIService As Object
    Dim colItems As Object
    Dim objItem As Object
    Dim db As DAO.Database
    Dim rs As DAO.Recordset
    Dim tdf As DAO.TableDef
    Dim fld As DAO.Field
    Dim tableName As String
    Dim ipAddress As Variant
    Dim gateway As Variant

    On Error GoTo ErrorHandler

    Set db = CurrentDb
    tableName = "NetworkAdapters_" & Format(Now, "yyyyMMdd_HHmmss")

    ' 新しいテーブル定義の作成
    Set tdf = db.CreateTableDef(tableName)
    With tdf
        Set fld = .CreateField("ID", dbLong)
        fld.Attributes = fld.Attributes Or dbAutoIncrField ' オートナンバー
        .Fields.Append fld

        .Fields.Append .CreateField("Description", dbText, 255)
        .Fields.Append .CreateField("MACAddress", dbText, 20)
        .Fields.Append .CreateField("IPAddress", dbText, 255) ' 複数の場合を考慮し文字列で結合
        .Fields.Append .CreateField("DefaultIPGateway", dbText, 255) ' 複数の場合を考慮し文字列で結合
        .Fields.Append .CreateField("DNSHostName", dbText, 255)
    End With
    db.TableDefs.Append tdf
    Set tdf = Nothing

    ' WMIサービスへの接続
    Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")

    ' IPアドレスが有効なネットワークアダプタの情報を取得
    ' WHERE句でIPEnabled=TRUEを指定して、物理/仮想アダプタでIPが割り当てられているものに絞る
    Set colItems = objWMIService.ExecQuery("SELECT Description, MACAddress, IPAddress, DefaultIPGateway, DNSHostName FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = TRUE")

    ' レコードセットを開いてデータを追加
    Set rs = db.OpenRecordset(tableName, dbOpenTable)
    For Each objItem In colItems
        With rs
            .AddNew
            .Fields("Description") = objItem.Description
            .Fields("MACAddress") = IIf(IsNull(objItem.MACAddress), "", objItem.MACAddress) ' Nullチェック

            ' IPAddressは配列の可能性があるため結合して文字列にする
            If Not IsNull(objItem.IPAddress) Then
                .Fields("IPAddress") = Join(objItem.IPAddress, ", ")
            Else
                .Fields("IPAddress") = ""
            End If

            ' DefaultIPGatewayも配列の可能性があるため結合して文字列にする
            If Not IsNull(objItem.DefaultIPGateway) Then
                .Fields("DefaultIPGateway") = Join(objItem.DefaultIPGateway, ", ")
            Else
                .Fields("DefaultIPGateway") = ""
            End If

            .Fields("DNSHostName") = IIf(IsNull(objItem.DNSHostName), "", objItem.DNSHostName) ' Nullチェック
            .Update
        End With
    Next

    MsgBox "ネットワークアダプタ情報をテーブル '" & tableName & "' に出力しました。", vbInformation

Finalize:
    If Not rs Is Nothing Then rs.Close
    Set rs = Nothing
    Set db = Nothing
    Set objWMIService = Nothing
    Set colItems = Nothing
    Set objItem = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description & vbCrLf & "エラー番号: " & Err.Number & vbCrLf & _
           "エラーソース: " & Err.Source, vbCritical
    ' エラー時に作成途中のテーブルがあれば削除
    If Not db Is Nothing And Not tableName = "" Then
        On Error Resume Next ' DeleteTableが失敗しても処理を続行
        db.DeleteTable tableName
        On Error GoTo 0
    End If
    GoTo Finalize
End Sub

検証

上記コードの検証は、以下の点に注目して行います。

  1. 実行結果の正確性:

    • Excel: GetBasicSystemInfoマクロを実行後、新しく作成されたシート (SystemInfo_YYYYMMDD_HHMMSS) の内容が、Windowsの「設定」->「システム」->「バージョン情報」や「タスクマネージャー」->「パフォーマンス」タブ、またはsysteminfoコマンドの出力と一致しているか確認します。特に、OS名、バージョン、物理メモリ総量、プロセッサ名が正しいことを確認します。インストール日時はWMIのUTC表記をVBAで変換しているため、少し異なる場合がありますが、日付自体が大きくずれていないか確認します。

    • Access: GetNetworkAdapterInfoマクロを実行後、作成されたテーブル (NetworkAdapters_YYYYMMDD_HHMMSS) を開きます。ipconfig /allコマンドの出力と比較し、各ネットワークアダプタのDescription、MACAddress、IPAddress、DefaultIPGateway、DNSHostNameが正しく取得されているか確認します。

  2. 性能の体感:

    • Excelマクロ実行時、シートへの書き込みが瞬時に行われることを確認します(ScreenUpdatingと配列バッファリングの効果)。これらの最適化をコメントアウトして実行した場合と比較すると、その差は歴然とします。
  3. エラーハンドリング:

    • 例えば、一時的にWMIサービスを停止させた状態で(非推奨ですがテスト目的で)、または存在しないWMIクラスをクエリするようコードを一時的に変更し、エラーメッセージが適切に表示されるか確認します。

運用

実行の自動化とスケジュール

  • Excel: 定期的なシステム情報収集には、Excelファイルをタスクスケジューラから起動し、VBAマクロを自動実行する設定が考えられます。excel.exe /m "YourWorkbook.xlsm!ModuleName.MacroName" の形式でコマンドラインから実行可能です。

  • Access: 同様に、タスクスケジューラからAccessデータベースを開き、VBAコードを実行できます。msaccess.exe "YourDatabase.accdb" /x MacroName の形式で、Accessマクロを介してVBAコードを呼び出すことができます。

スクリプトの配置場所

  • 共有ネットワークドライブ: 複数のユーザーがアクセスできるように、ネットワークドライブに配置することが考えられます。ただし、セキュリティゾーンの設定やアクセス権に注意が必要です。

  • 各PCローカル: ユーザー自身のPCで実行させる場合は、ローカルディスクに配置します。

セキュリティと権限

WMIはシステム情報を扱うため、適切な権限が必要です。通常、ローカル管理者権限を持つユーザーであればWMIへのアクセスは問題ありませんが、ドメイン環境などでは特定のWMIクラスへのアクセス権限が制限されている場合があります。スクリプトが想定通りに動作しない場合は、実行ユーザーの権限を確認してください。

落とし穴と対策

  1. WMIサービスが停止している:

    • 現象: GetObject呼び出しでエラーが発生します。

    • 対策: エラーハンドリングでWMIサービスが利用可能か確認し、必要であればユーザーにサービス開始を促すか、ログに記録します。サービスが停止していることは稀ですが、システム異常時には発生し得ます。

  2. WMIクエリのパフォーマンス:

    • 現象: 複雑なWMIクエリや、大量のインスタンスを持つクラス(例: Win32_LoggedOnUser)をクエリすると、処理に時間がかかります。

    • 対策: SELECT * ではなく、必要なプロパティのみを明示的に指定します(例: SELECT Name, Version FROM Win32_OperatingSystem)。WHERE句で条件を絞り込み、取得するインスタンス数を減らします。

  3. データ型の不一致(特に配列):

    • 現象: IPAddressDefaultIPGatewayのように、WMIのプロパティが配列を返す場合があります。これを直接VBAのString型変数に代入しようとすると型不一致エラーになります。

    • 対策: Join()関数を使って配列の要素を文字列に結合するか、ループで個々の要素を処理します。コード例2ではJoin()を使用しています。

  4. NULL値の扱い:

    • 現象: WMIプロパティの中には値が存在しない場合にNullを返すものがあります。これをVBAで直接文字列結合などしようとするとエラーになります。

    • 対策: IsNull()関数でチェックし、Nullの場合は空文字列("")や「N/A」などの代替値を代入します。コード例2のIIf(IsNull(...), "", ...) のように使用します。

まとめ

VBAからWMIを利用することで、Windowsシステムの詳細な情報を取得し、Officeアプリケーション内で自動的に処理・レポート化することが可能です。本記事では、基本的なシステム情報およびネットワークアダプタ情報を取得するVBAコードを、ExcelとAccessそれぞれに向けて提供しました。

特に、Application.ScreenUpdating = FalseApplication.Calculation = xlCalculationManual、そして配列バッファリングといった性能最適化手法は、VBAにおけるWMIデータ処理の効率を劇的に向上させます。これらの技術を組み合わせることで、数百件から数千件のデータでも高速に処理し、実用的な自動化ツールを構築できます。

運用においては、適切なエラーハンドリング、セキュリティ権限の考慮、および性能への影響を理解することが重要です。WMIは非常に強力なツールであり、この記事で紹介した内容を基盤として、さらに多くのシステム管理タスクをVBAから自動化できる可能性を秘めています。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました