[oR] The WMI architecture consists of five layers: -
A source of WMI data (e.g., a device driver) designated the data provider -
A central repository of WMI data that implements a CIM-compliant data store, based on the CIM object model (CIMOM) -
A WMI provider (e.g., the WDM WMI provider) that provides the necessary linkage between a WMI data source and the data store -
A consumer (or client) of the WMI data -
A protocol (e.g., COM) that presents the data store to a consumer The WMI provider for WDM consists of two parts: -
A user-mode Win32 DLL that provides linkage between a user-mode client -
A set of kernel-mode services that invoke and respond to driver code. An IRP, IRP_MJ_SYSTEM_CONTROL, is the mechanism by which these services make requests of the driver for WMI data. A diagram of the WMI architecture is shown in Figure 13.2. Figure 13.2. WMI architecture. Providing WMI Support in a WDM Driver In order to supply WMI data, a driver must perform the following steps: -
Provide a MOF description of the data, methods, and events that will be provided and generated by the driver. -
Compile the MOF source file (.MOF) using the mofcomp.exe tool provided with the DDK. This tool produces a platform-independent binary file (.BMF) for the next step. -
Include the compiled MOF data within the resource section of the driver binary. -
Provide an IRP_MJ_SYSTEM_CONTROL dispatch routine within the driver code. When received, the minor subcode field of the IRP specifies the exact kind of WMI request. -
Register as a provider of WMI data using IoWmiRegistrationControl. -
Process the IRP_MJ_SYSTEM_CONTROL IRP with minor subcode IRM_MN_REGINFO. The IRP for such a request contains an action code (in Parameters.WMI.DataPath) that is either WMIREGISTER or WMIUPDATE. The action code forms a third dispatch level for such an IRP. (Major, minor, and action code form the three dispatch levels.) -
Process the action code WMIREGISTER by providing information about the data and event blocks defined within the MOF description. -
Process the action code WMIUPDATE by invoking helper functions declared with WMILIB.h. Each of these eight steps is described in more detail below. MOF Syntax MOF syntax follows the standard structure of RPC interface definitions described by the Interface Definition Language (IDL). The basic form is [ATTRIUBUTES and QUALIFIERS] ENTITY TYPE and NAME { [DATA ITEM QUALIFIERS] DATA TYPE and NAME; ... }; The definitions can be nested, much as a structure is defined within a structure. The entity type can consist of such keywords as class, string, or boolean. The basic MOF description starts with a class name, which is case insensitive and must be unique within the entire WMI namespace. Classes can inherit the definitions of a base class, just as in C++. [ // Class qualifiers ] class HiResCamera : Camera { // Data definitions added by HiResCamera }; A derived class name is uniquely qualified by its base class name and thus the name "HiResCamera" need only be unique beneath the base class of Camera. Class qualifiers are listed in Table 13.1 and three of these attributes are required for a WMI-compliant driver: Provider, WMI, and Guid. Each data definition constitutes an item within the class, complete with its own set of item qualifiers, listed in Table 13. 2. The list of MOF-legal data types is described in Table 13.3. The MOF syntax requires two items within a class definition: Instance-Name and Active. These two items are managed internally by WMI and, therefore, should not be considered data that is part of the driver itself. The data types of InstanceName and Active are string and boolean, respectively. Table 13.1. Class Qualifiers for WMI MOF Definition Class Qualifier | Description | Dynamic | Data for the MOF block is supplied at runtime on a per instance basis of the class. | Static | Data for the MOF block is included as part of the WMI database. | Provider("WmiProv") | Required. Indicates that the provider of this class is WMI-compliant. | WMI | Required. Indicates WMI class. | Description("text") | Documentation or comment which can be made available to clients of the class. | Guid("guid") | The unique 128-bit number identifying the class. | Locale("MS\lcid") | The locale (language) ID for which the Description text is supplied. | WmiExpense(cost) | The collection cost in CPU cycles for data described and collected by the class. | Table 13.2. MOF Data Item Qualifiers Data Item Qualifier | Description | key | This data item uniquely identifies class instance. | read | WMI client can read the item. | write | WMI client can write the item. | BitMap | List of string names representing "bit names" corresponding to bit positions within BitValues. The string list is enclosed within braces ({,}) immediately after the BitMap keyword. | BitValues | Bit positions corresponding to the names specified by BitMap. The bit list is enclosed within braces immediately after the BitValues keyword. | ValueMap | List of string names representing the enumerated values within Values. The string list is enclosed within braces immediately after the ValueMap keyword. | Values | List of values corresponding to the names specified by ValueMap. The value list is enclosed within braces immediately after the Values keyword. | WmiDataId(ItemId) | Required. Specifies the location of the data item within the MOF data block. The first item has ID of 1, the fourth has ID of 4. | WmiSizeIs("ItemName") | Specifies another "ItemName" that indicates the number of elements for this array item (valid only for array data items). | WmiScale(ScaleFactor) | Specifies the magnitude (base 10) for the report of this data item. For example, with ScaleFactor = 3, a data item of value 7 would indicate 7000. | WmiTimeStamp | Indicates the 64-bit item value is a 100 nanosecond tick count since 01/01/1601. | WmiComplexity("Level") | A comment potentially used by clients in deciding whether to expose this item. Possible values are "Novice," "Advanced," "Expert," "Wizard." | WmiVolatility(Interval) | Specifies the interval in milliseconds for the frequency of updates to the item. | WmiEventTrigger("ItemName") | For an event block, specifies another ItemName that defines the value at which this event item triggers. | WmiEventRate("ItemName") | For an event block, specifies another ItemName that defines the frequency at which this event item fires. | Table 13.3. MOF Data Types Data Type | Description | sizeof | string | Counted Unicode string | 12 | boolean | TRUE or FALSE (zero or nonzero) | 1 | sint8 | Signed 8-bit integer | 1 | uint8 | Unsigned 8-bit integer | 1 | sint16 | Signed 16-bit integer | 2 | uint16 | Unsigned 16-bit integer | 2 | sint32 | Signed 32-bit integer | 4 | uint32 Unsigned | 32-bit integer | 4 | sint64 | Signed 64-bit integer | 8 | uint64 | Unsigned 64-bit integer | 8 | datetime | Unicode string holding date & time: "yyyymmddhhmmss: utc" where = microseconds utc = GMT (UTC) minute offset | Example MOF Class Definition The following is a simple example of a completed MOF class definition: [WMI, guid("12345678-1234-5678-9ABC-123456789ABC"), Dynamic, Provider("WMIProv"), WmiExpense(1), Locale("MS\\0x409"), Description("Example class")] class W2KDriverBook_Missile { // Required item definitions - unused by driver [key, read] string InstanceName; [read] boolean Active; // Property: Total Launches [read, WmiDataId(1), WmiScale(0), Description("Total Missile Launches")] uint32 TotalLaunches; //The number of silos in the SiloStatus array [read, WmiDataId(2) ] uint32 SiloCount; //SiloStatus Array [read, WmiDataId(3), WmiSizeIs("SiloCount") ] uint8 SiloStatus[]; }; Compiling the MOF Source To compile the MOF source, the tool mofcomp.EXE, located in the system32 directory, is used. Two switches are needed for WDM driver development. The -B switch instructs the MOF compiler to place the binary results into the specified filename, which is then inserted into the driver's resource area. The -WMI switch forces a second pass on the MOF input to validate compliance with WMI. For the example MOF file listed above, the command line mofcomp -B:Example.bmf -WMI Example.MOF successfully compiles the definitions. Once the binary MOF (.bmf) file is created, the driver must include a source resource file (.RC) into the project (makefile) with the following line: MofResource MOFDATA Example.bmf When the driver loads into system memory, its resource section is placed into RAM and is accessible to the driver. The resource name, specified by the .bmf filename, is announced in response to the WMI IRP request WMIREGISTER. Handling WMI IRP Requests The first step a driver must take to handle WMI requests is to register itself with the I/O Manager as a WMI participant. This is performed with the IoWMIRegistrationControl function, described in Table 13.4. Initially, a driver should perform the Action of WMIREG_ACTION_REGISTER, presumably during the AddDevice routine. Similarly, during RemoveDevice, the Action WMIREG_ACTION_DEREGISTER is performed. Once registered for WMI action, a driver must respond to the IRP Dispatch function for the major code IRP_MJ_SYSTEM_CONTROL. This IRP request supplies one of several minor subcodes, listed in Table 13.5. The IRP stack's Parameters' union contains a WMI structure, defined in Table 13.6. This WMI structure supplies data for the request (input and output). Table 13.4. IoWMIRegistrationControl Function Prototype NTSTATUS IoWMIRegistrationControl | IRQL == PASSIVE_LEVEL | Parameter | Description | IN PDEVICE_OBJECT pDeviceObject | Pointer to Device object | IN ULONG Action | WMIREG_ACTION_REGISTER | | WMIREG_ACTION_DEREGISTER | | WMIREG_ACTION_REREGISTER | | WMIREG_ACTION_UPDATE_GUIDS | | WMIREG_ACTION_BLOCK_IRPS | Return value | Success or failure code | Table 13.5. IRP_MJ_SYSTEM_CONTROL Minor Functions IRP_MJ_SYSTEM_CONTROL | Minor Function Subcodes | Description | IRP_MN_QUERY_ALL_DATA | Request for all instances of a data block | IRP_MN_QUERY_SINGLE_INSTANCE | Request for a specific instance | IRP_MN_CHANGE_SINGLE_INSTANCE | Request to modify all data of a specific instance | IRP_MN_CHANGE_SINGLE_ITEM | Request to modify one datum | IRP_MN_ENABLE_EVENTS | Informs driver that a data consumer has requested event notification | IRP_MN_DISABLE_EVENTS | Data consumer no longer wishes notification of events | IRP_MN_ENABLE_COLLECTION | Request to begin collection of "expensive" instrument data | IRP_MN_DISABLE_COLLECTION | Request to stop collecting | IRP_MN_REGINFO | Query or modify a driver's registration information | IRP_MN_EXECUTE_METHOD | Invokes a method defined in the MOF data block | Classes and Instances A data block described as a class within the MOF syntax declares a class data block. No instances of the class exist (i.e., no data space is reserved) until such time as the driver registers the data block, typically performed within the DpWmiQueryReginfo function, described below. While typically only one instance of a class is appropriate, some circumstances suggest the existence of an array of instances to best model the data being collected. For example, suppose the Missile driver collects data for each of the three engines powering the rocket. The fuel used by one engine may be separate data from that used by another. If each missile engine is described by a separate instance of a MOF Missile_Engine class, each query or update of data requires an additional instance index to fully qualify the request. Table 13.6. Parameters. WMI Structure Definition struct WMI | Field | Description | ProviderId | Pointer to target Device object | DataPath | GUID of MOF data block | BufferSize | Size of the Buffer | Buffer | Buffer whose contents are minor function code specific | Of course, the fuel level of each engine could also be kept as an array of values within a single instance. Thus, the decision to use multiple instances of data blocks is dictated by whether or not all the entries within the block should be stacked, one per instance. WMILIB Responding to WMI IRP requests is facilitated by a kernel-mode DLL, WMILIB. The heart of this library support is a routine, WmiSystemControl, described in Table 13.7. The function receives as input a data structure of type WMILIB_CONTEXT, described in Table 13.8, which is primarily a list of function pointers that get, set, or otherwise control MOF items. The functions are provided by the driver, and if not supplied, should be set to NULL. The GuidList member of the WMILIB_CONTEXT structure is an array of structures of type WMIGUIDREGINFO, one array element for each MOF data block class exposed by the driver. The WMIGUIDREGINFO structure is described in Table 13.9. WmiSystemControl returns a disposition status that describes how the IRP was handled. Table 13.7. WmiSystemControl Function Prototype NTSTATUS WmiSystemControl | IRQL == PASSIVE_LEVEL | Parameter | Description | IN PWMILIB_CONTEXT WmiLibInfo | Pointer to WMILIB_CONTEXT structure, provided and initialized by driver | IN PDEVICE_OBJECT pDeviceObject | Driver's Device Object | IN PIRP pIrp | Pointer to IRP of request | OUT PSYSCTL_IRP_DISPOSITION | IrpProcessed | pIrpDisposition | IrpNotCompleted | | IrpNotWmi | | IrpForward | Return value | Success or failure code | Table 13.8. WMILIB_CONTEXT Structure Definition struct WMILIB_CONTEXT | Field | Description | ULONG GuidCount | Number of MOF data blocks registered by driver | PWMIGUIDREGINFO GuidList | Array of GUIDs and instance counts | PWMI_QUERY_REGINFO QueryWmiRegInfo | Function pointer to driver-supplied routine, DpWmiQueryReginfo | PWMI_QUERY_DATABLOCK QueryWmiDataBlock | Function pointer to driver-supplied routine, DpWmiQueryDataBlock | PWMI_SET_DATABLOCK SetWmiDataBlock | Function pointer to driver-supplied routine, DpWmiSetDataBlock (may be NULL) | PWMI_SET_DATAITEM SetWmiDataItem | Function pointer to driver-supplied routine, DpWmiSetDataItem (may be NULL) | PWMI_EXECUTE_METHOD ExecuteWmiMethod | Function pointer to driver-supplied routine, DpWmiExecuteMethod (may be NULL) | PWMI_FUNCTION_CONTROL WmiFunctionControl | Function pointer to driver-supplied routine, DpWmiFunctionControl (may be NULL) | On the surface, WmiSystemControl does little more than dispatch the minor subcode of IRP_MJ_SYSTEM_CONTROL to the appropriate driver-supplied DpWmiXxx routine. The real advantage of WMILIB is that it handles the complete WMI protocol, leaving the driver with only the necessary task of MOF data-specific handling. Table 13.9. WMIGUIDREGINFO Structure Definition struct WMIGUIDREGINFO | Field | Description | LPCGUID Guid | Pointer to GUID that identifies block | ULONG InstanceCount | Number of instances to create | ULONG Flags | Characteristics of block: | | WMIREG_FLAG_INSTANCE_PDO | | WMIREG_FLAG_EVENT_ONLY_GUID | | WMIREG_FLAG_EXPENSIVE | | WMIREG_FLAG_REMOVE_GUID | Each dispatch function pointed to by an entry within WMILIB_CONTEXT is a unique function signature (prototype) with various responsibilities. These DpWmi functions are described in further detail below. With the exception of DpWmiQueryReginfo, each of these routines completes its normal work by invoking WmiCompleteRequest, described in Table 13.10. This WMILIB function marks the IRP as "complete" and finishes the WMI protocol sequence. DpWmiQueryReginfo The DpWmiQueryReginfo function, supplied by the driver, is described in Table 13.11. It is invoked by WMILIB when the requesting IRP contains the minor subcode IRP_MN_REGINFO with action code (Parameters.WMI.DataPath) equal to WMIREGISTER or WMIUPDATE. The prime purpose of this driver routine is to register one or more instances of data class. The implementation of this function should include code to implement the driver's strategy for the naming of data block instances. The RegFlags parameter includes a bit, WMIREG_FLAG_INSTANCE_BASENAME, which allows WMI to automatically append an instance counter number to the base name specified by InstanceName. Otherwise, the driver can implement its own naming strategy. It is noteworthy that these instance names are persistent (sticky) across boots of the OS. The names are registered in the CIMOM store. Yet another driver strategy is to use the PDO name for the instance base name. To utilize this strategy, a driver sets the RegFlags' bit WMI_FLAG_ INSTANCE_PDO. Table 13.10. WmiCompleteRequest Function Prototype NTSTATUS WmiCompleteRequest | IRQL <= DISPATCH_LEVEL | Parameter | Description | IN PDEVICE_OBJECT pDeviceObject | Pointer to driver's device object | IN PIRP pIrp | Pointer to IRP of request | IN NTSTATUS Status | Final status to return in IRP | IN ULONG BufferUsed | If buffer was too small for request, this value holds what was required; otherwise, holds bytes used. | IN CCHAR PriorityBoost | Same as IoCompleteRequest Priority- Boost, usually a fixed value | Table 13.11. DpWmiQueryRegInfo Function Prototype NTSTATUS DpWmiQueryReginfo | IRQL == PASSIVE_LEVEL | Parameter | Description | IN PDEVICE_OBJECT pDeviceObject | Pointer to driver's device object | OUT PULONG pRegFlags | Registration flags set by driver for all registered MOF data blocks | OUT PUNICODE_STRING InstanceName | When RegFlags has bit WMIREG_FLAG_INSTANCE_ BASENAME set, InstanceName is used as base with instance counter appended. WMI frees this string with a call to ExFreePool. | OUT PUNICODE_STRING *pRegistryPath | Same string passed to driver's DriverEntry | OUT PUNICODE_STRING MofResourceName | String containing name of MOF .bmf file | OUT PDEVICE_OBJECT *pPDO | PDO to use for generation of physical device instance names (only if RegFlags WMIREG_FLAG_ INSTANCE_PDO set) | Return value | STATUS_SUCCESS | DpWmiQueryDataBlock The DpWmiQueryDataBlock function is described in Table 13.12. It is invoked by WMILIB when the requesting IRP contains the minor subcode IRP_MN_QUERY_DATA_BLOCK or IRP_MN_QUERY_ALL_DATA. The purpose of this driver routine is to return the instance(s) data requested. DpWmiSetDataBlock The DpWmiSetDataBlock function is described in Table 13.13. It is invoked by WMILIB when the requesting IRP contains the minor subcode IRP_MN_CHANGE_SINGLE_INSTANCE. The purpose of this driver routine is to modify the specified instance data. As defined, only one instance can be changed at one time, but when multiple instances exist, the InstanceIndex value of zero remains undefined. DpWmiSetDataItem The DpWmiSetDataItem function is described in Table 13.14. It is invoked by WMILIB when the requesting IRP contains the minor subcode IRP_MN_ CHANGE_SINGLE_ITEM. The purpose of this driver routine is to modify the specified data item within the specified instance data. Table 13.12. DpWmiQueryDataBlock Function Prototype NTSTATUS DpWmiQueryDataBlock | IRQL == PASSIVE_LEVEL | Parameter | Description | IN PDEVICE_OBJECT pDeviceObject | Pointer to driver's device object | IN PIRP pIrp | Pointer to IRP of WMI request | IN ULONG GuidIndex | Index into WMILIB_CONTEXT structure's GuidList, identifying data block | IN ULONG InstanceIndex | Specific data instance being queried (0 if all instances being queried) | IN ULONG InstanceCount | Number of instances being queried | IN OUT PULONG InstanceLengthArray | Array of ULONGs specifying length of each instance being returned | IN ULONG BufferAvail | Size of Buffer | OUT PUCHAR Buffer | Buffer to receive instance data | Return value | STATUS_SUCCESS | | STATUS_BUFFER_TOO_SMALL | | STATUS_WMI_GUID_NOT_FOUND | | STATUS_WMI_INSTANCE_NOT_ FOUND | Table 13.13. DpWmiSetDataBlock Function Prototype NTSTATUS DpWmiSetDataBlock | IRQL == PASSIVE_LEVEL | Parameter | Description | IN PDEVICE_OBJECT pDeviceObject | Pointer to driver's device object | IN PIRP pIrp | Pointer to IRP of WMI request | IN ULONG GuidIndex | Index into WMILIB_CONTEXT structure's GuidList, identifying data block to modify | IN ULONG InstanceIndex | Specific data instance being modified | IN ULONG BufferSize | Size of Buffer | IN PUCHAR Buffer | Buffer supplying new instance data | Return value | STATUS_SUCCESS | | STATUS_PENDING | | STATUS_WMI_READ_ONLY | | STATUS_WMI_SET_FAILURE | | STATUS_WMI_GUID_NOT_FOUND | | STATUS_WMI_INSTANCE_NOT_ FOUND | Table 13.14. DpWmiSetDataItem Function Prototype NTSTATUS DpWmiSetDataItem | IRQL == PASSIVE_LEVEL | Parameter | Description | IN PDEVICE_OBJECT pDeviceObject | Pointer to driver's device object | IN PIRP pIrp | Pointer to IRP of WMI request | IN ULONG GuidIndex | Index into WMILIB_CONTEXT structure's GuidList, identifying data block | IN ULONG InstanceIndex | Specific data instance being modified | IN ULONG DataItemId | ID of data item being modified | IN ULONG BufferSize | Size of Buffer | IN PUCHAR Buffer | Buffer supplying new instance data | Return value | STATUS_SUCCESS | | STATUS_PENDING | | STATUS_WMI_READ_ONLY | | STATUS_WMI_SET_FAILURE | | STATUS_WMI_GUID_NOT_FOUND | | STATUS_WMI_INSTANCE_NOT_ FOUND | DpWmiExecuteMethod The DpWmiExecuteMethod function is described in Table 13.15. It is invoked by WMILIB when the requesting IRP contains the minor subcode IRP_MN_EXECUTE_METHOD. The purpose of this driver routine is to dispatch execution to the specified method. In many ways, this is a "direct call" interface from WMI clients into driver code. The interface is marshaled by the DpWmiExecuteMethod routine, which has the ability to validate and convert parameters before presentation to the requested method. Separately, the output buffer can be marshaled before it is returned to the client. The passing of addresses within the input or output buffer is problematic and should probably be avoided. DpWmiFunctionControl The DpWmiFunctionControl function is described in Table 13.16. It is invoked by WMILIB when the requesting IRP contains the minor subcode IRP_MN_ENABLE_COLLECTION, IRP_MN_DISABLE_COLLECTION, IRP_MN_ ENABLE_EVENTS, or IRP_MN_DISABLE_EVENTS. The purpose of this driver routine is to start or stop the collection of data (i.e., data that is marked as "expensive" with the bit WMIREG_FLAG_EXPENSIVE at registration) or to start or stop the generation of events. Table 13.15. DpWmiExecuteMethod Function Prototype NTSTATUS DpWmiExecuteMethod | IRQL == PASSIVE_LEVEL | Parameter | Description | IN PDEVICE_OBJECT pDeviceObject | Pointer to driver's device object | IN PIRP pIrp | Pointer to IRP of WMI request | IN ULONG GuidIndex | Index into WMILIB_CONTEXT structure's GuidList, identifying data block | IN ULONG InstanceIndex | Specific instance with method to execute | IN ULONG MethodId | ID of method to execute | IN ULONG InBufferSize | Number of bytes passed in Buffer to method | IN ULONG OutBufferSize | Size of Buffer | IN OUT PUCHAR Buffer | Input and output buffer for executing method | Return value | STATUS_SUCCESS | | STATUS_BUFFER_TOO_SMALL | | STATUS_INVALID_DEVICE_REQUEST | | STATUS_WMI_INSTANCE_NOT_FOUND | | STATUS_WMI_ITEM_ID_NOT_FOUND | Table 13.16. DpWmiFunctionControl Function Prototype NTSTATUS DpWmiFunctionControl | IRQL == PASSIVE_LEVEL | Parameter | Description | IN PDEVICE_OBJECT pDeviceObject | Pointer to driver's device object | IN PIRP pIrp | Pointer to IRP of WMI request | IN ULONG GuidIndex | Index into WMILIB_CONTEXT structure's GuidList, identifying data block | IN WMIENABLEDISABLECONTROL | WmiEventControl: control event generation | Function | WmiDataBlockControl: control collection | IN BOOLEAN bEnable | TRUE - Enable event or collection | | FALSE - Disable event or collection | Return value | STATUS_SUCCESS | | STATUS_WMI_GUID_NOT_FOUND | | STATUS_INVALID_DEVICE_REQUEST | |