Step-by-Step: Handling WMI Events permanently using VBScript
This could be called part 4 of WMI magician Kim Oppalfens 3 part series about WMI eventing in SCCM. In his series he first covered the basics of WMI eventing using wbemtest to create the event query and dig a bit through the returning objects. Then he guided us through a simple VBScript that monitored folders in SCCM to implement “securable” Configuration Manager folders. A pretty neat solution as folders aren’t securable objects within SCCM. However one drawback of his initial script is, that it needs to be running to be able to monitor events and do its magic. So as soon as the script stops due to user logging off, reboot of the server, etc. it will no longer handle any event. Pretty ugly for a solution that shall enhance SCCM usage all the time. I had the same requirement for the script I’ve published in my last post about Monitoring SCCM Task Sequences and other event scripts as well.
So lets fill this gap and get it working all the time.
What do we need ?
To let Kim’s magic happen all the time, we need three things:
1. The event query
It’s provided in the original script already:
SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA 'SMS_ObjectContainerItem'
This queries WMI every 5 seconds for all “SMS_ObjectContainerItem” objects being created.
To implement this query in WMI we need to create a new “__EventFilter” object. We use the following VBScript snippet:
Set oEventFilterClass = oWbemService.Get("__EventFilter") Set oEventFilter = oEventFilterClass.SpawnInstance_() oEventFilter.Name = "SCCMFolderMonitorEvent" oEventFilter.QueryLanguage = "WQL" sQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA 'SMS_ObjectContainerItem'" oEventFilter.Query = sQuery oEventFilter.EventNamespace = "root\sms\site_XXX" oEventFilter.Put_()
We first get the class for “__EventFilter” and create a new instance of it. Then we supply some necessary properties like the query language, the query itself and the namespace for the event. I also gave the Filter a name, so that we are able to reference it later if we e.g. would like to remove the event filter again. If no name is specified, it will be set to a GUID, making it a bit more complicated to find.
2. The event consumer
Now as we have our Event Filter, we need an Event Consumer. The event consumer should call our script that is handling the events we get from our event query. We are not going to cover what the script is actually doing with this event objects or what is required to get it working (the “dummy” object that holds the permission etc.). Please see Create “securable” Configuration Manager folders using WMI eventing of course for more details or see the script as its pretty well commented.
As mentioned by Kim in his series, there are a couple built-in event consumers available in WMI already. One of them is called “ActiveScriptEventConsumer” that “runs a predefined script in an arbitrary scripting language”. Sounds like what we need. So we use the following VBScript snippet to create the ActiveScriptEventConsumer:
Set oConsumerClass = oWbemService.Get("ActiveScriptEventConsumer") Set oConsumer = oConsumerClass.SpawnInstance_() ScriptPath = Replace(WScript.ScriptFullName, WScript.ScriptName, "") oConsumer.Name = "SCCMFolderMonitorConsumer" oConsumer.ScriptFileName = WScript.ScriptFullName oConsumer.ScriptingEngine = "VBScript" oConsumer.Put_
Again, we first get the class “ActiveScriptEventConsumer” and create a new instance of it. We define the scripting engine, which is VBScript in our case and can supply either the script itself as a string, or the path to the script. In this example I simply reference the script itself. This way we can create a script that registers and unregisters itself or executes the event handling, depending on the caller and keep everything at one place. As a sidenote, the WScript object is only available if the script is called by a user. On the event it’s not running in Windows Script Host (WSH) so no WScript object during execution!
You: Sounds nice, but how can we detect if the user or the event consumer called the script?
Me: Well, if the script is called by the event consumer, it supplies an object “TargetEvent” that contains the event object. Or nothing if called by the user. I leave this up to you to implement (You could actually cheat and look into the script I’ve published to CodePlex )
3. Binding the Event Query to the Event Consumer
And now the last part is gluing those two things together. We do this by a WMI object called “__FilterToConsumerBinding”. Let’s have a look on another VBScript snippet:
oEventFilter.Refresh_() oConsumer.Refresh_() Set oBindingClass = oWbemService.Get("__FilterToConsumerBinding") Set oBinding = oBindingClass.SpawnInstance_() oBinding.Filter = oEventFilter.Path_ oBinding.Consumer = oConsumer.Path_ oBinding.Put_()
Important thing to note here is, that we need to refresh our newly created Event Filter and Event Consumer objects first to update their “Path” property. Now as before, we get the class “__FiltertoConsumerBinding” and create a new instance, set the Filter and consumer to the appropriate entries and are done!
If everything went well, it will now query every 5 seconds for new SMS_ObjectContainerItem objects and if found, call the supplied script that should handle the event object appropriately.
A couple things to highlight:
a Script called from ActiveScriptEventConsumer runs as local System on default !!!
There aren’t many limits on what local System can do to your “local System”. So be sure you have tested your script thoroughly, especially if it “does” something (like the sample script).
Make sure you have tight permission set on the script so it can’t be exchanged by someone else, gaining permission he isn’t supposed to have.
As mentioned already, the ActiveScriptEventConsumer does not use WSH to run the script. By this, you don’t have the WScript object available and can’t use its methods in the code executed by the Event handler.
As running in system context, the script can’t interact with the user. Avoid any message or input boxes and be sure to have at least some error handling.
As it can’t interact with the user, troubleshooting/debugging the script if something fails is pretty tough. Please see my last post on some more information about this. I recommend adding extensive logging to your script that can be enabled or disabled if required.
Based on my script from the last post, I’ve rewritten the sample that Kim provided for this permanent event consuming and, with his permission, posted it to CodePlex. Be sure to properly test the script before using it in a production environment, as it will make changes to SCCM. It is provided AS IS and neither I nor Kim will be liable for any damage or problem this script might cause. The actually processing of the event object is more or less the same, I’ve just rewritten the parts for permanent event consuming, added some logging capabilities and refactored it to my preferred naming conventions.
If you have other kewl ideas on what you could do with WMI eventing, just drop me a note and I will happily reference what you did (or publish it for you, if you don’t want to publish it yourself).
Also be sure to check out a new CodePlex project called “PowerEvents for Windows PowerShell” published by Trevor Sullivan. It’s an awesome way of doing the same I’ve shown here with PowerShell. Which reminds me on spending much more time on PowerShell