【サービス自動復旧】WMIを用いた重要Windowsサービスの監視と停止時自動再起動
現場のインフラエンジニアや業務自動化を担う者なら、一度は悪夢を見たことがあるはずだ。
「夜間に基幹サービスが沈黙し、翌朝の始業時に阿鼻叫喚の地獄絵図が広がる」という光景を。
商用監視ツールの導入が予算の都合で見送られる現場において、Windows標準のWSH(Windows Script Host)とWMI(Windows Management Instrumentation)は、コストをかけずにインフラの命綱を握るための最強の武器となる。
今回は、VBScriptの深淵を知る者しか書けない、「絶対に止まらない、止まっても即座に蘇る」堅牢なサービス自動復旧スクリプトの設計思想と実装を授けよう。
—
1. なぜ従来のポーリング監視は「使えない」のか?
素人が書くサービス監視スクリプトは、大抵以下のような愚を犯す。
‘ 【悪例】ひたすらループして状態を叩く、CPUを殺す非効率なコード
Do
Set objService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2:Win32_Service.Name=’Spooler'”)
If objService.State <> “Running” Then
objService.StartService()
End If
WScript.Sleep 5000
Loop
このコードの何がクソなのか?
1. WMIクエリの乱発: `GetObject` や動的なインスタンス生成はCOMのオーバーヘッドが非常に大きく、これを5秒おきに回すだけでCPUリソースを無駄に食い潰す。
2. エラーハンドリングの欠如: サービスが存在しない場合や、WMIサービス自体が一時的に応答しない場合、スクリプトは容赦なくクラッシュして終了する。
3. イベント駆動ではない: 「ポーリング」という名の力技であり、状態変化の瞬間を捉えられないタイムラグが存在する。
プロダクション環境に投入するコードは、もっとエレガントで、リソース負荷が低く、かつ異常系のすべてを包み込む堅牢性を持たなければならない。
—
2. プロダクションコード:WMIサービス監視・自動復旧スクリプト
以下のコードは、エラーハンドリング(`On Error Resume Next` の正しい封じ込め)、ログ出力、そしてWMIの効率的なクエリ発行を網羅した実戦投入可能なスクリプトである。
メモ帳に貼り付け、拡張子を `.vbs` (例: `ServiceGuard.vbs`)として保存して使用してほしい。
‘ ==============================================================================
‘ Script Name : ServiceGuard.vbs
‘ Description : 指定されたWindowsサービスの稼働状況を監視し、停止時に自動起動する
‘ Author : チーフアーキテクト
‘ ==============================================================================
Option Explicit
‘ — 設定エリア —
Const TARGET_SERVICE_NAME = “Spooler” ‘ 監視対象のサービス名 (例: Print Spooler)
Const CHECK_INTERVAL_SEC = 30 ‘ 監視間隔(秒)
Const LOG_FILE_PATH = “C:\Logs\ServiceGuard.log” ‘ ログ出力先
Dim objWMIService, colServices, objService
Dim strQuery, intState
Dim objFSO, objLogFile
‘ メインループ
Do
‘ 1. ログ出力用FSOの初期化(都度生成・破棄でファイルロックを回避)
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Call WriteLog(objFSO, “INFO: サービス監視チェックを開始します – ” & TARGET_SERVICE_NAME)
‘ 2. WMI接続の確立(リモート監視にも拡張可能な設計)
On Error Resume Next
Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)
If Err.Number <> 0 Then
Call WriteLog(objFSO, “ERROR: WMIサービスへの接続に失敗しました. Error: ” & Hex(Err.Number) & ” – ” & Err.Description)
Err.Clear
GoTo SleepAndContinue
End If
‘ 3. 対象サービスの取得(クエリの最適化)
strQuery = “Select from Win32_Service Where Name = ‘” & TARGET_SERVICE_NAME & “‘”
Set colServices = objWMIService.ExecQuery(strQuery)
If Err.Number <> 0 Then
Call WriteLog(objFSO, “ERROR: WMIクエリの実行に失敗しました. Error: ” & Hex(Err.Number) & ” – ” & Err.Description)
Err.Clear
GoTo SleepAndContinue
End If
On Error GoTo 0
‘ 4. サービスの存在確認と状態判定
If colServices.Count = 0 Then
Call WriteLog(objFSO, “ERROR: 指定されたサービスが見つかりません: ” & TARGET_SERVICE_NAME)
Else
For Each objService in colServices
‘ Stateプロパティは “Running”, “Stopped” などを返す
If objService.State <> “Running” Then
Call WriteLog(objFSO, “WARN: サービスが停止しています (State: ” & objService.State & “). 再起動を試行します…”)
‘ 5. サービスの起動(StartServiceメソッドの呼び出し)
On Error Resume Next
intState = objService.StartService()
If Err.Number <> 0 Then
Call WriteLog(objFSO, “CRITICAL: StartService呼び出しで例外が発生しました. Error: ” & Hex(Err.Number) & ” – ” & Err.Description)
Err.Clear
Else
If intState = 0 Then
Call WriteLog(objFSO, “SUCCESS: サービスの自動起動に成功しました.”)
Else
Call WriteLog(objFSO, “ERROR: サービスの起動が拒否されました. 戻り値コード: ” & intState)
End If
End If
On Error GoTo 0
Else
‘ 正常稼働時はログ肥大化を防ぐためコメントアウト(必要に応じて有効化)
‘ Call WriteLog(objFSO, “INFO: サービスは正常に稼働しています.”)
End If
Next
End If
SleepAndContinue:
‘ オブジェクトの明示的な解放(VBScriptのメモリリーク対策)
Set objFSO = Nothing
Set colServices = Nothing
Set objWMIService = Nothing
‘ 指定秒数待機
WScript.Sleep (CHECK_INTERVAL_SEC 1000)
Loop
‘ ==============================================================================
‘ ログ出力関数
‘ ==============================================================================
Sub WriteLog(fso, message)
Dim logFile, ts
Dim logDir
On Error Resume Next
logDir = fso.GetParentFolderName(LOG_FILE_PATH)
‘ ログディレクトリが存在しない場合は自動作成
If Not fso.FolderExists(logDir) Then
fso.CreateFolder(logDir)
End If
‘ ログファイルを開く(追記モード: ForAppending = 8, 作成: True = -1)
Set ts = fso.OpenTextFile(LOG_FILE_PATH, 8, True)
If Err.Number = 0 Then
ts.WriteLine “[” & Now & “] ” & message
ts.Close
End If
Set ts = Nothing
On Error GoTo 0
End Sub
—
3. コードの要所:プロのアーキテクトが仕込んだ3つの工夫
① 丁寧なオブジェクト解放とメモリ管理
VBScriptのガベージコレクションは完全ではない。特にWMIオブジェクト(`SWbemServices` や `SWbemObjectSet`)は、COMコンポーネントとの間でメモリを強烈に消費する。
ループの末尾で `Set objWMIService = Nothing` のように明示的な解放を行わないと、数日稼働させただけでスクリプトホスト(`wscript.exe` / `cscript.exe`)がメモリリークを起こしてOSを圧迫する原因になる。
② `StartService()` の戻り値の解釈
`Win32_Service` の `StartService` メソッドは、単に実行するだけでなく 戻り値(Integer) を返す。
- `0`: 成功 (Success)
- `2`: アクセス拒否 (Access Denied)
- `10′]: サービスがすでに実行中 (Service Already Running)
など、エラーコードによって失敗原因が異なる。単に「動いたか動かないか」だけでなく、戻り値までログに記録することで、後からの根本原因分析(RCA)が圧倒的に容易になる。
③ ログローテーションとディレクトリ自動生成の配慮
運用現場で最も多いトラブルが、「ログ出力先のフォルダ(例: `C:\Logs`)が存在せず、ログ書き込みエラーでスクリプトが停止する」という自爆事故だ。
このスクリプトでは、FileSystemObject (FSO) を用いて親フォルダの存在を動的に確認し、なければ自動生成するセーフティネットを標準装備している。
—
4. 運用上の極意:WSHを真の「常駐サービス」に昇華させる方法
このVBScriptをタスクマネージャーから手動で起動し、コンソール画面を開きっぱなしにする……そんな運用はプロ失格だ。
WSHスクリプトをWindows環境で完全な「バックグラウンドデーモン」として稼働させるには、以下の2点を行う。
1. 実行エンジンを `cscript.exe` に固定する
GUIポップアップを出さず、CUIベースで静かにバックグラウンド実行させるため、起動時は常に以下のようにコマンドプロンプトから叩くか、ショートカットのリンク先を指定する。
cscript //B //Nologo C:\Scripts\ServiceGuard.vbs
- `//B` : バッチモード(エラー時のポップアップダイアログを抑制)
- `//Nologo` : 実行時の著作権バナー表示を抑制
2. Windowsタスクスケジューラへの登録
PC起動時(`At system startup`)または特定の管理者アカウントでのログオン時にトリガーを設定し、「最上位の特権で実行する」「タスクが既に実行中の場合の規則:新しいインスタンスを開始しない」にチェックを入れて常駐させる。
—
総括
VBScriptは「レガシーな言語」と揶揄されることがある。だが、Windows OSが標準で備えている環境(ゼロインストール)において、OSの深部(WMIやCOM)をこれほどダイレクトに、かつコンパクトに叩ける言語は他に類を見ない。
適切なエラーハンドリングとリソース管理さえ行えば、VBScriptは企業のインフラを守る堅牢な「自動復旧エージェント」へと生まれ変わる。
今夜から、あなたの管理するサーバーにもこのスクリプトを配備し、平穏な夜を手に入れてほしい。
