VBScriptを掌握する極限の知見:WMI監視とProcDump連携によるメモリダンプ自動取得基盤の構築
エンタープライズの現場において、最も悪夢なのは「本番稼働中の特定プロセスが、突発的に高負荷あるいは無応答(ハングアップ)に陥り、再現性がないままログも残さずに沈黙する」という現象だ。
原因究明の唯一の鍵は「その瞬間のメモリダンプ」であるが、人間が目視で監視し、タスクマネージャーからダンプを採取するなど到底間に合わない。
本稿では、追加のサードパーティ製監視エージェントを導入できないレガシーなWindows環境において、OS標準のWSH(Windows Script Host)とWMI(Windows Management Instrumentation)を極限までチューニングし、異常検知からSysinternalsのProcDumpによるメモリダンプ採取までを完全自動化するアーキテクチャを解説する。
—
1. アーキテクチャの核心:なぜVBScriptとWMIなのか
現代のクラウドネイティブな環境において、VBScriptはレガシーな存在と見なされがちだ。しかし、以下の要件を満たす必要がある現場では、依然として最強の選択肢である。
- ゼロ・インストール:追加のランタイム(.NET Frameworkの特定バージョンやPython等)を一切持ち込めない厳格なセキュア環境。
- OSネイティブ稼働:Windows 2000から最新のWindows Serverに至るまで、OSの標準機能だけで動作する普遍性。
- 軽量性:常駐させてもメモリフットプリントが極めて小さく、監視対象のシステムリソースを圧迫しない。
しかし、WMIとCOMコンポーネントを不適切に扱うと、VBScript特有の「メモリリーク」や「COMオブジェクトの解放漏れによるプロセス肥大化」を引き起こす。このアーキテクチャでは、オブジェクトのライフサイクルを完全に制御し、数週間・数ヶ月の連続稼働に耐えうる堅牢性を実装する。
—
2. 全体設計とシーケンス
1. ポーリング監視:WMIの `SWbemServices` を用いて、指定プロセスのCPU使用率や応答状態(`Responding` プロパティ)を定期的にポーリング。
2. 閾値判定:CPU使用率が指定閾値(例: 90%以上)を連続して超えた場合、または無応答状態を検知。
3. 外部ツール連携:`WScript.Shell` の `Exec` メソッドを使用し、非同期かつ確実に出力ストリームを制御しながら `procdump.exe` を起動。
4. 安全な終了:多重起動を防ぐガード機構と、リソースの確実なクリーンアップ。
—
3. 実装コード:完全自律型ダンプ採取モジュール
以下のコードを `ProcessWatcher.vbs` として保存し、管理者権限で実行する。
‘ ==============================================================================
‘ 業務自動化アーキテクチャ: 高負荷・無応答プロセス自動ダンプ採取モジュール
‘ Target OS: Windows Server 2008 R2 / 2012 R2 / 2016 / 2019 / 2022
‘ ==============================================================================
Option Explicit
‘ — 設定定数 —
Const TARGET_PROCESS = “w3wp.exe” ‘ 監視対象プロセス名
Const PROC_DUMP_PATH = “C:\Tools\procdump.exe” ‘ ProcDumpの絶対パス
Const DUMP_OUTPUT_DIR = “C:\MemoryDumps\” ‘ ダンプ保存先
Const CHECK_INTERVAL = 10000 ‘ ポーリング間隔 (ミリ秒)
Const CPU_THRESHOLD = 85 ‘ CPU使用率の閾値 (%)
Const CONTINUOUS_COUNT = 3 ‘ 閾値超えを検知する連続回数(誤検知防止)
‘ — グローバル変数・オブジェクト —
Dim objShell, objWMIService, colProcesses
Dim highCpuCounter
highCpuCounter = 0
‘ 初期化処理
Call Main()
Sub Main()
Dim wmiLocator, strComputer
strComputer = “.”
‘ WMI接続の確立(SWbemLocatorを使用することで認証・接続の安定性を向上)
Set wmiLocator = WScript.CreateObject(“WbemScripting.SWbemLocator”)
Set objWMIService = wmiLocator.ConnectServer(strComputer, “root\cimv2”)
‘ セキュリティと権限の調整(必要に応じてプロキシ設定など)
objWMIService.Security_.ImpersonationLevel = 3 ‘ Impersonate
objWMIService.Security_.Privileges.AddAsString “SeDebugPrivilege”, True
Set objShell = WScript.CreateObject(“WScript.Shell”)
‘ 出力ディレクトリの存在確認・作成
Call EnsureDirectory(DUMP_OUTPUT_DIR)
‘ メイン監視ループ
Do
Call MonitorProcessCycle()
WScript.Sleep CHECK_INTERVAL
Loop
End Sub
Sub MonitorProcessCycle()
On Error Resume Next
‘ WMIクエリの実行(特定プロセスの取得)
‘ 注意: Win32_ProcessのCPU使用率はリアルタイム値ではないため、
‘ 高精度な監視にはパフォーマンスカウンターとの併用が望ましいが、
‘ 環境依存を減らすためここではWin32_PerfFormattedData_PerfProc_Processを使用。
Dim query, colItems, objItem
query = “SELECT FROM Win32_PerfFormattedData_PerfProc_Process WHERE Name LIKE ‘” & Replace(TARGET_PROCESS, “.exe”, “”) & “%'”
Set colItems = objWMIService.ExecQuery(query, “WQL”, 48) ‘ WBemFlagForwardOnly | WBemFlagReturnImmediately
If Err.Number <> 0 Then
‘ WMIクエリ失敗時はログを残して次ループへ
Err.Clear
Exit Sub
End If
Dim processFound
processFound = False
For Each objItem in colItems
processFound = True
Dim cpuUsage, processId
cpuUsage = CDbl(objItem.PercentProcessorTime)
processId = objItem.IDProcess
‘ ログ出力(DEBUG用:必要に応じてコメントアウト解除)
‘ WScript.Echo “Process: ” & TARGET_PROCESS & ” (PID: ” & processId & “) CPU: ” & cpuUsage & “%”
‘ 閾値判定
If cpuUsage >= CPU_THRESHOLD Then
highCpuCounter = highCpuCounter + 1
If highCpuCounter >= CONTINUOUS_COUNT Then
Call TriggerDump(processId, “HighCPU_” & cpuUsage)
highCpuCounter = 0 ‘ カウンターリセット
End If
Else
‘ 正常値に戻ったらカウンターを減衰(完全リセットではなく、スパイク対策)
If highCpuCounter > 0 Then highCpuCounter = highCpuCounter – 1
End If
Next
‘ オブジェクトの明示的解放(メモリリーク防止の極意)
Set colItems = Nothing
On Error GoTo 0
End Sub
Sub TriggerDump(pid, reason)
On Error Resume Next
Dim dumpArgs, execCommand, oExec
‘ ProcDumpコマンドの構築
‘ -ma: フルダンプを採取
‘ -accepteula: EULAの自動同意(無人実行に必須)
dumpArgs = “-ma -accepteula ” & pid & ” “”” & DUMP_OUTPUT_DIR & “”””
execCommand = “””” & PROC_DUMP_PATH & “”” ” & dumpArgs
‘ WScript.Shell.Exec による非同期実行(標準出力・エラーの捕捉が可能)
Set oExec = objShell.Exec(execCommand)
‘ プロセスが完了するまで待機(タイムアウト制御付き)
Dim timeoutCounter
timeoutCounter = 0
Do While oExec.Status = 0
WScript.Sleep 500
timeoutCounter = timeoutCounter + 1
If timeoutCounter > 60 Then ‘ 30秒でタイムアウト
Exit Do
End If
Loop
‘ イベントログまたはテキストログへの記録
Call WriteLog(“DUMP TRIGGERED. PID: ” & pid & “, Reason: ” & reason & “, ExitCode: ” & oExec.ExitCode)
Set oExec = Nothing
On Error GoTo 0
End Sub
Sub EnsureDirectory(dirPath)
Dim fso
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FolderExists(dirPath) Then
fso.CreateFolder(dirPath)
End If
Set fso = Nothing
End Sub
Sub WriteLog(message)
On Error Resume Next
Dim fso, logFile
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set logFile = fso.OpenTextFile(DUMP_OUTPUT_DIR & “monitor.log”, 8, True)
logFile.WriteLine “[” & Now & “] ” & message
logFile.Close
Set logFile = Nothing
Set fso = Nothing
On Error GoTo 0
End Sub
—
4. チーフアーキテクトが教える:本番運用における極限のチューニングポイント
① WMIクエリの「フォワードオンリー」最適化
WMIから大量のコレクションを取得する場合、デフォルトのカーソルはメモリ上に結果セットを保持するため、長期間稼働させると確実にメモリリーク(VBScriptホスト自体の肥大化)を引き起こす。
上記のコードでは、`ExecQuery` のフラグに `48`(`wbemFlagForwardOnly` (32) + `wbemFlagReturnImmediately` (16))を指定している。これにより、カーソルが前進専用となり、メモリ消費を極限まで抑えることが可能になる。
② 明示的なオブジェクトの解放とスコープ管理
VBScriptのガベージコレクションは参照カウンタ方式であるため、オブジェクト変数に `Nothing` を代入しない限り、スクリプトが終了するまでメモリ上に残存する。特にループ内で生成される `SWbemObjectSet` や `FileSystemObject` は、ループのイテレーション毎、あるいは処理の終了時に確実に `Nothing` を代入して解放しなければならない。
③ 権限昇格(`SeDebugPrivilege`)の重要性
システムプロセスや、他のセキュリティコンテキストで動作しているIISのワーカプロセス(`w3wp.exe`)に対してProcDumpをアタッチするためには、スクリプトを実行するプロセス自体がデバッグ特権(`SeDebugPrivilege`)を持っている必要がある。
コード内の `objWMIService.Security_.Privileges.AddAsString “SeDebugPrivilege”, True` は、WMI経由でプロセスを操作・監視する上で、アクセス拒否(Access Denied)を防ぐための必須の呪文である。
—
5. 結言
VBScriptはレガシーな技術と揶揄されることもあるが、OSの深部を直接叩き、インフラストラクチャの挙動を完全に掌握するための「鋭利なメス」である。
ここで示したWMIとProcDumpの連携スクリプトは、単なる監視の自動化に留まらず、予測不能な障害に立ち向かうインフラエンジニアにとっての「最強の黒衣」となる。
オブジェクトのライフサイクルを慈しみ、メモリの挙動に気を配り、システムのリソースを極限まで最適化せよ。それこそが、真のエンジニアリングである。
