【テクニカル・上級編】【サービス自動復旧】WMI (Win32_Service) を用いた重要Windowsサービスの監視と停止時自動再起動 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【サービス自動復旧】WMIを用いた重要Windowsサービスの監視と自動再起動の極意

エンタープライズの現場において、レガシーシステムの背後で静かに、しかし絶えずシステムを支えているのはWindowsサービスである。基幹系バッチ、印刷スプーラー、あるいは独自開発のデータ連携サービス。これらが何らかの原因で予期せぬ停止(Abnormal Termination)を起こしたとき、夜間であれば即座にインシデントとなり、日中であれば業務停止に直結する。

GUIのサービス管理コンソールを開いて手動で「開始」をクリックする――そのような属人化された運用は、もはやシニアエンジニアの選択肢たり得ない。

今回は、追加のサードパーティ製ツールを一切使わず、Windows標準のWSH (Windows Script Host)WMI (Windows Management Instrumentation)を極限までチューニングし、堅牢かつ軽量な「サービス自動復旧監視スクリプト」を構築する。

1. なぜ「VBScript + WMI」なのか:レガシーの皮を被った最強のアーキテクチャ

PowerShell全盛の現代において、なぜ今さらVBScriptなのか。答えは明確である。「軽量性」「依存性の排除」「即時実行性」だ。

PowerShellは高機能だが、実行ポリシー(ExecutionPolicy)の壁、.NET Framework/Coreの初期化コスト、バージョンによる挙動の差異など、純粋な「常駐監視タスク」としてはオーバーヘッドが大きい。一方、VBScriptはOSの根幹にネイティブで組み込まれており、環境を選ばず、メモリフットプリントも極小である。

しかし、VBScriptには致命的な罠もある。COMオブジェクトの解放漏れによるメモリリークだ。長期間稼働する監視スクリプトにおいて、オブジェクトのライフサイクル管理を誤れば、WMIプロバイダ(`WMI Adapter`)を巻き込んだメモリ枯渇を引き起こす。この点を完璧に制御したコードこそが、プロフェッショナルの証明となる。

2. 実装:堅牢なるサービス監視・自動復旧スクリプト

以下のコードは、指定したサービス(例: `Spooler`)の状態をポーリングし、停止(Stopped)を検知した場合に即座に`StartService()`メソッドを叩いて復旧を試みる実用スクリプトである。

‘ ==============================================================================
‘ Script Name : ServiceAutoRecovery.vbs
‘ Description : WMIを用いた重要Windowsサービスの常時監視・自動復旧スクリプト
‘ Author :Chief Architect
‘ ==============================================================================

Option Explicit

‘ — コンフィグレーション —
Const TARGET_SERVICE = “Spooler” ‘ 監視対象のサービス名(システム名)
Const CHECK_INTERVAL = 10000 ‘ 監視間隔(ミリ秒: ここでは10秒)
Const LOG_PATH = “C:\Logs\ServiceRecovery.log”

‘ メインループの実行
Main

Sub Main()
Dim objShell
Set objShell = CreateObject(“WScript.Shell”)

WriteLog “INFO: サービス監視スクリプトを開始します。対象: ” & TARGET_SERVICE

Do
MonitorAndRecover TARGET_SERVICE
WScript.Sleep CHECK_INTERVAL
Loop
End Sub

Sub MonitorAndRecover(serviceName)
Dim strComputer, objWMIService, colServices, objService
Dim wmiQuery, intState

strComputer = “.”
wmiQuery = “Select from Win32_Service Where Name = ‘” & serviceName & “‘”

On Error Resume Next
‘ WMI接続の取得(DCOM/RPCの不調を考慮してエラーハンドリングを必須化)
Set objWMIService = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2”)

If Err.Number <> 0 Then
WriteLog “ERROR: WMIへの接続に失敗しました。Error: ” & Hex(Err.Number) & ” – ” & Err.Description
Err.Clear
Exit Sub
End If

Set colServices = objWMIService.ExecQuery(wmiQuery)

If Err.Number <> 0 Then
WriteLog “ERROR: WMIクエリの実行に失敗しました。Error: ” & Hex(Err.Number) & ” – ” & Err.Description
Err.Clear
‘ 参照オブジェクトの確実な解放
Set objWMIService = Nothing
Exit Sub
End If
On Error Goto 0

If colServices.Count > 0 Then
For Each objService in colServices
‘ Stateプロパティの確認 (“Running”, “Stopped”, etc.)
If objService.State <> “Running” Then
WriteLog “WARNING: サービス [” & serviceName & “] が停止状態を検知しました。現在の状態: ” & objService.State

‘ サービス開始メソッドの呼び出し
Dim intResult
intResult = objService.StartService()

If intResult = 0 Then
WriteLog “SUCCESS: サービス [” & serviceName & “] の再起動命令が正常に送信されました。”
Else
WriteLog “ERROR: サービス [” & serviceName & “] の起動に失敗しました。戻り値(Return Code): ” & intResult
End If
End If
Next
Else
WriteLog “ERROR: 指定されたサービス [” & serviceName & “] がシステム上に見つかりません。”
End If

‘ ————————————————————————–
‘ 【極限のメモリ最適化】
WMIコレクションおよびオブジェクトの明示的破棄
‘ ————————————————————————–
Set objService = Nothing
Set colServices = Nothing
Set objWMIService = Nothing

End Sub

Sub WriteLog(message)
Dim objFSO, objFile, timestamp
timestamp = Now()

On Error Resume Next
Set objFSO = CreateObject(“Scripting.FileSystemObject”)

‘ ログディレクトリが存在しない場合は強制生成
Dim logDir
logDir = objFSO.GetParentFolderName(LOG_PATH)
If Not objFSO.FolderExists(logDir) Then
objFSO.CreateFolder(logDir)
End If

‘ 追記モード(ForAppending = 8, Create = True)でオープン
Set objFile = objFSO.OpenTextFile(LOG_PATH, 8, True)
objFile.WriteLine “[” & timestamp & “] ” & message
objFile.Close

Set objFile = Nothing
Set objFSO = Nothing
On Error Goto 0
End Sub

3. チーフアーキテクトが解説するコードの急所

このスクリプトをただ動かすだけなら初級者でも書ける。しかし、24時間365日止まることのない基幹環境で耐えうる設計にするため、以下の3つのアーキテクチャ上の工夫が施されている。

① WMIオブジェクトのライフサイクル管理(メモリリーク対策)

VBScriptのバッドプラクティスとして最も多いのが、ループ内で `GetObject` や `ExecQuery` を繰り返し、変数に代入したまま放置するケースである。WMIのCOMラッパーは参照カウント方式をとっており、スクリプト側で `Set obj = Nothing` を明示的に行わない限り、VBScriptのガベージコレクタが即座に回収する保証はない。
上記のコードでは、`MonitorAndRecover` 関数のスコープ終端で、取得したコレクションおよびサービスオブジェクトをすべて `Nothing` に明示的にクリアしている。これにより、何日稼働させようともメモリ使用量は完全にフラットに維持される。

② WMIネットワーク・RPCの例外耐性

`winmgmts:` 経由のアクセスは、高負荷時やWindowsのRPC(Remote Procedure Call)サブシステムの瞬断によってタイムアウトやエラーを引き起こすことがある。
ここで `On Error Resume Next` を適切に配置し、エラー番号 (`Err.Number`) をトラップしてログに記録しつつ、スクリプト自体が異常終了(クラッシュ)しない堅牢なフェイルセーフ構造にしている。

③ Win32_Service.StartService() の戻り値解析

単にメソッドを叩くだけでなく、WMIが返す戻り値(Return Code)をログに記録している。

  • `0`: 成功 (The request was accepted)
  • `2`: アクセス拒否 (Access Denied)
  • `5`: サービスがすでに実行中、あるいは無効化されている等

これにより、「なぜ復旧できなかったのか」の原因切り分けがログファイルだけで完結するようになる。

4. 運用への組み込み:タスクスケジューラによる常駐化

このスクリプトを実運用に投入する場合、Windowsのタスクスケジューラを利用してOS起動時にバックグラウンド実行(ウィンドウ非表示)させるのが定石である。

1. 実行コマンド: `wscript.exe`
2. 引数: `”C:\Scripts\ServiceAutoRecovery.vbs”`
3. セキュリティオプション: 「ユーザーがログオンしているかどうかにかかわらず実行する」「最高特権で実行する」(サービスの制御にはローカル管理者権限が必須)。

さらに、万が一スクリプト自体がタスクの強制終了などで停止したときに備え、タスクスケジューラの「トリガー」設定で「タスクが失敗した場合の再起動」や「遅延時間を設定した無限ループ再起動」を組み込んでおけば、監視の監視すら不要な完全無人運用体制が完成する。

5. 総括

レガシー技術と呼ばれるVBScriptであっても、OSの仕様とCOMのライフサイクルを深く理解していれば、モダンな環境に劣らない極めて信頼性の高いインフラ自動化ツールへと昇華させることができる。

過剰に複雑なフレームワークに頼る前に、OSの足元を見つめ直すこと。それこそが、トラブルを未然に防ぐ真のシニアエンジニアリングである。

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