I can create non-permanent WMI event queries in script, such as this one, which logs the PIDs of the next 5 new Notepad.exe processes:
Set WMI = GetObject("winmgmts:\\.\ROOT\cimv2")
wql = "SELECT * FROM __InstanceCreationEvent WITHIN 2 " & _
"WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'Notepad.exe'"
Set EventSource = WMI.ExecNotificationQuery(wql)
For i = 1 To 5
With EventSource.NextEvent(-1)
Wscript.Echo .TargetInstance.ProcessId
End With
Next
But I'm missing a way of explicitly canceling the EventSource
. Without that, the event notification will continue to run indefinitely inside the WMI, even if the script that listens for the generated events terminates for whatever reason(*). This would lead to increasing build-up of overhead when the script is run multiple times.
MSDN documentation of IWbemServices::ExecNotificationQuery
says:
The IWbemServices::ExecNotificationQuery method executes a query to receive events. The call returns immediately, and the user can poll the returned enumerator for events as they arrive. Releasing the returned enumerator cancels the query.
How do I release the returned enumerator?
The EventSource
object does not seem to be enumerable. Trying to use For Each
on it fails with "VBScript runtime error: Object doesn't support this property or method", so I can't use the implicit Release
at the end of a For Each
loop.
(*) This follows from the documentation stating that "Releasing the returned enumerator cancels the query.", which implies that not releasing the enumerator causes the query to persist - but it can be confirmed explicitly, too:
Set up a notification for this query "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_Directory' And TargetInstance.Name = 'C:\\Test'"
and use Sysinternals Process Monitor to observe file system accesses from the WMI service (WmiPrvSE.exe
). The WMI service will start polling for folders named "C:\Test" every 2 seconds, and it will continue doing so after the script that set up the monitoring has ended.
Restarting the WMI Service gets rid of the polling, but obviously that's not a good solution for this situation.
Could you use ExecNotificationQueryAsync
instead? That way you can pass it a SWbemSink
object that you can later call Cancel()
method on to cancel the sink which should also remove any event consumer associated with that sink.
The problem with using the ExecNotificationQuery()
method is it only gives you access to a SWbemEventSource
object which allows calling the next event in the enumerator. It doesn't seem possible to use that method to remove the event consumer once it is registered.
Running this:
Set FSO = CreateObject("Scripting.FileSystemObject")
Set WMI = GetObject("winmgmts:\\.\ROOT\cimv2")
Set Sink = WScript.CreateObject("WbemScripting.SWbemSink", "Sink_")
wql = "SELECT * FROM __InstanceCreationEvent WITHIN 2 " & _
"WHERE TargetInstance ISA 'Win32_Directory' And TargetInstance.Name = 'C:\\Test'"
WScript.Echo "Waiting for events..."
WMI.ExecNotificationQueryAsync Sink, wql
For i = 1 To 10
WScript.Echo i
Wscript.Sleep 1000
If i = 5 Then
FSO.CreateFolder "C:\Test"
WScript.Echo "Test folder created."
End If
Next
Sink.Cancel
WScript.Echo "Sink canceled."
FSO.DeleteFolder "C:\Test"
WScript.Echo "Test folder deleted."
Sub Sink_OnObjectReady(eventObject, asyncContext)
Set folder = eventObject.TargetInstance
WScript.Echo "__InstanceCreationEvent: " & folder.Name
End Sub
outputs something similar to this:
Waiting for events... 1 2 3 4 5 Test folder created. 6 7 __InstanceCreationEvent: C:\Test 8 9 10 Sink canceled. Test folder deleted.
After that, Process Monitor will confirm that WMI has stopped background polling for folder creation.
Caveat: It's absolutely essential that Sink.Cancel
is called. If the script terminates unexpectedly, WMI will continue to poll in the background, and restarting the WMI service is the only way to get rid of the polling loop.