【入門編】【WMI イベント監視】__InstanceModificationEvent を用いた特定プロセスの CPU 使用率異常検知と自動警告システム – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

【極限の知見】ポーリングを捨て、イベントを待て。WMIを活用した「真のリアルタイム監視」への昇華

こんにちは。現場の最前線で、複雑怪奇なシステムの自動化と最適化を司るチーフアーキテクトです。

「VBScriptで、特定のプロセスのCPU使用率が高くなったら通知するプログラムを作りたい」

もしあなたがそう考えているなら、まずは今持っている「やり方」を一度疑ってみてください。多くの人が最初に思いつくのは、`Do While` ループの中で `WMI` を参照し、`WScript.Sleep` で少し休む……という「ポーリング(周期的な問い合わせ)」という手法です。

もちろん、それは間違いではありません。しかし、プロフェッショナルの世界では、それは「力技」と呼ばれます。数秒おきに「どうですか?」「まだですか?」と問いかけ続けるのは、サーバーのリソースを無駄に消費し、監視の遅延を生む、エレガントとは言い難い設計です。

今日は、そんな「初心者レベルのループ処理」を脱却し、Windowsの深層にあるWMI(Windows Management Instrumentation)のイベント通知機能を使いこなし、システムに負荷をかけずに「変化の瞬間」を捉える、極限の監視スクリプトを伝授しましょう。

ここをクリアすれば、あなたはもう「マクロを記録する人」ではなく、「システムを制御するエンジニア」への第一歩を踏み出すことになります。

1. 「ポーリング」と「イベント駆動」の決定的な違い

技術的な詳細に入る前に、概念を整理しておきましょう。ここが設計思想の根幹です。

ポーリング(Polling)の限界

ポーリングとは、「自ら能動的に確認しに行く」スタイルです。

  • 動き: `ループ開始` → `CPU確認` → `Sleep(5秒)` → `ループ開始`…
  • 欠点:
  • 5秒間、何も起きていなくてもCPUをわずかに使い続ける。
  • 5秒の間に異常が発生しても、次の確認まで検知できない(タイムラグ)。
  • 監視対象が増えると、スクリプト自体の負荷が指数関数的に増える。

イベント駆動(Event-Driven)の美学

イベント駆動とは、「変化が起きた時に、システムから通知を受ける」スタイルです。

  • 動き: `監視設定を登録` → `(スクリプトは静止)` → `異常発生!` → `システムがスクリプトを叩き起こす`
  • 利点:
  • 異常がない間、スクリプトはメモリに存在するだけで、CPU負荷はほぼゼロ。
  • 変化した瞬間に通知が来るため、リアルタイム性が極めて高い。

今回のテーマである `__InstanceModificationEvent` を使う手法は、まさにこの「イベント駆動」を実現するためのものです。

2. WMIイベント監視のアーキテクチャ

WMIには、特定のオブジェクトの状態が変化したことを検知する「イベント・サブスクリプション」という強力な仕組みがあります。

今回使用する鍵となるクラスは以下の通りです:

  • `__InstanceModificationEvent`: インスタンス(プロセスの状態など)が変更された時に発行されるイベント。
  • `Win32_PerfFormattedData_PerfProc_Process`: プロセスのパフォーマンスデータ(CPU使用率など)を保持するクラス。

これらを WQL (WMI Query Language) というSQLに似た言語で結合し、「特定のプロセスが、CPU使用率◯%を超えた瞬間にイベントを投げろ」という命令をWindows OSに直接予約するのです。

3. 実装:プロフェッショナル・監視スクリプト

それでは、実際に動作するコードを見ていきましょう。このコードは、特定のプロセス(例:`chrome.exe`)のCPU使用率が閾値を超えた瞬間に、警告メッセージを表示するものです。

' ==========================================================================
' Project: WMI Event-Driven CPU Monitor
' Description: 特定プロセスのCPU使用率異常を、低負荷かつリアルタイムに検知する
' Author: Chief Architect
' ==========================================================================

Option Explicit

' --- 設定セクション ---
Const TARGET_PROCESS = "chrome"       ' 監視対象のプロセス名 (拡張子なし)
Const CPU_THRESHOLD  = 80             ' 警告を出すCPU使用率の閾値 (%)
Const WATCH_INTERVAL = 1              ' WMIが状態をチェックする間隔 (秒)
' ----------------------

Dim objWMIService, objEvents, objLatestEvent
Dim strQuery, strProcessName, strClassName

' WMIの接続準備
' impersonationLevel=impersonate は、実行ユーザーの権限でWMI操作を行うための作法です
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")

' WQLクエリの構築
' __InstanceModificationEvent WITHIN [間隔] を使うことで、
' OSレベルで「指定秒数ごとに変化を監視し、条件に合致したら通知せよ」と命じます。
strClassName = "Win32_PerfFormattedData_PerfProc_Process"
strProcessName = TARGET_PROCESS

strQuery = "SELECT * FROM __InstanceModificationEvent WITHIN " & WATCH_INTERVAL & _
           " WHERE TargetInstance ISA '" & strClassName & "' " & _
           " AND TargetInstance.Name = '" & strProcessName & "' " & _
           " AND TargetInstance.PercentProcessorTime > " & CPU_THRESHOLD

WScript.Echo "--- 監視を開始しました ---"
WScript.Echo "対象プロセス: " & strProcessName
WScript.Echo "閾値: " & CPU_THRESHOLD & "% 以上"
WScript.Echo "(終了するには Ctrl+C を押してください)"

' 通知クエリの実行
' ExecNotificationQuery は、イベントが発生するまで「待機(ブロック)」する性質を持ちます
Set objEvents = objWMIService.ExecNotificationQuery("WQL", strQuery)

' --- メインイベントループ ---
' このループは、イベントが来るまでスクリプトを「静止状態」に保ちます
' つまり、CPUをほとんど消費しません。
On Error Resume Next ' 実行時エラーによる突然の停止を防止

Do
    ' 次のイベントが発生するまでここで「ブロック(待機)」する
    ' これがポーリングとの決定的な違いです!
    Set objLatestEvent = objEvents.NextEvent()

    If Err.Number = 0 Then
        ' イベントが発生した場合、TargetInstanceの中に変化したデータが入っている
        Dim cpuUsage, procName
        procName = objLatestEvent.TargetInstance.Name
        cpuUsage = objLatestEvent.TargetInstance.PercentProcessorTime

        ' 警告の出力(実運用ではログ出力やメール送信、プロセス停止などに置き換え)
        WScript.Echo "[!] ALERT: " & Now & " | Process: " & procName & " | CPU: " & cpuUsage & "%"
        
        ' ※注意: ここで重い処理を行うと、次のイベント検知が遅れるため、
        ' 警告処理は極力軽量にするのがプロの鉄則です。
    Else
        WScript.Echo "エラーが発生しました: " & Err.Description
        Exit Do
    End If
Loop

On Error GoTo 0

' 後片付け
Set objEvents = Nothing
Set objWMIService = Nothing

4. コードの深層解説:なぜこれが「強い」のか

このコードを単なる「動くもの」として終わらせてはいけません。エンジニアとして、以下の3点に注目してください。

① `ExecNotificationQuery` の魔法

通常の `ExecQuery` は「今の状態」を返して終了しますが、`ExecNotificationQuery` は「未来のイベント」を待ち受ける窓口を作ります。
`objEvents.NextEvent()` を呼び出した瞬間、スクリプトはOSに対して「イベントが来るまで、私を起こさないでください」と伝えます。これにより、Windowsのスケジューラは、このスクリプトにCPU時間を割り当てる必要がなくなるのです。

② WQLの `WITHIN` 句

`WITHIN 1` という記述に注目してください。これは「1秒ごとにWMI内部で状態をチェックせよ」という意味です。
「えっ、結局1秒ごとにチェックしてるならポーリングと同じじゃないか?」と思ったあなた、鋭い!
しかし、「チェックの主体がVBScript(ユーザー空間)か、WMIサービス(カーネルに近いシステム空間)か」という点が決定的に違います。システム空間で行われるチェックは、コンテキストスイッチ(CPUの切り替えコスト)が極めて少なく、圧倒的に低負荷なのです。

③ `TargetInstance` による情報の引き出し

イベントが発生した際、単に「何かが起きた」という通知だけではなく、`TargetInstance` というプロパティを通じて、「変化した後の最新データ」がセットで送られてきます。これにより、わざわざ再度クエリを投げ直す手間(二度手間)を省いています。

5. 陥りやすい罠(トラブルシューティング)

この高度な手法を使う際、初心者が必ずと言っていいほど躓くポイントが3つあります。

1. プロセス名の不一致:
WMIから見えるプロセス名は、タスクマネージャーの表示と微妙に異なることがあります(例:`chrome.exe` ではなく `chrome`)。`TargetInstance.Name` を一度ログに出力して確認する癖をつけましょう。
2. 権限不足:
WMIの高度なクエリ、特にパフォーマンスデータの取得には、管理者権限が必要です。コマンドプロンプトを「管理者として実行」してからスクリプトを叩いてください。
3. 無限ループとエラーハンドリング:
`NextEvent` はイベントが来るまで止まりますが、何らかの理由(WMIサービスの停止など)でエラーが起きると、スクリプトがクラッシュします。`On Error Resume Next` を適切に使い、エラーを捕捉して安全に終了させる設計が、プロフェッショナルの作法です。

最後に:ステップアップを目指すあなたへ

いかがでしたか?
「ループさせて待つ」という直感的な方法から、「イベントを待つ」というシステム的な設計への転換。これこそが、自動化エンジニアが「使い手」から「設計者」へと進化する境界線です。

VBScriptは古い言語だと言われることもありますが、WMIというWindowsの心臓部を直接操る術を知っていれば、今なおこれほど強力な武器はありません。

ここをクリアすれば、VBScriptの基礎はバッチリですよ。次は、この検知をトリガーに「自動でプロセスを再起動する」あるいは「管理者にメールを飛ばす」といった、より実戦的なワークフローに挑戦してみてください。

応援していますよ。次は、もっと複雑なシステムの自動化でお会いしましょう。

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