WMI Programming with Visual Basic.NET: Trapping System Events
In my previous article, part five of this series, we looked at some tips and tricks for using WMI. In this article we will focus on working with trapping system events through WMI.
You can download the zip of entire Visual Studio.NET solution (developed for this article) here.
The first part of my series gives you the details for creating a Visual Studio.NET solution for WMI, right from the scratch. It will not be repeated any more in any of the future articles in this series.
Can we trap system events through WMI?
In parts four and five, we looked at managing Windows OS using WMI. We used to call methods of the respective WMI class according to our needs. Trapping system events is also a part of managing the Windows OS. For example, all Performance Counters can be considered as event trappers of their respective events.
The answer to the above question is “yes.” We can trap system events using WMI as our main interface. Before WMI (and even now), we needed to trap those events only through the WIN32 API. Most probably, Microsoft Visual C++ is a very flexible tool for working with the WIN32 API, as the entire WIN32 API was developed through the same language (but is difficult to understand). There is no wonder that the entire Windows OS was developed using Microsoft Visual C++.
This article focuses on working with “trapping events” using WMI and Visual Basic.NET as core technologies. This is a bit of an advanced topic in WMI. Even though it is bit difficult to understand, try your best to follow.
As you recall from part two of my series, I explained the “types of WQL queries." But I didn’t say much about “Event Queries” in that article. In this article, I will introduce that concept, and we will implement it.
Let us discuss what an “event” in the Windows OS actually is. Every programmer knows that the entire Windows OS is event-driven. Anything you operate with the Windows OS is an event. Examples of events include, but are not limited to, logging in, logging out, printing a page, installing software, opening an application, connecting to a network, stopping and starting system services, and so forth.
Now, say I wanted to have some script executed (such as starting an application) when someone logs in. That means my script should register to the “login” event, and continuously listen for that event to occur. When the event occurs, my script gets executed. These types of scripts (listening for events of WMI) are called consumers. Consumers use “Event queries” to register and receive notification of events. Event providers use event queries to register (and of course listen for) one or more events. In general, the queries used for trapping event notifications are called “event queries.”
Instead of talking too much, let us do it practically. I would like to develop a simple Windows application, which starts to listen for only a single event. Once it receives the notification of a specified event instance, it just displays the status (or information) of that event and stops listening for that event any more. We start with this simple concept, and later develop complicated applications.
So, let us start by opening the Visual Studio.NET 2003 Enterprise Architect. Create a Windows application with only a single button and a multi-lined textbox. Don’t forget to add the reference to “System.Management” (as explained in part one) and import it into your application. Your design should look something like the following figure (Fig 1):
Copy the following code into the button click event:
Don’t try to understand the above code yet. Just execute it first. Press the button after you have executed your application. It starts listening for the event. To notify the listener, open “services.msc” from Start->Run and change any status of any process (choose a generic process rather than harmful system process like RPC). I generally use “Indexing Service” for demonstration purposes, and I suggest you do the same (unless it is not installed on your computer). When you change the status, you should be able to see the textbox updated accordingly as shown in the following figure (fig 2).
The listener stops listening once it receives the notification of event.
You can obtain the above code from the “form1.vb” file in the zip included for download. The coming sections give you a complete, detailed explanation of the above program.
Let me explain the above program line by line. Consider the first statement:
DimqueryAsNewWqlEventQuery( _
"__InstanceModificationEvent", _
NewTimeSpan(0, 0, 1), _
"TargetInstance isa ""Win32_Service""")
We see from the above statement that we are about to work with a new class “WqlEventQuery” in “System.Management” namespace. This is the class specially used to work with event based queries in WMI.
The “__InstanceModificationEvent” is a special WMI system class used to report an instance of a modification event, which is a type of intrinsic event generated when an instance changes (gets modified) in the namespace. Any event, treated as modification to a state of the event, will be immediately notified to this class. We also have similar event classes like “__InstanceCreationEvent” and “__InstanceDeletionEvent”. But all these get inherited from “__InstanceOperationEvent” class. And even “__InstanceOperationEvent” gets inherited from “__Event”. You can get an entire hierarchy of these classes from WMI SDK documentation.
The value of an instance of “TimeSpan” class represents a period of time. This interval is the maximum amount of time that can pass before notification of an event must be delivered. That means something like “send notification of the creation (or modification) of Win32_Service instances, with a ‘specified timespan’ of polling interval”. “TimeSpan(0,0,1)” specifies a time interval of zero hours, zero minutes and one second. So, it polls every second.
“__InstanceModificationEvent” may be reporting several types of instances and not only “Win32_Service” based events. So, we need to filter the events to only the ones we want (in this case, the events of type “Win32_Service”). Any instance of “__InstanceModificationEvent” has a property named “TargetInstance”. This property is the instance used to fire the event. In this case we are checking whether it is an instance of “Win32_Service” event being fired or not. If you carefully observe, there exists a keyword, “isa” keyword, which is defined as an operator in WQL. It is generally used to compare directly with the instances (rather than to the property values).
The above section defined only a simple query (instead of an Event Query). But we didn’t execute the query yet. And we cannot simply execute the query and leave off. We need to listen (or instead watch) for any events arriving based on that query. The query is not like a Data Query, which fetches existing information. It is like a query which waits (or watches) for certain information to arrive (which is nothing but the event).
So we need to attach the query to a “watcher”. This is where the “ManagementEventWatcher” class comes in. Let us consider the following statement:
DimwatcherAsNewManagementEventWatcher(query)
The above statement creates a “watcher” object based on the event query specified through an instance of ‘WqlEventQuery’. But make sure that it doesn’t start listening (or watching) yet. Let us consider the next statement.
Within the above statement, we have “watcher.WaitForNextEvent”, which actually waits for the event to occur. Once the event notification arrives, the information gets collected through “ManagementBaseObject” reference (which is “ev”). The application hangs (or may be unresponsive) until it receives the event notification. It will not even properly repair itself (unless you implement an asynchronous way of programming style, which is beyond the scope of this series).
In the previous section, I stated that “TargetInstance” is a property of “__InstanceModificationEvent”, which itself is an instance of the event fired. To get the information of the event fired, we need to extract the information from “TargetInstance” (which is actually a property containing object itself, not a value based property). So, we again convert the “TargetInstance” to “ManagementBaseObject” and then finally retrieve “DisplayName” and “State” properties of the generated event. The next statement is:
watcher.Stop()
I hope you can understand the above statement very easily. Every “Event Watcher” must be explicitly stopped (just like a database connection) or else it may eat up system resources.
Summary
I think this is the most typical article in this series. I strongly suggest you investigate thoroughly the above new classes introduced before going to the successive parts of this series. MSDN has plenty of information on all of the above classes, as well as examples.
The above example cannot be used for a production environment, as it still lacks some standards of implementation. It is provided just to help you learn a new concept for handling system events through WMI. Currently, I didn’t provide a very professional example (which requires knowledge of other concepts of .NET as well) by implementing permanent watchers or asynchronous model or Windows services or logging or other items. But will I try my best to provide some of those goodies in my upcoming articles. For any enhancements or suggestions to this part of series, I can be reached at jag_chat@yahoo.com.