【ICMP応答時間自動計測】WMI (Win32_PingStatus) を活用した複数拠点ネットワークのレイテンシ監視スクリプト
業務自動化エンジニアの私から言わせてもらえば、ネットワーク監視のバッチファイルを組む際に外部の `ping.exe` を実行し、その標準出力を `InStr` や `Mid` で無理やりパースしているコードを見かけるたびに、血の気が引く思いがする。
「日本語環境では『応答バイト数』、英語環境では『Reply from』になるから環境依存で死ぬ」
「パケットロス率を出すためにループ処理と文字列置換の泥沼にハマる」
こんな脆弱な設計を現場に持ち込むのはもうやめだ。WScript環境において、ネットワークの生死とレイテンシをエレガントに、かつ構造化データとして取得したいのであれば、答えは一つしかない。WMI (Windows Management Instrumentation) の `Win32_PingStatus` クラスを直接たたくことだ。
今回は、複数拠点のIPアドレスをマスタ化し、OSの言語依存を一切排除した堅牢なレイテンシ・パケットロス自動計測スクリプトの全貌を伝授する。
—
なぜ `ping.exe` のパースは悪であり、WMIが神なのか
開発現場のプロフェッショナルであれば、インフラの差異に依存するコードがいかに保守性を下げるか痛感しているはずだ。
1. 環境依存性の完全排除
`ping.exe` の出力はOSの表示言語(日本語、英語、中国語など)によって変わる。一方、WMIはCOMオブジェクト経由でプロパティ(構造体)としてデータを返す。取得するのは文字列ではなく、純粋な数値やブール値だ。
2. 非同期・詳細なステータスコード
タイムアウト、IPアドレスが見つからない、ハードウェアエラーなど、`Win32_PingStatus` は詳細な `StatusCode` を返す。これにより、「ネットワークが遅いのか、ホストが存在しないのか」を正確に切り分けられる。
3. 外部プロセス依存ゼロ
`WshShell.Run` でcmdを叩いてテキストファイルにリダイレクトし、それを `FSO` で読み直す……というI/Oの無駄を極限まで排除できる。メモリ上で完結するためパフォーマンスが圧倒的に高い。
—
プロダクションコード:複数拠点ネットワーク監視スクリプト
以下のコードは、実務の現場ですぐに動かせる完全版だ。設定ファイルや外部依存を持たず、スクリプト単体で複数拠点の疎通確認を行い、タイムスタンプ付きのCSVログとして出力する。
‘ ==============================================================================
‘ Script Name : NetworkLatencyMonitor.vbs
‘ Description : WMI (Win32_PingStatus) を用いた複数拠点ネットワーク監視
‘ Author :Enterprise Automation Architect
‘ ==============================================================================
Option Explicit
‘ エラーハンドリングの有効化(堅牢性の担保)
On Error Resume Next
Dim objFSO, objTextFile, strLogPath
Dim arrTargets, strTarget, objWMIService, colPingResults, objPingResult
Dim strTimestamp, intTimeout, intCount
Dim i
‘ — 1. 定数・環境設定 —
intTimeout = 1000 ‘ タイムアウトミリ秒 (1秒)
intCount = 3 ‘ 1ターゲットあたりの試行回数(平均値算出用)
‘ 監視対象拠点の定義 (ホスト名またはIPアドレス)
‘ 実務ではここをDBや外部INIファイルから読み込むように拡張せよ
arrTargets = Array(“192.168.1.1”, “10.0.50.10”, “dc01.corp.local”, “8.8.8.8”)
‘ ログファイルの出力先(スクリプト実行ディレクトリ配下の “logs” フォルダ)
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
strLogPath = objFSO.GetParentFolderName(WScript.ScriptFullName) & “\NetworkLog_” & FormatDateYYYYMMDD(Now) & “.csv”
‘ ログファイルの初期化(存在しない場合はヘッダーを書き込む)
If Not objFSO.FileExists(strLogPath) Then
Set objTextFile = objFSO.OpenTextFile(strLogPath, 2, True)
objTextFile.WriteLine “Timestamp,Target,Status,ResponseTime_ms,ProtocolAddress”
objTextFile.Close
Set objTextFile = Nothing
End If
‘ — 2. WMI接続の確立 (Local WMI Service) —
Set objWMIService = GetObject(“winmgmts:\\.\root\cimv2”)
If Err.Number <> 0 Then
WScript.Echo “CRITICAL: WMIサービスへの接続に失敗しました. Error: ” & Hex(Err.Number)
WScript.Quit 1
End If
‘ — 3. メインループ:複数拠点へのポーリング処理 —
For Each strTarget In arrTargets
Dim totalTime, successCount, avgTime
totalTime = 0
successCount = 0
‘ 精度を高めるために複数回Pingを飛ばす
For i = 1 To intCount
strTimestamp = FormatDateTime(Now, 0)
‘ WQLクエリの構築 (Win32_PingStatus)
Dim strQuery
strQuery = “SELECT FROM Win32_PingStatus WHERE Address = ‘” & strTarget & “‘ AND Timeout = ” & intTimeout
Set colPingResults = objWMIService.ExecQuery(strQuery)
If Err.Number <> 0 Then
‘ クエリ実行時エラーの捕捉
LogResult strLogPath, strTimestamp, strTarget, “WMI_ERROR”, 0, strTarget
Err.Clear
Else
For Each objPingResult in colPingResults
‘ StatusCode = 0 は疎通成功を意味する
If Not IsNull(objPingResult.StatusCode) And objPingResult.StatusCode = 0 Then
successCount = successCount + 1
totalTime = totalTime + objPingResult.ResponseTime
End If
Next
End If
Set colPingResults = Nothing
‘ ネットワーク負荷軽減のためのインターバル (100ms)
WScript.Sleep 100
Next
‘ — 4. 統計データの算出とログ出力 —
strTimestamp = FormatDateTime(Now, 0)
If successCount > 0 Then
avgTime = Round(totalTime / successCount, 2)
‘ 正常終了 (SUCCESS / 平均応答時間)
LogResult strLogPath, strTimestamp, strTarget, “SUCCESS”, avgTime, strTarget
Else
‘ タイムアウトまたは名前解決失敗 (TIMEOUT / 応答なし)
LogResult strLogPath, strTimestamp , strTarget, “TIMEOUT”, -1, strTarget
End If
Next
‘ クリーンアップ
Set objWMIService = Nothing
Set objFSO = Nothing
WScript.Echo “ネットワーク監視処理が正常終了しました。”
WScript.Quit 0
‘ ==============================================================================
‘ サブルーチン & ファンクション群
‘ ==============================================================================
‘ ログ追記関数(ファイル競合を防ぐためのミニマム実装)
Sub LogResult(filePath, timestamp, target, status, responseTime, address)
Dim fso, ts
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ ForAppending (8), Create (True)
Set ts = fso.OpenTextFile(filePath, 8, True)
ts.WriteLine timestamp & “,” & target & “,” & status & “,” & responseTime & “,” & address
ts.Close
Set ts = Nothing
Set fso = Nothing
End Sub
‘ 日付フォーマット補助 (YYYYMMDD)
Function FormatDateYYYYMMDD(d)
Dim y, m, day
y = CStr(Year(d))
m = CStr(Month(d))
If Len(m) = 1 Then m = “0” & m
day = CStr(Day(d))
If Len(day) = 1 Then day = “0” & day
FormatDateYYYYMMDD = y & m & day
End Function
—
現場で差が出る! アーキテクトが教える実装上の急所
このスクリプトをただ動かすだけなら誰でもできる。プロとして運用耐性を高めるための「急所」を解説する。
1. WQLクエリの罠とパフォーマンス
`Win32_PingStatus` はインスタンス生成時に実際にICMPパケットを飛ばす動的クラスだ。そのため、ループ内で大量のIPに対して一気に `ExecQuery` を投げると、クライアント側のWMIリポジトリやネットワークスタックに負荷がかかる。
コード内に挟んだ `WScript.Sleep 100` は、OSへの優しさであると同時に、スクリプトがCPUをスパイクさせないための必須の配慮だ。
2. ファイルI/Oのライフサイクル管理
VBScriptで最も多いトラブルが 「他のプロセスがログファイルを開いていて、Permission Deniedでスクリプトが異常終了する」 というものだ。
今回のコードでは、ログ出力のたびに `OpenTextFile` で開いて即閉じる (`ts.Close`) 設計にしている。ファイルハンドルをグローバルに保持し続けるような設計は、長期間稼働するタスクスケジューラ環境ではメモリリークやロックの元凶となるため絶対に行うな。
3. StatusCode の真実
`Win32_PingStatus.StatusCode` が返す値は `0` だけではない。
- `0` : 成功
- `11010` : リクエストタイムアウト
- `11001` : バッファ小 / IP不正
など、詳細なステータスが存在する。今回はシンプルに「成功か、それ以外(タイムアウト等)か」で判定しているが、厳密なインフラ監視が必要な場合は `objPingResult.StatusCode` の値をそのままログに落とし込むように拡張すると、障害解析の精度が劇的に向上する。
—
総括
VBScriptはレガシーな言語と揶揄されることがあるが、それは使い手の技量が低い言い訳に過ぎない。Windowsの深層(WMI)と正しく対話させれば、外部ツールを一切インストールできないセキュアなエンタープライズ環境であっても、これほど強力で美しい自動化基盤をノーコストで構築できる。
手元の環境に合わせて `arrTargets` を書き換え、タスクスケジューラに登録するだけで、明日からあなたのインフラ管理の信頼性はワンランク上に引き上げられるだろう。実務の現場でぜひ役立ててほしい。
