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

Creating WMI Permanent Event Subscriptions Using MOF

4.94/5 (18 votes)
29 Jul 2008CPOL17 min read 1   742  
The article shows some samples of setting up permanent event subscriptions using MOF and receiving WMI events at all times.

Image 1

Contents

  1. Introduction
  2. WQL Event Queries
  3. Event Helper Classes
  4. WITHIN and GROUP Clauses
  5. Temporary Event Consumers
  6. Permanent Event Subscription
  7. Using WMI Tools
  8. ActiveScriptEventConsumer Class
  9. Note about Strings
  10. TargetEvent and TargetInstance
  11. SMTPEventConsumer Class
  12. LogFileEventConsumer Class
  13. CommandLineEventConsumer Class
  14. Win32_LocalTime Class
  15. Final Note
  16. Useful Links

1. Introduction

The WMI Event Subsystem allows you to subscribe to WMI events. WMI events represent changes in WMI data: if you start Notepad, an instance of the Win32_Process WMI class is created, and an instance creation WMI event is also created. If you delete a file from the disk, an instance of the CIM_DataFile class is deleted, and an instance deletion WMI event is created. In fact, any change in WMI data can be used to create an event, so it is easy to see how WMI events can be useful in system administration.

WMI will not create events for you unless you subscribe to them. Applications that register their interest in WMI events are called event consumers.

There are two types of event consumers: temporary and permanent. Temporary event consumers are typically applications that use the .NET Framework and its System.Management namespace or WMI scripting library to receive WMI events, which means that they receive events only when they are started by a user. Permanent event consumers are different - they are designed to receive events at all times. Both temporary and permanent event consumers use WMI event queries to subscribe to events they are interested in.

2. WQL Event Queries

Just like other WMI queries, WMI event queries are issued using WQL (WMI Query Language). There are several differences between event queries and other query types, but the most significant one is that WMI event queries use WMI event classes. A WMI class is an event class if it is derived from the __Event system class. So, in order to see what tasks can be accomplished using WMI events, you first need to examine the WMI event classes. But, how can you do that? Since all event classes are derived from the __Event system class, you can use a query like this one:

SQL
Select * From Meta_Class Where __This Isa "__Event"

Although this query includes a reference to the __Event class, it is not an event query. Actually, it is a WMI schema query - it uses Meta_Class, a special class that represents all classes in a WMI namespace. Since you don't want all the classes, but just the __Event derived classes, you also need to add the WHERE clause. When issued, the query returns a list of WMI classes that looks like this:

. . . 
MSFT_WMI_GenericNonCOMEvent
MSFT_WmiSelfEvent
Msft_WmiProvider_OperationEvent
Msft_WmiProvider_ComServerLoadOperationEvent
Msft_WmiProvider_InitializationOperationFailureEvent
Msft_WmiProvider_LoadOperationEvent
Msft_WmiProvider_OperationEvent_Pre
Msft_WmiProvider_DeleteClassAsyncEvent_Pre
Msft_WmiProvider_GetObjectAsyncEvent_Pre
Msft_WmiProvider_AccessCheck_Pre
Msft_WmiProvider_CreateClassEnumAsyncEvent_Pre
Msft_WmiProvider_ExecQueryAsyncEvent_Pre
Msft_WmiProvider_CreateInstanceEnumAsyncEvent_Pre
Msft_WmiProvider_NewQuery_Pre
Msft_WmiProvider_DeleteInstanceAsyncEvent_Pre
Msft_WmiProvider_CancelQuery_Pre
Msft_WmiProvider_PutInstanceAsyncEvent_Pre
. . .

On my test Windows XP SP2 machine, the query returns a total of 136 classes. The number may be different on your computer, but if you examine the list closely, you'll notice that most commonly used WMI classes like Win32_Process or Win32_Service are not on it.

3. Event Helper Classes

So, the classes that you are really interested in are not derived from the __Event class, but it is still possible to use them in WMI event queries. You can use all WMI classes in event queries, only not directly. In order to use a class that is not derived from __Event in an event query, you need to use one of the helper classes like:

  • __InstanceCreationEvent
  • __InstanceModificationEvent
  • __InstanceDeletionEvent

All of the above classes are derived from __InstanceOperationEvent, and have a TargetInstance property, which is a reference to the class instance you want to receive event notifications from. So, if you use a query like this:

SQL
Select * From __InstanceCreationEvent
Where TargetInstance Isa "Win32_Process"

the TargetInstance property of the returned event will contain a reference to the Win32_Process instance that was created. If you want to refer to the Win32_Process.ExecutablePath property, use the __InstanceCreationEvent.TargetInstance.ExecutablePath property. In addition, the __InstanceModificationEvent class has the PreviousInstance property that contains a reference to a copy of the WMI class instance before it was modified. Classes derived from __InstanceOperationEvent and their TargetInstance property enable you to use all WMI classes in event queries.

4. WITHIN and GROUP Clauses

The WMI event subsystem uses a polling mechanism for event delivery. To specify the polling interval, use the WITHIN keyword followed by the polling interval (in seconds):

SQL
Select * From Win32_Process
Within 10
Where TargetInstance Isa "Win32_Process"

In this example, WMI initially enumerates all Win32_Process instances, and polls for changes every ten seconds. This means that it is possible to miss some events: if a process is created and destroyed in less than ten seconds, it will not raise an event.

The Group clause causes WMI to create only one event notification to represent a group of events. For example, this query:

SQL
Select * From __InstanceModificationEvent
Within 10
Where TargetInstance Isa
"Win32_PerfFormattedData_PerfOS_Processor"
Group Within 5
Having NumberOfEvents > 3

will create one event that represents all the modification events for Win32_PerfFormattedData_PerfOS_Processor that occurred within 5 seconds, but only if the number of events is greater than 3.

To summarize:

  • WQL event queries use classes derived from the __Event system class.
  • WQL event queries use Within and Group clauses.
  • WQL event queries use the Isa keyword to specify classes to receive events from.
  • WQL event queries use TargetInstance and PreviousInstance to access WMI data of interest.

5. Temporary Event Consumers

A temporary event consumer is any application that requests event notifications from WMI. In most cases, it is a VBScript or code that uses the System.Management namespace. Here is a sample VBScript that subscribes to the Win32_Process creation events:

VBScript
' VBScript source code

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "\\" & strComputer & "\root\cimv2")

Set colMonitoredProcesses = objWMIService. _        
ExecNotificationQuery("select * from __InstanceCreationEvent " _ 
& " Within 1 Where TargetInstance isa 'Win32_Process'")

Do
Set objLatestProcess = colMonitoredProcesses.NextEvent
Wscript.Echo objLatestProcess.TargetInstance.Name
Loop

Although this code works, it has at least three disadvantages:

  • The script process needs to run all the time. The above script will receive WMI events only while it is running, and while it is running, it consumes system resources. This may become a problem if you have multiple scripts running at the same time.
  • The process can be easily interrupted. Scripts such as the above are typically run from the Command Prompt, so they can easily be interrupted, either by closing the Command Prompt box, or by pressing Ctrl+C.
  • Even if the CScript process is interrupted, the event notification is not canceled. This may be the most serious disadvantage.

Here is a sample of the third case:

VBScript
' VBScript source code

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "\\" & strComputer & "\root\cimv2")

Set colEvents =  objWMIService.ExecNotificationQuery _
("Select * From __InstanceCreationEvent Within 2" _
& "Where TargetInstance Isa 'Win32_Directory' " _
& "And TargetInstance.Path = '\\Scripts\\'")

Do
Set objEvent = colEvents.NextEvent()
WScript.Echo objEvent.TargetInstance.Name
Loop

Scripts like this one are typically run from the Command Prompt. But, even if you stop the script, the event notification is not canceled - you can easily observe that, because the floppy disk drive is still flashing every two seconds (I call this the 'FDD Light Show'). This is true not only of the file system monitoring scripts, but every other. The only way to cancel the event notification in this case is to stop the Winmgmt service itself, using:

net stop winmgmt

The Windows Firewall service depends on Winmgmt, so it is easy to imagine a situation where this can become a problem.

6. Permanent Event Subscription

WMI permanent event subscription can remedy all these problems. It doesn't depend on a running process (save for svchost.exe that hosts the Winmgmt service). To interrupt it, you need knowledge of WMI, so it is not easy to stop it accidentally, and you can cancel it anytime, without having to restart the Winmgmt service. In its basis, permanent event subscription is a set of static WMI classes stored in a CIM repository. Of course, you can use VBScript or the .NET Framework System.Management classes to create these instances and set up a permanent event subscription, but the easiest way is (at least in my opinion) to use MOF. Here is a sample MOF that you can use as a template for creating permanent event subscriptions:

VBScript
// 1. Change the context to Root\Subscription namespace
//    All standard consumer classes are
//    registered there.

#pragma namespace("\\\\.\\root\\subscription")


// 2. Create an instance of __EventFilter class
//    and use it's Query property to store
//    your WQL event query.

instance of __EventFilter as $EventFilter
{
    Name  = "Event Filter Instance Name";
    EventNamespace = "Root\\Cimv2";
    Query = "WQL Event query text";
    QueryLanguage = "WQL";
};


// 3. Create an instance of __EventConsumer
//    derived class. (ActiveScriptEventConsumer
//    SMTPEventConsumer etc...) 

instance of __EventConsumer derived class as $Consumer
{
    Name = "Event Consumer Instance";
    // Specify any other relevant properties.
};


// 4. Join the two instances by creating
//    an instance of __FilterToConsumerBinding
//    class.

instance of __FilterToConsumerBinding
{
    Filter = $EventFilter;
    Consumer   = $Consumer;
}; 

To create a permanent WMI event subscription, you need to follow these steps:

  • Change the WMI context to the Root\Subscription namespace. Starting with Windows XP, it is Microsoft's recommendation to store all permanent event subscriptions there.
  • Create an instance of the __EventFilter class. The __EventFilter class is derived from the __IndicationRelated system class, and its main purpose is to hold a WQL event query. To create an instance of __EventFilter, use 'instance of' keywords. First, you give the class instance a unique name using its Name property. Second, specify its EventNamespace property - typically, this will be the Root\Cimv2 namespace, as the majority of useful classes are located there. Third, set its Query property to the WQL event query you want to use.
  • Create an instance of the __EventConsumer derived class. This is the instance that represents the event consumer. Although it is possible to create custom __EventConsumer derived classes, this requires creating a COM object that reacts to the specified event, which is probably not a viable solution for most system administrators. Luckily, Microsoft provides a set of standard event consumer classes, like ActiveScriptEventConsumer, LogFileEventConsumer, etc. In this article, I will use only standard event consumer classes.
  • Associate the instances of __EventFilter and __EventConsumer derived classes by creating an instance of the __FilterToConsumerBinding class. Its Filter property is a reference to the previously created instance of the __EventFilter class, and its Consumer property is a reference to one of the standard event consumer class instances.

In the rest of this article, I will attempt to walk you through several samples of permanent event subscription that use standard event consumer classes.

7. Using WMI Tools

A tool named WMI Event Registration is included with WMI Tools, and it is very helpful when working with permanent subscription: it allows you to explore existing filters, consumers, or timers, and also create new ones using a user-friendly interface. You can also cancel event subscriptions using this tool.

When this tool is first opened, you are offered to connect to the Root\Cimv2 namespace, but connect to Root\Subscription instead - this is where you will create most of the permanent event subscriptions. Once connected, if you select 'Consumers' from the leftmost dropdown, you will see a list of all available standard event consumer classes listed in the left hand pane, as they are already registered there.

If an instance of any of the standard event consumer classes already exists, by selecting it, you can view the available __EventFilter instances in the right hand pane. If any of the __EventFilter instances is joined with the selected consumer instance, it is checked, so, the green checkmark actually represents an instance of the __FilterToConsumerBinding class.

All permanent event subscription samples presented here are created using MOF - you need a tool called mofcomp.exe to store instance definitions contained in a MOF file into the CIM repository. Mofcomp.exe is stored in the Windows directory (typically C:\Windows\System32\Wbem\) and its basic syntax is:

mofcomp FileName.mof

8. ActiveScriptEventConsumer Class

The ActiveScriptEventConsumer class is one of the standard event consumer classes: it allows you to run ActiveX script code whenever an event is delivered to it. To create an instance of the ActiveScriptEventConsumer class, you need to assign values to its properties:

  • Name: Gives a unique name to an instance of ActiveScriptEventConsumer.
  • ScriptingEngine: This is the name of the scripting engine that will be used. Although the documentation states that this can be any arbitrary scripting engine, the only ones I have used are 'VBScript' and 'JScript'.
  • ScriptText: This is a string property that contains a VBScript or JScript code to be executed when an event is delivered to the ActiveScriptEventConsumer instance.
  • ScriptFileName: This property holds the full path to the VBScript or JSCript file to be executed on event arrival. ScriptText and ScriptFileName properties are mutually exclusive.

As a test, you can create an event consumer that executes some arbitrary VBScript code whenever an instance of Win32_Process named 'notepad.exe' is created. To create a permanent event subscription that uses ActiveScriptEventConsumer:

  • Change the current WMI namespace to Root\Subscription:
    VBScript
    #pragma namespace("\\\\.\\root\\subscription")
  • Create an instance of the __EventFilter class to monitor the Win32_Process creation:
    VBScript
    instance of __EventFilter as $EventFilter
    {
        EventNamespace = "Root\\Cimv2";
        Name  = "New Process Instance Filter";
        Query = "Select * From __InstanceCreationEvent Within 2" 
                "Where TargetInstance Isa \"Win32_Process\" "
                "And Targetinstance.Name = \"notepad.exe\" ";
        QueryLanguage = "WQL";
    };

    In this case, you will receive a __InstanceCreationEvent class instance, but from the Root\Cimv2 namespace, as the Win32_Process class is located there.

  • Create an instance of the ActiveScriptEventConsumer class:
    VBScript
    instance of ActiveScriptEventConsumer as $Consumer
    {
        Name = "TestConsumer";
        ScriptingEngine = "VBScript";
        ScriptText = 
        "Set objFSO = CreateObject(\"Scripting.FileSystemObject\")\n"
        "Set objFile = objFSO.OpenTextFile(\"c:\\log.txt\", 8, True)\n"
        "objFile.WriteLine Time & \" \" & \" Notepad started\"\n"
        "objFile.Close\n";
    };

    The VBScript code that is assigned to its ScriptText property simply logs the time of the notepad.exe process creation to a text file.

  • Bind the two previously created instances using the __FilterToConsumerBinding class:
    VBScript
    instance of __FilterToConsumerBinding
    {
        Consumer   = $Consumer;
        Filter = $EventFilter;
    }

When you compile the above MOF using mofcomp.exe, every time Notepad is opened, the time of the notepad.exe process creation is logged to the c:\log.txt file. If the file doesn't already exist, it is created when the first event notification is received.

Instead of ScriptText, you can also use the ScriptFileName property:

VBScript
instance of ActiveScriptEventConsumer as $Consumer
{
    Name = "ExternalScriptConsumer";
    ScriptingEngine = "VBScript";
    ScriptFileName = "C:\\Consumer.vbs";
};

In that case, you also need an external script file: c:\Consumer.vbs.

When creating VBScript or JScript scripts to use with ActiveScriptEventCosumer, you need to be aware of some limitations:

  • ActiveScriptEventConsumer doesn't use the Windows Script Host (WSH), which is widely used in system administration scripts. This means that you can not use the WScript object or any of its properties and methods (like WScript.CreateObject, WScript.Sleep etc.).
  • The script can not generate any screen output, which means that you can not use the VBScript MsgBox function.
  • The script does not have network access.
  • The script can't use any user specific data, such as environment variables or network shares.

9. Note about Strings

When setting up a permanent event subscription, it is likely that you will need to use strings, so here is a quick note:

An MOF string is a sequence of characters enclosed in double quotes. Successive strings are joined together, so this:

SQL
"Select * From __InstanceCreationEvent "
"Within 30 "

becomes:

SQL
"Select * From __InstanceCreationEvent Within 30 "

You can also use the following escape sequences:

\b   backspace 
\t   horizontal 
\n   linefeed 
\f   form feed 
\r   carriage return 
\"   double quote 
\'   single quote 
\\   backslash

10. TargetEvent and TargetInstance

A script executed by an ActiveScriptEventConsumer instance can access an environment variable called TargetEvent, which holds a reference to the event class:

VBScript
instance of ActiveScriptEventConsumer as $Consumer
{
    Name = "TargetEventConsumer";
    ScriptingEngine = "VBScript";
    ScriptText = 
    "Const ForReading = 1\n"
    "Const ForWriting = 2\n"
    "\n"
    "Set objFso = CreateObject(\"Scripting.FileSystemobject\")\n"
    "Set objStream = objFso.OpenTextFile( _\n"
    "    TargetEvent.TargetInstance.Name, ForReading, False)\n"
    "\n"
    "strContent = objStream.ReadAll()\n"
    "objStream.Close\n"
    "\n"
    "Set objStream = objFso.OpenTextFile( _\n"
    "    TargetEvent.TargetInstance.Name, ForWriting, False)\n"
    "\n"
    "objStream.Write( _\n"
    "    Replace(strContent, \"127.0.0.1\", \"Localhost\"))\n"
    "objStream.Close\n";
};

The event class is typically one of various __InstanceOperationEvent derived classes, whose TargetInstance property, in turn, is a reference to the actual class instance of what was created. If that class is, for example, CIM_DataFile, you need to use the following to access its Name property:

VBScript
TargetEvent.TargetInstance.Name

11. SMTPEventConsumer Class

This class sends an e-mail message each time an event is delivered to it. To create an instance of the SMTPEventConsumer class, assign values to its properties:

  • Name: Gives a unique name to the instance.
  • SMTPServer: Name of the SMTP server through which the message will be sent.
  • ToLine: To line of the e-mail message.
  • FromLine: From line of the e-mail message.
  • Subject: Subject line of the mail message.
  • Message: The body of the e-mail message.

As an example, set up a permanent event subscription that uses the SMTPEventConsumer class to send an e-mail message each time a printer status changes. To use SMTPEventConsumer in permanent event subscription:

  • Change the context to the Root\Subscription namespace:
    VBScript
    #pragma namespace("\\\\.\\root\\subscription")
  • Create an instance of the __EventFilter class:
    VBScript
    instance of __EventFilter as $EventFilter
    {
        EventNamespace = "Root\\Cimv2";
        Name  = "SMTPEventFilter";
        Query = "Select * From __InstanceModificationEvent "
                "Within 2 " 
                "Where TargetInstance Isa \"Win32_Printer\" "
                "And (TargetInstance.PrinterStatus = 1 "
                "Or TargetInstance.PrinterStatus = 2) "
                "And Not (PreviousInstance.PrinterStatus = 1 "
                "Or PreviousInstance.PrinterStatus = 2)";
        QueryLanguage = "WQL";
    };

    With the above WQL query, you subscribe to modification events of the Win32_Printer class instances. Note the usage of the __InstanceModificationEvent.PreviousInstance property, which contains a copy of the Win32Printer instance before it was changed. It is useful for comparing the instance properties before and after it was modified. In this case, we are only interested in the events in which the Win32_Printer.PrinterStatus value is changed from anything else to 1 or 2.

  • Create an instance of the SMTPEventConsumer class:
    VBScript
    instance of SMTPEventConsumer as $Consumer
    {
        Name = "Printer Error Event Consumer";
        SMTPServer = "SMTPServerName";
        ToLine = "Recipient@nn.com";
        FromLine = "Sender@nn.com";
        Subject = "Printer Error!";
        Message = "An error is detected in one of the printers!\n"
                  "Printer Name: %TargetInstance.Name%\n"
                  "Server:       %TargetInstance.__Server%\n"
                  "Event Date:   %TIME_CREATED%";
    };

    If you look at the SMTPEventConsumer class MOF code, you will see that most of its properties are marked with a Template qualifier. This means that you can use WMI standard string templates when setting their values. Using standard string templates, you can access the event class properties, just as you can use the TargetEvent environment variable with ActiveScriptEventConsumer. So, for example, if TargetInstance is Win32_Printer, this:

    "Printer Name: %TargetInstance.Name%"

    will be translated into something like:

    "Printer Name: HP LaserJet III PostScript Plus v2010.118"

    Also, this:

    "Event Date:   %TIME_CREATED%"

    will become:

    "Event Date:    128611400690000000"

    __InstanceModificationEvent.Time_Created is the number of 100-nanosecond intervals after January 1, 1601, so if you want to convert it to a readable format, it will probably take a bit of work.

  • Bind the two instances by creating an instance of the __FilterToConsumerBinding class:
    VBScript
    instance of __FilterToConsumerBinding
    {
        Consumer   = $Consumer;
        Filter = $EventFilter;
    };

12. LogFileEventConsumer Class

The LogFileEventConsumer class writes customized strings to a text file each time an event is delivered to it. Significant properties:

  • Name: Gives a unique name to the instance.
  • Text: Contains the text to be written to the log file when an event arrives. You can use standard string templates to compose it.
  • Filename: Path to the text file where the value of the Text property is to be written.

A sample usage of LogFileEventConsumer could be to log changes in a Windows service's state. To use LogFileEventConsumer with permanent event subscription:

  • Change the context to the Root\Subscription namespace:
    VBScript
    #pragma namespace("\\\\.\\root\\subscription")
  • Create an instance of the __EventFilter class that monitors the Win32_Service modification events:
    VBScript
    instance of __EventFilter as $EventFilter
    {
        EventNamespace = "Root\\Cimv2";
        Name  = "Service State Event Filter";
        Query = "Select * From __InstanceModificationEvent "
                "Within 2 " 
                "Where TargetInstance Isa \"Win32_Service\" "
                "And TargetInstance.State <> "
                "PreviousInstance.State";
        QueryLanguage = "WQL";
    };
  • Create an instance of LogFileEventConsumer:
    VBScript
    instance of LogFileEventConsumer as $Consumer
    {
        Name = "Service State Log Consumer";
        Filename = "c:\\scripts\\ServiceStateLog.csv";
        IsUnicode = true;
        Text = "\"%TargetInstance.Name%\","
               "\"%PreviousInstance.State%\","
               "\"%TargetInstance.State%\","
               "\"%TIME_CREATED%\"";
    };
  • Bind the two instances using the __FilterToConsumerBinding class:
    VBScript
    instance of __FilterToConsumerBinding
    {
        Consumer   = $Consumer;
        Filter = $EventFilter;
    };

When assigning value to the LogFileEventConsumer.Text property, use WMI standard string templates to access event-related data.

13. CommandLineEventConsumer Class

The CommandLineEventConsumer class launches an arbitrary process when an event is delivered to it. Important properties are:

  • Name: Gives a unique name to the instance.
  • ExecutablePath: Path to the executable. This property can be Null, but even if you assign a value to it, you still need to include the executable path at the beginning of the CommandLineTemplate property.
  • CommandLineTemplate: Standard string template that specifies the executable to be launched, followed by any command line arguments. You can use standard string templates when creating it.

Here is a sample of a CommandLineEventConsumer MOF that monitors PNP device changes. To create a permanent event subscription that uses CommandLineEventConsumer:

  • Change the WMI context to the Root\Subscription namespace:
    VBScript
    #pragma namespace("\\\\.\\root\\subscription")
  • Create an instance of the __EventFilter class that detects the Win32_PNPEntity instance creation:
    VBScript
    instance of __EventFilter as $EventFilter
    {
        EventNamespace = "Root\\Cimv2";
        Name  = "Test Command Line Event Filter";
        Query = "Select * From __InstanceCreationEvent "
                "Within 2 " 
                "Where TargetInstance Isa \"Win32_PNPEntity\" ";
        QueryLanguage = "WQL";
    };
  • Create an instance of the CommandLineEventConsumer class:
    VBScript
    instance of CommandLineEventConsumer as $Consumer
    {
        Name = "Test CommandLine Event Consumer";
        RunInteractively = false;
        CommandLineTemplate = "cmd /c "
            "WMIC /Output:"
            "C:\\HWLogs\\PNPDeviceLog%TIME_CREATED%.html "
            "Path Win32_PNPEntity "
            "Get Caption, DeviceId, PNPDeviceId "
            "/Format:HTable.xsl";
    };

    This CommandLineEventConsumer instance uses the WMI command line utility (WMIC) to create a simple HTML file that contains a list of all Win32_PNPEntity instances each time a new Win32_PNPEntity instance is created.

  • Bind the instances using __FilterToConsumerBinding:
    VBScript
    instance of __FilterToConsumerBinding
    {
        Consumer   = $Consumer;
        Filter = $EventFilter;
    };

14. Win32_LocalTime Class

The Win32_LocalTime class is an exception: it is not derived from the __Event class, but you can still use it in WQL event queries, which means that you can also use it to set up a permanent event subscription. An interesting use of the Win32_LocalTime class can be to mimic the Windows Scheduler service. To create a permanent event subscription that subscribes to Win32_LocalTime events:

  • Change the context to the Root\Subscription namespace:
    VBScript
    #pragma namespace("\\\\.\\root\\subscription")
  • Create an instance of the __EventFilter class:
    VBScript
    instance of __EventFilter as $EventFilter
    {
        EventNamespace = "Root\\Cimv2";
        Name  = "Sample Timer Event Filter";
        Query = "Select * From __InstanceModificationEvent "
                "Where TargetInstance Isa \"Win32_LocalTime\" "
                "And TargetInstance.Hour = 18 "
                "And TargetInstance.Minute = 10 "
                "And TargetInstance.Second = 30";
        QueryLanguage = "WQL";
    };

    Use this filter to subscribe to Win32_LocalTime modification events. In the query, you can use any combination of Win32_LocalTime properties: Day, DayOfWeek, Hour, Milliseconds, Minute, Month, Quarter, Second, WeekInMonth, and Year.

  • Create an instance of a __EventConsumer derived class:
    VBScript
    instance of CommandLineEventConsumer as $Consumer
    {
        Name = "Test CommandLine Event Consumer";
        RunInteractively = false;
        CommandLineTemplate = "cmd /c "
            "C:\\Backup\\LocalBackup.bat";
    };

    In this case, it is an instance of the CommandLineEventConsumer class, but it can be any of the standard consumer classes.

  • Bind __EventFilter and CommandLineEventConsumer instances by creating an instance of the __FilterToConsumerBinding class:
    VBScript
    instance of __FilterToConsumerBinding
    {
        Consumer   = $Consumer;
        Filter = $EventFilter;
    };

15. Final Note

Permanent event subscription has several advantages over temporary event subscription, but it also has one disadvantage: temporary event subscription is easier to debug. If you are using the System.Management namespace to create an application that subscribes to WMI events, you have all Visual Studio debugging tools at your disposal. If you are using VBScript, you can test WQL event queries separately from the rest of the code, and you receive meaningful (at least sometimes) error messages from WMI. While you are testing permanent event subscription, the only source of debugging information is the WMI event subsystem log file, named Wbemess.log (it is typically located in the C:\Windows\System32\Wbem\Logs\ directory) - all errors detected both in the event filter and the event consumer instances are logged there, and the messages are not always easy to decipher. So, it is probably better to test WQL queries you want to use for permanent event subscription using System.Management or VBScript first.

Permanent event subscription can be useful, but if you don't use it carefully, it can consume too much system resources and become inefficient. There are two ways to deal with this:

  • Increase the polling interval using the Within clause. In the samples presented here, I used very short polling intervals, but, depending on your requirements, you can increase it at your will.
  • Make WQL queries more selective using the Where clause. For example, this query:
    SQL
    Select * From __InstanceCreationEvent
    Where TargetInstance Isa "CIM_DataFile"
    And TargetInstance.Path = "\\Logs\\"

    will be less efficient than this one:

    SQL
    Select * From __instanceCreationEvent
    Where TargetInstance Isa "CIM_DataFile"
    And TargetInstance.Drive = "C:"
    And TargetInstance.Path = "\\Logs\\"
    And TargetInstance.Extension = "Log"

Queries that include file system classes like CIM_DataFile or Win32_Directory can be very resource consuming, in general: a query that monitors a couple of hundreds of files can slow down your system noticeably.

WQL is a version of SQL, and for SQL queries, it is often recommended not to select all fields in a table (using '*') unless you really need all of them. I haven't tested this recommendation with WQL queries, but I don't think this advice applies to WQL.

16. Useful Links

There is not much documentation concerning permanent event subscription, but you can find some in MSDN:

License

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