Flylib.com

Books Software

 
 
 

Handling Events with System.Management


Handling Events with System.Management

The majority of event monitoring scenarios can easily be covered using the WMI permanent event consumer framework. This approach is clearly superior under many circumstances because it alleviates many concerns typically associated with a custom monitoring tool. It is simple from both the conceptual and the implementation prospective . It is also reliable because instead of relying on an application program, the monitoring task is done by WMI. Finally, it is extremely cost-effective because no, or very little, development effort is required. Although you may argue that a custom event consumer provider may have to be developed to satisfy certain monitoring needs, standard consumer providers, which are distributed as part of the WMI SDK, are usually adequate. Thus, building an event-handling utility is rarely required because there are very few compelling reasons to engage into such activity.

Nevertheless, the System.Management namespace comes well equipped for handling management events. The functionality afforded by System.Management event-handling types, however, is intended to support temporary, rather than permanent event consumers. Although it is theoretically possible to implement an event consumer provider using FCL and the .NET languages, System.Management does not include any facilities that are specifically designed to address consumer provider development. Therefore, the rest of this chapter will concentrate on explaining the mechanics of event handling from the prospective of temporary event consumers.

ManagementEventWatcher Type

The entire event-handling mechanism is packaged as a single System.Management type called ManagementEventWatcher . This type is solely responsible for handling all types of WMI events in both synchronous and asynchronous fashion. Just like most of the FCL types, ManagementEventWatcher is fairly straightforward and selfexplanatory.

Perhaps, the simplest thing that can be achieved with ManagementEventWatcher is handling management events in synchronous mode. For example, the following code snippet initiates the subscription for all process creation events and then polls for notification in a synchronous fashion:

ManagementEventWatcher ew = new ManagementEventWatcher( @" SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Process'"); while( true ) { ManagementBaseObject mo = ew.WaitForNextEvent(); Console.WriteLine("Event arrived: {0}", mo["__CLASS"]); mo = (ManagementBaseObject)mo["TargetInstance"]; Console.WriteLine("Process handle: {0}. Executable

path

: {1}", mo["Handle"], mo["ExecutablePath"]); }

Here, the instance of ManagementEventWatcher is created using a constructor that takes a single query string parameter. The code than enters an endless loop and starts polling for events using the WaitForNextEvent method. This method is built around the IWbemServices::ExecNotificationQuery method, which is what WMI uses to initiate synchronous event subscriptions. If you remember the discussion of synchronous query processing, you may assume that WaitForNextEvent essentially mimics the functionality of IWbemServices::ExecNotificationQuery by returning an instance of ManagementObjectCollection immediately after it is invoked. If this were true, the consumer would iterate through the collection so that each request for the next collection element would block until an event notification arrives. This, however, is not the case. Instead, WaitForNextEvent blocks until an appropriate event is triggered, and then it returns a single instance of the ManagementBaseObject type, which represents the delivered event. Such an approach, while certainly simpler, lacks some flexibility because events are always delivered one by one. The COM API IWbemServices::ExecNotificationQuery method, on the other hand, leaves enough room for delivering events in blocks, which may contribute to some performance gains.

Be careful when you are examining the delivered event. For the code above, the returned ManagementBaseObject embodies an instance of __InstanceCreationEvent . As you may remember, the TargetInstance property of the event object refers to an embedded object that triggers the event—in this case, an instance of the Win32_Process class. This instance can be retrieved by accessing the TargetInstance property through the ManagementBaseObject indexer or its Properties collection and casting the result back to ManagementBaseObject .

If you bother to compile and run the code above, it may produce an output similar to the following, assuming you launch a notepad.exe process:

Event arrived: __InstanceCreationEvent Process handle: 160. Executable path: C:\WINNT\System32\notepad.exe

The preceding code example sets up the event registration for those events that occur on a local computer. It is, however, entirely possible to initiate a subscription to events that takes place on a remote machine. All that you need to do to listen to remote events is set up an instance of ManagementEventWatcher that is bound to a remote computer. This can be achieved by using an alternative version of its constructor, which, in addition to the query strings, takes a scope string that identifies the target machine and namespace:

ManagementEventWatcher ew = new ManagementEventWatcher( @"\\BCK_OFFICE\root\CIMV2", @" SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Process'"); while( true ) { ManagementBaseObject mo = ew.WaitForNextEvent(); Console.WriteLine("Event arrived: {0}", mo["__CLASS"]); mo = (ManagementBaseObject)mo["TargetInstance"]; Console.WriteLine("Process handle: {0}. Executable path: {1}", mo["Handle"], mo["ExecutablePath"]); Console.WriteLine("Originating Machine: {0}", mo["__SERVER"]); }

The code above registers for receiving the events that take place on a remote machine BCK_OFFICE . Note that the origins of an event can be traced by interrogating the __SERVER system property of the received event object.

As I mentioned earlier, there are certain security implications involved when you subscribe for specific categories of management events. For instance, in order to receive Windows log events, SeSecurityPrivilege must be granted and enabled. Granting the privilege is a task that should be carried out using an appropriate user management tool. Enabling the privileges, however, should be done on a per process basis, and therefore, your management code should include enough provisions to get the security issues out of the way.

Assuming that all the right privileges are granted, clearing WMI security is remarkably easy. Thus, the following code snippet successfully sets up a subscription for Windows log events, assuming that a user is granted SeSecurityPrivilege :

ManagementEventWatcher ew = new ManagementEventWatcher( @" SELECT * FROM __InstanceCreationEvent WHERE TargetInstance ISA 'Win32_NTLogEvent'"); ew.Scope.Options.Impersonation = ImpersonationLevel.Impersonate; ew.Scope.Options.EnablePrivileges = true; while(true) { ManagementBaseObject mo = ew.WaitForNextEvent(); Console.WriteLine("Event arrived: {0}", mo["__CLASS"]); }

The only difference here is the two lines of code that follow the construction of the ManagementEventWatcher object. It turns out that all security- related settings are packaged into an instance of the ConnectionOptions class. The ConnectionOptions object, which controls the security context of the WMI connection, is contained in the instance of the ManagementScope class, which is associated with ManagementEventWatcher object. The code above simply sets two of the properties of ConnectionOptions object— Impersonation and EnablePrivileges —which control the COM impersonation level and security privileges respectively. Once these two properties are set correctly, the code is granted the required access level. Although the detailed overview of WMI security will not be presented until Chapter 8, the technique just demonstrated, should allow you to get around most of the security-related issues that you may encounter.

Although synchronous mode is definitely the simplest event-processing option available to the developers of management applications, it is not very flexible and not all that efficient. Its main drawback is that it needs to continuously poll WMI using the WaitForNextEvent method. A much better approach would be to register for events once and then handle the notifications as they arrive . This is where the asynchronous mode proves to be very helpful, although setting up an asynchronous event subscription may require just a bit more coding.

The following code snippet duplicates the functionality of the previous example, but this time in asynchronous mode:

{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}

class Monitor { bool stopped = true; public bool IsStopped { get { return stopped; } set { stopped = value; } } public void OnEventArrived(object sender, EventArrivedEventArgs e) { ManagementBaseObject mo = e.NewEvent; Console.WriteLine("Event arrived: {0}", mo["__CLASS"]); mo = (ManagementBaseObject)mo["TargetInstance"]; Console.WriteLine("Process handle: {0}. Executable path: {1}", mo["Handle"], mo["ExecutablePath"]); } public void OnStopped(object sender, StoppedEventArgs e) { stopped = true; } public static void Main(string[] args) { Monitor mon = new Monitor(); ManagementEventWatcher ew = new ManagementEventWatcher( @" SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Process'"); ew.EventArrived += new EventArrivedEventHandler(mon.OnEventArrived); ew.

Stopped

+= new StoppedEventHandler(mon.OnStopped); ew.Start(); mon.IsStopped = false; while( true ) { // do something useful.. System.Threading.Thread.Sleep(10000); } } }

This code is fairly straightforward and should remind you of the techniques used to perform asynchronous operations with the ManagementOperationObserver type. Essentially, the ManagementEventWatcher type exposes two events: EventArrived is raised whenever a management event notification is received from WMI, and Stopped is triggered when a given instance of ManagementEventWatcher stops listening for management events. Thus, setting up an asynchronous event subscription comes down to hooking up an event handler for at least the EventArrived event. The EventArrivedEventArgs object, passed as an argument to the event handler method, has one property, NewEvent , which points to an instance of the ManagementBaseObject class that represents the management event.

An asynchronous event subscription is initiated by calling the Start method of the ManagementEventWatcher type. This is the method that internally invokes IWbemServices::ExecNotificationQueryAsync , which registers a consumer for asynchronous event delivery. Once started, ManagementEventWatcher continues listening for management events until stopped, either explicitly or implicitly. To explicitly terminate an event registration, consumers may call the Stop method, which internally invokes the IWbemServices::CancelAsyncCall method. Implicit termination may occur for a variety of reasons. Perhaps, the most obvious one is that the ManagementEventWatcher variable goes out of scope. This may happen as a result of a premature program termination or a function return, or simply because a programmer forgot to explicitly call Stop . Another reason is any kind of error condition detected by WMI, such as an invalid event query or some internal error. In order to cleanly shut down any outstanding event subscriptions, ManagementEventWatcher is equipped with a destructor method, Finalize , which is invoked automatically by .NET runtime. Although destructors are not very popular when it comes to garbage-collecting architectures, in this particular case, having one is a necessity. After all, leaving dangling event registrations around is not a very good idea.

For obvious reasons Finalize invokes the same old Stop method, which in turn , fires the Stopped event. Thus, it is pretty much a guarantee that a Stopped event will be raised regardless of whether the subscription is terminated explicitly or implicitly. The event carries enough useful information to be able to diagnose a problem, if there is any. The StoppedEventArgs object, passed as a parameter to the handler for the Stopped event, has a single property, Status , of type ManagementStatus . This is an enumeration that contains all currently defined WMI error codes. To illustrate how it works, I will change the event handler for the Stopped event so that it will print out the value of Status property:

public void OnStopped(object sender, StoppedEventArgs e) { Console.WriteLine("Stopped with status {0}", e.Status.ToString()); stopped = true; }

Assuming that the ManagementEventWatcher object is created with an event query that references a nonexisting event class in its FROM clause, the code will produce the following output:

Stopped with status NotFound

The string " NotFound " is a textual description associated with the ManagementStatus.NotFound enumeration member, which in turn, corresponds to the WBEM_E_NOT_FOUND ( 0x80041002 ) WMI error code. In this case a ManagementException will be thrown as soon as the Start method is invoked, but the Stopped event is still triggered.

Just as it is the case with synchronous event processing, asynchronous events are always delivered one-by-one. This is a bit less efficient than the native IWbemServices::ExecNotificationQueryAsync model, which allows several events to be received at once. Curiously, there is a separate type, called EventWatcherOptions , which, like all other options types, is designed to control various aspects of event processing. Besides the Context and Timeout properties inherited from its superclass ManagementOptions , EventWatcherOptions has the BlockSize property, which seems to be designed for batching the events together. However, this property is not used by any code in the System.Management namespace and it appears to have no effect on event handling. Moreover, the design of the ManagementEventWatcher type does not really support receiving multiple events at once, thus making the BlockSize option fairly useless.

EventQuery Type

An event query does not have to be represented by a plain string. There is a special type, called EventQuery , that is dedicated to handling event queries. However, unlike the other query classes described in Chapter 3, EventQuery is neither sophisticated nor very useful. In fact, it is just a container for the query string and, as such, it does not provide for query building or parsing.

In addition to a default parameterless constructor, the EventQuery type has two parameterized constructor methods : one that takes a query string, and the other that takes a language identifier and a query string. Thus, a simple query object can be created as follows :

EventQuery q = new EventQuery( @" SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Process'");

While the first constructor automatically assumes that the language of the query is WQL, the second one allows the language to be set explicitly:

EventQuery q = new EventQuery("WQL", @" SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Process'");

The problem is that WQL is the only language supported at this time. So, if you attempt to create a query with a language string of, say, " XYZ ", and then you feed it into ManagementEventWatcher , an exception will be thrown.

Using the EventQuery type with ManagementEventWatcher is also very straightforward. The latter offers a constructor method that takes an object of EventQuery type, rather than a query string:

EventQuery q = new EventQuery( @" SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Process'"); ManagementEventWatcher ew = new ManagementEventWatcher(q);

A query can also be explicitly associated with an instance of ManagementEventWatcher by setting its Query property. Thus, the following code is equivalent to the previous example:

EventQuery q = new EventQuery( @" SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Process'"); ManagementEventWatcher ew = new ManagementEventWatcher(); ew.Query = q;

Besides the properties inherited from its base type ManagementQuery such as QueryString and QueryLanguage , the EventQuery type does not offer any additional functionality, and therefore, it is not very useful.