VBScriptを掌握する極限の知見:WMIイベントでユーザーセッションの息遣いを捉える!
やあ、みんな!VBScriptの世界へようこそ。マクロの記録だけじゃ物足りない、もっと深くWindowsを操ってみたい、そんな君のために、今日はとっておきのテクニックを伝授しよう。
「ユーザーがログインした」「ログオフした」「席を離れて画面がロックされた」… こういったユーザーセッションの切り替わりって、普段意識しないかもしれないけど、実はWindowsの裏側で常に起こっているんだ。この「イベント」を、VBScriptとWMI(Windows Management Instrumentation)を使ってリアルタイムに検知できたら、どうだろう?例えば、ログオンしたら特定のアプリを起動したり、ログオフしたらバックアップ処理を実行したり、画面ロックされたらセキュリティ警告を出したり… 可能性は無限大だ!
今回のテーマは、まさにその「ユーザーセッション切り替え検知」。WMIのイベントクエリという強力な武器を使い、ログオン・ログオフ、そして画面ロック状態の検出を、非同期でキャッチして処理を分岐させる方法を、基礎から丁寧に解説していくよ。
「WMI?イベントクエリ?なんだか難しそう…」って思うかもしれないけど、心配いらない。僕が君の理解を全力でサポートするから、安心して読み進めてほしい。この講座を終える頃には、君もWMIの達人への第一歩を踏み出せているはずだ!
—
1. VBScriptの基本:まずはここから!
WMIの世界に入る前に、VBScriptの基本的な考え方をサラッと復習しておこう。
1.1 VBScriptって何?
VBScriptは、Microsoftが開発したスクリプト言語で、Windowsの自動化に特化しているんだ。特別な開発環境も不要で、メモ帳とWindowsさえあればすぐに書き始められるのが魅力。
- 特徴:
- シンプルで学習しやすい
- Windowsの様々な機能(ファイル操作、レジストリ操作、プロセス管理など)を操作できる
- WSH(Windows Script Host)という実行環境で動く
1.2 WSH(Windows Script Host)って何?
WSHは、VBScriptやJavaScriptといったスクリプトを実行するためのWindowsの標準機能だよ。コマンドプロンプトやエクスプローラーから `.vbs` ファイルをダブルクリックするだけで、スクリプトが実行されるのは、このWSHのおかげなんだ。
- 主な機能:
- スクリプトの実行
- COMオブジェクト(WMIなどもこれに含まれる)の利用
- ダイアログボックスの表示(`MsgBox` や `InputBox`)
1.3 VBScriptの基本構文
まずは、一番基本的な「メッセージを表示する」ことから見てみよう。
‘ これはコメントです。コードの説明に使います。
MsgBox “こんにちは、VBScriptの世界へようこそ!”
- `MsgBox`: 画面にメッセージボックスを表示する命令だよ。
- `”こんにちは、VBScriptの世界へようこそ!”`: メッセージボックスに表示する文字列。ダブルクォーテーション(`”`)で囲むのがルールだ。
- `’`: シングルクォーテーション(`’`)は、その行の残りをコメントとして扱うための記号。コードの説明を記述するのに便利だよ。
1.4 変数を使ってみよう
データを入れておくための「箱」が変数だ。VBScriptでは、`Dim` キーワードを使って変数を宣言するんだ。
Dim userName
userName = “田中太郎” ‘ 変数に値を代入
MsgBox “こんにちは、” & userName & “さん!” ‘ 変数の値をメッセージに含める
- `Dim userName`: `userName` という名前の変数を宣言している。
- `userName = “田中太郎”`: `userName` という変数に `”田中太郎”` という文字列を代入している。
- `&`: 文字列を連結(くっつける)ための演算子だ。
1.5 実行環境の確認
君のPCにWSHがインストールされていない、ということはまずないだろうけど、念のため確認しておこう。コマンドプロンプトを開いて、以下のコマンドを入力してEnterキーを押してみて。
cscript //Logo VBScript.vbs
もし「Microsoft (R) Windows Script Host Version …」といったバージョン情報が表示されればOK。もしエラーが出るようなら、Windowsが正常にインストールされていない可能性も考えられるけど、通常は問題ないはずだよ。
—
2. WMI(Windows Management Instrumentation)の扉を開ける
さて、いよいよ本題のWMIだ。WMIは、Windowsシステムの様々な情報を取得したり、設定を変更したりできる、まさにWindows管理の「司令塔」。VBScriptからこのWMIを操作することで、これまで不可能だった自動化が可能になるんだ。
2.1 WMIの心臓部:COMオブジェクト
VBScriptからWMIを操作するには、COM(Component Object Model)という仕組みを使う。VBScriptでは、`CreateObject` という関数を使って、WMIの機能を提供するCOMオブジェクトを作成するんだ。
‘ WMIサービスに接続するためのオブジェクトを作成
Set objWMIService = CreateObject(“WbemScripting.SWbemLocator”)
Set objSWbemServices = objWMIService.ConnectServer(“.”, “root\cimv2”) ‘ ローカルマシンに接続
- `CreateObject(“WbemScripting.SWbemLocator”)`: WMIへの接続を管理する `SWbemLocator` オブジェクトを作成している。
- `Set objWMIService = …`: 作成したオブジェクトを `objWMIService` という変数に格納している。`Set` キーワードは、オブジェクトを代入する際に必要だよ。
- `objWMIService.ConnectServer(“.”, “root\cimv2”)`: `objWMIService` を使って、ローカルマシン(`.` で表される)の `root\cimv2` 名前空間に接続している。`root\cimv2` は、Windowsのシステム情報が格納されている標準的な場所なんだ。
- `Set objSWbemServices = …`: 接続したWMIサービスへの参照を `objSWbemServices` という変数に格納している。この `objSWbemServices` を通じて、様々なWMI情報を取得できるようになる。
2.2 WMIの「クラス」と「インスタンス」
WMIの世界では、管理したい対象のことを「クラス」と呼ぶ。例えば、「コンピューターシステム」を表す `Win32_ComputerSystem` クラスや、「ディスクドライブ」を表す `Win32_DiskDrive` クラスなどがある。
そして、そのクラスの実際の「実体」のことを「インスタンス」と呼ぶんだ。例えば、君のPCに搭載されている「Cドライブ」は、`Win32_LogicalDisk` クラスのインスタンス、といった具合だ。
2.3 WMIイベントとは?
ここが今回の肝!WMIイベントとは、Windowsシステムで発生した特定の出来事(イベント)を検知する仕組みのこと。例えば、
- ログオンイベント: ユーザーがPCにログインしたとき
- ログオフイベント: ユーザーがPCからログオフしたとき
- 画面ロックイベント: ユーザーが画面をロック(離席など)したとき
- ディスク容量変更イベント: ディスクの空き容量が一定量以下になったとき
など、様々なイベントを監視できるんだ。
3. WMIイベントクエリでログオン・ログオフを検知!
さあ、いよいよWMIイベントクエリを使って、ユーザーのログオン・ログオフをリアルタイムに検知するコードを書いてみよう。
3.1 イベントの「発生源」を探る:Win32_VolumeChangeEvent と Win32_ComputerSystemEvent
ユーザーセッションの切り替わりに関連するイベントは、主に以下のクラスで発生するんだ。
- `Win32_VolumeChangeEvent`:
- ドライブの挿入/取り出し(USBメモリなど)
- ユーザーのログオン/ログオフ(これを使う!)
- システムシャットダウン/再起動
- `Win32_ComputerSystemEvent`:
- コンピューターの起動/シャットダウン
- ユーザーのログオン/ログオフ(これも使える!)
- システムの状態変化(スタンバイ、休止状態など)
今回は、より直接的にユーザーセッションに紐づくイベントを捉えやすい `Win32_VolumeChangeEvent` と `Win32_ComputerSystemEvent` を中心に見ていこう。
3.2 イベントクエリの書き方
WMIイベントを監視するには、「イベントクエリ」という特別なSQLライクなクエリを使うんだ。
- `SELECT FROM <イベントクラス名>`: 指定したイベントクラスで発生する全てのイベントを取得する。
- `WHERE <条件>`: 特定の条件に合致するイベントのみを取得する。
3.3 ログオン・ログオフイベントを検知するコード
まずは、ユーザーがログオンしたときとログオフしたときにメッセージを表示するスクリプトを作ってみよう。
‘ — WMIイベントリスナー設定 —
‘ WMIサービスへの接続オブジェクトを作成
Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)
‘ ログオンイベントを監視するクエリ (Win32_ComputerSystemEventを使用)
‘ EventType 2はユーザーログオンを表す
strLogonQuery = “SELECT FROM Win32_ComputerSystemEvent WHERE EventType = 2”
‘ ログオフイベントを監視するクエリ (Win32_ComputerSystemEventを使用)
‘ EventType 3はユーザーログオフを表す
strLogoffQuery = “SELECT FROM Win32_ComputerSystemEvent WHERE EventType = 3”
‘ — イベントリスナーの登録 —
‘ ログオンイベントの監視を開始
Set objLogonEventSink = New CEventSink
Set objLogonEventConsumer = objSWbemServices.ExecNotificationQuery(strLogonQuery)
objLogonEventConsumer.Indicate = True ‘ イベント通知を有効にする
‘ ログオフイベントの監視を開始
Set objLogoffEventSink = New CEventSink
Set objLogoffEventConsumer = objSWbemServices.ExecNotificationQuery(strLogoffQuery)
objLogoffEventConsumer.Indicate = True ‘ イベント通知を有効にする
MsgBox “ユーザーセッションイベントの監視を開始しました。Ctrl+Cで停止できます。”, vbInformation
‘ — イベント処理クラス —
‘ イベントが発生したときに実行される処理を定義するクラス
Class CEventSink
Public Sub OnObjectReady(objObject, objAsyncContext)
‘ イベント発生時の処理
Select Case objObject.EventType
Case 2 ‘ ログオンイベント
MsgBox “ユーザーがログオンしました! (ユーザー名: ” & objObject.UserName & “)”, vbInformation
‘ ここにログオン時の処理を記述
‘ 例: ログファイルに記録
‘ Example: LogToFile “Logon detected for user: ” & objObject.UserName
Case 3 ‘ ログオフイベント
MsgBox “ユーザーがログオフしました! (ユーザー名: ” & objObject.UserName & “)”, vbWarning
‘ ここにログオフ時の処理を記述
‘ 例: バックアップ処理の実行
‘ Example: RunBackupProcess
End Select
End Sub
Public Sub OnCompleted(objObject, objAsyncContext)
‘ イベント処理の完了時(通常はあまり使わない)
End Sub
Public Sub OnError(objObject, objAsyncContext)
‘ エラー発生時
MsgBox “イベント処理中にエラーが発生しました。”, vbCritical
End Sub
End Class
‘ — 監視ループ (イベントを待ち続ける) —
‘ このスクリプトはイベントを非同期で受け取るため、
‘ スクリプト自体は待機状態を保つ必要があります。
‘ Infinite loop to keep the script running and listening for events
Do While True
Wscript.Sleep 1000 ‘ 1秒ごとにチェック (CPU負荷軽減のため)
Loop
‘ — 終了処理 (通常はこのループを抜けることはない) —
‘ 実際には、Ctrl+Cなどでスクリプトを終了させる
‘ Clean up objects
Set objLogonEventConsumer = Nothing
Set objLogonEventSink = Nothing
Set objLogoffEventConsumer = Nothing
Set objLogoffEventSink = Nothing
Set objSWbemServices = Nothing
Set objWMIService = Nothing
コードの解説:
1. `Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)`:
- `GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)` は、WMIサービスに接続するためのショートカット。`{impersonationLevel=impersonate}` は、スクリプトを実行しているユーザーの権限でWMIにアクセスすることを意味するよ。
- `\\.\root\cimv2` は、ローカルコンピューターのWMI名前空間を指定している。
2. `strLogonQuery = “SELECT FROM Win32_ComputerSystemEvent WHERE EventType = 2″`:
- `SELECT FROM Win32_ComputerSystemEvent`: `Win32_ComputerSystemEvent` クラスで発生する全てのイベントを取得するという意味。
- `WHERE EventType = 2`: WMIの `Win32_ComputerSystemEvent` クラスでは、`EventType` というプロパティでイベントの種類を表している。`2` は「ユーザーログオン」を意味するんだ。
3. `strLogoffQuery = “SELECT FROM Win32_ComputerSystemEvent WHERE EventType = 3″`:
- 同様に、`EventType = 3` は「ユーザーログオフ」を意味する。
4. `Set objLogonEventSink = New CEventSink`:
- `CEventSink` という名前の「イベントシンク」クラスの新しいインスタンスを作成している。このクラスの中に、イベントが発生したときに実行したい処理を記述するんだ。`New` キーワードでクラスのインスタンスを作成するよ。
5. `Set objLogonEventConsumer = objSWbemServices.ExecNotificationQuery(strLogonQuery)`:
- `ExecNotificationQuery` メソッドは、WMIイベントクエリを実行し、イベント通知を受け取るためのオブジェクトを返す。ここで、先ほど定義した `strLogonQuery` を渡して、ログオンイベントの監視を開始している。
6. `objLogonEventConsumer.Indicate = True`:
- これは、イベント通知を有効にするための設定。これがないと、イベントが発生してもスクリプトに通知されない。
7. `Class CEventSink … End Class`:
- これが、イベント発生時の処理を定義するクラスだよ。
- `OnObjectReady(objObject, objAsyncContext)`: イベントが発生したときに自動的に呼び出されるメソッド。`objObject` には、発生したイベントの詳細情報(`EventType` や `UserName` など)が入っている。
- `Select Case objObject.EventType … End Select`: `objObject.EventType` の値を見て、ログオン(2)かログオフ(3)かを判別し、それぞれの処理を実行している。
- `MsgBox …`: ここでは分かりやすくメッセージボックスで通知しているけど、実際にはファイルへの書き込みや別のスクリプトの実行など、好きな処理を記述できる。
- `OnCompleted` / `OnError`: イベント処理の完了時やエラー発生時に呼び出されるメソッド。今回は `OnObjectReady` でほとんどの処理を完結させている。
8. `Do While True … Loop`:
- このループは、スクリプトを「実行し続けさせる」ためのもの。WMIイベントは非同期で通知されるため、スクリプトが終了してしまうとイベントを受け取れなくなってしまう。このループでスクリプトをバックグラウンドで待機させて、イベントの発生を待ち受けているんだ。
- `Wscript.Sleep 1000`: 1秒(1000ミリ秒)ごとに処理を一時停止させる。これがないとCPUを100%使い切ってしまうので、必ず入れておこう。
実行方法:
1. 上記のコードを `SessionMonitor.vbs` のような名前で保存する。
2. コマンドプロンプトを開き、保存したファイルがあるディレクトリに移動する。
3. 以下のコマンドを実行する:
cscript //nologo SessionMonitor.vbs
(`//nologo` をつけると、スクリプト実行時のWSHのロゴが表示されなくなるのでスッキリするよ)
これで、君がPCにログオンしたりログオフしたりするたびに、メッセージボックスが表示されるはずだ!
—
4. 画面ロック状態の検出:離席を捉える!
ユーザーがPCから離席する際などによく行われる「画面ロック」。この状態の変化も、WMIイベントで検知できるんだ。
4.1 画面ロックイベントの発生源
画面ロックや解除に関連するイベントは、主に `Win32_LogonSession` クラスの `LogonType` プロパティや、`Win32_OperatingSystem` クラスの `SystemState` プロパティの変化を監視することで検知できる場合がある。
しかし、より直接的で安定した方法として、`Win32_ProcessStartTrace` や `Win32_ProcessStopTrace` といった、プロセスの生成・終了を追跡するイベントを組み合わせる、あるいは、`Win32_SessionStateChangeEvent` のような、よりセッション状態の変化に特化したクラス(利用可能な場合)を調べるのが一般的だ。
ここでは、より汎用的に使われやすい `Win32_ProcessStartTrace` と `Win32_ProcessStopTrace` を利用した、画面ロックに関連するプロセス(例えば `LogonUI.exe` など)の開始・終了を検知するアプローチを例に説明しよう。ただし、これはあくまで「画面ロックの 可能性が高い 状態」を検知するもので、絶対的な確実性があるわけではない点に注意してほしい。より確実な方法は、より低レベルなAPI呼び出しが必要になる場合があるからだ。
まずは、画面ロック(またはそれに類する状態)と関連性の高いプロセスが開始されたとき、そして終了したときを検知してみよう。
‘ — WMIイベントリスナー設定 —
‘ WMIサービスへの接続オブジェクトを作成
Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)
‘ プロセス開始イベントを監視するクエリ
‘ Win32_ProcessStartTrace はプロセスが開始されたときに発生する
strProcessStartQuery = “SELECT FROM Win32_ProcessStartTrace”
‘ プロセス終了イベントを監視するクエリ
‘ Win32_ProcessStopTrace はプロセスが終了したときに発生する
strProcessStopQuery = “SELECT FROM Win32_ProcessStopTrace”
‘ 画面ロックに関連する可能性のあるプロセス名(例: LogonUI.exe)
Const SCREEN_LOCK_PROCESS_NAME = “LogonUI.exe” ‘ Windowsのバージョンによって異なる場合がある
‘ — イベントリスナーの登録 —
‘ プロセス開始イベントの監視を開始
Set objProcessStartEventSink = New CProcessEventSink
Set objProcessStartEventConsumer = objWMIService.ExecNotificationQuery(strProcessStartQuery)
objProcessStartEventConsumer.Indicate = True
‘ プロセス終了イベントの監視を開始
Set objProcessStopEventSink = New CProcessEventSink
Set objProcessStopEventConsumer = objWMIService.ExecNotificationQuery(strProcessStopQuery)
objProcessStopEventConsumer.Indicate = True
MsgBox “画面ロック関連イベントの監視を開始しました。Ctrl+Cで停止できます。”, vbInformation
‘ — イベント処理クラス —
Class CProcessEventSink
Public Sub OnObjectReady(objObject, objAsyncContext)
‘ プロセス開始イベントの場合
If TypeName(objObject) = “SWbemObject” And objObject.Path_.Class = “Win32_ProcessStartTrace” Then
‘ プロセス名を取得 (Name プロパティがない場合があるので、CommandLineなどを確認する必要がある場合も)
Dim processName
‘ プロセス名を取得するために、WMIでクエリを実行してプロセス情報を取得する
‘ objObject.ProcessID でプロセスIDがわかるので、それを使ってプロセス名を取得
Dim objProcesses, objProcess
Set objProcesses = objWMIService.ExecQuery(“SELECT Name FROM Win32_Process WHERE ProcessId = ” & objObject.ProcessID)
For Each objProcess In objProcesses
processName = objProcess.Name
Exit For
Next
If Not IsNull(processName) Then
‘ 画面ロックに関連するプロセス名かチェック
If StrComp(processName, SCREEN_LOCK_PROCESS_NAME, vbTextCompare) = 0 Then
MsgBox “画面ロックが開始された可能性があります! (プロセス: ” & processName & “)”, vbExclamation
‘ ここに画面ロック検知時の処理を記述
‘ 例: セキュリティ警告を表示
‘ Example: MsgBox “画面がロックされました。セキュリティにご注意ください。”, vbCritical
End If
End If
‘ プロセス終了イベントの場合
ElseIf TypeName(objObject) = “SWbemObject” And objObject.Path_.Class = “Win32_ProcessStopTrace” Then
‘ プロセス名を取得 (Name プロパティがない場合があるので、CommandLineなどを確認する必要がある場合も)
Dim processName
‘ プロセス名を取得するために、WMIでクエリを実行してプロセス情報を取得する
‘ objObject.ProcessID でプロセスIDがわかるので、それを使ってプロセス名を取得
Dim objProcesses, objProcess
Set objProcesses = objWMIService.ExecQuery(“SELECT Name FROM Win32_Process WHERE ProcessId = ” & objObject.ProcessID)
For Each objProcess In objProcesses
processName = objProcess.Name
Exit For
Next
If Not IsNull(processName) Then
‘ 画面ロックに関連するプロセス名かチェック
If StrComp(processName, SCREEN_LOCK_PROCESS_NAME, vbTextCompare) = 0 Then
MsgBox “画面ロックが解除された可能性があります! (プロセス: ” & processName & “)”, vbInformation
‘ ここに画面ロック解除時の処理を記述
‘ 例: 特定のアプリケーションを起動
‘ Example: Shell(“notepad.exe”, vbNormalFocus)
End If
End If
End If
End Sub
Public Sub OnCompleted(objObject, objAsyncContext)
‘ イベント処理の完了時
End Sub
Public Sub OnError(objObject, objAsyncContext)
‘ エラー発生時
MsgBox “イベント処理中にエラーが発生しました。”, vbCritical
End Sub
End Class
‘ — 監視ループ —
Do While True
Wscript.Sleep 1000
Loop
‘ — 終了処理 —
Set objProcessStartEventConsumer = Nothing
Set objProcessStartEventSink = Nothing
Set objProcessStopEventConsumer = Nothing
Set objProcessStopEventSink = Nothing
Set objWMIService = Nothing
コードの解説:
1. `strProcessStartQuery = “SELECT FROM Win32_ProcessStartTrace”`:
- `Win32_ProcessStartTrace` クラスは、新しいプロセスが開始されるたびにイベントを発生させます。
2. `strProcessStopQuery = “SELECT FROM Win32_ProcessStopTrace”`:
- `Win32_ProcessStopTrace` クラスは、プロセスが終了するたびにイベントを発生させます。
3. `Const SCREEN_LOCK_PROCESS_NAME = “LogonUI.exe”`:
- 画面ロック処理に関連するプロセスの名前を定数として定義しています。Windowsのバージョンや設定によって、このプロセス名は異なる場合があります。`LogonUI.exe` は、Windows 10/11でロック画面やサインイン画面を表示する主要なプロセスの一つです。
4. `Set objProcessStartEventSink = New CProcessEventSink` と `Set objProcessStopEventSink = New CProcessEventSink`:
- プロセス開始イベントと終了イベントの両方を処理するために、同じ `CProcessEventSink` クラスのインスタンスを2つ作成しています。
5. `objWMIService.ExecQuery(“SELECT Name FROM Win32_Process WHERE ProcessId = ” & objObject.ProcessID)`:
- `Win32_ProcessStartTrace` や `Win32_ProcessStopTrace` から得られるのは、イベントが発生したプロセスのID (`ProcessID`) です。
- このIDを使って、`Win32_Process` クラスから実際のプロセス名 (`Name`) を取得しています。WMIのクエリをイベントハンドラ内で実行する、という少し高度なテクニックを使っています。
6. `If StrComp(processName, SCREEN_LOCK_PROCESS_NAME, vbTextCompare) = 0 Then`:
- 取得したプロセス名が、定義した `SCREEN_LOCK_PROCESS_NAME` と一致するかどうかを比較しています。`vbTextCompare` は大文字・小文字を区別しない比較を指定します。
7. 画面ロック検知時の処理:
- プロセス開始イベントで `SCREEN_LOCK_PROCESS_NAME` が見つかった場合、「画面ロックが開始された可能性」を通知しています。
- プロセス終了イベントで `SCREEN_LOCK_PROCESS_NAME` が見つかった場合、「画面ロックが解除された可能性」を通知しています。
注意点:
- この方法は、`LogonUI.exe` が起動・終了するタイミングを捉えているため、必ずしも画面ロックと1対1で対応するわけではありません。例えば、サインイン画面の表示なども含まれる可能性があります。
- より正確に画面ロック状態を検知するには、`GetTickCount` APIなどを利用して、セッションのアイドル時間や、特定のウィンドウメッセージを監視するなど、より複雑な処理が必要になる場合があります。
4.2 陥りやすいエラーとその対策
- 「Invalid class」エラー: WMIクラス名が間違っている、またはそのクラスが環境に存在しない場合に発生します。クラス名は正確に記述しましょう。
- 「Access denied」エラー: WMIにアクセスするための権限がない場合に発生します。スクリプトを実行するユーザーに適切な権限があるか確認してください。通常は管理者権限で実行すると回避できます。
- スクリプトがすぐに終了してしまう: イベントを待ち受ける `Do While True` ループがないと、スクリプトはすぐに終了してしまいます。イベントリスナーを有効にしたままにするために、このループは必須です。
- CPU負荷が高い: `Wscript.Sleep` の間隔が短すぎると、CPU使用率が高くなることがあります。通常は1秒(1000ミリ秒)程度で十分です。
—
5. 実践!ログオン時に特定フォルダをマウントする
ここまでの知識を活かして、具体的な応用例を見てみよう。例えば、「ユーザーがログオンしたら、特定のネットワークフォルダをドライブとしてマウントする」という処理だ。
‘ — WMIイベントリスナー設定 —
Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2”)
‘ ログオンイベントを監視するクエリ
strLogonQuery = “SELECT FROM Win32_ComputerSystemEvent WHERE EventType = 2”
‘ — イベントリスナーの登録 —
Set objLogonEventSink = New CLogonEventHandler
Set objLogonEventConsumer = objWMIService.ExecNotificationQuery(strLogonQuery)
objLogonEventConsumer.Indicate = True
MsgBox “ログオンイベント監視を開始しました。ネットワークドライブのマウントを試みます。”, vbInformation
‘ — イベント処理クラス —
Class CLogonEventHandler
Public Sub OnObjectReady(objObject, objAsyncContext)
If objObject.EventType = 2 Then ‘ ログオンイベント
Dim userName
userName = objObject.UserName ‘ ログオンしたユーザー名を取得
MsgBox “ユーザー ‘” & userName & “‘ がログオンしました。ネットワークドライブのマウントを試みます。”, vbInformation
‘ — ネットワークドライブのマウント処理 —
Dim objNetwork, driveLetter, networkPath, mountResult
‘ マウントしたいドライブ文字とネットワークパスを指定
driveLetter = “Z:”
networkPath = “\\your_server\your_share” ‘ ここを実際のパスに変更してください!
Set objNetwork = CreateObject(“WScript.Network”)
‘ 既にドライブが割り当てられているかチェック
On Error Resume Next ‘ エラーが発生しても続行
Dim existingDrive
existingDrive = objNetwork.EnumNetworkDrives()
Dim isMapped As Boolean
isMapped = False
For i = 0 To existingDrive.Count – 1 Step 2
If UCase(existingDrive.Item(i)) = UCase(driveLetter) Then
isMapped = True
Exit For
End If
Next
On Error GoTo 0 ‘ エラーハンドリングを元に戻す
If isMapped Then
MsgBox driveLetter & ” は既にマウントされています。”, vbInformation
Else
‘ マウント実行
‘ ‘Persistent:=False’ は再起動時に接続を維持しない設定
‘ 接続を維持したい場合は ‘True’ にするか、この引数を省略する (デフォルトはTrue)
mountResult = objNetwork.MapNetworkDrive(driveLetter, networkPath, False)
If mountResult = 0 Then ‘ 成功した場合
MsgBox driveLetter & ” を ” & networkPath & ” にマウントしました。”, vbInformation
‘ ここにマウント成功時の追加処理を記述
Else
MsgBox “ネットワークドライブ ‘” & driveLetter & “‘ のマウントに失敗しました。エラーコード: ” & mountResult, vbCritical
‘ ここにマウント失敗時の追加処理を記述
End If
End If
Set objNetwork = Nothing
‘ — マウント処理ここまで —
End If
End Sub
Public Sub OnCompleted(objObject, objAsyncContext)
End Sub
Public Sub OnError(objObject, objAsyncContext)
MsgBox “イベント処理中にエラーが発生しました。”, vbCritical
End Sub
End Class
‘ — 監視ループ —
Do While True
Wscript.Sleep 1000
Loop
‘ — 終了処理 —
Set objLogonEventConsumer = Nothing
Set objLogonEventSink = Nothing
Set objWMIService = Nothing
このコードのポイント:
- `CreateObject(“WScript.Network”)`: ネットワークドライブのマッピングや解除を行うための `WScript.Network` オブジェクトを作成します。
- `objNetwork.EnumNetworkDrives()`: 現在マッピングされているネットワークドライブの一覧を取得します。これを利用して、重複してマウントしようとするのを防いでいます。
- `objNetwork.MapNetworkDrive(driveLetter, networkPath, False)`: 指定したドライブ文字 (`driveLetter`) に、指定したネットワークパス (`networkPath`) をマッピングします。最後の `False` は、永続的な接続(再起動後も維持される接続)にしないことを意味します。
- エラーハンドリング (`On Error Resume Next`): `EnumNetworkDrives` や `MapNetworkDrive` は、ドライブが既に存在する場合などにエラーを返すことがあります。`On Error Resume Next` でエラーを無視し、後で `mountResult` を確認することで、成功・失敗を判定しています。
実行前の準備:
- `\\your_server\your_share` の部分を、実際にアクセスしたいネットワーク共有フォルダのパスに置き換えてください。
- スクリプトを実行するユーザーが、そのネットワーク共有フォルダにアクセスできる権限を持っていることを確認してください。
—
6. VBScriptのオブジェクトライフサイクルとパフォーマンスへの配慮
ここまでWMIイベントを扱うコードを見てきたけど、VBScriptでオブジェクトを扱う上で、その「ライフサイクル」と「パフォーマンス」について少しだけ触れておこう。
6.1 オブジェクトのライフサイクル
VBScriptで `CreateObject` や `GetObject`、`New` で作成したオブジェクトは、メモリ上に存在し続ける。君が `Set obj = Nothing` のように明示的に解放しない限り、スクリプトが終了するまで、あるいはそのオブジェクトへの参照がなくなるまで、メモリを消費し続けるんだ。
- `Set obj = Nothing`: オブジェクトへの参照を解除し、メモリから解放する。特に、ループ内で大量のオブジェクトを作成したり、長時間実行されるスクリプトでは、この解放を意識することが重要だ。
- 今回のコードでの注意点:
- WMIイベントリスナーは、スクリプトが終了するまで実行され続けるため、イベントリスナー関連のオブジェクト(`objLogonEventConsumer` など)は、スクリプトの最後に明示的に解放する `Set … = Nothing` を入れている。
- ループ内で毎回 `CreateObject` するのではなく、ループの外で一度だけ作成し、必要に応じて `Set … = Nothing` で解放する方が効率的だ。
6.2 パフォーマンスへの配慮
WMIは非常に強力だけど、使い方によってはパフォーマンスに影響を与えることがある。
- 頻繁すぎるクエリ発行:
- 例えば、ループの中で毎回 `ExecQuery` を実行すると、WMIプロバイダーに負荷がかかり、スクリプト全体の動作が遅くなる可能性がある。
- 今回のようなイベント駆動型のアプローチは、状態変化があったときにだけ処理が走るため、比較的パフォーマンスが良い。
- もし、定期的(例:5分ごと)に情報を取得したい場合は、`Wscript.Sleep` を使って間隔を空けるのが基本だ。
- 大量のデータ取得:
- `SELECT ` で全てのプロパティを取得すると、不要な情報まで取得してしまい、メモリ使用量や処理時間が無駄になることがある。
- 必要なプロパティだけを指定する `SELECT Property1, Property2 FROM ClassName` のように書くことで、パフォーマンスが向上することがある。
- 非同期処理の活用:
- `ExecNotificationQuery` は非同期でイベント通知を受け取るためのメソッド。これにより、イベントを待っている間も他の処理を進めることができる。
- 今回のスクリプトのように、イベントを受け取ってから処理を実行する、という非同期な設計は、Windowsのイベント駆動型アーキテクチャに合致しており、効率的な自動化に繋がる。
7. まとめ:VBScriptとWMIで、Windowsを君の手に!
どうだったかな?WMIイベントクエリを使うことで、Windowsのユーザーセッションの切り替わりといった、普段は意識しない「イベント」を捉え、自動化に活かせることを理解してもらえたと思う。
- WMIの基本: `GetObject(“winmgmts:…”)` で接続し、クラスとインスタンスという概念を理解する。
- イベントクエリ: `SELECT FROM EventClass WHERE Condition` の形式で、発生したいベントを監視する。
- イベントシンク: `Class CEventSink … End Class` で、イベント発生時の処理を記述する。
- 非同期処理: `ExecNotificationQuery` と `Do While True / Wscript.Sleep` で、イベントを待ち受け続ける。
今回紹介したログオン・ログオフ、画面ロックの検知は、まさに「ユーザーセッション」というWindowsの根幹に関わる部分を操作するテクニックだ。これをマスターすれば、君の自動化の幅は格段に広がるはずだよ。
「ここをクリアすれば、VBScriptの基本はバッチリですよ」と、自信を持って言えるレベルに達したはずだ!
この知識を足がかりに、ぜひ色々なイベントを監視して、自分だけの便利な自動化ツールを作り上げてみてほしい。もし分からないことがあれば、いつでも僕に聞いてくれ。
VBScriptとWMIの世界は、まだまだ奥が深い。この冒険を楽しんで!
