Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VBScript

WMI and File System Monitoring

5.00/5 (3 votes)
8 Sep 2009CPOL4 min read 97K  
An article on WMI and file system monitoring.

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:

VBScript
' VBScript source code

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:

VBScript
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:

VBScript
' VBScript source code

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:

VB
' VBScript source code

intInterval = "2"
strDrive = "C:" 
strFolder = "\\scripts\\"
strComputer = "." 

' Connect to WMI

Set objWMIService = GetObject( "winmgmts:" &_ 
    "{impersonationLevel=impersonate}!\\" &_ 
    strComputer & "\root\cimv2" )

' The query string

strQuery =  _
    "Select * From __InstanceOperationEvent" _
    & " Within " & intInterval _
    & " Where Targetinstance Isa 'CIM_DataFile'" _
    & " And TargetInstance.Drive='" & strDrive & "'"_
    & " And TargetInstance.Path='" & strFolder & "'"

' Execute the query

Set colEvents = _
    objWMIService. ExecNotificationQuery (strQuery) 

' The loop

Do 
    ' Wait for the next event  
    ' Get SWbemEventSource object
    ' Get SWbemObject for the target instance
    
    Set objEvent = colEvents.NextEvent()
    Set objTargetInst = objEvent.TargetInstance
    
    ' Check the class name for SWbemEventSource
    ' It cane be one of the following:
    ' - __InstanceCreationEvent
    ' - __INstanceDeletionEvent
    ' - __InstanceModificationEvent
    
    Select Case objEvent.Path_.Class 
        
        ' If it is file creation or deletion event
        ' just echo the file name
        
        Case "__InstanceCreationEvent" 
            WScript.Echo "Created: " & objTargetInst.Name 

        Case "__InstanceDeletionEvent" 
            WScript.Echo "Deleted: " & objTargetInst.Name 
        
        ' If it is file modification event, 
        ' compare property values of the target and previous
        ' instance and echo the properties that have changed
        
        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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)