In the first article of this series we saw how WMI can be used to build an event driven script. In this segment we will be learning what other events WMI makes available to us. Let’s get started.
Before we begin, let’s take another look at the code from the last article. We’ll be using that as the base for all of the samples in this one.
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & strComputer _
& "rootcimv2")
Set colEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE " _
& "Targetinstance ISA 'CIM_DirectoryContainsFile' and " _
& "TargetInstance.GroupComponent= " _
& "'Win32_Directory.Name=""c:\test""'")
Do While True
Set objEvent = colEvents.NextEvent()
WScript.Echo "File Deleted:", _
objEvent.TargetInstance.PartComponent
Loop
As we’ve learned, this code watches the C:test folder for file deletions. We connect to the WMI service, issue an event based query, and then process the results in an endless Do…Loop.
The important things to note here are the use of the __InstanceCreationEvent event and the use of the NextEvent function to move through returned events.
We’re going to be using this same construct with our next event. We’ll simply plug in a new event and the code will function in an identical fashion.
We know how to watch for creations. We also know how to watch for deletions. The third event type we’ll be looking at will watch for modifications. Again, our examples will be watching for files, but this could easily be altered to watch for changed registry keys or any other scriptable object.
We’ll be using the __InstanceModificationEvent event. This event watches an object for any modifications. These include: changing the object name, adding or removing content, or altering any attributes. Essentially, it watches for any time the object is changed or modified in any way.
This is useful for watching a file or folder for updates, or watching specific registry keys for unwanted changes.
To implement this event, simply use a drop-in replacement in our code sample.
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & strComputer _
& "rootcimv2")
Set colEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM __InstanceModificationEvent WITHIN 10 WHERE " _
& "Targetinstance ISA 'CIM_DirectoryContainsFile' and " _
& "TargetInstance.GroupComponent= " _
& "'Win32_Directory.Name=""c:\test""'")
Do While True
Set objEvent = colEvents.NextEvent()
WScript.Echo "File Modified:", _
objEvent.TargetInstance.PartComponent
Loop
You can test this by again creating a folder called C:test. Create a plain text file. (It doesn’t matter what you name it.) Now run your script. While your script is running, open the text file in Notepad and add some text. Now save your changes. You script should notify you that the file has been changed.
As you’ve probably noticed by now, WMI returns a pretty interesting file path.
Okay, there’s lot of things going on here. Let me try to explain what’s happening. If you look at the string path returned by WMI you will see that we only need the information to the right of the equal sign. So the first part returns just that.
We use the InStr function to return the position of the equals sign. By subtracting that number from the total length of the string, we can determine how many characters appear after it. Then we use the Right function to return only that portion of the string.
"c:scriptsDocument.txt"
Now we have our file path. However, it’s enclosed in quotation marks. We probably wouldn’t want that either so we use the next two lines to clip a single character from each side of the string.
c:scriptsDocument.txt
This leaves us with our file path, but there’s still one more problem. Our file path still contains WMI’s escaped backslashes. To eliminate those, we’ll use VBScript’s Replace function to replace all occurrences of a double backslash with a single one.
c:scriptsDocument.txt
Now strPath contains only our formatted file path. We wrap up our function by telling it to return the contents of strPath.
Now to implement this in our code we need to make one minor change inside of our Do…Loop. Rather than echoing back WMI’s path, we need to pass it through our function first.
Do While True
Set objEvent = colEvents.NextEvent()
WScript.Echo "File Modified:", _
parsePath(objEvent.TargetInstance.PartComponent)
Loop
Now our script returns exactly the information we wanted. If we wanted, we could pass this path on to FileSystemObject method, for example, to back up the newly modified file. Or, as in our example, we simply echo it back.
So now we’ve learned how to implement WMI events to watch for creations, deletions, and modifications. By now you’re probably wondering what you can do if you want to watch for any or all of these events at the same time.
Let’s take this code as an example.
Set colEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM __InstanceCreationEvent OR __InstanceDeletionEvent " _
& WITHIN 10 WHERE " _
& "Targetinstance ISA 'CIM_DirectoryContainsFile' and " _
& "TargetInstance.GroupComponent= " _
& "'Win32_Directory.Name=""c:\test""'")
In this query we’ve instructed WMI to look for either a creation event or a deletion event. It seems simple enough, but there’s one problem. It won’t work.
WMI will issue an “Unparsable query” error when you try to do this. As it turns out, WMI only likes to deal with one event at a time. This is a very logical script so how can we solve this problem?
Enter WMI’s fourth and final event type. The __InstanceOperationEvent event will monitor for any of the creation, deletion, or modification events. It works a little bit differently though.
You’re going to start out with the same code construct. Simply drop the new event into your query.
strComputer = "."
Set objWMIService = GetObject("winmgmts:" & strComputer _
& "rootcimv2")
Set colEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM __InstanceOperationEvent WITHIN 10 WHERE " _
& "Targetinstance ISA 'CIM_DirectoryContainsFile' and " _
& "TargetInstance.GroupComponent= " _
& "'Win32_Directory.Name=""c:\test""'")
Here’s where things get a little tricky. The __InstanceOperationEvent still returns a collection of events, and we’re still going to process them with an endless do loop. But now we have to determine which event has occurred. We’ll do that by setting up a Select statement for each event type we're looking for.
Do While True
Set objEvent = colEvents.NextEvent()
Select Case objEvent.Path_.Class
Case "__InstanceCreationEvent"
WScript.Echo "File Created:", _
parsePath(objEvent.TargetInstance.PartComponent)
Case "__InstanceDeletionEvent"
WScript.Echo "File Deleted:", _
parsePath(objEvent.TargetInstance.PartComponent)
Case "__InstanceModificationEvent"
WScript.Echo "File Modified:", _
parsePath(objEvent.TargetInstance.PartComponent)
End Select
Loop
The key to this whole code segment is the use of the Path_.Class property. This property contains the type of event that was returned. The Select Case statement chooses how to process the event based on what type it was. If we were only concerned with creation and deletion events, we could simply omit the Case statement for modified ones.
If you were looking for changes to a specific file, you would want to implement an If statement to check the file name. This is just a base script and should be customized to fit your needs.
The topics in this series are quite advanced. You should play around with them to get a good handle on what’s really happening behind the scenes. Once you understand the concept, you’ll find that building your own customized script isn’t as daunting a task as it might seem.
Making use of event driven programming will open many possibilities to you as a programmer. You now have the power to create scripts capable of reacting to the system in real time. Now go explore and see what you can come up with. Until next time…keep coding!