本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
VBAからWMIでPC情報を取得
背景/要件
背景
現代のビジネス環境において、IT資産管理やシステム監視は不可欠です。各PCのハードウェア構成、OSバージョン、ネットワーク設定といった情報を手動で収集することは、膨大な時間と労力を要し、ヒューマンエラーのリスクも伴います。 Windows Management Instrumentation (WMI) は、Windowsオペレーティングシステムに標準搭載された管理インフラストラクチャであり、PCのあらゆる情報を統一された方法で取得・管理することを可能にします。VBA (Visual Basic for Applications) を用いることで、ExcelやAccessといった普段使い慣れたOfficeアプリケーションから、このWMIの強力な機能にアクセスし、PC情報を自動で取得・集計するシステムを構築できます。これにより、手作業による情報収集の手間を劇的に削減し、常に最新かつ正確なデータを維持することが可能になります。
要件
、以下の要件を満たすVBAとWMI連携ソリューションについて解説します。
広範な情報取得: OS情報、CPU、メモリ、ディスク、ネットワークアダプタといった基本的なPC情報をWMI経由で取得します。
Officeアプリケーションとの連携: ExcelとAccessを対象とし、それぞれの特性に応じた情報取得・管理ロジックを提供します。
性能最適化: 大量データや複数PCからの情報取得を想定し、処理性能を最大限に高めるためのVBAコーディングテクニック(配列バッファ、画面更新抑制、計算モード、DAO/ADO最適化)を導入します。
堅牢性: エラーハンドリング、オブジェクト解放など、安定稼働のための考慮を行います。
Win32 APIの活用: 必要に応じて、WMIでは得にくい、または補完的な情報を取得するためにWin32 APIを宣言し使用します。
設計
WMIを使ったPC情報取得の基本的な流れは、WMIサービスへの接続、WQL (WMI Query Language) を用いた情報問い合わせ、取得した結果セットの処理です。VBAからはCOMオブジェクトを介してこれらの操作を行います。
処理フローの設計
VBAからWMIを操作する際の処理フローを以下に示します。
graph TD
A["VBAスクリプト開始"] --> B{"WMIサービス接続"};
B -- |成功| --> C["WQLクエリ生成"];
B -- |失敗| --> H["エラー処理"];
C --> D{"クエリ実行"};
D -- |成功| --> E["結果セット取得"];
D -- |失敗| --> H;
E --> F["取得データの処理 (Excel/Accessへ格納)"];
F --> G["WMIオブジェクト解放"];
G --> I["処理終了"];
H --> I;
データモデル(取得情報例)
WMIは多様なクラスを提供しますが、ここでは以下の情報を取得対象とします。
OS情報:
Win32_OperatingSystem(OS名, バージョン, ビルド番号, インストール日付)CPU情報:
Win32_Processor(CPU名, コア数, スレッド数)メモリ情報:
Win32_ComputerSystem(合計物理メモリ)ディスク情報:
Win32_LogicalDisk(ドライブレター, 容量, 空き容量)ネットワークアダプタ情報:
Win32_NetworkAdapterConfiguration(IPアドレス, MACアドレス)
性能チューニングの設計原則
Excel:
Application.ScreenUpdating = False: 画面更新を抑制し、描画によるオーバーヘッドを削減。Application.Calculation = xlCalculationManual: 計算モードを手動にし、セル値変更ごとの再計算を抑制。Application.EnableEvents = False: イベント発生を抑制し、イベントハンドラによるオーバーヘッドを削減。配列バッファ: WMIから取得したデータを一度VBAの配列に格納し、その配列をワークシートのRangeオブジェクトに一括で書き込む。これにより、セルへの個別の書き込み処理を劇的に削減します。
Access:
- DAO/ADO最適化: 複数のレコードをデータベースに挿入する際は、
CurrentDb.ExecuteメソッドによるSQLアクションクエリ (INSERT INTO) を利用するか、またはRecordsetオブジェクトをAddNew/Updateで操作する際にトランザクション処理 (BeginTrans/CommitTrans) を適用し、ディスクI/O回数を最小限に抑えます。
- DAO/ADO最適化: 複数のレコードをデータベースに挿入する際は、
Win32 APIの利用
WMIは多くの情報を提供しますが、例えば、ホスト名をWMIから取得する以外に、VBAのネイティブ関数やWin32 APIで取得することも可能です。ここでは、GetComputerName APIを例に、VBAでのWin32 API宣言と利用方法を示します。これは、WMIが利用できない、またはWMIアクセスが遅い場合に代替手段として機能する可能性を示唆します。
' Win32 API の宣言例
#If VBA7 Then
Private Declare PtrSafe Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, ByRef nSize As Long) As Long
#Else
Private Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, ByRef nSize As Long) As Long
#End If
実装
コード例1: ExcelでPC情報を取得しワークシートに出力
この例では、現在実行中のPCのOS、CPU、メモリ、ディスク、ネットワークアダプタの情報をExcelワークシートに整理して出力します。性能最適化のため、画面更新と計算モードを無効化し、配列バッファを用いてデータを一括で書き込みます。また、Win32 APIでコンピュータ名を取得する例も含まれます。
' Excel VBA コード
Option Explicit
' Win32 API の宣言
' コンピュータ名を取得するために使用
#If VBA7 Then
Private Declare PtrSafe Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, ByRef nSize As Long) As Long
#Else
Private Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, ByRef nSize As Long) As Long
#End If
Sub GetPcInfoToExcel()
Dim objWMIService As Object
Dim colItems As Object
Dim objItem As Object
Dim ws As Worksheet
Dim lastRow As Long
Dim i As Long
Dim arrData() As Variant ' 配列バッファ
Dim varBufferSize As Long
Dim strBuffer As String
Dim lngResult As Long
Dim startTime As Double
' 性能計測開始
startTime = Timer
' シートの設定
Set ws = ThisWorkbook.Sheets("PC情報") ' "PC情報" シートが存在すること
On Error Resume Next
Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
ws.Name = "PC情報"
On Error GoTo ErrorHandler
ws.Cells.ClearContents
' 性能最適化設定
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
' ヘッダーの書き込み (配列バッファの準備)
ReDim arrData(1 To 20, 1 To 2) ' 最大20行、2列 (項目, 値) を想定
arrData(1, 1) = "項目": arrData(1, 2) = "値"
lastRow = 1
' Win32 APIでコンピュータ名を取得
varBufferSize = 255
strBuffer = String$(varBufferSize, Chr$(0))
lngResult = GetComputerName(strBuffer, varBufferSize)
If lngResult <> 0 Then
lastRow = lastRow + 1
arrData(lastRow, 1) = "コンピュータ名 (API)": arrData(lastRow, 2) = Left$(strBuffer, varBufferSize)
End If
' WMIサービスへの接続
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
' --- OS情報取得 ---
Set colItems = objWMIService.ExecQuery("SELECT Caption, Version, BuildNumber, InstallDate FROM Win32_OperatingSystem")
For Each objItem In colItems
lastRow = lastRow + 1: arrData(lastRow, 1) = "OS名": arrData(lastRow, 2) = objItem.Caption
lastRow = lastRow + 1: arrData(lastRow, 1) = "OSバージョン": arrData(lastRow, 2) = objItem.Version
lastRow = lastRow + 1: arrData(lastRow, 1) = "ビルド番号": arrData(lastRow, 2) = objItem.BuildNumber
lastRow = lastRow + 1: arrData(lastRow, 1) = "インストール日付": arrData(lastRow, 2) = WMITimeToDate(objItem.InstallDate)
Next
' --- CPU情報取得 ---
Set colItems = objWMIService.ExecQuery("SELECT Name, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor")
For Each objItem In colItems
lastRow = lastRow + 1: arrData(lastRow, 1) = "CPU名": arrData(lastRow, 2) = objItem.Name
lastRow = lastRow + 1: arrData(lastRow, 1) = "コア数": arrData(lastRow, 2) = objItem.NumberOfCores
lastRow = lastRow + 1: arrData(lastRow, 1) = "スレッド数": arrData(lastRow, 2) = objItem.NumberOfLogicalProcessors
Next
' --- メモリ情報取得 ---
Set colItems = objWMIService.ExecQuery("SELECT TotalPhysicalMemory FROM Win32_ComputerSystem")
For Each objItem In colItems
lastRow = lastRow + 1: arrData(lastRow, 1) = "合計物理メモリ (GB)": arrData(lastRow, 2) = Format(objItem.TotalPhysicalMemory / (1024 ^ 3), "0.00")
Next
' --- ディスク情報取得 ---
Set colItems = objWMIService.ExecQuery("SELECT Caption, Size, FreeSpace FROM Win32_LogicalDisk WHERE DriveType = 3") ' DriveType=3: ローカルディスク
For Each objItem In colItems
lastRow = lastRow + 1: arrData(lastRow, 1) = "ドライブ (" & objItem.Caption & ")": arrData(lastRow, 2) = _
Format(objItem.FreeSpace / (1024 ^ 3), "0.00") & " GB (空き) / " & Format(objItem.Size / (1024 ^ 3), "0.00") & " GB (合計)"
Next
' --- ネットワークアダプタ情報取得 ---
Set colItems = objWMIService.ExecQuery("SELECT Description, MACAddress, IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = TRUE")
Dim ipAddresses As Variant
For Each objItem In colItems
lastRow = lastRow + 1: arrData(lastRow, 1) = "NIC (" & objItem.Description & ")"
lastRow = lastRow + 1: arrData(lastRow, 1) = " MACアドレス": arrData(lastRow, 2) = objItem.MACAddress
If Not IsNull(objItem.IPAddress) Then
ipAddresses = objItem.IPAddress
For i = LBound(ipAddresses) To UBound(ipAddresses)
lastRow = lastRow + 1: arrData(lastRow, 1) = " IPアドレス": arrData(lastRow, 2) = ipAddresses(i)
Next i
End If
Next
' 配列のサイズを実際のデータ数に調整
If lastRow > UBound(arrData, 1) Then ReDim Preserve arrData(1 To lastRow, 1 To UBound(arrData, 2))
' ワークシートへの一括書き込み
ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, 2)).Value = arrData
' 書式設定
With ws
.Columns("A:B").AutoFit
.Range("A1:B1").Font.Bold = True
.Range("A1:B1").Interior.Color = RGB(220, 230, 241)
.Cells.VerticalAlignment = xlVAlignTop
End With
' 性能最適化設定を元に戻す
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
.EnableEvents = True
End With
MsgBox "PC情報の取得が完了しました。処理時間: " & Format(Timer - startTime, "0.00") & "秒", vbInformation
Exit Sub
ErrorHandler:
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
.EnableEvents = True
End With
MsgBox "エラーが発生しました: " & Err.Description, vbCritical
End Sub
' WMI日付時刻形式をVBAのDate型に変換するヘルパー関数
Function WMITimeToDate(strWMIDate As String) As Date
On Error Resume Next
If Len(strWMIDate) >= 14 Then
WMITimeToDate = DateSerial(CInt(Mid(strWMIDate, 1, 4)), _
CInt(Mid(strWMIDate, 5, 2)), _
CInt(Mid(strWMIDate, 7, 2))) + _
TimeSerial(CInt(Mid(strWMIDate, 9, 2)), _
CInt(Mid(strWMIDate, 11, 2)), _
CInt(Mid(strWMIDate, 13, 2)))
Else
WMITimeToDate = CDate("1900/01/01") ' 無効な日付の場合はデフォルト値を返す
End If
On Error GoTo 0
End Function
性能チューニングの数値例:
上記Excelコードでは、WMIから取得した約20~30項目(複数NIC、ディスクを含む)の情報を
Range.Value = arrDataで一括書き込みしています。もしこれを
ws.Cells(lastRow, 1).Value = ...のように個別のセルに書き込む場合、数十ミリ秒で完了する処理が数百ミリ秒〜数秒かかる可能性があります。特に、100行を超えるデータや複数のシートに書き込む場合は、その差は顕著となり、配列バッファを利用することで処理速度を5〜10倍以上改善できる場合があります。ScreenUpdating = Falseも視覚的なチラつきを抑えるだけでなく、描画処理のオーバーヘッドを削減し、体感速度・実処理速度の両方で効果を発揮します。
コード例2: Accessで複数PCの情報をテーブルに格納
この例では、複数のPC名をリストアップし、それぞれのPCからOS情報を取得してAccessのテーブルに格納します。DAO.Database.Executeメソッドを使用してSQLアクションクエリを実行することで、複数のレコードを効率的に挿入します。
' Access VBA コード
Option Compare Database
Option Explicit
Sub GetRemotePcOsInfoToAccess()
Dim db As DAO.Database
Dim strPCNames() As String ' 複数PCのリスト
Dim strPCName As Variant
Dim objWMIService As Object
Dim colItems As Object
Dim objItem As Object
Dim strSQL As String
Dim startTime As Double
Dim i As Long
' 性能計測開始
startTime = Timer
Set db = CurrentDb
' 対象PC名のリスト (例: 実際にはテーブルやファイルから読み込む)
' ローカルPCのみの場合は ".\" を指定
strPCNames = Split(".,PC01,PC02", ",") ' 実際の環境に合わせて変更してください
' テーブルが存在しない場合は作成
On Error Resume Next
db.Execute "CREATE TABLE T_PcOsInfo (" & _
"PCName TEXT(255), " & _
"OSCaption TEXT(255), " & _
"OSVersion TEXT(255), " & _
"BuildNumber TEXT(255), " & _
"InstallDate DATETIME, " & _
"PRIMARY KEY (PCName))", dbFailOnError
On Error GoTo ErrorHandler
db.Execute "DELETE FROM T_PcOsInfo", dbFailOnError ' 既存データをクリア
db.BeginTrans ' トランザクション開始
For Each strPCName In strPCNames
On Error Resume Next ' エラーが発生しても次のPCを処理
Set objWMIService = GetObject("winmgmts:\\" & strPCName & "\root\cimv2")
On Error GoTo ErrorHandler
If Not objWMIService Is Nothing Then
Set colItems = objWMIService.ExecQuery("SELECT Caption, Version, BuildNumber, InstallDate FROM Win32_OperatingSystem")
For Each objItem In colItems
' パラメータ化クエリまたは直接埋め込みでINSERT
' 注: パラメータ化クエリの方が安全だが、Executeでの直接SQLでは文字列エスケープが必要
strSQL = "INSERT INTO T_PcOsInfo (PCName, OSCaption, OSVersion, BuildNumber, InstallDate) VALUES (" & _
"'" & Replace(strPCName, "'", "''") & "', " & _
"'" & Replace(objItem.Caption, "'", "''") & "', " & _
"'" & Replace(objItem.Version, "'", "''") & "', " & _
"'" & Replace(objItem.BuildNumber, "'", "''") & "', " & _
"#" & WMITimeToDate(objItem.InstallDate) & "#)"
db.Execute strSQL, dbFailOnError
Exit For ' OS情報は通常1つなので、最初の1件で終了
Next objItem
Else
Debug.Print "PC '" & strPCName & "' に接続できませんでした。"
End If
Set objWMIService = Nothing ' オブジェクト解放
Next strPCName
db.CommitTrans ' トランザクションコミット
MsgBox "複数PCのOS情報の取得とテーブル格納が完了しました。処理時間: " & Format(Timer - startTime, "0.00") & "秒", vbInformation
Exit Sub
ErrorHandler:
If db.Transactions Then db.Rollback ' エラー時はトランザクションをロールバック
MsgBox "エラーが発生しました: " & Err.Description, vbCritical
Set objWMIService = Nothing
Set db = Nothing
End Sub
' WMI日付時刻形式をVBAのDate型に変換するヘルパー関数 (Excel版と同一)
Function WMITimeToDate(strWMIDate As String) As Date
On Error Resume Next
If Len(strWMIDate) >= 14 Then
WMITimeToDate = DateSerial(CInt(Mid(strWMIDate, 1, 4)), _
CInt(Mid(strWMIDate, 5, 2)), _
CInt(Mid(strWMIDate, 7, 2))) + _
TimeSerial(CInt(Mid(strWMIDate, 9, 2)), _
CInt(Mid(strWMIDate, 11, 2)), _
CInt(Mid(strWMIDate, 13, 2)))
Else
WMITimeToDate = CDate("1900/01/01")
End If
On Error GoTo 0
End Function
性能チューニングの数値例:
Accessのコードでは、3台のPC(ローカル1台、リモート2台)からOS情報を取得し、
db.Executeメソッドでテーブルに挿入しています。db.BeginTransとdb.CommitTransでトランザクションを囲むことで、複数のINSERT文が単一のデータベース操作として扱われます。これにより、ディスクI/Oの回数が大幅に削減され、例えば1000レコードを個別にINSERTする場合に比べて、処理速度が数倍から10倍以上改善されることがあります。リモートPCへのWMI接続はネットワークの状態に依存しますが、ローカルPCへのアクセスは非常に高速です。
検証
取得情報の正確性:
ExcelまたはAccessで取得されたPC情報(OS名、CPU名、メモリ量など)を、Windowsの「システム情報 (
msinfo32.exe)」や「タスクマネージャー」などと比較し、値が一致することを確認します。特に、単位変換(バイトからGBなど)が正しく行われているか注意深く確認します。
処理速度の測定:
- VBAコード内で
Timer関数を用いて処理時間を計測し、期待される性能(特にチューニングの効果)が得られているかを確認します。例えば、Excelの配列書き込みは直接セル書き込みよりも明らかに高速であることを数値で示します。
- VBAコード内で
エラーハンドリングの確認:
- WMIサービスが停止している場合、リモートPCが存在しない場合、権限がない場合など、意図的にエラーを発生させ、適切にエラーメッセージが表示され、処理が停止しないことを確認します。
複数環境での動作確認:
異なるバージョンのWindows OS (例: Windows 10, Windows 11) や、異なるOfficeバージョン (例: Excel 2016, Microsoft 365) で動作することを確認します。
ドメイン環境とワークグループ環境の両方でテストし、リモートPCへの接続権限に関する問題がないかを確認します。
運用
実行手順
Excelの場合:
上記VBAコードをExcelブックの標準モジュールにコピー&ペーストします。
開発タブから「マクロ」を選択し、「GetPcInfoToExcel」を実行します。または、ボタンを配置し、そのボタンにマクロを割り当ててクリックで実行します。
新しいシート「PC情報」が作成され、そこにPC情報が出力されます。
Accessの場合:
上記VBAコードをAccessデータベースの標準モジュールにコピー&ペーストします。
データベースツールタブから「マクロ」を選択し、「GetRemotePcOsInfoToAccess」を実行します。または、フォームにボタンを配置し、クリックイベントで実行します。
「T_PcOsInfo」テーブルが作成(または更新)され、PC情報が格納されます。
権限
WMIローカルアクセス: 通常、現在のユーザーアカウントの権限で実行されます。管理者権限が必要なWMIクラスへのアクセスでは、VBAを実行するOfficeアプリケーションも管理者として実行する必要がある場合があります。
WMIリモートアクセス: リモートPCへのWMIアクセスには、通常、リモートPCへのアクセス権限が必要です。DCOM設定やファイアウォール設定(ポート135、WMI/RPC)が適切に構成されていることを確認してください。
スケジュール実行
Windowsのタスクスケジューラを利用することで、VBAマクロを定期的に自動実行できます。
Excelの場合:
excel.exe "C:\Path\To\YourWorkbook.xlsm"!MacroNameの形式で実行コマンドを設定します。例:
schtasks /create /tn "DailyPCInfoCollect" /tr "excel.exe 'C:\MyDocs\PCInfo.xlsm'!GetPcInfoToExcel" /sc daily /st 09:00
Accessの場合:
msaccess.exe "C:\Path\To\YourDatabase.accdb" /x MacroNameの形式で実行コマンドを設定します(MacroNameはVBAプロシージャを呼び出すマクロの名前)。または、直接VBAプロシージャを呼び出す場合は、スタートアップオプションやフォームの読み込みイベントを利用します。
ロールバック方法
Excel:
情報取得によって既存データが上書きされたくない場合は、マクロ実行前にブックのバックアップを作成します。
今回のコードは新しいシートに書き込むため、既存データの上書きリスクは低いですが、マクロを修正する際はバックアップを推奨します。
誤って保存してしまった場合は、Windowsの以前のバージョン機能やファイルバックアップから復元します。
Access:
GetRemotePcOsInfoToAccessプロシージャはトランザクション処理 (db.BeginTrans/db.CommitTrans) を使用しており、エラー発生時にはdb.Rollbackで変更が破棄されます。重要なデータ変更を行う場合は、マクロ実行前にデータベースファイル (.accdb) のバックアップを必ず取得してください。
落とし穴
WQLクエリの複雑さとパフォーマンス: WMIクラスは多数あり、必要な情報を効率的に取得するWQLクエリを作成するには学習が必要です。誤ったクエリや効率の悪いクエリは、処理に時間がかかる原因となります。
権限問題: リモートPCへのWMIアクセスは、通常、管理者権限が必要です。また、ローカルであっても一部のWMIクラスは高い権限を要求します。権限不足の場合、「アクセス拒否」エラーが発生します。
リモートWMI接続: ファイアウォール、DCOM設定、ネットワークの問題により、リモートPCへのWMI接続が失敗することがあります。特にDCOM設定は複雑で、トラブルシューティングが難しい場合があります。
WMIサービスの状態: 対象PCのWMIサービスが停止している、または破損している場合、情報取得はできません。
メモリリーク: WMIオブジェクト (
objWMIService,colItems,objItem) は使用後にSet obj = Nothingで明示的に解放しないと、メモリリークやリソース枯渇の原因となる可能性があります。互換性: OSのバージョンやパッチレベルによっては、WMIクラスやプロパティの名称・存在が異なる場合があります。本記事の例は一般的なものですが、特定の環境では調整が必要になる可能性があります。
WMIのタイムアウト: リモートPCへの接続が遅い場合、WMIクエリがタイムアウトすることがあります。
SWbemLocator.ConnectServerメソッドの引数でタイムアウト値を設定することで調整できますが、過度に長くするとスクリプト全体の応答性が悪化します。
まとめ
本記事では、VBAからWMI (Windows Management Instrumentation) を活用してPC情報を効率的に取得する方法を、ExcelとAccessの実装例を交えて詳細に解説しました。WMIはWindowsシステムの情報にアクセスするための強力なツールであり、VBAと組み合わせることで、IT資産管理、システム監視、レポート作成といった業務を大幅に自動化・効率化できます。
特に、以下の点に焦点を当てました。
豊富な情報源: OS、CPU、メモリ、ディスク、ネットワークといった多岐にわたるPC情報をWMI経由で取得できること。
性能最適化: Excelでの配列バッファリングや画面更新抑制、Accessでのトランザクション処理と
db.Executeによる高速なデータ格納により、実用的な処理速度を実現しました。Win32 APIの活用:
Declare PtrSafeによるWin32 APIの利用例を組み込み、VBAの拡張性を示しました。堅牢な運用: エラーハンドリング、オブジェクトの適切な解放、そして詳細な実行手順とロールバック方法を提示し、安定した運用をサポートします。
WMIは非常に強力である一方で、WQLクエリの理解やリモート接続時の権限設定、DCOM構成など、いくつかの「落とし穴」も存在します。これらの注意点を踏まえつつ、本記事のコードと知見が、皆様のOffice自動化プロジェクトの一助となれば幸いです。今後、WMIのさらなる活用(イベント監視、プロセス制御など)を通じて、より高度なシステム管理を実現していくことができるでしょう。

