【テクニカル・上級編】【メモリダンプ自動取得指示】WMI による高負荷・フリーズプロセスの自動検知と外部ツール(ProcDump)連携起動 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

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の連携スクリプトは、単なる監視の自動化に留まらず、予測不能な障害に立ち向かうインフラエンジニアにとっての「最強の黒衣」となる。

オブジェクトのライフサイクルを慈しみ、メモリの挙動に気を配り、システムのリソースを極限まで最適化せよ。それこそが、真のエンジニアリングである。

タイトルとURLをコピーしました