Programming the Microsoft Windows Driver Model
WDM Drivers and WMI
The kernel-mode support for WMI is based primarily on IRPs with the major code IRP_MJ_SYSTEM_CONTROL. You must register your desire to receive these IRPs by making the following call:
IoWMIRegistrationControl(fdo, WMI_ACTION_REGISTER);
The appropriate time to make the registration call is in the AddDevice routine at a point when it would be safe for the system to send the driver a system control IRP. In due course, the system will send you an IRP_MJ_SYSTEM_CONTROL request to obtain detailed registration information about your device. You ll balance the registration call with another call at RemoveDevice time:
IoWMIRegistrationControl(fdo, WMI_ACTION_DEREGISTER);
If any WMI requests are outstanding at the time you make the deregistration call, IoWMIRegistrationControl waits until they complete. It s therefore necessary to make sure that your driver is still capable of responding to IRPs when you deregister. You can have new IRPs fail with STATUS_DELETE_PENDING, but you have to respond.
Before explaining how to service the registration request, I ll describe how you handle system control IRPs in general. An IRP_MJ_SYSTEM_CONTROL request can have any of the minor function codes listed in Table 10-2.
Minor Function Code | Description |
IRP_MN_QUERY_ALL_DATA | Gets all instances of every item in a data block |
IRP_MN_QUERY_SINGLE_INSTANCE | Gets every item in a single instance of a data block |
IRP_MN_CHANGE_SINGLE_INSTANCE | Replaces every item in a single instance of a data block |
IRP_MN_CHANGE_SINGLE_ITEM | Changes one item in a data block |
IRP_MN_ENABLE_EVENTS | Enables event generation |
IRP_MN_DISABLE_EVENTS | Disables event generation |
IRP_MN_ENABLE_COLLECTION | Starts collecting expensive statistics |
IRP_MN_DISABLE_COLLECTION | Stops collecting expensive statistics |
IRP_MN_REGINFO_EX | Gets detailed registration information |
IRP_MN_EXECUTE_METHOD | Executes a method function |
The Parameters union in the stack location includes a WMI substructure with parameters for the system control request:
struct { ULONG_PTR ProviderId; PVOID DataPath; ULONG BufferSize; PVOID Buffer; } WMI;
ProviderId is a pointer to the device object to which the request is directed. Buffer is the address of an input/output area where the first several bytes are mapped by the WNODE_HEADER structure. BufferSize gives the size of the buffer area. Your dispatch function will extract some information from this buffer and will also return results in the same memory area. For all the minor functions except IRP_MN_REGINFO, DataPath is the address of a 128-bit GUID that identifies a class of data block. The DataPath field is either WMIREGISTER or WMIUPDATE (0 or 1, respectively) for an IRP_MN_REGINFO request, depending on whether you re being told to provide initial registration information or just to update the information you supplied earlier.
When you design your driver, you must choose between two ways of handling system control IRPs. One method is relying on the facilities of the WMILIB support driver. WMILIB is really a kernel-mode DLL that exports services you can call from your driver to handle some of the annoying mechanics of IRP processing. The other method is simply handling the IRPs yourself. If you use WMILIB, you ll end up writing less code but you won t be able to use every last feature of WMI to its fullest you ll be limited to the subset supported by WMILIB. Furthermore, your driver won t run under the original retail release of Microsoft Windows 98 because WMILIB wasn t available then. Before you let the lack of WMILIB in original Windows 98 ruin your day, consult the compatibility notes at the end of this chapter.
WMILIB suffices for most drivers, so I m going to limit my discussion to using WMILIB. The DDK documentation describes how to handle system control IRPs yourself if you absolutely have to.
Delegating IRPs to WMILIB
In your dispatch routine for system control IRPs, you delegate most of the work to WMILIB with code like the following:
WMIGUIDREGINFO guidlist[] = { {&Wmi42_GUID, 1, 0}, };
-
The WMILIB_CONTEXT structure declared at file scope describes the class GUIDs your driver supports and lists several callback functions that WMILIB uses to handle WMI requests in the appropriate device-dependent and driver-dependent way. It s OK to use a static context structure if the information in it doesn t change from one IRP to the next.
-
This statement calls WMILIB to handle the IRP. We pass the address of our WMILIB_CONTEXT structure. WmiSystemControl returns two pieces of information: an NTSTATUS code and a SYSCTL_IRP_DIS POSITION value.
-
Depending on the disposition code, we might have additional work to perform on this IRP. If the code is IrpProcessed, the IRP has already been completed and we need do nothing more with it. This case would be the normal one for minor functions other than IRP_MN_REGINFO.
-
If the disposition code is IrpNotCompleted, completing the IRP is our responsibility. This case would be the normal one for IRP_MN_REG INFO. WMILIB has already filled in the IoStatus block of the IRP, so we need only call IoCompleteRequest.
-
The default and IrpNotWmi cases shouldn t arise in Windows XP. We d get to the default label if we weren t handling all possible disposition codes; we d get to the IrpNotWmi case label if we sent an IRP to WMILIB that didn t have one of the minor function codes that specifies WMI functionality.
-
The IrpForward case occurs for system control IRPs that are intended for some other driver. Recall that the ProviderId parameter indicates the driver that is supposed to handle this IRP. WmiSystemControl compares that value with the device object pointer we supply as the second function argument. If they re not the same, it returns IrpForward so that we ll send the IRP down the stack to the next driver.
The way a WMI consumer matches up to your driver in your driver s role as a WMI provider is based on the GUID or GUIDs you supply in the context structure. When a consumer wants to retrieve data, it (indirectly) accesses the data dictionary in the WMI repository to translate a symbolic object name into a GUID. The GUID is part of the MOF syntax I showed you earlier. You specify the same GUID in your context structure, and WMILIB takes care of the matching.
WMILIB will call routines in your driver to perform device-dependent or driver-dependent processing. Most of the time, the callback routines will perform the requested operation synchronously. However, except in the case of IRP_MN_REGINFO, you can defer processing by returning STATUS_PENDING and completing the request later.
The QueryRegInfo Callback
The first system control IRP we ll receive after making our registration call has the minor function code IRP_MN_REGINFO. When we pass this IRP to WmiSystemControl, it turns around and calls the QueryRegInfo function it finds the function s address in our WMILIB_CONTEXT structure. Here s how WMI42.SYS handles this callback:
NTSTATUS QueryRegInfo(PDEVICE_OBJECT fdo, PULONG flags, PUNICODE_STRING instname, PUNICODE_STRING* regpath, PUNICODE_STRING resname, PDEVICE_OBJECT* pdo) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; *flags = WMIREG_FLAG_INSTANCE_PDO; *regpath = &servkey; RtlInitUnicodeString(resname, L"MofResource"); *pdo = pdx->Pdo; return STATUS_SUCCESS; }
We set regpath to the address of a UNICODE_STRING structure that contains the name of the service registry key describing our driver. This key is the one below \System\CurrentControlSet\Services. Our DriverEntry routine received the name of this key as an argument and saved it in the global variable servkey. We set resname to the name we chose to give our schema in our resource script. Here s the resource file for WMI42.SYS so that you can see where this name comes from:
#include <windows.h> LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL MofResource MOFDATA wmi42.bmf
WMI42.BMF is where our build script puts the compiled MOF file. You can name this resource anything you want to, but MofResource is traditional (in a tradition stretching back to, uh, last Tuesday). All that matters about the name is that you specify the same name when you service the QueryRegInfo call.
How we set the remaining values depends on how our driver wants to handle instance naming. I ll come back to the subject of instance naming later in this chapter (in Instance Naming ). The simplest choice, and the one Microsoft strongly recommends, is the one I adopted in WMI42.SYS: have the system automatically generate names that are static based on the name the bus driver gave to the physical device object (PDO). When we make this choice of naming method, we do the following tasks in QueryRegInfo:
-
Set the WMIREG_FLAG_INSTANCE_PDO flag in the flags value we re returning to WMILIB. Setting the flag here tells WMILIB that at least one of our objects uses PDO naming.
-
Set the pdo value we re returning to WMILIB. In my sample drivers, my device extension has a field named Pdo that I set at AddDevice time to make it available at times like this.
Apart from making your life easier, basing your instance names on the PDO allows viewer applications to automatically determine your device s friendly name and other properties without you doing anything more in your driver.
When you return a successful status from QueryRegInfo, WMILIB goes on to create a complicated structure called a WMIREGINFO that includes your GUID list, your registry key, your resource name, and information about your instance names. It returns to your dispatch function, which then completes the IRP and returns. Figure 10-3 diagrams this process.
Figure 10-3. Control flow for IRP_MN_REGINFO.
The QueryDataBlock Callback
The information you provide in your answer to the initial registration query allows the system to route relevant data operations to you. User-mode code can use various COM interfaces to get and set data values at several levels of aggregation. Table 10 3 summarizes the four possibilities.
IRP Minor Function | WMILIB Callback | Description |
IRP_MN_QUERY_ALL_DATA | QueryDataBlock | Gets all items of all instances |
IRP_MN_QUERY_SINGLE_INSTANCE | QueryDataBlock | Gets all items of one instance |
IRP_MN_CHANGE_SINGLE_INSTANCE | SetDataBlock | Sets all items of one instance |
IRP_MN_CHANGE_SINGLE_ITEM | SetDataItem | Sets one item in one instance |
When someone wants to learn the value or values of the data you re keeping, he or she sends you a system control IRP with one of the minor function codes IRP_MN_QUERY_ALL_DATA or IRP_MN_QUERY_SINGLE_INSTANCE. If you re using WMILIB, you ll delegate the IRP to WmiSystemControl, which will then call your QueryDataBlock callback routine. You ll provide the requested data, call another WMILIB routine named WmiCompleteRequest to complete the IRP, and then return to WMILIB to unwind the process. In this situation, WmiSystemControl will return the IrpProcessed disposition code because you ve already completed the IRP. Refer to Figure 10-4 for a diagram of the overall control flow.
Figure 10-4. Control flow for data queries.
Your QueryDataBlock callback can end up being a relatively complex function if your driver is maintaining multiple instances of a data block that varies in size from one instance to the next. I ll discuss the complications later in Dealing with Multiple Instances. The WMI42 sample shows how to handle a simpler case in which your driver maintains only one instance of the WMI class:
NTSTATUS QueryDataBlock(PDEVICE_OBJECT fdo, PIRP Irp, ULONG guidindex, ULONG instindex, ULONG instcount, PULONG instlength, ULONG bufsize, PUCHAR buffer) {
-
The WMI subsystem should already have verified that we re being queried for an instance of a class that we actually support. Thus, guidindex should ordinarily be within the bounds of the GUID list, and inst index and instcount ought not to exceed the number of instances we ve said we own. If, however, we ve just changed our registration information, we might be servicing a request that was already in flight, and these tests would be needed to avoid mistakes.
-
We re obliged to make this check to verify that the buffer area is large enough to accommodate the data and data length values we re going to put there. The first part of the test is there an instlength array? is boilerplate. The second part of the test is the buffer big enough for a Wmi42 structure? is where we verify that all of our data values will fit.
-
The buffer parameter points to a memory area where we can put our data. The instlength parameter points to an array where we re supposed to place the length of each data instance we re returning. Here we install the single data value our schema calls for the value of the TheAnswer property and its length. Figuring out what TheAnswer actually is numerically is left as an exercise for the reader.
-
The WMILIB specification requires us to complete the IRP by calling the WmiCompleteRequest helper routine. The fourth argument indicates how much of the buffer area we used for data values. By now, the other arguments should be self-explanatory.
The SetDataBlock Callback
The system might ask you to change an entire instance of one of your classes by sending you an IRP_MN_CHANGE_SINGLE_INSTANCE request. WmiSystemControl processes this IRP by calling your SetDataBlock callback routine. A simple version of this routine might look like this:
NTSTATUS SetDataBlock(PDEVICE_OBJECT fdo, PIRP Irp, ULONG guidindex, ULONG instindex, ULONG bufsize, PUCHAR buffer) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;
-
These are the same sanity checks I showed earlier for the QueryData Block callback function.
-
The system should already know based on the MOF declaration how big an instance of each class is and should give us a buffer that s exactly the right size. If it doesn t, we ll end up causing this IRP to fail. Otherwise, we ll copy a new value for the data block into the place where we keep our copy of that value.
-
We re responsible for completing the IRP by calling WmiComplete Request.
The SetDataItem Callback
Sometimes consumers want to change just one field in one of the WMI objects we support. Each field has an identifying number that appears in the WmiDataId property of the field s MOF declaration. (The Active and InstanceName properties aren t changeable and don t have identifiers. Furthermore, they re implemented by the system and don t even appear in the data blocks we work with.) To change the value of one field, the consumer references the field s ID. We then receive an IRP_MN_CHANGE_SINGLE_ITEM request, which WmiSystemControl processes by calling our SetDataItem callback routine:
NTSTATUS SetDataItem(PDEVICE_OBJECT fdo, PIRP Irp, ULONG guidindex, ULONG instindex, ULONG id, ULONG bufsize, PUCHAR buffer) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; NTSTATUS status; ULONG info; if (quidindex > arraysize(guidlist)) return WmiCompleteRequest(fdo, Irp, STATUS_WMI_GUID_NOT_FOUND, 0, IO_NO_INCREMENT); if (instindex != 0 instcount != 1) return WmiCompleteRequest(fdo, Irp, STATUS_WMI_INSTANCE_NOT_FOUND, 0, IO_NO_INCREMENT); if (id != Wmi42_TheAnswer_ID) return WmiCompleteRequest(fdo, Irp, STATUS_WMI_ITEMID_NOT_FOUND, 0, IO_NO_INCREMENT); if (bufsize == Wmi42_SIZE) { pdx->TheAnswer = (PWmi42) buffer)->TheAnswer; status = STATUS_SUCCESS; info = Wmi42_SIZE; } else status = STATUS_INFO_LENGTH_MISMATCH, info = 0; return WmiCompleteRequest(fdo, Irp, status, info, IO_NO_INCREMENT); }
In this example, the only difference between the SetDataItem and SetData Block callbacks is the additional test of the field identifier, shown in boldface.
You can use the -c option when you run WMIMOFCK. This option generates a C-language source file with a number of TODO items that you can complete in order to end up with pretty much all the code you need. I don t use this feature myself, because WDMWIZ generates skeleton code that fits better into my own framework and actually requires fewer changes. I do, however, use the -h option of WMIMOFCK as described earlier in this chapter, because there s no good alternative for getting the correct structure mapping.
Advanced Features
The preceding discussion covers much of what you need to know to provide meaningful performance information for metering applications. Use your imagination here: instead of providing just a single statistic (TheAnswer), you can accumulate and return any number of performance measures that are relevant to your specific device. You can support some additional WMI features for more specialized purposes. I ll discuss these features now.
Dealing with Multiple Instances
WMI allows you to create multiple instances of a particular class data block for a single device object. You might want to provide multiple instances if your device is a controller or some other device in to which other devices plug; each instance might represent data about one of the child devices. Mechanically, you specify the number of instances of a class in the WMIGUIDREGINFO structure for the GUID associated with the class. If WMI42 had three different instances of its standard data block, for example, it would have used the following GUID list in its WMILIB_CONTEXT structure:
WMIGUIDREGINFO guidlist[] = { {&Wmi42_GUID, 3, 0}, };
The only difference between this GUID list and the one I showed you earlier is that the instance count here is 3 instead of 1. This list declares that there will be three instances of the WMI42 data block, each with its own value for the three properties (that is, InstanceName, Active, and TheAnswer) that belong in that block.
If the number of instances changes over time, you can call IoWmiRegistrationControl with the action code WMIREG_ACTION_UPDATE_GUID to cause the system to send you another registration request, which you ll process using an updated copy of your WMILIB_CONTEXT structure. If you re going to be changing your registration information, you should probably allocate the WMILIB_CONTEXT structure and GUID list from the free pool rather than use static variables, by the way.
If user-mode code were to enumerate all instances of GUID_WMI 42_SCHEMA, it would find three instances. This result might present a confusing picture to user-mode code, though. Depending on which WDM platform you re running on, it may be difficult to tell a priori that the three instances disclosed by the enumeration belong to a single device, as opposed to a situation in which three WMI42 devices each expose a single instance of the same class. To allow WMI clients to sort out the difference between the two situations, your schema should include a property (a device name or the like) that can function as a key.
Once you allow for the possibility of multiple instances, several of your WMILIB callbacks will require changes from the simple examples I showed you earlier. In particular:
-
QueryDataBlock should be able to return the data block for a single instance or for any number of instances beginning at a specific index.
-
SetDataBlock should interpret its instance number argument to decide which instance to change.
-
SetDataItem should likewise interpret its instance number argument to locate the instance within which the affected data item will be found.
Figure 10-5 illustrates how your QueryDataBlock function uses the output buffer when it s asked to provide more than one instance of a data block. Imagine that you were asked to provide data for two instances beginning at instance number 2. You ll copy the data values, which I ve shown as being of different sizes, into the data buffer. You start each instance on an 8-byte boundary. You indicate the total number of bytes you consume when you complete the query, and you indicate the lengths of each individual instance by filling in the inst length array, as shown in the figure.
Figure 10-5. Getting multiple data block instances.
Instance Naming
Each instance of a WMI class has a unique name. Consumers who know the name of an instance can perform queries and invoke method routines. Consumers who don t know the name or names of the instance or instances you provide can learn them by enumerating the class. In any case, you re responsible for generating the names that consumers use or discover.
I showed you the simplest way from the driver s perspective, that is of naming instances of a custom data block, which is to request that WMI automatically generate a static, unique name based on the name of the PDO for your device. If your PDO has the name Root\SAMPLE\0000, for example, a PDO-based name for a single instance of a given data block will be Root\SAMPLE\0000_0.
Basing instance names on the PDO name is obviously convenient because all you need to do in the driver is set the WMIREG_FLAG_INSTANCE_PDO flag in the flags variable that WMILIB passes to your QueryRegInfo callback routine. The author of a consumer application can t know what this name will be, however, because the name will vary depending on how your device was installed. To make the instance names slightly more predictable, you can elect to use a constant base name for object instances instead. You indicate this choice by responding in the following way to the registration query:
NTSTATUS QueryRegInfo(PDEVICE_OBJECT fdo, PULONG flags, PUNICODE_STRING instname, PUNICODE_STRING* regpath, PUNICODE_STRING resname, PDEVICE_OBJECT* pdo) { *flags = WMIREG_FLAG_INSTANCE_BASENAME; *regpath = &servkey; RtlInitUnicodeString(resname, L"MofResource"); static WCHAR basename[] = L"WMIEXTRA"; instname->Buffer = (PWCHAR) ExAllocatePool(PagedPool, sizeof(basename)); if (!instname->Buffer) return STATUS_INSUFFICIENT_RESOURCES; instname->MaximumLength = sizeof(basename); instname->Length = sizeof(basename) - 2; RtlCopyMemory(instname->Buffer, basename, sizeof(basename)); }
The parts of this function that differ from the previous example of Query RegInfo are in boldface. In the WMIEXTRA sample, only one instance of each data block exists, and each receives the instance name WMIEXTRA with no additional decoration.
If you elect to use a base name, try to avoid generic names such as Toys because of the confusion that can ensue. The purpose of this feature is to let you use specific names such as TailspinToys.
In some circumstances, static instance names won t suit your needs. If you maintain a population of data blocks that changes frequently, using static names means that you have to request a registration update each time the population changes. The update is relatively expensive, and you should avoid requesting one often. You can assign dynamic instance names to the instances of your data blocks instead of static names. The instance names then become part of the queries and replies that you deal with in your driver. Unfortunately, WMILIB doesn t support the use of dynamic instance names. To use this feature, therefore, you ll have to fully implement support for the IRP_MJ_SYSTEM_CONTROL requests that WMILIB would otherwise interpret for you. Describing how to handle these IRPs yourself is beyond the scope of this book, but the DDK documentation contains detailed information about how to go about it.
Dealing with Multiple Classes
WMI42 deals with only one class of data block. If you want to support more than one class, you need to have a bigger array of GUID information structures, as WMIEXTRA does:
WMIGUIDREGINFO guidlist[] = { {&wmiextra_event_GUID, 1, WMIREG_FLAG_EVENT_ONLY_GUID}, {&wmiextra_expensive_GUID, 1, WMIREG_FLAG_EXPENSIVE }, {&wmiextra_method_GUID, 1, 0}, };
Before calling one of your callback routines, WMILIB looks up the GUID accompanying the IRP in your list. If the GUID isn t in the list, WMILIB causes the IRP to fail. If it s in the list, WMILIB calls your callback routine with the guidindex parameter set equal to the index of the GUID in your list. By inspecting this parameter, you can tell which data block you re being asked to work with.
You can use the special flag WMIREG_FLAG_REMOVE_GUID in a GUID information structure. The purpose of this flag is to remove a particular GUID from the list of supported GUIDs during a registration update. Using this flag also prevents WMILIB from calling you to perform an operation on a GUID that you re trying to remove.
Expensive Statistics
It can sometimes be burdensome to collect all of the statistics that are potentially useful to an end user or administrator. For example, it would be possible for a disk driver (or, more likely, a filter driver sitting in the same stack as a disk driver) to collect histogram data showing how often I/O requests reference a particular sector of the disk. This data would be useful to a disk-defragmenting program because it would allow the most frequently accessed sectors to be placed in the middle of a disk for optimal seek time. You wouldn t want to routinely collect this data, though, because of the amount of memory needed for the collection. That memory would have to be nonpaged too because of the possibility that a particular I/O request would be for page swapping.
WMI allows you to declare a particular data block as being expensive so that you don t need to collect it except on demand, as shown in this excerpt from the WMIEXTRA sample program:
WMIGUIDREGINFO guidlist[] = {
The WMIREG_FLAG_EXPENSIVE flag indicates that the data block identified by wmiextra_expensive_GUID has this expensive characteristic.
When an application expresses interest in retrieving values from an expensive data block, WMI sends you a system control IRP with the minor function code IRP_MN_ENABLE_COLLECTION. When no applications are interested in an expensive data block anymore, WMI sends you another IRP with the minor function code IRP_MN_DISABLE_COLLECTION. If you delegate these IRPs to WMILIB, it will turn around and call your FunctionControl callback routine to either enable or disable collection of the values in the data block:
NTSTATUS FunctionControl(PDEVICE_OBJECT fdo, PIRP Irp, ULONG guidindex, WMIENABLEDISABLECONTROL fcn, BOOLEAN enable) {
In these arguments, guidindex is the index of the GUID for the expensive data block in your list of GUIDs, fcn will equal the enumeration value WmiData BlockControl to indicate that collection of an expensive statistic is being either enabled or disabled, and enable will be TRUE or FALSE to indicate whether you should or should not collect the statistic, respectively. As shown in this fragment, you call WmiCompleteRequest prior to returning from this function.
An application expresses interest in a data block, by the way, by retrieving an IWbemClassObject interface pointer bound to a particular instance of your data block s WMI class. Notwithstanding the fact that an application has to discover an instance of the class, no instance index appears in the call to your FunctionControl callback. The instruction to collect or not collect the expensive statistic therefore applies to all instances of your class.
WMI Events
WMI provides a way for providers to notify consumers of interesting or alarming events. A device driver might use this facility to alert a user to some facet of device operation that requires user intervention. For example, a disk driver might notice that an unusually large number of bad sectors have accumulated on a disk. Logging such an event as described in Chapter 14 is one way to inform the human world of this fact, but an administrator has to actively look at the event log to see the entry. If someone were to write an event monitoring applet, however, and if you were to fire a WMI event when you noticed the degradation, the event could be brought immediately to the user s attention.
NOTE
The network connection tray icon responds to a kernel-mode driver (namely NDIS.SYS) signalling a WMI event.
WMI events are just regular WMI classes used in a special way. In MOF syntax, you must derive the data block from the abstract WMIEvent class, as illustrated in this excerpt from WMIEXTRA s MOF file:
[Dynamic, Provider("WMIProv"), WMI, Description("Event Info from WMIExtra"), guid("c4b678f6-b6e9-11d2-bb87-00c04fa330a6"), locale("MS\\0x409")] class wmiextra_event : WMIEvent { [key, read] string InstanceName; [read] boolean Active; [WmiDataId(1), read] uint32 EventInfo; };
Although events can be normal data blocks, you might not want to allow applications to read and write them separately. If not, use the EVENT_ONLY flag in your declaration of the GUID:
WMIGUIDREGINFO guidlist[] = {
When an application expresses interest in knowing about a particular event, WMI sends your driver a system control IRP with the minor function code IRP_MN_ENABLE_EVENTS. When no application is interested in an event anymore, WMI sends you another IRP with the minor function code IRP_MN_DISABLE_EVENTS. If you delegate these IRPs to WMILIB, you ll receive a call in your FunctionControl callback to specify the GUID index in your list of GUIDs, the fcn code WmiEventControl, and a Boolean enable flag.
To fire an event, construct an instance of the event class in nonpaged memory and call WmiFireEvent. For example:
Pwmiextra_event junk = (Pwmiextra_event) ExAllocatePool(NonPagedPool, wmiextra_event_SIZE); junk->EventInfo = 42; WmiFireEvent(fdo, (LPGUID) &wmiextra_event_GUID, 0, sizeof(wmiextra_event), junk);
The WMI subsystem will release the memory that s occupied by the event object in due course.
WMI Method Routines
In addition to defining mechanisms for transferring data and signalling events, WMI prescribes a way for consumers to invoke method routines implemented by providers. WMIEXTRA defines the following class, which includes a method routine:
[Dynamic, Provider("WMIProv"), WMI, Description("WMIExtra class with method"), guid("cd7ec27d-b6e9-11d2-bb87-00c04fa330a6"), locale("MS\\0x409")] class wmiextra_method { [key, read] string InstanceName; [read] boolean Active; [Implemented, WmiMethodId(1)] void AnswerMethod([in,out] uint32 TheAnswer); };
This declaration indicates that AnswerMethod accepts an input/output argument named TheAnswer (a 32-bit unsigned integer). Note that all method functions exposed by WDM drivers must be void functions because there s no way to indicate a return value. You can still have output arguments or input/output arguments.
When you delegate system control IRPs to WMILIB, a method routine call manifests itself in a call to your ExecuteMethod callback routine:
NTSTATUS ExecuteMethod(PDEVICE_OBJECT fdo, PIRP Irp, ULONG guidindex, ULONG instindex, ULONG id, ULONG cbInbuf, ULONG cbOutbuf, PUCHAR buffer) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension; NTSTATUS status = STATUS_SUCCESS; ULONG bufused = 0; if (guidindex != INDEX_WMIEXTRA_METHOD) return WmiCompleteRequest(fdo, Irp, STATUS_WMI_GUID_NOT_FOUND, 0, IO_NO_INCREMENT); if (instindex != 0) return WmiCompleteRequest(fdo, Irp, STATUS_WMI_INSTANCE_NOT_FOUND, 0, IO_NO_INCREMENT); if (id != AnswerMethod) return WmiCompleteRequest(fdo, Irp, STATUS_WMI_ITEMID_NOT_FOUND, 0, IO_NO_INCREMENT); if (cbInbuf < AnswerMethod_IN_SIZE) status = STATUS_INVALID_PARAMETER; else if (cbOutbuf < AnswerMethod_OUT_SIZE) status = STATUS_BUFFER_TOO_SMALL; else { PAnswerMethod_IN in = (PAnswerMethod_IN) buffer; PAnswerMethod_OUT out = (PAnswerMethod_OUT) buffer; out->TheAnswer = in->TheAnswer + 1; bufused = AnswerMethod_OUT_SIZE; } return WmiCompleteRequest(fdo, Irp, status, bufused, IO_NO_INCREMENT); }
WMI method calls use an input class to contain the input arguments and a potentially different output class to contain the returned values. The buffer area contains an image of the input class, whose length is cbInbuf. Your job is to perform the method and overstore the buffer area with an image of the output class. You complete the request with the byte size (bufused) of the output class.
This particular method routine simply adds 1 to its input argument.
Simply enumerating an instance of a class such as wmiextra_method triggers a request for the data block. You must cause the data query to succeed even if the class that contains the method routine has no data members. In such a case, you can just complete the query with a 0 data length.
Standard Data Blocks
Microsoft has defined some standardized data blocks for various types of devices. If your device belongs to a class for which standardized data blocks are defined, you should support those blocks in your driver. Consult WMICORE.MOF in the DDK to see the class definitions, and see Table 10-4.
Device Type | Standard Class | Description |
Keyboard | MSKeyboard_PortInformation | Configuration and performance information |
| MSKeyboard_ExtendedID | Extended type and subtype identifiers |
| MSKeyboard_ClassInformation | Device identification number |
Mouse | MSMouse_PortInformation | Configuration and performance information |
| MSMouse_ClassInformation | Device identification number |
Disk | MSDiskDriver_Geometry | Format information |
| MSDiskDriver_Performance | Performance information and internal device name |
| MSDiskDriver_Performance Data | Performance information alone |
Storage | MSStorageDriver_FailurePredict Status | Determines whether drive is predicting a failure |
| MSStorageDriver_FailurePredict Data | Failure prediction data |
| MSStorageDriver_FailurePredict Event | Event fired when failure is predicted |
| MSStorageDriver_FailurePredict Function | Methods related to failure prediction |
| MSStorageDriver_ATAPISmart Data | ATAPI failure prediction data |
| MSStorageDriver_FailurePredict Thresholds | Vendor-specific information |
| MSStorageDriver_ScsiInfo Exceptions | Flags and options relative to reporting informational exceptions |
Serial | MSSerial_PortName | Name of port |
| MSSerial_CommInfo | Communication parameters |
| MSSerial_Hardware Configuration | I/O resource information |
| MSSerial_Performance Information | Performance information |
| MSSerial_CommProperties | Communication parameters |
Parallel | MSParallel_AllocFreeCounts | Counts of allocation and free operations |
| MSParallel_DeviceBytes Transferred | Transfer counts |
IDE | MSIde_PortDeviceInfo | Pseudo-SCSI identification for an IDE port |
Redbook | MSRedbook_DriverInformation | Configuration information about a device used for Redbook audio |
| MSRedbook_Performance | Performance info about Redbook audio driver |
Tape | MSTapeDriveParam | Feature information about tape drive |
| MSTapeMediaCapacity | Information about current media |
| MSTapeSymbolicName | Symbolic name (e.g., Tape0) of drive |
| MSTapeDriveProblemEvent | Event used to signal a problem |
| MSTapeProblemIoError | Statistics about I/O errors |
| MSTapeProblemDeviceError | Summary of drive problems |
Changer | MSChangerParameters | Feature information about CD changer |
| MSChangerProblemEvent | Event used to signal a problem |
| MSChangerProblemDevice Error | Summary of device problems |
All device types | MSPower_DeviceEnable | Controls whether driver automatically powers device down |
| MSPower_DeviceWakeEnable | Enables or disables system wake-up feature |
| MSDeviceUI_FirmwareRevision | Revision level of device firmware |
To implement your support for a standard data block, include the corresponding GUID in the list you report back from the registration query. Implement supporting code for getting and putting data, enabling and disabling events, and so on, using the techniques I ve already discussed. Don t include definitions of the standard data blocks in your own schema; those class definitions are already in the repository, and you don t want to override them.
You can include the DDK header file WMIDATA.H in your driver to get GUID definitions and class structure layouts.
In many cases, by the way, a Microsoft class driver will be providing the actual WMI support for these standard classes you might not have any work to do.