【ハードウェアインベントリ一括取得】WMIを用いた端末個体情報の自動集約と堅牢なVBScript設計
業務自動化の現場において、キッティングされたPCのハードウェア情報の吸い上げは、IT資産管理の第一歩だ。シリアルナンバー、メーカー、モデル名、UUID、BIOSバージョン――これらを手作業でExcelに入力させるような前時代的な運用は、ヒューマンエラーの温床でしかない。
Windows環境であれば、追加のサードパーティ製ツールを導入せずとも、WMI(Windows Management Instrumentation)とVBScriptを組み合わせることで、ログオン時やネットワーク経由で一瞬にして端末個体情報を集約し、CSVデータベースへ自動登録する仕組みを構築できる。
今回は、数千台規模の端末管理を支えてきた私が、「現場で絶対に壊れない、エラーハンドリングを極めたプロダクションコード」とその設計思想のすべてを伝授しよう。
—
1. なぜ「雑なWMIスクリプト」は現場で爆発するのか?
多くのエンジニアが書くWMIを使ったサンプルコードは、以下のような致命的な欠陥を抱えている。
- エラーハンドリングの欠如: 特定のPCでBIOS情報が取得できなかった瞬間、スクリプト全体が「ランタイムエラー」で強制終了する。
- コレクションの破滅的遅延 (`Select ` の悪夢): 不必要なプロパティまで全件スキャンするため、ネットワーク越しの実行や低スペック端末でフリーズする。
- 文字コードの無頓着: 出力先がShift-JIS(CP932)固定になり、特殊文字やマルチバイト環境でCSVが文字化けする。
プロのエンジニアが作るべきスクリプトは、「何が起きようとも最後まで走り切り、確実にデータを吐き出すタフさ」を持たなければならない。
—
2. アーキテクチャ設計のポイント
今回のスクリプトで実装する要件は以下の通りだ。
1. WMIの動的クエリ(WQL)の最適化: 必要なプロパティのみをピンポイントで取得し、オーバーヘッドを最小化。
2. COMエラーの局所化 (`On Error Resume Next`): 各クエリの成否を個別に判定し、取得失敗時は「N/A」を代入して処理を継続。
3. ファイルI/Oの安全性: ネットワーク共有フォルダなどの競合を考慮した、追記型CSV出力。
—
3. プロダクションコード:ハードウェアインベントリ自動集約スクリプト
以下のコードをメモ帳に貼り付け、拡張子を `.vbs` (例: `InventoryCollector.vbs`)として保存してほしい。
‘ ==============================================================================
‘ Script Name : InventoryCollector.vbs
‘ Description : WMI経由でハードウェア固有情報を取得し、CSVに自動集約する
‘ Author : Chief Architecture Engineer
‘ ==============================================================================
Option Explicit
‘ メイン処理の実行
Main
Sub Main()
Dim fso, logFile, csvPath
Dim strComputer, objWMIService, colItems
Dim manufacturer, model, serialNumber, uuid, biosVersion
Dim csvLine
‘ 出力先CSVパスの定義(例: 社内共有サーバーのインベントリ管理フォルダ)
‘ ※実運用に合わせてパスを変更してください
csvPath = “\\server\share\Inventory\HardwareInventory.csv”
‘ ターゲット端末(”.” はローカルマシンを指す。リモート指定もWQL拡張で可能)
strComputer = “.”
‘ — WMI接続の確立 —
On Error Resume Next
Set objWMIService = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2”)
If Err.Number <> 0 Then
WScript.Echo “CRITICAL: WMIサービスへの接続に失敗しました。Error: ” & Hex(Err.Number)
Exit Sub
End If
On Error GoTo 0
‘ — 各種ハードウェア情報の取得 —
manufacturer = GetManufacturer(objWMIService)
model = GetModel(objWMIService)
serialNumber = GetSerialNumber(objWMIService)
uuid = GetUUID(objWMIService)
biosVersion = GetBIOSVersion(objWMIService)
‘ — CSV用データの組み立て —
‘ 形式: タイムスタンプ, ホスト名, メーカー, モデル, シリアルナンバー, UUID, BIOSバージョン
csvLine = FormatDateTime(Now, 0) & “,” & _
GetHostName() & “,” & _
EscapeCSV(manufacturer) & “,” & _
EscapeCSV(model) & “,” & _
EscapeCSV(serialNumber) & “,” & _
EscapeCSV(uuid) & “,” & _
EscapeCSV(biosVersion)
‘ — CSVファイルへの書き込み(ファイルI/O) —
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ ファイルが存在しない、またはサイズが0の場合はヘッダーを書き込む
If Not fso.FileExists(csvPath) Then
Dim headerFile
Set headerFile = fso.OpenTextFile(csvPath, 2, True, 0) ‘ 2:Writing, True:Create, 0:ASCII(ANSI)
headerFile.WriteLine “Timestamp,ComputerName,Manufacturer,Model,SerialNumber,UUID,BIOSVersion”
headerFile.Close
End If
‘ データを追記モードでオープン (8:ForAppending)
‘ ネットワーク上の競合を防ぐためエラー監視を入れる
On Error Resume Next
Set logFile = fso.OpenTextFile(csvPath, 8, False, 0)
If Err.Number <> 0 Then
‘ ローカルのフォールバック先へ退避するなどの防衛策
WScript.Echo “WARNING: 共有フォルダへの書き込みに失敗しました。ローカルに保存します。”
csvPath = “C:\Temp\HardwareInventory_Fallback.csv”
If Not fso.FileExists(csvPath) Then
Set headerFile = fso.OpenTextFile(csvPath, 2, True, 0)
headerFile.WriteLine “Timestamp,ComputerName,Manufacturer,Model,SerialNumber,UUID,BIOSVersion”
headerFile.Close
End If
Set logFile = fso.OpenTextFile(csvPath, 8, False, 0)
End If
logFile.WriteLine csvLine
logFile.Close
On Error GoTo 0
‘ オブジェクトの解放
Set logFile = Nothing
Set fso = Nothing
Set objWMIService = Nothing
End Sub
‘ — 以下、各情報取得用ヘルパー関数群(局所的エラーハンドリング実装) —
Function GetHostName()
Dim wshNetwork
Set wshNetwork = CreateObject(“WScript.Network”)
GetHostName = wshNetwork.ComputerName
Set wshNetwork = Nothing
End Function
Function GetManufacturer(wmi)
Dim colItems, objItem
GetManufacturer = “N/A”
On Error Resume Next
Set colItems = wmi.ExecQuery(“Select Manufacturer from Win32_ComputerSystem”)
For Each objItem in colItems
If Not IsNull(objItem.Manufacturer) Then GetManufacturer = Trim(objItem.Manufacturer)
Next
On Error GoTo 0
End Function
Function GetModel(wmi)
Dim colItems, objItem
GetModel = “N/A”
On Error Resume Next
Set colItems = wmi.ExecQuery(“Select Model from Win32_ComputerSystem”)
For Each objItem in colItems
If Not IsNull(objItem.Model) Then GetModel = Trim(objItem.Model)
Next
On Error GoTo 0
End Function
Function GetSerialNumber(wmi)
Dim colItems, objItem
GetSerialNumber = “N/A”
On Error Resume Next
‘ BIOSクラスからの取得
Set colItems = wmi.ExecQuery(“Select SerialNumber from Win32_BIOS”)
For Each objItem in colItems
If Not IsNull(objItem.SerialNumber) Then
GetSerialNumber = Trim(objItem.SerialNumber)
End If
Next
‘ 一部のメーカー(自作PCや特定仮想環境)でBIOSにない場合はSystemEnclosureを確認
If GetSerialNumber = “N/A” Or GetSerialNumber = “” Then
Set colItems = wmi.ExecQuery(“Select SerialNumber from Win32_SystemEnclosure”)
For Each objItem in colItems
If Not IsNull(objItem.SerialNumber) Then GetSerialNumber = Trim(objItem.SerialNumber)
Next
End If
On Error GoTo 0
End Function
Function GetUUID(wmi)
Dim colItems, objItem
GetUUID = “N/A”
On Error Resume Next
Set colItems = wmi.ExecQuery(“Select UUID from Win32_ComputerSystemProduct”)
For Each objItem in colItems
If Not IsNull(objItem.UUID) Then GetUUID = Trim(objItem.UUID)
Next
On Error GoTo 0
End Function
Function GetBIOSVersion(wmi)
Dim colItems, objItem, versionArr, i
GetBIOSVersion = “N/A”
On Error Resume Next
Set colItems = wmi.ExecQuery(“Select SMBIOSBIOSVersion, Version from Win32_BIOS”)
For Each objItem in colItems
If Not IsNull(objItem.SMBIOSBIOSVersion) Then
GetBIOSVersion = Trim(objItem.SMBIOSBIOSVersion)
Exit For
ElseIf Not IsNull(objItem.Version) Then
GetBIOSVersion = Trim(objItem.Version)
Exit For
End If
Next
On Error GoTo 0
End Function
Function EscapeCSV(str)
‘ カンマやダブルクォーテーションが含まれる場合のCSVエスケープ処理
If InStr(str, “,”) > 0 Or InStr(str, “”””) > 0 Or InStr(str, vbCrLf) > 0 Then
EscapeCSV = “””” & Replace(str, “”””, “”””””) & “”””
Else
EscapeCSV = str
End If
End Function
—
4. コードの深掘り:プロの設計思想
① シリアルナンバー取得の多重フォールバック
メーカー製PC(Dell, HP, Lenovo等)であれば `Win32_BIOS` の `SerialNumber` で十分に取得できる。しかし、一部の環境や特殊なマザーボードではここが空(あるいは “To be filled by O.E.M.” など)になる。
このコードでは、値が取得できなかった場合に `Win32_SystemEnclosure`(筐体情報)へ二次クエリを投げる二段構えの構造を採用している。これにより、取得漏れを劇的に削減できる。
② ネットワーク切断・権限エラーへの備え
社内共有サーバー(`\\server\share\…`)にCSVを出力する際、VPN切断や権限不足によってファイルオープンに失敗することがある。
このスクリプトでは、書き込みエラーを検知した場合にローカルの `C:\Temp` へ自動フォールバックする堅牢なフェイルセーフを組み込んでいる。ミッションクリティカルな環境でデータを消失させないための必須テクニックだ。
③ CSVインジェクションとデータ破損の防止
取得したモデル名やBIOSバージョンに予期せぬカンマ(`,`)やダブルクォーテーションが含まれていると、CSVの列が破壊される。`EscapeCSV` 関数を通すことで、RFC 4180に準拠した安全なテキスト整形を行っている。
—
5. 運用への組み込み方
作成したインベントリ収集スクリプトは、以下のいずれかの方法で実運用に投入するのが最も効果的だ。
- Active Directory グループポリシー (GPO) のログオンスクリプト:
ユーザーが社内ネットワークに接続してログオンした瞬間にバックグラウンドでサイレント実行(`wscript.exe` を使用)させ、常に最新のインベントリを共有フォルダに集約する。
- タスクスケジューラによる定期実行:
端末側で毎日1回、定時にサイレント実行させる。
※サイレント実行(コマンドプロンプト画面を出さない)させるには、実行時に `cscript.exe` ではなく `wscript.exe` を使用すること。
wscript.exe “\\server\share\Inventory\InventoryCollector.vbs”
—
総括
VBScriptはレガシーな言語と揶揄されることもあるが、「OS標準機能だけで動作し、インストーラー不要で即座に展開できる」という圧倒的なアドバンテージは、現代の業務自動化においても色褪せることがない。
場当たり的なコードを書くのではなく、例外を想定し尽くした「壊れないコード」を設計・配置すること。それこそが、現場を裏から支えるプロフェッショナルエンジニアの仕事である。
