Introduction
I first ran into WMI while I was trying to find a way to monitor a folder for newly created files. The script I found looked like this:
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & _
strComputer & "\root\cimv2")
Set colMonitoredEvents = objWMIService.ExecNotificationQuery _
("Select * From __InstanceCreationEvent Within 5 Where " _
& "Targetinstance Isa 'CIM_DirectoryContainsFile' and " _
& "TargetInstance.GroupComponent= " _
& "'Win32_Directory.Name=""c:\\\\scripts""'")
Do
Set objLatestEvent = colMonitoredEvents.NextEvent
Wscript.Echo objLatestEvent.TargetInstance.PartComponent
Loop
Frankly, even now the script looks pretty horrible. Back then, it took some time and investigation for me to understand its mechanics, but when I finally got it, it worked well.
So, how does the script work? The first part connects to the WMI service on the local computer and gets the WMI Scripting API object that represents it - SWbemServices
. The second part issues a WMI notification query – we want to receive a notification every time an instance of the CIM_DirectoryContainsFile
class is created. CIM_DirectoryContainsFile
is an association WMI class - it associates an instance of the CIM_Directory
class that represents a file system directory (as CIM_DirectoryContainsFile.GroupComponent
) and an instance of CIM_DataFile
, a logical file contained within that directory (as CIM_DirectoryContainsFile.PartComponent
). This means that there is an instance of the CIM_DirectoryContainsFile
class for every file in every folder on your computer, and it also means that an instance of it is created every time you create a new file. But we want to limit our notifications to just one folder, so we use the Where clause, telling WMI to notify us only when the CIM_DirectoryContainsFile
GroupComponent
property is equal to “C:\\Scripts”, i.e., when a file is created in the C:\Scripts folder.
CIM_DirectoryContainsFile
is not an event class (meaning that it is not derived from the WMI system class called __Event
), so we can’t use it directly in WQL event queries, we need to use it with an auxiliary WMI class – __InstanceCreationEvent
– this class is actually derived from the __Event
class, so it is possible to use it in event queries.
The query is passed to SWbemServices.ExecNotificationQuery(
) as a string. This function returns an instance of the SWbemEventSource
object, which we can use to receive events from our event query.
The third part of the script is an endless loop. Each time we enter the loop, we use the SWbemEventSource.NextEvent()
function to pause the script and wait for the next event that we subscribed for in our WQL event query. When an event occurs, SWbemEventSource.NextEvent()
returns a SWbemObject
that represents an instance of the __InstanceCreationEvent
class – this is exactly what we requested in the query. The __InstanceCreatioEvent.TargetInstance
property, in turn, will be a reference to the instance of CIM_DirectoryContainsFile
that triggered the event. Finally, CIM_DirectoryContainsFile.PartComponent
will point to the CIM_DataFile
instance that was created in C:\Scripts. This means that we have two levels of indirection, and to actually echo the file name, we have to use:
objLatestEvent.TargetInstance.PartComponent
This will result in an output similar to this:
\\AName\root\cimv2:CIM_DataFile.Name="c:\\scripts\\New Text Document (16).txt"
After that, the script reenters the loop and waits for the next event. The loop is endless, so we need to use Ctrl+C to stop it (this is not the whole story about cancelling WQL event queries, but perhaps more on it later).
It is possible to monitor file deletion with the script – just replace ‘__InstanceCreationEvent
’ with ‘__InstanceDeletionEvent
’ in the query:
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & _
strComputer & "\root\cimv2")
Set colMonitoredEvents = objWMIService.ExecNotificationQuery _
("Select * From __InstanceDeletionEvent Within 5 Where " _
& "Targetinstance Isa 'CIM_DirectoryContainsFile' and " _
& "TargetInstance.GroupComponent= " _
& "'Win32_Directory.Name=""c:\\\\scripts""'")
Do
Set objLatestEvent = colMonitoredEvents.NextEvent
Wscript.Echo objLatestEvent.TargetInstance.PartComponent
Loop
This is good but will get us only so far: we can’t use this query to monitor file modification events within a folder; the query returns an instance of CIM_DirectoryContainsFile
, but we are really interested in the CIM_Data
file and its properties (we could use CIM_DirectoryContainsFile.GroupComponent
to get the CIM_DataFile
instance, but that would require another query); the script needs to be running all the time in order to be able to receive events, so it could be easily interrupted by accident; it uses an endless loop, etc.
By changing the script a bit more, we can monitor the file creation, deletion, and modification events at the same time:
intInterval = "2"
strDrive = "C:"
strFolder = "\\scripts\\"
strComputer = "."
Set objWMIService = GetObject( "winmgmts:" &_
"{impersonationLevel=impersonate}!\\" &_
strComputer & "\root\cimv2" )
strQuery = _
"Select * From __InstanceOperationEvent" _
& " Within " & intInterval _
& " Where Targetinstance Isa 'CIM_DataFile'" _
& " And TargetInstance.Drive='" & strDrive & "'"_
& " And TargetInstance.Path='" & strFolder & "'"
Set colEvents = _
objWMIService. ExecNotificationQuery (strQuery)
Do
Set objEvent = colEvents.NextEvent()
Set objTargetInst = objEvent.TargetInstance
Select Case objEvent.Path_.Class
Case "__InstanceCreationEvent"
WScript.Echo "Created: " & objTargetInst.Name
Case "__InstanceDeletionEvent"
WScript.Echo "Deleted: " & objTargetInst.Name
Case "__InstanceModificationEvent"
Set objPrevInst = objEvent.PreviousInstance
For Each objProperty In objTargetInst.Properties_
If objProperty.Value <> _
objPrevInst.Properties_(objProperty.Name) Then
WScript.Echo "Changed: " _
& objTargetInst.Name
WScript.Echo "Property: " _
& objProperty.Name
WScript.Echo "Previous value: " _
& objPrevInst.Properties_(objProperty.Name)
WScript.Echo "New value: " _
& objProperty.Value
WScript.Echo
End If
Next
End Select
Loop
There are a few things that have changed from the previous script:
We subscribe to the CIM_DataFile
events instead of the CIM_DirectoryContainsFile
events. This way, we can monitor not only file creation and deletion events in a directory, but also file modification – we will receive an event for each CIM_DataFile
property change for each file in the directory.
Instead of __InstanceCreationEvent
, __InstanceDeletionEvent
, or __InstanceModificationEvent
, we use __InstanceOperationEvent
– this is the class that all the three classes are derived from. This way, we are able to monitor all three types of events at the same time.
Because of this, when an event is received, we need to check for its type – event though we subscribed to __InstanceOperationEvent
events, this class is abstract, so the received event will be one of its three derived classes. We can check it using the SWbemObject.Path_.Class
property.
The __InstanceModificationEvent
class has an additional property called PreviousInstance
that you can use to get a copy of the CIM_DataFile
instance prior to modification – comparing the TargetInstance
and PreviousInstance
properties, we can find out which property has changed.
When you run the script, you will immediately receive a modification event for every file in the monitored directory. This is because the way WMI monitoring works – it first enumerates all the files and their properties, and then polls for changes within the given interval. This is the reason why it is not a good idea to use WMI to monitor a large number of files.