【重複起動排他制御】WMI `Win32_Process` と起動パラメータ比較によるスクリプトの多重実行ガード
業務自動化の現場において、VBScriptによるWSH(Windows Script Host)スクリプトは、今なおレガシーシステムとの統合やインフラの簡易自動化において強力な武器だ。
しかし、現場でよくあるトラブルの筆頭が 「処理が終わっていないのに、タスクスケジューラやユーザーの手動実行によってスクリプトが多重起動し、ファイルやデータベースがロックされてクラッシュする」 という現象である。
「プロセス名(`cscript.exe`など)だけで判定していませんか?」
もしそうであれば、それは極めて危険な設計だ。他の重要なバッチ処理まで巻き込んで強制終了させたり、肝心の多重起動を防げなかったりする。
今回は、WMI(Windows Management Instrumentation)の `Win32_Process` を駆使し、「自分自身のプロセスID」と「起動パラメータ(コマンドライン引数)」を完全一致で監視・制御する、プロダクション品質の極限の排他制御アーキテクチャを伝授する。
—
なぜ「プロセス名ベース」の排他制御は破綻するのか?
多くの初学者がやりがちなのが、`WScript.FullName` や単一のプロセス名で実行数をカウントするアプローチだ。
‘ 【アンチパターン】これではシステム全体のcscriptをすべて検知してしまう
Set colProcesses = GetObject(“winmgmts:”).ExecQuery _
(“Select from Win32_Process where Name = ‘cscript.exe'”)
If colProcesses.Count > 1 Then
WScript.Quit ‘ 強制終了
End If
この実装には致命的な欠陥が2つある。
1. 他業務のスクリプトも巻き込む: 同じサーバー上で稼働している全く別の `cscript.exe` によるバッチ処理まで「重複」とみなされ、意図せず停止させられる。
2. 引数の違いを無視している: 同じスクリプトファイルであっても、引数(例: `process.vbs /type:A` と `process.vbs /type:B`)が異なれば、本来は並行稼働させるべき別タスクであるはずだ。
真に堅牢な排他制御とは、「同一のスクリプトファイルが、同一の引数の組み合わせで既にメモリ上に存在するかどうか」をミリ秒単位で正確に突き止めることである。
—
アーキテクチャ設計:WMIとコマンドライン解析の極意
今回の実装では、以下のステップで多重起動をガードする。
1. 自己情報の取得: 実行中のプロセスID(`WScript.ProcessId`)と、自分自身がどのようなパスと引数で起動されたかをWMIから特定する。
2. クエリの精密化: `Win32_Process` から `cscript.exe` または `wscript.exe` のリストを取得し、コマンドライン文字列を走査する。
3. 自己除外とパラメータ一致判定: 自分自身のプロセスIDを除外しつつ、スクリプトのフルパスと引数が完全に一致するプロセスを検索する。
4. 安全な離脱: 既存プロセスが検知された場合は、ログを残して静かに処理を中断(Exit)する。
ここで注意すべきは、WMIクエリのコストと、スクリプト実行エンジン(`cscript` / `wscript`)のホストプロセスの挙動だ。VBScriptファイル自体は直接プロセスにならないため、実体である実行ホストのコマンドラインをハックする必要がある。
—
プロダクションコード:コピペで使える堅牢な排他制御テンプレート
実務の現場でそのまま組み込める、エラーハンドリングを網羅した完成版のコードを提示する。スクリプトの最上位(処理の開始直後)にこのロジックを配置すること。
‘ =================================================================00文字列
‘ Script Name : SafeBatchRunner.vbs
‘ Description : WMIを用いた完全パラメータ一致型・多重起動排他制御テンプレート
‘ Author : Enterprise Automation Architect
‘ ==========================================================================
Option Explicit
‘ メイン処理の実行
Call Main()
Sub Main()
‘ 1. 多重起動チェックの実行
If CheckAlreadyRunning() Then
WScript.Echo “【警告】同一のパラメータを持つプロセスが既に実行中のため、処理を中断します。”
WScript.Quit 99 ‘ 独自の終了コードを返して異常終了扱いを防ぐ
End If
‘ ———————————————————————-
‘ ここから実際の業務ロジックを記述
‘ ———————————————————————-
WScript.Echo “メイン処理を開始します: ” & Now()
‘ 例:重い処理のシミュレーション(10秒待機)
WScript.Sleep 10000
WScript.Echo “メイン処理を終了します: ” & Now()
End Sub
‘ ————————————————————————–
‘ 関数名: CheckAlreadyRunning
‘ 概要 : 自身のプロセスIDとコマンドラインを基準に、重複実行を検知する
‘ 戻り値: Boolean (True = 既に実行中 / False = 実行可能)
‘ ————————————————————————–
Function CheckAlreadyRunning()
CheckAlreadyRunning = False
Dim objWMIService, colProcesses, objProcess
Dim currentPID, currentCommandLine
Dim wmiQuery
On Error Resume Next
‘ 現在のプロセスIDを取得
currentPID = CStr(WScript.ProcessId)
‘ WMIサービスへの接続
Set objWMIService = GetObject(“winmgmts:\\.\root\cimv2”)
If Err.Number <> 0 Then
‘ WMI接続失敗時は安全側に倒して重複なしとする(または環境に合わせて例外処理)
On Error GoTo 0
Exit Function
End If
‘ 自身の完全なコマンドラインを取得するため、Win32_Processから自PIDを引く
wmiQuery = “Select CommandLine from Win32_Process Where ProcessId = ‘” & currentPID & “‘”
Set colProcesses = objWMIService.ExecQuery(wmiQuery)
For Each objProcess in colProcesses
currentCommandLine = objProcess.CommandLine
Next
If currentCommandLine = “” Then
‘ コマンドラインが取得できない場合は判定不能のためスルー
On Error GoTo 0
Exit Function
End If
‘ 全ての cscript および wscript プロセスを取得
wmiQuery = “Select ProcessId, CommandLine from Win32_Process Where Name = ‘cscript.exe’ OR Name = ‘wscript.exe'”
Set colProcesses = objWMIService.ExecQuery(wmiQuery)
Dim targetProcess
For Each objProcess in colProcesses
‘ 自分自身は除外する
If CStr(objProcess.ProcessId) <> currentPID Then
‘ コマンドラインが完全に一致するか評価
‘ ※必要に応じてスクリプト名のみの一致、あるいは特定引数のみの比較にカスタマイズ可能
If LCase(objProcess.CommandLine) = LCase(currentCommandLine) Then
CheckAlreadyRunning = True
Exit For
End If
End If
Next
On Error GoTo 0
End Function
—
現場でエンジニアが陥りがちな罠とチーフアーキテクトからの助言
1. パス区切り文字と大文字小文字の罠
Windowsのファイルパスは大文字小文字を区別しないが、コマンドライン引数内のパスやクォーテーション(`””`)の有無によって、WMIから返却される文字列のフォーマットが微妙に異なるケースがある。
厳密な比較を行いたい場合は、比較前に `LCase()` で小文字化し、さらに余分なスペースやダブルクォーテーションを正規化するラッパー関数を挟むと、より堅牢性が増す。
2. 権限(UAC)とWMIの視界
タスクスケジューラから「最高特権で実行」しているバッチと、開発者がデスクトップから手動で叩いたバッチでは、セッションや権限の分離によりWMI経由でもプロセスが見えないケースが希にある。
業務自動化スクリプトは、「実行ユーザーのコンテキストを統一する」ことがインフラ設計の基本であることを忘れてはならない。
3. ロギングの重要性
単に `WScript.Quit` で終わらせるのではなく、重複検知時に「いつ、どの引数で起動されたプロセスがブロックしたか」をWindowsイベントログやテキストファイルに吐き出す設計にしておくこと。これが後日のオペレーション障害解析における最強の武器となる。
—
総括
VBScriptは古くからある技術だが、その分、OSの深部(WMIやCOM)とダイレクトに対話できる圧倒的なポテンシャルを秘めている。
「動けばいいや」の精神で作られた安易なスクリプトは、やがてファイルロックの嵐を引き起こし、現場の信頼を失墜させる。
今回紹介したWMIパラメータ比較による排他制御をマスターし、「止まらない、しかし無駄に重複しない」プロフェッショナルな自動化基盤を構築してほしい。
