<p><!--META
{
"title": "VBAでWMIによるPC情報取得",
"primary_category": "VBA",
"secondary_categories": ["WMI", "Windows API", "Excel", "Access"],
"tags": ["VBA", "WMI", "Win32 API", "Excel", "Access", "性能チューニング", "PC情報取得", "GlobalMemoryStatusEx"],
"summary": "VBAでWMIとWin32 APIを駆使し、PC情報を効率的に取得する手法を解説。Excel/Accessでの実務適用と性能最適化に焦点を当てる。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"VBAでWMIとWin32 APIを活用し、PC情報を効率的に取得する実践ガイド。Excel/Accessでの実装例や性能チューニング、落とし穴まで徹底解説。 #VBA #WMI #Office自動化"},
"link_hints": ["https://learn.microsoft.com/ja-jp/windows/win32/wmisdk/wmi-start-page","https://learn.microsoft.com/ja-jp/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex"]
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">VBAでWMIによるPC情報取得</h1>
<h2 class="wp-block-heading">背景/要件</h2>
<p>現代のビジネス環境において、PCは業務遂行に不可欠なツールであり、その適切な管理は企業運営の根幹を成します。しかし、多数のPCが導入された環境では、各PCのハードウェア構成、OS情報、ネットワーク設定などを手動で把握し続けることは困難です。資産管理、トラブルシューティング、セキュリティ監査といった業務では、常に正確なPC情報が求められます。</p>
<p>VBA (Visual Basic for Applications) は、Microsoft Officeアプリケーションの自動化を強力に推進するプログラミング言語です。VBAからWMI (Windows Management Instrumentation) を利用することで、Windows OSが持つ豊富なシステム情報をプログラム的に取得・操作することが可能になります。WMIはWindowsに標準搭載された管理インフラであり、外部ライブラリを追加することなくPCの詳細情報にアクセスできるため、VBAによるOffice自動化との相性は抜群です。
、VBAを用いてWMIからPC情報を取得する実践的な手法を解説します。特に、ExcelやAccessといったOfficeアプリケーションでの実務レベルでの再現性を重視し、以下の要件を満たすことを目指します。</p>
<ul class="wp-block-list">
<li><p><strong>主要なPC情報の取得</strong>: OS、CPU、メモリ、ディスク、ネットワークなどの基本情報を網羅的に取得します。</p></li>
<li><p><strong>Win32 APIの活用</strong>: WMIでは取得しにくい、あるいは補完的な情報について、<code>Declare PtrSafe</code> を用いたWin32 APIの呼び出しを組み込みます。</p></li>
<li><p><strong>Officeアプリケーションでの実用例</strong>: Excelへのシート出力、Accessへのテーブル格納といった具体的な実装コードを提示します。</p></li>
<li><p><strong>性能チューニング</strong>: 大量データや複数PCからの情報取得を想定し、処理速度向上のための最適化手法(配列バッファ、ScreenUpdating停止など)を具体的に示します。</p></li>
<li><p><strong>堅牢性</strong>: エラーハンドリングや運用時の注意点についても触れます。</p></li>
</ul>
<h2 class="wp-block-heading">設計</h2>
<h3 class="wp-block-heading">データモデル</h3>
<p>取得するPC情報は、以下のカテゴリと項目に分類し、取得したデータを構造化して扱います。</p>
<ul class="wp-block-list">
<li><p><strong>OS情報</strong>: OS名 (Caption), バージョン (Version), アーキテクチャ (OSArchitecture), 物理メモリ総量 (TotalPhysicalMemory)</p></li>
<li><p><strong>CPU情報</strong>: CPU名 (Name), コア数 (NumberOfCores), 論理プロセッサ数 (NumberOfLogicalProcessors)</p></li>
<li><p><strong>メモリ情報 (WMI)</strong>: 物理メモリ総量 (TotalPhysicalMemory, <code>Win32_OperatingSystem</code>から)</p></li>
<li><p><strong>メモリ情報 (Win32 API)</strong>: メモリ使用率 (dwMemoryLoad), 物理メモリ総量 (ullTotalPhys), 利用可能物理メモリ (ullAvailPhys) など詳細情報</p></li>
<li><p><strong>ディスク情報</strong>: ドライブレター (DeviceID), ボリューム名 (Caption), サイズ (Size), 空き容量 (FreeSpace)</p></li>
<li><p><strong>ネットワーク情報</strong>: アダプタ名 (Description), MACアドレス (MACAddress), IPアドレス (IPAddress)</p></li>
</ul>
<h3 class="wp-block-heading">WMIクラス選定</h3>
<p>上記のデータモデルに基づき、主に以下のWMIクラスを使用します。</p>
<ul class="wp-block-list">
<li><p><code>Win32_OperatingSystem</code>: OSに関する情報</p></li>
<li><p><code>Win32_Processor</code>: CPUに関する情報</p></li>
<li><p><code>Win32_LogicalDisk</code>: 論理ディスクに関する情報</p></li>
<li><p><code>Win32_NetworkAdapterConfiguration</code>: ネットワークアダプタに関する情報 (IPEnabled=Trueで有効なアダプタに絞る)</p></li>
</ul>
<h3 class="wp-block-heading">Win32 APIの選定</h3>
<p>メモリの利用状況を詳細に取得するため、<code>GlobalMemoryStatusEx</code> 関数をWin32 APIとして採用します。この関数は、システム全体のメモリ使用状況をバイト単位で提供し、WMIだけでは得にくい詳細な動的情報を補完します。</p>
<h3 class="wp-block-heading">処理フロー</h3>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["開始"] --> B{"性能チューニング設定"};
B --> C["WMIサービス接続"];
C --> D{"WMIクエリ実行"};
D --|OS情報取得|--> E[Win32_OperatingSystem];
D --|CPU情報取得|--> F[Win32_Processor];
D --|ディスク情報取得|--> G[Win32_LogicalDisk];
D --|ネットワーク情報取得|--> H[Win32_NetworkAdapterConfiguration];
E --> I["情報収集"];
F --> I;
G --> I;
H --> I;
C --> J{"Win32 API呼び出し"};
J --|メモリ詳細取得|--> K[GlobalMemoryStatusEx];
K --> I;
I --> L["データバッファリング"];
L --> M{"Officeアプリケーションへの出力"};
M --|Excelへ出力|--> N["ワークシートに一括書き込み"];
M --|Accessへ出力|--> O["レコードセットでテーブルに書き込み"];
N --> P{"性能チューニング解除"};
O --> P;
P --> Q["終了"];
B --|エラー発生|--> R["エラーハンドリング"];
Q --|正常終了|--> R;
</pre></div>
<h3 class="wp-block-heading">性能最適化戦略</h3>
<ol class="wp-block-list">
<li><p><strong>一括書き込み</strong>: Officeアプリケーションへの書き込みは、セルやレコード単位でループ処理を行うと非常に低速になります。取得したデータは配列にバッファリングし、Rangeオブジェクトへの一括代入 (Excel) や、<code>DAO.Recordset</code>を用いたバッチ更新/トランザクション処理 (Access) で高速化を図ります。</p></li>
<li><p><strong>画面更新/イベント/計算モードの停止</strong>: Excelにおいては、<code>Application.ScreenUpdating = False</code>、<code>Application.EnableEvents = False</code>、<code>Application.Calculation = xlCalculationManual</code> を設定することで、描画処理やイベント発生、自動再計算によるオーバーヘッドを削減します。Accessでは、同様の直接的な設定はありませんが、レコードセットの最適化やトランザクション利用が有効です。</p></li>
<li><p><strong>WMIクエリの最適化</strong>: <code>SELECT * FROM ...</code> のように全てのプロパティを取得するのではなく、必要なプロパティのみを <code>SELECT</code> 句で指定することで、ネットワーク帯域や処理負荷を軽減します。</p></li>
</ol>
<h2 class="wp-block-heading">実装</h2>
<p>Win32 APIを宣言するためのモジュールと、Excel/AccessそれぞれにPC情報を取得・出力するコードを記述します。</p>
<h3 class="wp-block-heading">共通モジュール (標準モジュールに記述)</h3>
<p><code>GlobalMemoryStatusEx</code>関数とその関連構造体を宣言します。VBA7 (64-bit Office) では <code>LongLong</code> 型を、VBA6 (32-bit Office) では <code>Currency</code> 型を <code>ULONGLONG</code> の代用として使用するため、条件付きコンパイルを適用します。</p>
<pre data-enlighter-language="generic">' 標準モジュール(例: Module1)に記述
#If VBA7 Then
Private Declare PtrSafe Function GlobalMemoryStatusEx Lib "kernel32" (lpBuffer As MEMORYSTATUSEX) As Long
Private Type MEMORYSTATUSEX
dwLength As Long
dwMemoryLoad As Long
ullTotalPhys As LongLong ' Total physical memory (bytes)
ullAvailPhys As LongLong ' Available physical memory (bytes)
ullTotalPageFile As LongLong
ullAvailPageFile As LongLong
ullTotalVirtual As LongLong
ullAvailVirtual As LongLong
ullAvailExtendedVirtual As LongLong
End Type
#Else
Private Declare Function GlobalMemoryStatusEx Lib "kernel32" (lpBuffer As MEMORYSTATUSEX) As Long
Private Type MEMORYSTATUSEX
dwLength As Long
dwMemoryLoad As Long
ullTotalPhys As Currency ' Use Currency as a workaround for ULONGLONG on 32-bit (scaled by 10000)
ullAvailPhys As Currency
ullTotalPageFile As Currency
ullAvailPageFile As Currency
ullTotalVirtual As Currency
ullAvailVirtual As Currency
ullAvailExtendedVirtual As Currency
End Type
#End If
' GlobalMemoryStatusEx から取得したバイト数をGBに変換するヘルパー関数
Function BytesToGB(ByVal Bytes As Variant) As Double
#If VBA7 Then
BytesToGB = CDbl(Bytes) / (1024 ^ 3)
#Else
' Currency型の場合、実際の値は10000倍されているため、まず10000で割る
BytesToGB = CDbl(Bytes * 10000#) / (1024 ^ 3)
#End If
End Function
</pre>
<h3 class="wp-block-heading">Excel向けコード (標準モジュールに記述)</h3>
<p>新しいシートを作成し、取得したPC情報を表形式で出力します。配列バッファとアプリケーション設定の最適化を適用しています。</p>
<pre data-enlighter-language="generic">' Excel: PC情報取得とシートへの出力
Sub GetPCInfoToExcel()
Dim objWMIService As Object
Dim colItems As Object
Dim objItem As Object
Dim ws As Worksheet
Dim rowIdx As Long ' 配列の行インデックス
Dim dataArr() As Variant
Dim i As Long
Dim startTime As Double, endTime As Double ' 性能計測用
startTime = Timer ' 処理開始時間計測
' 性能チューニング開始
With Application
.ScreenUpdating = False
.EnableEvents = False
.Calculation = xlCalculationManual
End With
On Error GoTo ErrorHandler
Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
ws.Name = "PC情報_" & Format(Now, "YYYYMMDD_HHMMSS")
' ヘッダーの定義
Dim headers() As Variant
headers = Array("項目", "値")
ws.Range("A1:B1").Value = headers
ws.Range("A1:B1").Font.Bold = True
ws.Range("A1:B1").Interior.Color = RGB(220, 230, 241)
' データバッファリング用の配列を準備
' 取得する項目数を考慮し、十分なサイズで初期化 (例: OS=4, CPU=3, MemAPI=5, Disk=N*3, Net=N*2+N*M)
' 最悪ケースを想定して多めに確保
ReDim dataArr(1 To 100, 1 To 2)
rowIdx = 0 ' ヘッダーを除くデータ開始位置
' WMI サービスに接続
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
' --- OS情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Caption, Version, OSArchitecture, TotalPhysicalMemory FROM Win32_OperatingSystem")
For Each objItem In colItems
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "OS名": dataArr(rowIdx, 2) = objItem.Caption
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "OSバージョン": dataArr(rowIdx, 2) = objItem.Version
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "OSアーキテクチャ": dataArr(rowIdx, 2) = objItem.OSArchitecture
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "物理メモリ総量(WMI)": dataArr(rowIdx, 2) = Format(CDbl(objItem.TotalPhysicalMemory) / (1024 ^ 3), "0.00") & " GB"
Next
' --- CPU情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Name, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor")
For Each objItem In colItems
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "CPU名": dataArr(rowIdx, 2) = objItem.Name
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "コア数": dataArr(rowIdx, 2) = objItem.NumberOfCores
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "論理プロセッサ数": dataArr(rowIdx, 2) = objItem.NumberOfLogicalProcessors
Next
' --- メモリ情報 (Win32 API) ---
Dim memStatus As MEMORYSTATUSEX
memStatus.dwLength = Len(memStatus)
If GlobalMemoryStatusEx(memStatus) Then
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "--- メモリ詳細 (Win32 API) ---": dataArr(rowIdx, 2) = ""
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "メモリ使用率": dataArr(rowIdx, 2) = memStatus.dwMemoryLoad & " %"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "物理メモリ総量": dataArr(rowIdx, 2) = Format(BytesToGB(memStatus.ullTotalPhys), "0.00") & " GB"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "利用可能物理メモリ": dataArr(rowIdx, 2) = Format(BytesToGB(memStatus.ullAvailPhys), "0.00") & " GB"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "ページファイル総量": dataArr(rowIdx, 2) = Format(BytesToGB(memStatus.ullTotalPageFile), "0.00") & " GB"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "利用可能ページファイル": dataArr(rowIdx, 2) = Format(BytesToGB(memStatus.ullAvailPageFile), "0.00") & " GB"
Else
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "メモリ詳細 (Win32 API)": dataArr(rowIdx, 2) = "取得失敗"
End If
' --- ディスク情報 ---
Set colItems = objWMIService.ExecQuery("SELECT DeviceID, Caption, Size, FreeSpace FROM Win32_LogicalDisk WHERE DriveType = 3") ' DriveType=3: ローカルディスク
For Each objItem In colItems
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "--- ドライブ: " & objItem.DeviceID & " ---": dataArr(rowIdx, 2) = ""
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "ボリューム名": dataArr(rowIdx, 2) = objItem.Caption
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "サイズ": dataArr(rowIdx, 2) = Format(CDbl(objItem.Size) / (1024 ^ 3), "0.00") & " GB"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "空き容量": dataArr(rowIdx, 2) = Format(CDbl(objItem.FreeSpace) / (1024 ^ 3), "0.00") & " GB"
Next
' --- ネットワークアダプタ情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Description, MACAddress, IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")
For Each objItem In colItems
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "--- ネットワークアダプタ: " & objItem.Description & " ---": dataArr(rowIdx, 2) = ""
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "MACアドレス": dataArr(rowIdx, 2) = objItem.MACAddress
If Not IsNull(objItem.IPAddress) Then
For i = LBound(objItem.IPAddress) To UBound(objItem.IPAddress)
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "IPアドレス(" & i + 1 & ")": dataArr(rowIdx, 2) = objItem.IPAddress(i)
Next i
End If
Next
' 実際のデータ数に合わせて配列をリサイズし、シートに一括書き込み
If rowIdx > 0 Then
ReDim Preserve dataArr(1 To rowIdx, 1 To 2)
ws.Range("A2").Resize(rowIdx, 2).Value = dataArr ' ヘッダーの下から書き込み
End If
' 整形
ws.Columns("A:B").AutoFit
ws.Columns("A:B").HorizontalAlignment = xlLeft
endTime = Timer ' 処理終了時間計測
MsgBox "PC情報の取得が完了し、シート '" & ws.Name & "' に出力されました。" & vbCrLf & _
"処理時間: " & Format(endTime - startTime, "0.00") & " 秒", vbInformation
ExitHandler:
' 性能チューニング解除
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = xlCalculationAutomatic
End With
Set ws = Nothing
Set objWMIService = Nothing
Set colItems = Nothing
Set objItem = Nothing
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Number & " - " & Err.Description, vbCritical
Resume ExitHandler
End Sub
</pre>
<h3 class="wp-block-heading">Access向けコード (標準モジュールに記述)</h3>
<p>新しいテーブルを作成し、取得したPC情報を格納します。<code>DAO.Recordset</code>を用いた効率的なデータ追加を実装しています。</p>
<pre data-enlighter-language="generic">' Access: PC情報取得とテーブルへの出力
Sub GetPCInfoToAccess()
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 strTableName As String
Dim i As Long
Dim startTime As Double, endTime As Double ' 性能計測用
startTime = Timer ' 処理開始時間計測
Set db = CurrentDb
strTableName = "PC_Info_" & Format(Now, "YYYYMMDD_HHMMSS")
On Error GoTo ErrorHandler
' テーブル定義
Set tdf = db.CreateTableDef(strTableName)
With tdf
.Fields.Append .CreateField("InfoCategory", dbText, 50)
.Fields.Append .CreateField("InfoName", dbText, 100)
.Fields.Append .CreateField("InfoValue", dbMemo) ' 値が長くなる可能性があるのでMemo型
End With
db.TableDefs.Append tdf
' レコードセットを開く
' 性能向上のため、トランザクションとバッチ更新を適用
db.BeginTrans
Set rs = db.OpenRecordset(strTableName, dbOpenTable)
rs.BatchUpdates = True ' バッチ更新を有効にする (ADOではCursorLocation, UpdateBatch)
' WMI サービスに接続
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
' --- OS情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Caption, Version, OSArchitecture, TotalPhysicalMemory FROM Win32_OperatingSystem")
For Each objItem In colItems
With rs
.AddNew
.Fields("InfoCategory").Value = "OS情報"
.Fields("InfoName").Value = "OS名"
.Fields("InfoValue").Value = objItem.Caption
.Update
.AddNew
.Fields("InfoCategory").Value = "OS情報"
.Fields("InfoName").Value = "OSバージョン"
.Fields("InfoValue").Value = objItem.Version
.Update
.AddNew
.Fields("InfoCategory").Value = "OS情報"
.Fields("InfoName").Value = "OSアーキテクチャ"
.Fields("InfoValue").Value = objItem.OSArchitecture
.Update
.AddNew
.Fields("InfoCategory").Value = "OS情報"
.Fields("InfoName").Value = "物理メモリ総量(WMI)"
.Fields("InfoValue").Value = Format(CDbl(objItem.TotalPhysicalMemory) / (1024 ^ 3), "0.00") & " GB"
.Update
End With
Next
' --- CPU情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Name, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor")
For Each objItem In colItems
With rs
.AddNew
.Fields("InfoCategory").Value = "CPU情報"
.Fields("InfoName").Value = "CPU名"
.Fields("InfoValue").Value = objItem.Name
.Update
.AddNew
.Fields("InfoCategory").Value = "CPU情報"
.Fields("InfoName").Value = "コア数"
.Fields("InfoValue").Value = objItem.NumberOfCores
.Update
.AddNew
.Fields("InfoCategory").Value = "CPU情報"
.Fields("InfoName").Value = "論理プロセッサ数"
.Fields("InfoValue").Value = objItem.NumberOfLogicalProcessors
.Update
End With
Next
' --- メモリ情報 (Win32 API) ---
Dim memStatus As MEMORYSTATUSEX
memStatus.dwLength = Len(memStatus)
If GlobalMemoryStatusEx(memStatus) Then
With rs
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "メモリ使用率": .Fields("InfoValue").Value = memStatus.dwMemoryLoad & " %": .Update
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "物理メモリ総量": .Fields("InfoValue").Value = Format(BytesToGB(memStatus.ullTotalPhys), "0.00") & " GB": .Update
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "利用可能物理メモリ": .Fields("InfoValue").Value = Format(BytesToGB(memStatus.ullAvailPhys), "0.00") & " GB": .Update
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "ページファイル総量": .Fields("InfoValue").Value = Format(BytesToGB(memStatus.ullTotalPageFile), "0.00") & " GB": .Update
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "利用可能ページファイル": .Fields("InfoValue").Value = Format(BytesToGB(memStatus.ullAvailPageFile), "0.00") & " GB": .Update
End With
Else
With rs
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "取得ステータス": .Fields("InfoValue").Value = "取得失敗": .Update
End With
End If
' --- ディスク情報 ---
Set colItems = objWMIService.ExecQuery("SELECT DeviceID, Caption, Size, FreeSpace FROM Win32_LogicalDisk WHERE DriveType = 3")
For Each objItem In colItems
With rs
.AddNew: .Fields("InfoCategory").Value = "ディスク情報(" & objItem.DeviceID & ")": .Fields("InfoName").Value = "ボリューム名": .Fields("InfoValue").Value = objItem.Caption: .Update
.AddNew: .Fields("InfoCategory").Value = "ディスク情報(" & objItem.DeviceID & ")": .Fields("InfoName").Value = "サイズ": .Fields("InfoValue").Value = Format(CDbl(objItem.Size) / (1024 ^ 3), "0.00") & " GB": .Update
.AddNew: .Fields("InfoCategory").Value = "ディスク情報(" & objItem.DeviceID & ")": .Fields("InfoName").Value = "空き容量": .Fields("InfoValue").Value = Format(CDbl(objItem.FreeSpace) / (1024 ^ 3), "0.00") & " GB": .Update
End With
Next
' --- ネットワークアダプタ情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Description, MACAddress, IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")
For Each objItem In colItems
With rs
.AddNew: .Fields("InfoCategory").Value = "ネットワークアダプタ(" & objItem.Description & ")": .Fields("InfoName").Value = "MACアドレス": .Fields("InfoValue").Value = objItem.MACAddress: .Update
If Not IsNull(objItem.IPAddress) Then
For i = LBound(objItem.IPAddress) To UBound(objItem.IPAddress)
.AddNew: .Fields("InfoCategory").Value = "ネットワークアダプタ(" & objItem.Description & ")": .Fields("InfoName").Value = "IPアドレス(" & i + 1 & ")": .Fields("InfoValue").Value = objItem.IPAddress(i): .Update
Next i
End If
End With
Next
' バッチ更新とトランザクションのコミット
rs.UpdateBatch ' 未コミットの変更をすべてコミット
db.CommitTrans
endTime = Timer ' 処理終了時間計測
MsgBox "PC情報の取得が完了し、テーブル '" & strTableName & "' に出力されました。" & vbCrLf & _
"処理時間: " & Format(endTime - startTime, "0.00") & " 秒", vbInformation
ExitHandler:
If db.Transactions > 0 Then db.Rollback ' エラー時はトランザクションをロールバック
If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close
Set rs = Nothing
Set tdf = Nothing
Set db = Nothing
Set objWMIService = Nothing
Set colItems = Nothing
Set objItem = Nothing
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Number & " - " & Err.Description, vbCritical
Resume ExitHandler
End Sub
</pre>
<h2 class="wp-block-heading">検証</h2>
<h3 class="wp-block-heading">実行手順</h3>
<ol class="wp-block-list">
<li><p><strong>VBAエディタを開く</strong>: ExcelまたはAccessを開き、<code>Alt + F11</code>キーを押してVBAエディタ (VBE) を開きます。</p></li>
<li><p><strong>標準モジュールの作成</strong>: プロジェクトエクスプローラーで該当するOfficeアプリケーションのプロジェクトを選択し、<code>挿入</code> > <code>標準モジュール</code> をクリックします。</p></li>
<li><p><strong>共通モジュールの貼り付け</strong>: 作成した標準モジュールに、上記「共通モジュール」のコードを貼り付けます。</p></li>
<li><p><strong>アプリケーション別コードの貼り付け</strong>:</p>
<ul>
<li><p><strong>Excelの場合</strong>: 別の標準モジュールを作成し、「Excel向けコード」を貼り付けます。</p></li>
<li><p><strong>Accessの場合</strong>: 別の標準モジュールを作成し、「Access向けコード」を貼り付けます。</p></li>
</ul></li>
<li><p><strong>コードの実行</strong>:</p>
<ul>
<li><p><strong>Excel</strong>: VBEのツールバーにある <code>実行</code> > <code>Sub/ユーザーフォームの実行</code> (または <code>F5</code> キー) をクリックし、<code>GetPCInfoToExcel</code> を選択して実行します。</p></li>
<li><p><strong>Access</strong>: VBEのツールバーにある <code>実行</code> > <code>Sub/ユーザーフォームの実行</code> (または <code>F5</code> キー) をクリックし、<code>GetPCInfoToAccess</code> を選択して実行します。</p></li>
</ul></li>
</ol>
<h3 class="wp-block-heading">確認項目</h3>
<ul class="wp-block-list">
<li><p><strong>Excel</strong>: 新しいシートが追加され、OS、CPU、メモリ、ディスク、ネットワークなどのPC情報がA列とB列に正しく出力されていることを確認します。メモリ情報がWin32 API (<code>GlobalMemoryStatusEx</code>) によって詳細に取得されているか確認します。</p></li>
<li><p><strong>Access</strong>: 新しいテーブル (<code>PC_Info_YYYYMMDD_HHMMSS</code>のような名前) が作成され、カテゴリ、項目名、値の形式でPC情報が格納されていることを確認します。</p></li>
<li><p><strong>情報の正確性</strong>: 出力された情報が、ご自身のPCの設定(OSバージョン、搭載メモリ量、ディスク容量など)と一致しているか確認します。</p></li>
<li><p><strong>エラーハンドリング</strong>: 例えば、WMIサービスが停止している、権限がないなどの状況を意図的に作り出し、エラーメッセージが適切に表示されるかを確認します。</p></li>
</ul>
<h3 class="wp-block-heading">性能チューニング効果の計測</h3>
<p>上記のコードには<code>Timer</code>関数による処理時間計測が組み込まれています。</p>
<ul class="wp-block-list">
<li><p><strong>性能チューニングあり(現状のコード)</strong>:</p>
<ul>
<li><p>Excel: 0.1秒~0.5秒程度 (PCのスペックやWMIクエリ数、ネットワークアダプタ数による)</p></li>
<li><p>Access: 0.2秒~0.8秒程度 (同上)</p></li>
</ul></li>
<li><p><strong>性能チューニングなし(比較例)</strong>:</p>
<ul>
<li><p>Excelで<code>Application.ScreenUpdating = True</code>、<code>Application.Calculation = xlCalculationAutomatic</code>のまま、かつセルに1つずつ書き込む場合 (<code>ws.Cells(rowNum, 1).Value = "項目"; ws.Cells(rowNum, 2).Value = "値"</code>)</p>
<ul>
<li>約100項目の出力で、<strong>3秒~5秒以上</strong>かかる可能性があります。これは、シートの再描画、セルの再計算、イベント発生といったオーバーヘッドが各セル書き込みごとに発生するためです。</li>
</ul></li>
<li><p>Accessで<code>db.BeginTrans</code> / <code>db.CommitTrans</code>や<code>rs.BatchUpdates</code>を使用しない場合、各<code>rs.Update</code>が即座にディスクI/Oを伴うため、処理時間は<strong>1秒~3秒以上</strong>に延びる可能性があります。</p></li>
</ul></li>
</ul>
<p><strong>結果として、配列バッファとアプリケーション設定の最適化により、Excel/Accessともに処理速度が数倍から数十倍向上することが期待できます。</strong></p>
<h2 class="wp-block-heading">運用</h2>
<h3 class="wp-block-heading">実行方法</h3>
<ul class="wp-block-list">
<li><p><strong>手動実行</strong>: 開発者がVBEから直接実行するか、Officeアプリケーションのリボンにボタンを配置し、クリックイベントでマクロを実行します。</p></li>
<li><p><strong>定期実行</strong>: Windowsのタスクスケジューラを利用して、指定したOfficeファイルを開き、特定のVBAプロシージャを呼び出すことで定期的な情報収集が可能です。</p>
<ul>
<li>例: <code>excel.exe "C:\Path\To\YourWorkbook.xlsm" /r "GetPCInfoToExcel"</code> (起動時に指定したマクロを実行)</li>
</ul></li>
</ul>
<h3 class="wp-block-heading">取得データの活用</h3>
<ul class="wp-block-list">
<li><p><strong>資産管理台帳</strong>: 取得したPC情報をExcelシートやAccessテーブルに集約し、社内PCの資産管理台帳として活用します。</p></li>
<li><p><strong>レポート生成</strong>: 収集したデータを基に、各部署のPCスペック一覧やOSバージョン分布などのレポートを自動生成します。</p></li>
<li><p><strong>異常検知/監視</strong>: ディスク空き容量の急減、メモリ使用率の異常な上昇などを定期的に監視し、しきい値を超えた場合にアラートを出すシステムと連携します。</p></li>
<li><p><strong>セキュリティ監査</strong>: 特定のセキュリティパッチの適用状況や、不正なソフトウェアの有無をWMIで確認し、セキュリティ監査に役立てます。</p></li>
</ul>
<h3 class="wp-block-heading">ロールバック方法</h3>
<ul class="wp-block-list">
<li><p><strong>Excel</strong>: 新規シートに情報が出力されるため、問題が発生した場合はそのシートを削除するだけで済みます。既存のデータや設定には影響を与えません。</p></li>
<li><p><strong>Access</strong>: 新規テーブルに情報が格納されるため、問題が発生した場合は作成されたテーブルを削除するだけで済みます。既存のテーブルやデータには影響を与えません。テーブル名は日付と時刻を含んでいるため、誤って既存テーブルを上書きするリスクも低いです。</p></li>
</ul>
<h3 class="wp-block-heading">セキュリティ考慮事項</h3>
<ul class="wp-block-list">
<li><p><strong>WMIのアクセス権限</strong>: WMIは通常、管理者権限で動作します。非管理者ユーザーで実行する場合、必要なWMIクラスへのアクセス権限を事前に付与する必要があります。</p></li>
<li><p><strong>コードの保護</strong>: VBAコードは容易に閲覧・改変される可能性があるため、配布時にはプロジェクトにパスワードを設定し、VBAコードを保護することを検討してください。</p></li>
<li><p><strong>リモートPCからの情報取得</strong>: 他のPCからWMI経由で情報を取得する場合、ネットワークファイアウォールの設定、DCOMの設定、認証情報の取り扱い(ユーザー名/パスワードをハードコードしない)など、より高度なセキュリティ対策が必要になります。</p></li>
</ul>
<h2 class="wp-block-heading">落とし穴</h2>
<ul class="wp-block-list">
<li><p><strong>WMIクエリの複雑化と性能劣化</strong>: 複雑なWMIクエリや、大量のWMIオブジェクトから多くのプロパティを取得する処理は、実行に時間がかかり性能を著しく低下させることがあります。必要な情報のみを厳選し、シンプルなクエリを心がけましょう。</p></li>
<li><p><strong>リモートPCへのWMI接続時の問題</strong>: ファイアウォール、DCOM設定、適切な認証情報 (通常は<code>SWbemLocator</code>と<code>ConnectServer</code>メソッドで認証情報を渡す) の欠如などにより、リモートPCへの接続が失敗することがよくあります。これらの設定は事前に確認し、必要に応じて構成する必要があります。</p></li>
<li><p><strong>Win32 APIの32bit/64bit互換性</strong>: <code>Declare</code>ステートメントは、VBAのバージョンやOfficeのビット数 (32bit/64bit) によって記述が異なります。<code>PtrSafe</code>キーワードは64bit環境でのアドレスを正しく扱うために必須です。本記事のコードは <code>#If VBA7</code> による条件付きコンパイルで対応していますが、古い環境で実行する場合は注意が必要です。特に <code>LongLong</code> 型が利用できない32bit VBA環境では、<code>ULONGLONG</code> を扱うために <code>Currency</code> を使うなどの回避策が必要になります。</p></li>
<li><p><strong>エラーハンドリングの不備</strong>: ネットワーク障害、WMIサービスの停止、アクセス拒否など、様々な理由で情報取得が失敗する可能性があります。<code>On Error GoTo</code> を適切に使用し、予期せぬエラー発生時にもプログラムがクラッシュしないよう、堅牢なエラーハンドリングを実装することが重要です。</p></li>
<li><p><strong>取得情報の機微性</strong>: PC情報には、IPアドレスやMACアドレスなど、個人を特定可能な情報やネットワーク構成に関する情報が含まれる場合があります。これらの情報の取り扱いには、プライバシー保護や情報セキュリティに関する社内規定を遵守する必要があります。</p></li>
</ul>
<h2 class="wp-block-heading">まとめ</h2>
<p>VBAとWMI、そしてWin32 APIを組み合わせることで、OfficeアプリケーションからPCのシステム情報を高度に取得し、自動化されたPC管理システムを構築することが可能です。本記事では、ExcelおよびAccessでの具体的な実装例を示し、性能最適化のためのテクニック(配列バッファ、画面更新停止、トランザクション利用)や、運用における考慮点、潜在的な落とし穴についても詳細に解説しました。</p>
<p>これらの知識と技術を活用することで、これまで手動で行っていたPC情報の収集・管理業務を劇的に効率化し、より正確で迅速な意思決定を支援できるでしょう。VBAによるOffice自動化は、ビジネスプロセス改善のための強力なツールとなり得ます。</p>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
VBAでWMIによるPC情報取得
背景/要件
現代のビジネス環境において、PCは業務遂行に不可欠なツールであり、その適切な管理は企業運営の根幹を成します。しかし、多数のPCが導入された環境では、各PCのハードウェア構成、OS情報、ネットワーク設定などを手動で把握し続けることは困難です。資産管理、トラブルシューティング、セキュリティ監査といった業務では、常に正確なPC情報が求められます。
VBA (Visual Basic for Applications) は、Microsoft Officeアプリケーションの自動化を強力に推進するプログラミング言語です。VBAからWMI (Windows Management Instrumentation) を利用することで、Windows OSが持つ豊富なシステム情報をプログラム的に取得・操作することが可能になります。WMIはWindowsに標準搭載された管理インフラであり、外部ライブラリを追加することなくPCの詳細情報にアクセスできるため、VBAによるOffice自動化との相性は抜群です。
、VBAを用いてWMIからPC情報を取得する実践的な手法を解説します。特に、ExcelやAccessといったOfficeアプリケーションでの実務レベルでの再現性を重視し、以下の要件を満たすことを目指します。
主要なPC情報の取得: OS、CPU、メモリ、ディスク、ネットワークなどの基本情報を網羅的に取得します。
Win32 APIの活用: WMIでは取得しにくい、あるいは補完的な情報について、Declare PtrSafe
を用いたWin32 APIの呼び出しを組み込みます。
Officeアプリケーションでの実用例: Excelへのシート出力、Accessへのテーブル格納といった具体的な実装コードを提示します。
性能チューニング: 大量データや複数PCからの情報取得を想定し、処理速度向上のための最適化手法(配列バッファ、ScreenUpdating停止など)を具体的に示します。
堅牢性: エラーハンドリングや運用時の注意点についても触れます。
設計
データモデル
取得するPC情報は、以下のカテゴリと項目に分類し、取得したデータを構造化して扱います。
OS情報: OS名 (Caption), バージョン (Version), アーキテクチャ (OSArchitecture), 物理メモリ総量 (TotalPhysicalMemory)
CPU情報: CPU名 (Name), コア数 (NumberOfCores), 論理プロセッサ数 (NumberOfLogicalProcessors)
メモリ情報 (WMI): 物理メモリ総量 (TotalPhysicalMemory, Win32_OperatingSystem
から)
メモリ情報 (Win32 API): メモリ使用率 (dwMemoryLoad), 物理メモリ総量 (ullTotalPhys), 利用可能物理メモリ (ullAvailPhys) など詳細情報
ディスク情報: ドライブレター (DeviceID), ボリューム名 (Caption), サイズ (Size), 空き容量 (FreeSpace)
ネットワーク情報: アダプタ名 (Description), MACアドレス (MACAddress), IPアドレス (IPAddress)
WMIクラス選定
上記のデータモデルに基づき、主に以下のWMIクラスを使用します。
Win32_OperatingSystem
: OSに関する情報
Win32_Processor
: CPUに関する情報
Win32_LogicalDisk
: 論理ディスクに関する情報
Win32_NetworkAdapterConfiguration
: ネットワークアダプタに関する情報 (IPEnabled=Trueで有効なアダプタに絞る)
Win32 APIの選定
メモリの利用状況を詳細に取得するため、GlobalMemoryStatusEx
関数をWin32 APIとして採用します。この関数は、システム全体のメモリ使用状況をバイト単位で提供し、WMIだけでは得にくい詳細な動的情報を補完します。
処理フロー
graph TD
A["開始"] --> B{"性能チューニング設定"};
B --> C["WMIサービス接続"];
C --> D{"WMIクエリ実行"};
D --|OS情報取得|--> E[Win32_OperatingSystem];
D --|CPU情報取得|--> F[Win32_Processor];
D --|ディスク情報取得|--> G[Win32_LogicalDisk];
D --|ネットワーク情報取得|--> H[Win32_NetworkAdapterConfiguration];
E --> I["情報収集"];
F --> I;
G --> I;
H --> I;
C --> J{"Win32 API呼び出し"};
J --|メモリ詳細取得|--> K[GlobalMemoryStatusEx];
K --> I;
I --> L["データバッファリング"];
L --> M{"Officeアプリケーションへの出力"};
M --|Excelへ出力|--> N["ワークシートに一括書き込み"];
M --|Accessへ出力|--> O["レコードセットでテーブルに書き込み"];
N --> P{"性能チューニング解除"};
O --> P;
P --> Q["終了"];
B --|エラー発生|--> R["エラーハンドリング"];
Q --|正常終了|--> R;
性能最適化戦略
一括書き込み: Officeアプリケーションへの書き込みは、セルやレコード単位でループ処理を行うと非常に低速になります。取得したデータは配列にバッファリングし、Rangeオブジェクトへの一括代入 (Excel) や、DAO.Recordset
を用いたバッチ更新/トランザクション処理 (Access) で高速化を図ります。
画面更新/イベント/計算モードの停止: Excelにおいては、Application.ScreenUpdating = False
、Application.EnableEvents = False
、Application.Calculation = xlCalculationManual
を設定することで、描画処理やイベント発生、自動再計算によるオーバーヘッドを削減します。Accessでは、同様の直接的な設定はありませんが、レコードセットの最適化やトランザクション利用が有効です。
WMIクエリの最適化: SELECT * FROM ...
のように全てのプロパティを取得するのではなく、必要なプロパティのみを SELECT
句で指定することで、ネットワーク帯域や処理負荷を軽減します。
実装
Win32 APIを宣言するためのモジュールと、Excel/AccessそれぞれにPC情報を取得・出力するコードを記述します。
共通モジュール (標準モジュールに記述)
GlobalMemoryStatusEx
関数とその関連構造体を宣言します。VBA7 (64-bit Office) では LongLong
型を、VBA6 (32-bit Office) では Currency
型を ULONGLONG
の代用として使用するため、条件付きコンパイルを適用します。
' 標準モジュール(例: Module1)に記述
#If VBA7 Then
Private Declare PtrSafe Function GlobalMemoryStatusEx Lib "kernel32" (lpBuffer As MEMORYSTATUSEX) As Long
Private Type MEMORYSTATUSEX
dwLength As Long
dwMemoryLoad As Long
ullTotalPhys As LongLong ' Total physical memory (bytes)
ullAvailPhys As LongLong ' Available physical memory (bytes)
ullTotalPageFile As LongLong
ullAvailPageFile As LongLong
ullTotalVirtual As LongLong
ullAvailVirtual As LongLong
ullAvailExtendedVirtual As LongLong
End Type
#Else
Private Declare Function GlobalMemoryStatusEx Lib "kernel32" (lpBuffer As MEMORYSTATUSEX) As Long
Private Type MEMORYSTATUSEX
dwLength As Long
dwMemoryLoad As Long
ullTotalPhys As Currency ' Use Currency as a workaround for ULONGLONG on 32-bit (scaled by 10000)
ullAvailPhys As Currency
ullTotalPageFile As Currency
ullAvailPageFile As Currency
ullTotalVirtual As Currency
ullAvailVirtual As Currency
ullAvailExtendedVirtual As Currency
End Type
#End If
' GlobalMemoryStatusEx から取得したバイト数をGBに変換するヘルパー関数
Function BytesToGB(ByVal Bytes As Variant) As Double
#If VBA7 Then
BytesToGB = CDbl(Bytes) / (1024 ^ 3)
#Else
' Currency型の場合、実際の値は10000倍されているため、まず10000で割る
BytesToGB = CDbl(Bytes * 10000#) / (1024 ^ 3)
#End If
End Function
Excel向けコード (標準モジュールに記述)
新しいシートを作成し、取得したPC情報を表形式で出力します。配列バッファとアプリケーション設定の最適化を適用しています。
' Excel: PC情報取得とシートへの出力
Sub GetPCInfoToExcel()
Dim objWMIService As Object
Dim colItems As Object
Dim objItem As Object
Dim ws As Worksheet
Dim rowIdx As Long ' 配列の行インデックス
Dim dataArr() As Variant
Dim i As Long
Dim startTime As Double, endTime As Double ' 性能計測用
startTime = Timer ' 処理開始時間計測
' 性能チューニング開始
With Application
.ScreenUpdating = False
.EnableEvents = False
.Calculation = xlCalculationManual
End With
On Error GoTo ErrorHandler
Set ws = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
ws.Name = "PC情報_" & Format(Now, "YYYYMMDD_HHMMSS")
' ヘッダーの定義
Dim headers() As Variant
headers = Array("項目", "値")
ws.Range("A1:B1").Value = headers
ws.Range("A1:B1").Font.Bold = True
ws.Range("A1:B1").Interior.Color = RGB(220, 230, 241)
' データバッファリング用の配列を準備
' 取得する項目数を考慮し、十分なサイズで初期化 (例: OS=4, CPU=3, MemAPI=5, Disk=N*3, Net=N*2+N*M)
' 最悪ケースを想定して多めに確保
ReDim dataArr(1 To 100, 1 To 2)
rowIdx = 0 ' ヘッダーを除くデータ開始位置
' WMI サービスに接続
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
' --- OS情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Caption, Version, OSArchitecture, TotalPhysicalMemory FROM Win32_OperatingSystem")
For Each objItem In colItems
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "OS名": dataArr(rowIdx, 2) = objItem.Caption
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "OSバージョン": dataArr(rowIdx, 2) = objItem.Version
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "OSアーキテクチャ": dataArr(rowIdx, 2) = objItem.OSArchitecture
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "物理メモリ総量(WMI)": dataArr(rowIdx, 2) = Format(CDbl(objItem.TotalPhysicalMemory) / (1024 ^ 3), "0.00") & " GB"
Next
' --- CPU情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Name, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor")
For Each objItem In colItems
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "CPU名": dataArr(rowIdx, 2) = objItem.Name
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "コア数": dataArr(rowIdx, 2) = objItem.NumberOfCores
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "論理プロセッサ数": dataArr(rowIdx, 2) = objItem.NumberOfLogicalProcessors
Next
' --- メモリ情報 (Win32 API) ---
Dim memStatus As MEMORYSTATUSEX
memStatus.dwLength = Len(memStatus)
If GlobalMemoryStatusEx(memStatus) Then
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "--- メモリ詳細 (Win32 API) ---": dataArr(rowIdx, 2) = ""
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "メモリ使用率": dataArr(rowIdx, 2) = memStatus.dwMemoryLoad & " %"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "物理メモリ総量": dataArr(rowIdx, 2) = Format(BytesToGB(memStatus.ullTotalPhys), "0.00") & " GB"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "利用可能物理メモリ": dataArr(rowIdx, 2) = Format(BytesToGB(memStatus.ullAvailPhys), "0.00") & " GB"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "ページファイル総量": dataArr(rowIdx, 2) = Format(BytesToGB(memStatus.ullTotalPageFile), "0.00") & " GB"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "利用可能ページファイル": dataArr(rowIdx, 2) = Format(BytesToGB(memStatus.ullAvailPageFile), "0.00") & " GB"
Else
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "メモリ詳細 (Win32 API)": dataArr(rowIdx, 2) = "取得失敗"
End If
' --- ディスク情報 ---
Set colItems = objWMIService.ExecQuery("SELECT DeviceID, Caption, Size, FreeSpace FROM Win32_LogicalDisk WHERE DriveType = 3") ' DriveType=3: ローカルディスク
For Each objItem In colItems
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "--- ドライブ: " & objItem.DeviceID & " ---": dataArr(rowIdx, 2) = ""
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "ボリューム名": dataArr(rowIdx, 2) = objItem.Caption
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "サイズ": dataArr(rowIdx, 2) = Format(CDbl(objItem.Size) / (1024 ^ 3), "0.00") & " GB"
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "空き容量": dataArr(rowIdx, 2) = Format(CDbl(objItem.FreeSpace) / (1024 ^ 3), "0.00") & " GB"
Next
' --- ネットワークアダプタ情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Description, MACAddress, IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")
For Each objItem In colItems
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "--- ネットワークアダプタ: " & objItem.Description & " ---": dataArr(rowIdx, 2) = ""
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "MACアドレス": dataArr(rowIdx, 2) = objItem.MACAddress
If Not IsNull(objItem.IPAddress) Then
For i = LBound(objItem.IPAddress) To UBound(objItem.IPAddress)
rowIdx = rowIdx + 1: dataArr(rowIdx, 1) = "IPアドレス(" & i + 1 & ")": dataArr(rowIdx, 2) = objItem.IPAddress(i)
Next i
End If
Next
' 実際のデータ数に合わせて配列をリサイズし、シートに一括書き込み
If rowIdx > 0 Then
ReDim Preserve dataArr(1 To rowIdx, 1 To 2)
ws.Range("A2").Resize(rowIdx, 2).Value = dataArr ' ヘッダーの下から書き込み
End If
' 整形
ws.Columns("A:B").AutoFit
ws.Columns("A:B").HorizontalAlignment = xlLeft
endTime = Timer ' 処理終了時間計測
MsgBox "PC情報の取得が完了し、シート '" & ws.Name & "' に出力されました。" & vbCrLf & _
"処理時間: " & Format(endTime - startTime, "0.00") & " 秒", vbInformation
ExitHandler:
' 性能チューニング解除
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = xlCalculationAutomatic
End With
Set ws = Nothing
Set objWMIService = Nothing
Set colItems = Nothing
Set objItem = Nothing
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Number & " - " & Err.Description, vbCritical
Resume ExitHandler
End Sub
Access向けコード (標準モジュールに記述)
新しいテーブルを作成し、取得したPC情報を格納します。DAO.Recordset
を用いた効率的なデータ追加を実装しています。
' Access: PC情報取得とテーブルへの出力
Sub GetPCInfoToAccess()
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 strTableName As String
Dim i As Long
Dim startTime As Double, endTime As Double ' 性能計測用
startTime = Timer ' 処理開始時間計測
Set db = CurrentDb
strTableName = "PC_Info_" & Format(Now, "YYYYMMDD_HHMMSS")
On Error GoTo ErrorHandler
' テーブル定義
Set tdf = db.CreateTableDef(strTableName)
With tdf
.Fields.Append .CreateField("InfoCategory", dbText, 50)
.Fields.Append .CreateField("InfoName", dbText, 100)
.Fields.Append .CreateField("InfoValue", dbMemo) ' 値が長くなる可能性があるのでMemo型
End With
db.TableDefs.Append tdf
' レコードセットを開く
' 性能向上のため、トランザクションとバッチ更新を適用
db.BeginTrans
Set rs = db.OpenRecordset(strTableName, dbOpenTable)
rs.BatchUpdates = True ' バッチ更新を有効にする (ADOではCursorLocation, UpdateBatch)
' WMI サービスに接続
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
' --- OS情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Caption, Version, OSArchitecture, TotalPhysicalMemory FROM Win32_OperatingSystem")
For Each objItem In colItems
With rs
.AddNew
.Fields("InfoCategory").Value = "OS情報"
.Fields("InfoName").Value = "OS名"
.Fields("InfoValue").Value = objItem.Caption
.Update
.AddNew
.Fields("InfoCategory").Value = "OS情報"
.Fields("InfoName").Value = "OSバージョン"
.Fields("InfoValue").Value = objItem.Version
.Update
.AddNew
.Fields("InfoCategory").Value = "OS情報"
.Fields("InfoName").Value = "OSアーキテクチャ"
.Fields("InfoValue").Value = objItem.OSArchitecture
.Update
.AddNew
.Fields("InfoCategory").Value = "OS情報"
.Fields("InfoName").Value = "物理メモリ総量(WMI)"
.Fields("InfoValue").Value = Format(CDbl(objItem.TotalPhysicalMemory) / (1024 ^ 3), "0.00") & " GB"
.Update
End With
Next
' --- CPU情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Name, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor")
For Each objItem In colItems
With rs
.AddNew
.Fields("InfoCategory").Value = "CPU情報"
.Fields("InfoName").Value = "CPU名"
.Fields("InfoValue").Value = objItem.Name
.Update
.AddNew
.Fields("InfoCategory").Value = "CPU情報"
.Fields("InfoName").Value = "コア数"
.Fields("InfoValue").Value = objItem.NumberOfCores
.Update
.AddNew
.Fields("InfoCategory").Value = "CPU情報"
.Fields("InfoName").Value = "論理プロセッサ数"
.Fields("InfoValue").Value = objItem.NumberOfLogicalProcessors
.Update
End With
Next
' --- メモリ情報 (Win32 API) ---
Dim memStatus As MEMORYSTATUSEX
memStatus.dwLength = Len(memStatus)
If GlobalMemoryStatusEx(memStatus) Then
With rs
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "メモリ使用率": .Fields("InfoValue").Value = memStatus.dwMemoryLoad & " %": .Update
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "物理メモリ総量": .Fields("InfoValue").Value = Format(BytesToGB(memStatus.ullTotalPhys), "0.00") & " GB": .Update
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "利用可能物理メモリ": .Fields("InfoValue").Value = Format(BytesToGB(memStatus.ullAvailPhys), "0.00") & " GB": .Update
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "ページファイル総量": .Fields("InfoValue").Value = Format(BytesToGB(memStatus.ullTotalPageFile), "0.00") & " GB": .Update
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "利用可能ページファイル": .Fields("InfoValue").Value = Format(BytesToGB(memStatus.ullAvailPageFile), "0.00") & " GB": .Update
End With
Else
With rs
.AddNew: .Fields("InfoCategory").Value = "メモリ情報(API)": .Fields("InfoName").Value = "取得ステータス": .Fields("InfoValue").Value = "取得失敗": .Update
End With
End If
' --- ディスク情報 ---
Set colItems = objWMIService.ExecQuery("SELECT DeviceID, Caption, Size, FreeSpace FROM Win32_LogicalDisk WHERE DriveType = 3")
For Each objItem In colItems
With rs
.AddNew: .Fields("InfoCategory").Value = "ディスク情報(" & objItem.DeviceID & ")": .Fields("InfoName").Value = "ボリューム名": .Fields("InfoValue").Value = objItem.Caption: .Update
.AddNew: .Fields("InfoCategory").Value = "ディスク情報(" & objItem.DeviceID & ")": .Fields("InfoName").Value = "サイズ": .Fields("InfoValue").Value = Format(CDbl(objItem.Size) / (1024 ^ 3), "0.00") & " GB": .Update
.AddNew: .Fields("InfoCategory").Value = "ディスク情報(" & objItem.DeviceID & ")": .Fields("InfoName").Value = "空き容量": .Fields("InfoValue").Value = Format(CDbl(objItem.FreeSpace) / (1024 ^ 3), "0.00") & " GB": .Update
End With
Next
' --- ネットワークアダプタ情報 ---
Set colItems = objWMIService.ExecQuery("SELECT Description, MACAddress, IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True")
For Each objItem In colItems
With rs
.AddNew: .Fields("InfoCategory").Value = "ネットワークアダプタ(" & objItem.Description & ")": .Fields("InfoName").Value = "MACアドレス": .Fields("InfoValue").Value = objItem.MACAddress: .Update
If Not IsNull(objItem.IPAddress) Then
For i = LBound(objItem.IPAddress) To UBound(objItem.IPAddress)
.AddNew: .Fields("InfoCategory").Value = "ネットワークアダプタ(" & objItem.Description & ")": .Fields("InfoName").Value = "IPアドレス(" & i + 1 & ")": .Fields("InfoValue").Value = objItem.IPAddress(i): .Update
Next i
End If
End With
Next
' バッチ更新とトランザクションのコミット
rs.UpdateBatch ' 未コミットの変更をすべてコミット
db.CommitTrans
endTime = Timer ' 処理終了時間計測
MsgBox "PC情報の取得が完了し、テーブル '" & strTableName & "' に出力されました。" & vbCrLf & _
"処理時間: " & Format(endTime - startTime, "0.00") & " 秒", vbInformation
ExitHandler:
If db.Transactions > 0 Then db.Rollback ' エラー時はトランザクションをロールバック
If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close
Set rs = Nothing
Set tdf = Nothing
Set db = Nothing
Set objWMIService = Nothing
Set colItems = Nothing
Set objItem = Nothing
Exit Sub
ErrorHandler:
MsgBox "エラーが発生しました: " & Err.Number & " - " & Err.Description, vbCritical
Resume ExitHandler
End Sub
検証
実行手順
VBAエディタを開く: ExcelまたはAccessを開き、Alt + F11
キーを押してVBAエディタ (VBE) を開きます。
標準モジュールの作成: プロジェクトエクスプローラーで該当するOfficeアプリケーションのプロジェクトを選択し、挿入
> 標準モジュール
をクリックします。
共通モジュールの貼り付け: 作成した標準モジュールに、上記「共通モジュール」のコードを貼り付けます。
アプリケーション別コードの貼り付け:
コードの実行:
確認項目
Excel: 新しいシートが追加され、OS、CPU、メモリ、ディスク、ネットワークなどのPC情報がA列とB列に正しく出力されていることを確認します。メモリ情報がWin32 API (GlobalMemoryStatusEx
) によって詳細に取得されているか確認します。
Access: 新しいテーブル (PC_Info_YYYYMMDD_HHMMSS
のような名前) が作成され、カテゴリ、項目名、値の形式でPC情報が格納されていることを確認します。
情報の正確性: 出力された情報が、ご自身のPCの設定(OSバージョン、搭載メモリ量、ディスク容量など)と一致しているか確認します。
エラーハンドリング: 例えば、WMIサービスが停止している、権限がないなどの状況を意図的に作り出し、エラーメッセージが適切に表示されるかを確認します。
性能チューニング効果の計測
上記のコードにはTimer
関数による処理時間計測が組み込まれています。
性能チューニングあり(現状のコード):
性能チューニングなし(比較例):
ExcelでApplication.ScreenUpdating = True
、Application.Calculation = xlCalculationAutomatic
のまま、かつセルに1つずつ書き込む場合 (ws.Cells(rowNum, 1).Value = "項目"; ws.Cells(rowNum, 2).Value = "値"
)
- 約100項目の出力で、3秒~5秒以上かかる可能性があります。これは、シートの再描画、セルの再計算、イベント発生といったオーバーヘッドが各セル書き込みごとに発生するためです。
Accessでdb.BeginTrans
/ db.CommitTrans
やrs.BatchUpdates
を使用しない場合、各rs.Update
が即座にディスクI/Oを伴うため、処理時間は1秒~3秒以上に延びる可能性があります。
結果として、配列バッファとアプリケーション設定の最適化により、Excel/Accessともに処理速度が数倍から数十倍向上することが期待できます。
運用
実行方法
取得データの活用
資産管理台帳: 取得したPC情報をExcelシートやAccessテーブルに集約し、社内PCの資産管理台帳として活用します。
レポート生成: 収集したデータを基に、各部署のPCスペック一覧やOSバージョン分布などのレポートを自動生成します。
異常検知/監視: ディスク空き容量の急減、メモリ使用率の異常な上昇などを定期的に監視し、しきい値を超えた場合にアラートを出すシステムと連携します。
セキュリティ監査: 特定のセキュリティパッチの適用状況や、不正なソフトウェアの有無をWMIで確認し、セキュリティ監査に役立てます。
ロールバック方法
セキュリティ考慮事項
WMIのアクセス権限: WMIは通常、管理者権限で動作します。非管理者ユーザーで実行する場合、必要なWMIクラスへのアクセス権限を事前に付与する必要があります。
コードの保護: VBAコードは容易に閲覧・改変される可能性があるため、配布時にはプロジェクトにパスワードを設定し、VBAコードを保護することを検討してください。
リモートPCからの情報取得: 他のPCからWMI経由で情報を取得する場合、ネットワークファイアウォールの設定、DCOMの設定、認証情報の取り扱い(ユーザー名/パスワードをハードコードしない)など、より高度なセキュリティ対策が必要になります。
落とし穴
WMIクエリの複雑化と性能劣化: 複雑なWMIクエリや、大量のWMIオブジェクトから多くのプロパティを取得する処理は、実行に時間がかかり性能を著しく低下させることがあります。必要な情報のみを厳選し、シンプルなクエリを心がけましょう。
リモートPCへのWMI接続時の問題: ファイアウォール、DCOM設定、適切な認証情報 (通常はSWbemLocator
とConnectServer
メソッドで認証情報を渡す) の欠如などにより、リモートPCへの接続が失敗することがよくあります。これらの設定は事前に確認し、必要に応じて構成する必要があります。
Win32 APIの32bit/64bit互換性: Declare
ステートメントは、VBAのバージョンやOfficeのビット数 (32bit/64bit) によって記述が異なります。PtrSafe
キーワードは64bit環境でのアドレスを正しく扱うために必須です。本記事のコードは #If VBA7
による条件付きコンパイルで対応していますが、古い環境で実行する場合は注意が必要です。特に LongLong
型が利用できない32bit VBA環境では、ULONGLONG
を扱うために Currency
を使うなどの回避策が必要になります。
エラーハンドリングの不備: ネットワーク障害、WMIサービスの停止、アクセス拒否など、様々な理由で情報取得が失敗する可能性があります。On Error GoTo
を適切に使用し、予期せぬエラー発生時にもプログラムがクラッシュしないよう、堅牢なエラーハンドリングを実装することが重要です。
取得情報の機微性: PC情報には、IPアドレスやMACアドレスなど、個人を特定可能な情報やネットワーク構成に関する情報が含まれる場合があります。これらの情報の取り扱いには、プライバシー保護や情報セキュリティに関する社内規定を遵守する必要があります。
まとめ
VBAとWMI、そしてWin32 APIを組み合わせることで、OfficeアプリケーションからPCのシステム情報を高度に取得し、自動化されたPC管理システムを構築することが可能です。本記事では、ExcelおよびAccessでの具体的な実装例を示し、性能最適化のためのテクニック(配列バッファ、画面更新停止、トランザクション利用)や、運用における考慮点、潜在的な落とし穴についても詳細に解説しました。
これらの知識と技術を活用することで、これまで手動で行っていたPC情報の収集・管理業務を劇的に効率化し、より正確で迅速な意思決定を支援できるでしょう。VBAによるOffice自動化は、ビジネスプロセス改善のための強力なツールとなり得ます。
コメント