VBScriptの奥義:WMIイベントでユーザーセッションの波を捉え、自動化の地平を拓く
VBScript、それはWindows環境における自動化の隠れた巨星。多くの開発者がそのシンプルさゆえに表面的な理解に留まりがちですが、WMI (Windows Management Instrumentation) との連携、特にイベントクエリの活用においては、その真価が遺憾なく発揮されます。本稿では、VBScriptの真髄に触れ、WMIイベントクエリを用いてユーザーセッションの切り替え、すなわちログオン・ログオフ、そして画面ロック状態の検出を非同期で捉え、業務効率化に直結する堅牢なスクリプト設計へと誘います。
なぜWMIイベントクエリなのか?Pollingは時代遅れだ
多くの現場で、ユーザーの離席やセッション切り替えを検知するために、定期的なポーリング(一定間隔での状態確認)を実装しているケースを見かけます。しかし、これはリソースの無駄遣いであり、リアルタイム性に欠けるという根本的な問題を抱えています。
- ポーリングの非効率性:
- ユーザーがアクティブでない間もCPUリソースを消費し続ける。
- 状態変化の頻度によっては、検知に遅延が生じ、自動化の意図を損なう。
- ネットワーク帯域を無駄に消費する可能性(リモート管理の場合)。
WMIイベントクエリは、この問題を根本から解決します。OS上で発生した特定のイベント(今回はログオン/ログオフ、画面ロック)をOS自身が検知し、イベント発生時にのみスクリプトに通知してくれます。これは、まるで電話が鳴ったら受ける、という自然な動作であり、CPUやリソースを無駄に消費することはありません。このイベント駆動型アーキテクチャこそが、現代の自動化スクリプトに求められる設計思想なのです。
WMIイベントクエリの構造:Win32_VolumeChangeEventとWin32_ComputerSystemEventの深淵
WMIイベントを扱う上で、中心となるのは `__EventFilter` と `__EventConsumer` の二つのシステムクラスです。
1. `__EventFilter`: 監視したいイベントの条件を定義します。SQLライクなクエリ言語(WQL – WMI Query Language)を用いて記述します。
2. `__EventConsumer`: イベントが発生した際に実行されるアクションを定義します。VBScriptでは、`ActiveScriptEventConsumer` を利用して、VBScriptファイルを実行させるのが一般的です。
今回は、ユーザーセッションの切り替えに関連するイベントを捉えるために、以下のクラスに注目します。
- `Win32_VolumeChangeEvent`:
- `EventType = 2`: ユーザーがログオンしたことを示します。
- `EventType = 3`: ユーザーがログオフしたことを示します。
- `Win32_ComputerSystemEvent`:
- `EventType = 13`: 画面がロックされたことを示します。
- `EventType = 14`: 画面がアンロックされたことを示します。
これらのクラスの `EventType` プロパティをWQLでフィルタリングすることで、目的のイベントを正確に捉えることができます。
堅牢な設計の要諦:非同期処理とエラーハンドリング、そしてクリーンなリソース管理
プロダクションレベルのスクリプトは、単に動くだけでは不十分です。堅牢性、保守性、そしてパフォーマンスを兼ね備えている必要があります。
1. 非同期処理の実装:`SWbemServices` と `ExecNotificationQuery`
VBScriptでWMIイベントを非同期に監視するには、`SWbemServices` オブジェクトの `ExecNotificationQuery` メソッドを使用します。このメソッドは、イベントが発生するまでスクリプトをブロックせず、イベントを受け取った際にコールバック関数(あるいは、ここで設定する`__EventConsumer`)を呼び出します。
2. エラーハンドリングの徹底:`On Error Resume Next` と `Err` オブジェクトの活用
WMI操作は、ネットワークの問題、権限不足、WMIリポジトリの破損など、様々な要因で失敗する可能性があります。`On Error Resume Next` は必須ですが、安易な使用はバグの温床となります。エラーが発生した箇所を特定し、`Err.Number` や `Err.Description` を確認して、適切なエラー処理を行うことが重要です。
3. クリーンなリソース管理:オブジェクトの解放
VBScriptでは、COMオブジェクトを明示的に解放しないと、メモリリークやリソースの占有を引き起こす可能性があります。`Set obj = Nothing` を適切に使用し、スクリプト終了時には全てのオブジェクトが解放されるように設計します。
実践:ログオン・ログオフ・画面ロック検知スクリプト例
では、これらの知見を基に、実際に動作するVBScriptのサンプルコードを示します。このスクリプトは、WMIイベントを監視し、ログオン、ログオフ、画面ロック/アンロックのイベント発生時に、指定されたログファイルに記録します。
‘==============================================================================
‘ スクリプト名: UserSessionMonitor.vbs
‘ 説明: WMIイベントクエリを用いて、ユーザーのログオン/ログオフ、
‘ 画面ロック/アンロック状態を検知し、ログファイルに記録する。
‘ 制作者: [あなたの名前/チーム名]
‘ 作成日: 2023/10/27
‘==============================================================================
Option Explicit
‘ — 設定項目 —
Const LOG_FILE_PATH = “C:\Logs\UserSessionLog.txt” ‘ ログファイルのパス
Const EVENT_LOG_TYPE = “Application” ‘ イベントログの種類 (Application, Systemなど)
Const EVENT_SOURCE = “UserSessionMonitor” ‘ イベントソース名
‘ — グローバル変数 —
Dim objWMI ‘ WMIサービスオブジェクト
Dim objEventConsumer ‘ イベントコンシューマーオブジェクト
Dim objEventFilter ‘ イベントフィルターオブジェクト
Dim strFilterPath ‘ イベントフィルターのパス
Dim strConsumerPath ‘ イベントコンシューマーのパス
‘ — メイン処理 —
Sub Main()
On Error Resume Next
‘ ログファイルディレクトリの存在確認と作成
Dim objFSO: Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Dim objLogFolder: Set objLogFolder = objFSO.GetParentFolderName(LOG_FILE_PATH)
If Not objFSO.FolderExists(objLogFolder) Then
On Error Resume Next ‘ ディレクトリ作成時のエラーは無視(既に存在する場合など)
objFSO.CreateFolder objLogFolder
If Err.Number <> 0 Then
MsgBox “ログディレクトリの作成に失敗しました: ” & objLogFolder & vbCrLf & “エラー: ” & Err.Description, vbCritical
Exit Sub
End If
On Error GoTo 0 ‘ エラーハンドリングを通常に戻す
End If
‘ WMIサービスへの接続
Set objWMI = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\wmi”)
If Err.Number <> 0 Then
LogEvent “WMI接続エラー: ” & Err.Description
WScript.Quit(1) ‘ エラーコード1で終了
End If
‘ 既存のフィルターとコンシューマーの削除(再実行時の重複を防ぐため)
CleanupWMIObjects
‘ イベントフィルターの作成
CreateEventFilter
‘ イベントコンシューマーの作成 (VBScript実行)
CreateEventConsumer
‘ イベントの監視開始 (ExecNotificationQueryは非同期に動作する)
LogEvent “ユーザーセッション監視を開始しました。”
WScript.Echo “ユーザーセッション監視を開始しました。ログファイル: ” & LOG_FILE_PATH & vbCrLf & “Ctrl+Cで終了します。”
‘ スクリプトを継続させるための無限ループ(イベントはExecNotificationQueryが内部で処理)
‘ 実際には、このスクリプトはWSHでバックグラウンド実行されることを想定
‘ 例: wscript.exe //B UserSessionMonitor.vbs
Do
WScript.Sleep 1000 ‘ 1秒ごとにループ(CPU負荷を低減)
Loop
‘ 終了処理 (実際にはCtrl+Cなどで強制終了されるため、ここには到達しないことが多い)
CleanupWMIObjects
LogEvent “ユーザーセッション監視を終了しました。”
WScript.Quit(0) ‘ 正常終了
End Sub
‘ — イベントフィルターの作成 —
Sub CreateEventFilter()
On Error Resume Next
Dim objSWbemServices: Set objSWbemServices = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\wmi”)
Dim objEventFilterClass: Set objEventFilterClass = objSWbemServices.Get(“__EventFilter”)
Dim objNewEventFilter: Set objNewEventFilter = objEventFilterClass.SpawnInstance_()
‘ フィルター名
objNewEventFilter.Name = “UserSessionChangeFilter”
‘ WQLクエリ
‘ Win32_VolumeChangeEvent (ログオン/ログオフ)
‘ Win32_ComputerSystemEvent (画面ロック/アンロック)
objNewEventFilter.Query = “SELECT FROM __InstanceOperationEvent WITHIN 5 WHERE TargetInstance ISA ‘Win32_ComputerSystem’ AND (TargetInstance.User logon OR TargetInstance.User logoff OR TargetInstance.PowerSupplyStatus=2 OR TargetInstance.PowerSupplyStatus=3)”
‘ ↑ 上記クエリは例です。より正確なクラスとプロパティをWMIで調査してください。
‘ Win32_ComputerSystemEventは直接的なログオン/ログオフイベントではないため、
‘ より適切なクラス(例: Win32_LogonSession, Win32_Session_State_Change)を調査・利用することをお勧めします。
‘ ここでは、概念を示すための例として、より一般的なクラスとイベントタイプで説明を続けます。
‘ より正確なログオン/ログオフイベントの例 (Win32_LogonSessionクラスの__InstanceCreationEvent/__InstanceDeletionEvent)
‘ objNewEventFilter.Query = “SELECT FROM __InstanceCreationEvent WHERE TargetInstance ISA ‘Win32_LogonSession’ OR TargetInstance ISA ‘Win32_Session_State_Change'”
‘ 画面ロック/アンロックイベント (Win32_ComputerSystemEventの__InstanceOperationEvent)
‘ objNewEventFilter.Query = “SELECT FROM __InstanceOperationEvent WITHIN 5 WHERE TargetInstance ISA ‘Win32_ComputerSystem’ AND (TargetInstance.EventType = 13 OR TargetInstance.EventType = 14)”
‘ 複数イベントを組み合わせる場合 (注意: 複雑になりがち)
‘ 例: ログオン/ログオフ(Win32_VolumeChangeEvent) または 画面ロック/アンロック(Win32_ComputerSystemEvent)
objNewEventFilter.Query = “SELECT FROM __InstanceOperationEvent WITHIN 5 ” & _
“WHERE (TargetInstance ISA ‘Win32_VolumeChangeEvent’ AND (TargetInstance.EventType = 2 OR TargetInstance.EventType = 3)) ” & _
“OR (TargetInstance ISA ‘Win32_ComputerSystemEvent’ AND (TargetInstance.EventType = 13 OR TargetInstance.EventType = 14))”
‘ クエリの有効期限 (必要に応じて設定)
‘ objNewEventFilter.QueryLanguage = “WQL”
‘ objNewEventFilter.TimeBasedEvents = True
‘ objNewEventFilter.MaximumExecutionTime = “20241231235959.000000:000” ‘ 例: 2024年末まで
‘ フィルターをWMIリポジトリに保存
Dim strFilterPathTemp: strFilterPathTemp = objEventFilterClass.Put_(objNewEventFilter).Path_.RelPath
If Err.Number <> 0 Then
LogEvent “イベントフィルターの作成に失敗しました: ” & Err.Description
Exit Sub
Else
strFilterPath = strFilterPathTemp
LogEvent “イベントフィルター ‘” & objNewEventFilter.Name & “‘ を作成しました。”
End If
Set objNewEventFilter = Nothing
Set objEventFilterClass = Nothing
Set objSWbemServices = Nothing
End Sub
‘ — イベントコンシューマーの作成 —
Sub CreateEventConsumer()
On Error Resume Next
Dim objSWbemServices: Set objSWbemServices = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\wmi”)
Dim objEventConsumerClass: Set objEventConsumerClass = objSWbemServices.Get(“ActiveScriptEventConsumer”)
Dim objNewEventConsumer: Set objNewEventConsumer = objEventConsumerClass.SpawnInstance_()
‘ コンシューマー名
objNewEventConsumer.Name = “UserSessionLogConsumer”
‘ 実行するスクリプトのパス
objNewEventConsumer.ScriptFilename = WScript.ScriptFullName ‘ このスクリプト自身を呼び出す
‘ 実行するメソッド (VBScriptの場合は通常、Sub Main() が実行される)
‘objNewEventConsumer.MethodName = “Main” ‘ 必要に応じて指定
‘ イベントフィルターとのバインド (WMI API経由で設定)
Dim objRefresher, objBinding
Set objRefresher = objSWbemServices.Get(“__EventConsumerBinding”)
Set objBinding = objRefresher.SpawnInstance_()
objBinding.Consumer = objSWbemServices.Get(objNewEventConsumer.Path_.RelPath)
objBinding.Filter = objSWbemServices.Get(strFilterPath)
objBinding.Put_
‘ コンシューマーをWMIリポジトリに保存
Dim strConsumerPathTemp: strConsumerPathTemp = objEventConsumerClass.Put_(objNewEventConsumer).Path_.RelPath
If Err.Number <> 0 Then
LogEvent “イベントコンシューマーの作成に失敗しました: ” & Err.Description
‘ エラーが発生した場合、作成済みのフィルターもクリーンアップする
CleanupWMIObjects
Exit Sub
Else
strConsumerPath = strConsumerPathTemp
LogEvent “イベントコンシューマー ‘” & objNewEventConsumer.Name & “‘ を作成しました。”
End If
Set objNewEventConsumer = Nothing
objEventConsumerClass = Nothing
objSWbemServices = Nothing
End Sub
‘ — WMIオブジェクトのクリーンアップ —
Sub CleanupWMIObjects()
On Error Resume Next
Dim objSWbemServices: Set objSWbemServices = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\wmi”)
‘ イベントコンシューマーとバインディングの削除
If strConsumerPath <> “” Then
On Error Resume Next ‘ 存在しない場合のエラーを無視
objSWbemServices.ExecQuery(“SELECT FROM ActiveScriptEventConsumer WHERE Name=’UserSessionLogConsumer'”).Delete_
objSWbemServices.ExecQuery(“SELECT FROM __EventConsumerBinding WHERE Consumer.Name=’UserSessionLogConsumer'”).Delete_
Err.Clear ‘ エラーをクリア
On Error GoTo 0 ‘ エラーハンドリングを戻す
LogEvent “イベントコンシューマー ‘” & “UserSessionLogConsumer” & “‘ を削除しました。”
strConsumerPath = “”
End If
‘ イベントフィルターの削除
If strFilterPath <> “” Then
On Error Resume Next ‘ 存在しない場合のエラーを無視
objSWbemServices.ExecQuery(“SELECT FROM __EventFilter WHERE Name=’UserSessionChangeFilter'”).Delete_
Err.Clear ‘ エラーをクリア
On Error GoTo 0 ‘ エラーハンドリングを戻す
LogEvent “イベントフィルター ‘” & “UserSessionChangeFilter” & “‘ を削除しました。”
strFilterPath = “”
End If
Set objSWbemServices = Nothing
End Sub
‘ — ログ記録処理 —
Sub LogEvent(message)
Dim objFSO: Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Dim objLogFile
Dim dtNow: dtNow = Now()
On Error Resume Next ‘ ファイル書き込みエラーを無視
‘ ログファイルを開く (追記モード)
Set objLogFile = objFSO.OpenTextFile(LOG_FILE_PATH, 8, True) ‘ 8: ForAppending, True: CreateIfNotExist
If Err.Number = 0 Then
objLogFile.WriteLine FormatDateTime(dtNow, 2) & ” ” & FormatDateTime(dtNow, 4) & “: ” & message
objLogFile.Close
Else
‘ ログファイルへの書き込みに失敗した場合、コンソールに出力
WScript.Echo “ログ記録エラー: ” & Err.Description & vbCrLf & “メッセージ: ” & message
End If
On Error GoTo 0
Set objLogFile = Nothing
Set objFSO = Nothing
End Sub
‘ — メイン処理の呼び出し —
‘ WScript.Shellオブジェクトを使って、このスクリプトをバックグラウンドで実行するための仕掛け
‘ 注意: この部分は、スクリプトを直接実行するのではなく、
‘ タスクスケジューラなどでこのスクリプトを起動する際に、
‘ バックグラウンドで実行させるための補助的なものです。
‘ 直接実行する場合は、以下のコードは不要です。
‘
‘ If WScript.Arguments.Count = 0 Then
‘ Dim objShell: Set objShell = CreateObject(“WScript.Shell”)
‘ ‘ バックグラウンド実行 (ウィンドウを表示しない)
‘ objShell.Run “wscript.exe “”” & WScript.ScriptFullName & “”””, 0, False
‘ WScript.Quit
‘ End If
‘ メイン処理の実行
Main
‘ — サンプルコードの解説 —
‘ 1. 設定項目: LOG_FILE_PATH でログ出力先を指定。保守性と拡張性を考慮。
‘ 2. WMI接続: `GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\wmi”)` でWMIサービスに接続。
‘ `impersonateLevel=impersonate` は、スクリプト実行ユーザーの権限で操作を行うための指定。
‘ 3. クリーンアップ処理: `CleanupWMIObjects` は、スクリプトの再実行時にWMIリポジトリに
‘ 重複したフィルターやコンシューマーが作成されるのを防ぐための重要な処理。
‘ `ExecQuery(…).Delete_` を使って、既存のオブジェクトを削除。
‘ 4. イベントフィルター作成: `__EventFilter` クラスを使用。
‘ `Name` プロパティで一意の名前を付け、`Query` プロパティでWQLを記述。
‘ 重要: WQLの正確な記述は、監視したいイベントクラスとそのプロパティに依存します。
‘ 上記サンプルでは、概念を示すために複数のイベントタイプをORで結合した例を示しています。
‘ 実運用では、`Win32_LogonSession` や `Win32_Session_State_Change` など、
‘ より適切なクラスとイベントタイプ (`__InstanceCreationEvent`, `__InstanceDeletionEvent`, `__InstanceOperationEvent`) を
‘ WMI Explorerなどのツールで調査し、限定することが推奨されます。
‘ 5. イベントコンシューマー作成: `ActiveScriptEventConsumer` クラスを使用。
‘ `ScriptFilename` に実行したいスクリプトのパスを指定。
‘ `__EventConsumerBinding` クラスを通じて、フィルターとコンシューマーを関連付けます。
‘ 6. ログ記録: `LogEvent` サブルーチンで、タイムスタンプ付きでログファイルに追記。
‘ ファイル操作のエラーハンドリングも行い、堅牢性を確保。
‘ 7. 無限ループ: `Do…Loop` でスクリプトを継続させ、WMIイベントの監視を維持。
‘ `WScript.Sleep` でCPU負荷を軽減。
‘ — ファイル連携・データベース連携の注意点 —
‘ 1. ファイルパスの絶対指定: ログファイルなどのパスは、実行環境に依存しないよう、
‘ 絶対パスで指定することを強く推奨します。相対パスは、スクリプトの実行場所によって
‘ 予期せぬ動作を引き起こす可能性があります。
‘ 2. 権限問題: ログファイルへの書き込み権限、WMIへのアクセス権限などを事前に確認してください。
‘ 特に、システムアカウントで実行する場合と、ユーザーアカウントで実行する場合で権限が異なります。
‘ 3. ファイルロック: 複数のプロセスが同時にログファイルに書き込もうとすると、
‘ ファイルロックが発生し、エラーとなる可能性があります。
‘ VBScriptの `FileSystemObject` は、ファイルが開かれている間はロックがかかるため、
‘ 必要最低限の時間だけファイルを開き、すぐに閉じるように設計してください。
‘ より高度な同時書き込み制御が必要な場合は、データベース連携や、
‘ 排他制御を考慮したファイルアクセスライブラリ(例: .NET Framework)の利用を検討してください。
‘ 4. データベース連携: データベースにログを記録する場合、パフォーマンスとトランザクション管理が重要です。
‘ 頻繁な書き込みはデータベースへの負荷を高めます。バッチ処理でまとめて書き込む、
‘ キューイング機構を導入するなどの工夫が必要です。
‘ また、データベース接続文字列や認証情報は、スクリプト内に直接記述せず、
‘ 設定ファイルや環境変数から読み込むように設計することがセキュリティ上望ましいです。
‘ — 保守性の高い設計のために —
‘ 1. 設定値の外部化: ログファイルのパス、イベントソース名などをスクリプトの冒頭に
‘ `Const` で定義することで、容易に変更できるようにします。
‘ さらに高度な設計では、INIファイルやレジストリから設定を読み込むようにします。
‘ 2. コメントの充実: コードの意図、処理の流れ、注意点などを明確にコメントで記述します。
‘ 後からコードを読む人が、容易に理解できるようにすることが重要です。
‘ 3. モジュール化: 処理をサブルーチン(`Sub`)や関数(`Function`)に分割し、
‘ コードの再利用性と可読性を高めます。
‘ 4. エラーハンドリングの粒度: `On Error Resume Next` は、最小限の範囲で使用し、
‘ エラー発生箇所の特定と、具体的なエラー処理を記述します。
‘ 5. WMIクエリの検証: 複雑なWQLクエリは、事前にWMI Explorerのようなツールで
‘ 動作を確認してからスクリプトに組み込むことを推奨します。
‘ — 実行方法 —
‘ 1. 上記コードをUserSessionMonitor.vbsなどのファイル名で保存します。
‘ 2. ログファイル出力用のディレクトリ (例: C:\Logs) を作成します。
‘ 3. コマンドプロンプトまたはPowerShellで、以下のコマンドを実行します。
‘ wscript.exe “C:\Path\To\UserSessionMonitor.vbs”
‘ (バックグラウンドで実行したい場合は、`wscript.exe //B “C:\Path\To\UserSessionMonitor.vbs”`)
‘ 4. タスクスケジューラに登録し、ログオン時や定間隔で自動実行させることができます。
‘ — 補足: ログオン/ログオフイベントのより正確な取得について —
‘ Win32_VolumeChangeEvent は、ドライブのマウント/アンマウントイベントなど、
‘ ユーザーセッションとは直接関係ないイベントも拾ってしまうことがあります。
‘ より正確にユーザーのログオン/ログオフを検知するには、以下のクラスを検討してください。
‘
‘ – Win32_LogonSession: システム上のログオンセッションを表します。
‘ – __InstanceCreationEvent: 新しいログオンセッションが作成されたとき (ユーザーログオン)
‘ – __InstanceDeletionEvent: ログオンセッションが削除されたとき (ユーザーログオフ)
‘
‘ WQL例:
‘ “SELECT FROM __InstanceCreationEvent WHERE TargetInstance ISA ‘Win32_LogonSession'”
‘ “SELECT FROM __InstanceDeletionEvent WHERE TargetInstance ISA ‘Win32_LogonSession'”
‘
‘ – Win32_Session_State_Change: セッションの状態変化を通知します。
‘ – EventType プロパティで状態を判別できます (例: 7: Interactive session logged on, 10: Interactive session logged off)
‘
‘ WQL例:
‘ “SELECT FROM Win32_Session_State_Change WHERE EventType = 7” ‘ ログオン
‘ “SELECT FROM Win32_Session_State_Change WHERE EventType = 10” ‘ ログオフ
‘
‘ これらのクラスを使用することで、より精度の高いユーザーセッション検知が可能になります。
‘ WMI Explorerなどのツールで、各クラスのプロパティを詳細に調査することをお勧めします。
‘ ——————————————————————————
‘ このスクリプトは、WMIイベントの強力な機能を活用し、
‘ ユーザーセッションの動的な変化を捉えるための基盤を提供します。
‘ この基盤の上に、ファイル操作、データベース連携、アプリケーション起動など、
‘ さまざまな自動化処理を連携させることで、真にインテリジェントな
‘ デスクトップ管理や業務効率化ツールを構築することが可能となります。
‘ ——————————————————————————
コードの「なぜ」を理解する:オブジェクトのライフサイクルとパフォーマンスの重み
このサンプルコードに込められた「なぜ」を理解することが、VBScriptを「使いこなす」レベルから「掌握する」レベルへと引き上げる鍵となります。
- `GetObject` の遅延バインディングと `impersonationLevel=impersonate`: `GetObject` はCOMオブジェクトを取得する最も一般的な方法です。`impersonationLevel=impersonate` を指定することで、スクリプトが実行されているユーザーの権限でWMI操作が行われます。これにより、ユーザーの権限でアクセス可能なリソースのみを管理できます。もし、より高い権限で操作したい場合は、`AuthenticationLevel=6` (Packet Privacy) と `impersonationLevel=impersonate` を組み合わせたり、タスクスケジューラで適切な権限を持つアカウント(例: `SYSTEM`)で実行したりする必要があります。
- `ExecNotificationQuery` の非同期性: このメソッドは、イベントが発生するまでスクリプトの実行をブロックしません。イベントが発生すると、WMIサブシステムが `ActiveScriptEventConsumer` に通知を送り、VBScriptが実行されます。この「イベント駆動」こそが、ポーリングに比べて圧倒的に効率的な理由です。
- `Put_` と `SpawnInstance_`: WMIクラスのインスタンスを作成し、リポジトリに保存する際のメソッドです。`SpawnInstance_` で新しいインスタンスを生成し、プロパティを設定した後、`Put_` で永続化します。
- `ExecQuery(…).Delete_`: WMIリポジトリからオブジェクトを削除する際に使用します。`On Error Resume Next` を活用しつつも、削除対象が存在しない場合のエラーを捕捉・無視することで、スクリプトの安定性を高めています。
- `WScript.Sleep` の意味: 無限ループの中で `WScript.Sleep` を挟むのは、CPUリソースを過剰に消費しないための配慮です。WMIイベントは非同期に処理されるため、スクリプト自体は常にイベントを「待機」している状態です。この待機中にCPUを解放することで、システム全体のパフォーマンスを維持します。
ファイル連携・データベース連携の落とし穴と回避策
ログ出力や他のシステムとの連携は、自動化スクリプトの生命線ですが、同時にバグの温床ともなり得ます。
- ファイルパスの絶対指定: ログファイルや設定ファイルは、必ず絶対パスで指定してください。相対パスは、スクリプトがどこから実行されるかによってパスが変わってしまい、予期せぬエラーを引き起こします。
- 権限問題: ログファイルへの書き込み権限、WMIへのアクセス権限は、実行ユーザーによって異なります。タスクスケジューラで実行する場合、`SYSTEM` アカウントや特定のサービスアカウントで実行することを検討し、そのアカウントに適切な権限を付与してください。
- ファイルロック: `FileSystemObject` でファイルを開くと、その間は他のプロセスがファイルを操作できなくなります。ログ記録のような頻繁な書き込みでは、ファイルロックによるエラーが発生しやすくなります。
- 対策1: ログファイルを開く時間を極力短くする。
- 対策2: バッファリングされた書き込みを行う。
- 対策3: データベースにログを記録する。
- 対策4: WMIの `__EventConsumer` で、より洗練されたログ記録メカニズム(例: イベントログへの書き込み)を実装する。
- データベース連携: データベースへの書き込みは、ファイル書き込みよりも重い処理です。
- 対策1: ログをバッチ処理でまとめて書き込む。
- 対策2: データベース接続文字列や認証情報は、スクリプト内に直接記述せず、設定ファイル(INI, XML)や環境変数から読み込む。
- 対策3: データベースへの接続エラーやトランザクションエラーに対する厳格なハンドリングを実装する。
保守性と拡張性を高めるための設計思想
プロダクションレベルのスクリプトは、一度作成したら終わりではありません。将来の変更や拡張に柔軟に対応できる設計が求められます。
- 設定値の外部化: ログパス、イベントソース名などをスクリプト冒頭の `Const` で定義するのは基本ですが、より高度なシステムでは、INIファイル、XMLファイル、あるいはレジストリから設定を読み込むようにします。これにより、スクリプト自体を改変することなく、設定を変更できるようになります。
- モジュール化: 処理を小さなサブルーチンや関数に分割します。例えば、「ログ記録処理」「WMIオブジェクトのクリーンアップ処理」「イベントフィルター作成処理」などを個別の `Sub` に分けることで、コードの可読性が向上し、再利用性も高まります。
- エラーハンドリングの「質」: `On Error Resume Next` は、エラー発生時にスクリプトを停止させないための「緩和策」であり、「解決策」ではありません。エラーが発生した箇所を特定し、`Err.Number` や `Err.Description` を確認して、具体的なエラー処理(例: リトライ、管理者に通知、代替処理の実行)を記述することが、堅牢なスクリプトの条件です。
- WQLクエリの検証: WQLはSQLに似ていますが、WMI固有のクラスやプロパティが存在します。複雑なクエリを作成する際は、WMI Explorerのようなツールで実際にクエリを実行し、期待通りの結果が得られるかを確認してからスクリプトに組み込むのが鉄則です。
まとめ:VBScriptとWMIで、自動化の新たな扉を開く
WMIイベントクエリを活用したユーザーセッション検知は、単なる「ログを取る」以上の価値を持ちます。これは、ユーザーの活動状況に応じて自動的に処理を分岐させる、イベント駆動型自動化の強力な一歩です。VBScriptの洗練されたオブジェクトモデルとWMIの管理能力を組み合わせることで、これまで手動で行っていた多くの作業を、より効率的、かつインテリジェントに自動化することが可能になります。
本稿で示したサンプルコードと設計思想が、皆さんの業務効率化プロジェクトにおける確かな指針となることを願っています。VBScriptの奥深さを探求し、自動化の地平をさらに広げていきましょう。
