52964.fb2
This chapter looks at Windows Management Instrumentation (WMI), the first of two methods of reporting management information to system administrators. The alternative — NT events for NT 3.51, NT 4, and Windows 2000 drivers — is covered in the next chapter. NT events are not recorded in Windows 98.
Windows Management Instrumentation is supposed to work in most Windows platforms. However, it did not work for me in Windows 98. As well as reporting information and firing events, WMI lets users set values that control the operation of a device or even invoke driver methods.
If possible, a driver should support the "WMI extensions for WDM". Although not mandatory, it can make it easier to debug or control your driver in the field. WMI support is required for some system functions (e.g., if you want to support user control of Power Management options in the device properties tab). If you do not support WMI, please provide a default WMI IRP handler such as in the Wdm1SystemControl routine.
The example Wdm3 driver builds on the Wdm2 driver, adding WMI and NT event support. It handles the standard MSPower_DeviceEnable WMI data block and defines its own custom WMI data block called Wdm3Information. The Wdm3Information data block returns three pieces of information to a user for each Wdm3 device: the length of the buffer, the first DWORD of the buffer contents, and the symbolic link name of the device.
I was not able to get these two further aspects of WMI working in the Beta version of W2000. A user is supposed to be able to invoke a function within the driver. The driver is supposed to be able to fire a WMI event that will be displayed in a user application.
I must first start with an overview of WMI. Although this overview appears quite complicated, it is actually fairly straightforward to support WMI in a driver.
A driver that implements the WMI extensions for WDM provides information and events to user applications, and these applications can invoke driver methods. The emphasis is on providing diagnostic and management information and tools, not the regular control of a driver.
WMI is part of the Web-Based Enterprise Management (WBEM) initiative that aims to reduce the total cost of computer ownership. WBEM is on-line at www.microsoft.com/management/wbem/. WBEM gives network professionals easy access to all the resources under their control. As the name suggests, the emphasis is on using browsers to access the information across a whole enterprise, either using COM ActiveX controls or a Java API on top of the HyperMedia Management Protocol (HMMP). Standard Win32 user programs can access WBEM repositories using COM APIs.
The WBEM core components are installed by default in Windows 2000 and are available in Windows 98[30]. To inspect the WBEM repository, you will need the WBEM SDK, available on-line and on the MSDN CDs.
WBEM embraces existing technologies, such as HMMP, Simple Network Management Protocol (SNMP), Desktop Management Interface (DMI), and Common Management Information Protocol (CMIP).
Microsoft has defined a Win32 implementation of WBEM and supplies various standard providers of information, giving access to the registry, the NT event log, Win32 information, and WDM drivers. In addition to the COM APIs, you can access the repository through an ODBC driver. Microsoft's WBEM SDK includes tools to browse and edit the WBEM repository.
The WBEM Query Language (WQL) is a subset of SQL with some extensions and can only be used to read information.
WBEM is based on the Common Information Model (CIM), detailed at www.dmtf.org/work/cim.html. CIM is a structure for defining objects that need to be managed. A CIM Object Manager (CIMOM) stores these objects for the current computer in a repository. CIMOM is the heart of the WBEM system. It responds to requests from user mode management application clients and obtains information from providers.
Servers and Namespaces
Each WBEM enabled computer has its own CIM object database. Each computer is called a server and is named after the computer (e.g., \\MyComputer\). The current computer can be called \\.\.
The objects in the CIM database are grouped into namespaces, which can be arranged into a hierarchy, although a namespace does not inherit anything from others higher in the tree. There is always a Root namespace. The Root\Default and Root\Security namespaces have standard contents. Microsoft defines the Win32 namespaces Root\CimV2 and Root\WMI. If need be, you can define new namespaces.
CIM Objects
The objects in a namespace are defined as being classes, arranged in a hierarchy so that classes do inherit properties from base classes, possibly in different namespaces. A class is defined in a text file in the Managed Object Format (MOF). The compiled MOF data is presented by a provider so that CIMOM knows what object types can be created. Use the mofcomp tool to compile a custom WMI class definition and include it as a resource in your driver.
CIM class definitions look vaguely similar to C++ classes. Classes have properties and methods. A class can override a base class definition, including standard classes in the Default namespace. You can have abstract classes that cannot be instantiated. Singleton classes support only a single instance. See the WBEM documentation or CIM specification for full class details.
One of the properties of a class is called the key property, used to differentiate separate instances of a class. If the key property is called InstanceName, the full object path of a class instance might be \\Server\Namespace:Class.InstanceName="PCI\VEN..".
Each class can have zero or more instances. For example, the Win32_LogicalDisk class may have two instances to represent drives C and D, differentiated by the DeviceId key property. Locally, the full object path for drive C is \\.\Root\CimV2:Win32_LogicalDisk.DeviceId="C". You can use relative paths once attached to a namespace (e.g., Win32_LogicalDisk.DeviceId="C").
You can denote a class as being "expensive to collect" if it takes significant extra processing to collect the information. In this case, a user application must specifically request that the information be collected.
CIM object classes can be static or dynamic. Static classes can have static or dynamic instances. However, dynamic classes have only dynamic instances. Static instances are preserved across reboots. A static instance can have static or dynamic properties. Static information is provided from the CIMOM registry while dynamic information is supplied by a provider.
The WDM Provider is a Windows service that interacts with drivers and the CIM Object Manager. It is a WMI class, instance, method, and event provider. The WDM class provider retrieves class definitions in binary MOF format from drivers and sends them to CIMOM. It updates the definitions as driver changes occur.
The WDM instance provider is a dynamic provider, creating instances on demand. The WDM method provider lets applications invoke methods in a WMI driver. The WDM event provider receives events from WMI devices and translates them into an instance derived from WMI Event, itself a subclass of the CIM_ExtrinisicEvent system class.
There are standard WMI classes defined that you can and should use. However, you can define your own if need be, each identified by a new GUID. In this case, you write a MOF file and compile it using mofcomp. You can include the binary MOF data either in your driver's resource or in a separate DLL. You must write a separate class for each WMI data block and WMI event block.
An event block class must be derived from the WMIEvent class. All WMI blocks appear in the Root\WMI CIMOM namespace.
Standard WMI Objects
The Windows WDM provider has several standard Win32 WMI blocks defined. You can find these using the WBEM Object Browser. The MOF definitions for most of the standard Win32 WMI blocks are in the W2000 DDK file src\storage\class\disk\WMICORE.MOF.
The W2000 DDK sources also have plenty of examples of how to implement WMI. For example, the standard Windows 2000 serial driver provides five WMI data blocks of information, MSSerial_CommInfo, MSSerial_CommProperties, MSSerialHardwareConfiguration, MSSerial_PerformanceInformation and MSSerial_PortName. In each case the InstanceName property is the key, named after the PnP driver instance, not the serial port name. However, the MSSerial_PortName data object lets you retrieve the real port name in its PortName property. As another example, the MSSerial_CommInfo BaudRate property has the serial port's baud rate.
If you are writing a driver that is similar to a system driver, then consider reporting the standard system WMI blocks. The Wdm3 driver implements the standard MSPower_DeviceEnable WMI data block. As described in Chapter 10, this is used by the Device Manager to let users stop a device from powering down. This means that the Wdm3 must be able to accept a changed value for the Enable property, as well as reporting the current setting.
Listing 12.1 shows the source for the MSPower_DeviceEnable WMI data block, taken from WMICORE.MOF. A GUID is used to identify the block. This is {827c0a6f-feb0-11d0-bd26-00aa00b7b32a}, which is defined as GUID_POWER_DEVICE_ENABLE in the standard header WDMGUID.H.
All WMI classes have a key string property called InstanceName and Boolean property called Active. The MSPower_DeviceEnable class has only one "real" property, called Enable, that can be read and written and is identified as the first WmiDataId.
Listing 12.1 MSPower_DeviceEnable WMI data block
[Dynamic, Provider("WMIProv"), WMI,
Description("The buffer for this control is a BOOLEAN and indicates if the device should dynamically power on and off while the system is working. A driver would only support such a setting if there is a significant user noticeable effect for powering off the device. E.g., turning on the device may cause a user noticeable delay. Regardless of this setting, the driver is still required to support system sleeping states irps (which likely translates to powering off the device when a system sleep occurs)."),
guid("827c0a6f-feb0-11d0-bd26-00aa00b7b32a"), locale("MS\\0x409")]
class MSPower_DeviceEnable {
[key, read]
string InstanceName;
[read]
boolean Active;
[WmiDataId(1), read, write]
boolean Enable;
};
A WDM driver can use WMI mechanisms to publish information, permit configuration of its device, and supply notifications of events. It should continue to report standard Windows 2000 events, as many system administrators will still be looking for this event information. However, WMI is the way forward, as it allows information to be made available on non-W2000 systems, and it allows remote inspection and control of the device.
WDM drivers, NT style drivers, and miniport drivers/minidriver can support WMI. In the latter case, you must fit in with the WMI reporting mechanism supported by the class driver. For example, a SCSI miniport must set the WmiDataProvider BOOLEAN in the PORT_CONFIGURATION_INFORMATION structure and handle SRB_FUNCTION_WMI requests.
The Wdm3 driver defines two custom WMI blocks. Listing 12.2 shows how the Wdm3Information data block and the Wdm3Event event block are defined in Wdm3.mof. The identifying GUID for each block is defined in the GUIDs.h header: WDM3_WMI_GUID and WDM3_WMI_EVENT_GUID.
The Wdm3Information data block contains two 32-bit unsigned properties followed by a counted wide string property. The first real property, BufferLen, is the length of the shared memory buffer. BufferFirstWord is the first 32 bits of the shared memory buffer, or zero if the buffer is not long enough. Finally, SymbolicLinkName is the symbolic link name of the Wdm3 device interface.
The Wdm3Inforination data block has a definition for a PowerDown function. I found that this did not compile, so I commented it out.
The Wdm3Event WMI event block must be derived from the standard WMI Event class. It simply defines a Message property.
Listing 12.2 Wdm3Iinformation and Wdm3Event block
/* WMI data block: Information about Wdm3 device Wdm3Information, identified by WDM3_WMI_GUID */
[WMI, Dynamic, Provider("WMIProv"), Description("Wdm3 information"), guid("{C0CF0643-5F6E-11d2-B677-0OC0DFE4C1F3}"), locale("MS\\0x409")]
class Wdm3Information {
[key, read]
string InstanceName;
[read]
boolean Active;
[WmiDataId(1), read, Description("Shared memory buffer length") ]
uint32 BufferLen;
[WmiDataId(2), read, Description("First ULONG of shared memory buffer") ]
uint32 BufferFirstWord;
[WmiDataId(3), read, Description("Symbolic link name") ]
string SymbolicLinkName;
/* Doesn't compile
[Implemented]
void PowerDown(); */
};
/* WMI event block: Wdm3 device event Wdm3Event, identified by WDM3_WMI_EVENT_GUID */
[WMI, Dynamic, Provider("WMIProv"), guid("{C0CF0644-5F6E-11d2-B677-00C0DFE4C1F3}"), locale("MS\\0x409"), Description("Wdm3 event message")]
class Wdm3Event : WMIEvent {
[key, read]
string InstanceName;
[read]
boolean Active;
[WmiDataId(1), read, Description("Message")]
string Message;
};
This example MOF file shows how a driver defines static instance names when it registers a WMI block. Alternatively, if necessary, the driver can define dynamic instance names if the instances change frequently at run time. Handling dynamic instance names is harder and is not a common requirement, so it is not discussed here.
Quite a few changes must be made to a project to support WMI.
1. All the WMI functions, including support for the Wdm3SystemControl WMI IRP, are in Wmi.cpp. Include this file in the SOURCES list of files to compile in the SOURCES file.
2. I found that this line had to be included in SOURCES to ensure that the WMI library file was found.
TARGETLIBS=C:\NTDDK\LIBFRE\I386\WmiLib.Lib
3. The prebuild steps had to be altered to persuade build to run the mofcomp tool to compile Wdm3.mof. The SOURCES file NTTARGETFILE0 macro is changed to ensure that makefile.inc runs the following command. The complete makefile.inc is given in the next chapter.
mofcomp –B:Wdm3.bmf –WMI Wdm3.mof
This command line compiles MOF source file Wdm3.mof into the binary MOF Wdm3.bmf file. The options to mofcomp ensure that the source code is checked for WMI compatibility and that the output goes in the correct file.
4. The main resource file Wdm3.rc must now include the binary MOF file Wdm3.bmf. The following line in the resource script identifies the data using the name MofResource.
MOFRESOURCE MOFDATA MOVEABLE PURE "Wdm3.bmf"
5. The main header in Wdm3.h had to use standard headers wmilib.h and wmistr.h. It also included wdmguid.h to get the definition of the standard GUID, GUID_POWER_DEVICE_ENABLE.
6. The device extension has these extra fields added.
WMILIB_CONTEXT WmiLibInfo; // WMI Context
BOOLEAN IdlePowerDownEnable; // Enable power down option
BOOLEAN WMIEventEnabled; // Enable WMI events
WmiLibInfo is used to pass the Wdm3 WMI GUIDs and various callbacks, as described later.
IdlePowerDownEnable implements the MSPower_DeviceEnable WMI data block. If IdlePowerDownEnable is TRUE, the driver can power down when it is idle.
Finally, WMIEventEnabled is used to enable WMI event reporting.
7. The main WMI code needs to refer to the driver registry path that was passed to DriverEntry. Therefore, DriverEntry saves a copy in its Wdm3RegistryPath global variable.
// Save a copy of our RegistryPath for WMI
Wdm3RegistryPath.MaximumLength = RegistryPath->MaximumLength;
Wdm3RegistryPath.Length = 0;
Wdm3RegistryPath.Buffer = (PWSTR)ExAllocatePool(PagedPool, Wdm3RegistryPath.MaximumLength);
if (Wdm3RegistryPath.Buffer == NULL) return STATUS_INSUFFICIENT_RESOURCES;
RtlCopyUnicodeString(&Wdm3RegistryPath, RegistryPath);
The driver unload routine Wdm3Unload deletes the Wdm3RegistryPath buffer.
You must register as a WMI provider by calling IoWmiRegistrationControl with a WMIREG_ACTION_REGISTER command for each device when it is ready to handle WMI IRPs. The Wdm3 driver makes this call in its RegisterWmi routine. RegisterWmi is called at the end of Wdm3AddDevice. The corresponding DeregisterWmi routine deregisters the WMI support by calling IoWmiRegistrationControl with the WMIREG_ACTION_DEREGISTER command. DeregisterWmi is called when the device is removed.
Listing 12.3 shows the RegisterWmi and DeregisterWMI routines. RegisterWmi also sets up the WmiLibInfo structure in the device extension, ready for processing by the System Control WMI IRP. Table 12.1 shows the WmiLibInfo WMILIB_CONTEXT structure fields.
WmiLibInfo first contains the list of WMI block GUIDs that are handled by this driver. Wdm3GuidList is an array of these WMIGUIDREGINFO structures. Each specifies the GUID pointer, a count of instances, and optionally some flags. There is only one instance of each WMI block for this device. No flags are specified, as the appropriate flags are used later in the call to QueryWmiRegInfo.
The WMILIB_CONTEXT WmiLibInfo field also sets up several callback routines that we shall meet later. These are used to help the processing of the IRP_MJ_SYSTEM_CONTROL WMI IRP.
After calling IoWmiRegistrationControl, your driver will then be sent an IRP_MN_REGINFO System Control IRP to obtain the device's WMI MOF information, as described in the following.
Listing 12.3 RegisterWmi and DeregisterWMI code
const int GUID_COUNT = 3;
WMIGUIOREGINFO Wdm3GuidList[GUID_CЩUNT] = {
{ &WDM3_WMI_GUID, 1. 0 }, // Wdm3Information
{ &GUID_POWER_DEVICE_ENABLE, 1, 0}, // MSPower_DeviceEnable
{ &WDM3_WMI_EVENT_GUID, 1, 0}, // Wdm3Event
};
const ULONG WDM3_WMI_GUID_INDEX = 0;
const ULONG GUID_POWER_DEVICE_ENABLE_INDEX = 1;
const ULONG WDM3_WMI_EVENT_GUID_INDEX = 2;
void RegisterWmi(IN PDEVICE_OBJECT fdo) {
PWDM3_DEVICE_EXTENSION dx=(PWDM3_DEVICE_EXTENSION)fdo->DeviceExtension;
dx->WmiLibInfo.GuidCount = GUID_COUNT;
dx->WmiLibInfo.GuidList = Wdm3GuidList;
dx->WmiLibInfo.QueryWmiRegInfo = QueryWmiRegInfo;
dx->WmiLibInfo.QueryWmiDataBlock = QueryWmiDataBlock;
dx->WmiLibInfo.SetWmiDataBlock = SetWmiDataBlock;
dx->WmiLibInfo.SetWmiDataItem = SetWmiDataItem;
dx->WmiLibInfo.ExecuteWmiMethod = ExecuteWmiMethod;
dx->WmiLibInfo.WmiFunctionControl = WmiFunctionControl;
NTSTATUS status = IoWMIRegistrationControl(fdo, WMIREG_ACTION_REGISTER);
DebugPrint("RegisterWmi %x", status);
}
void DeregisterWmi( IN PDEVICE_OBJECT fdo) {
IoWMIRegistrationControl(fdo, WMIREG_ACTION_DEREGISTER);
DebugPrintMsg("DeregisterWmi");
}
Table 12.1 WMILIB_CONTEXT structure
GuidCount | ULONG | Required | Count of WMI blocks |
GuidList | Required | Array with the GUIDs of the WMI blocks supported, etc. | |
QueryWmiRegInfo | Required | Provide further information about the WMIblocks you are registering | |
QueryWmiDataBlock | Callback | Required | Return a single instance or all instances of adata block |
SetWmiDataBlock | Callback | Optional | Set all data items in a single instance of adata block |
SetWmiDataItem | Callback | Optional | Set a single data item in a single instance of a data block |
ExecuteWmiMethod | Callback | Optional | Execute a method associated with a data block |
WmiFunctionControl | Callback | Optional | Enable and disable event notification andexpensive data block collection |
Your driver must then handle System Control IRPs in your SystemControl routine. Table 12.2 shows the various minor codes associated with this IRP.
Table 12.2 System control minor request codes
IRP_MN_REGINFO | Query a driver's registration information |
IRP_MN_QUERY_ALL_DATA | Get all instances in a given data block |
IRP_MN_QUERY_SINGLE_INSTANCE | Get a single instance in a given data block |
IRP_MN_CHANGE_SINGLE_INSTANCE | Change all data items in a given data block |
IRP_MN_CHANGE_SINGLE_ITEM | Change a single data item in a given data block |
IRP_MN_ENABLE_EVENTS | Enable event notification |
IRP_MN_DISABLE_EVENTS | Disable event notification |
IRP_MN_ENABLE_COLLECTION | Start collection of data that is expensive to collect |
IRP_MN_DISABLE_COLLECTION | Stop collection of data that is expensive to collect |
IRP_MN_EXECUTE_METHOD | Execute a method in a data block |
If you have dynamic instance names, you must process the IRP and call WmiCompleteRequest, or pass it onto the next driver if you do not recognize the GUID.
For static instances, as used by Wdm3, you simply have to call WmiSystemControl, passing a pointer to your WMILIBCONTEXT structure. WmiSystemControl processes the IRPs as much as possible. If the request is destined for your driver, it invokes the appropriate callback to let you process the IRP. On return, WmiSystemControl sets its IrpDisposition parameter to tell you how to continue processing the IRP, as shown in Table 12.3. For example, if the IRP was not destined for your driver, IrpDisposition is set to IrpForward and the IRP is sent down the stack.
Table 12.3 WmiSystemControl IrpDisposition handling
IrpProcessed | Either call WmiCompleteRequest here or in your call-back routine. |
IrpNotComplete | IRP processed but an error detected, so just call IoCompleteRequest. |
IrpNotWMI or IrpForward | Forward to next driver: call IoSkipCurrentIrpStackLocation and IoCallDriver |
Listing 12.4 shows how the Wdm3SystemControl routine processes System Control IRPs. The main processing is carried out by the system function WmiSystemControl. Wdm3SystemControl acts on the IrpDisposition parameter in the recommended way.
Listing 12.4 Wdm3SystemControl routine
NTSTATUS Wdm3SystemControl (IN PDEVICE_OBJECT fdo, IN PIRP Irp) {
DebugPrintMsg("Wdm3SystemControl");
PWDM3_DEVICE_EXTENSION dx = (PWDM3_DEVICE_EXTENSION)fdo->DeviceExtension;
SYSCTL_IRP_DISPOSITION disposition;
NTSTATUS status = WmiSystemControl(&dx->WmiLibInfo, fdo, Irp, &disposition);
switch(disposition) {
case IrpProcessed:
// This irp has been processed and may be completed or pending.
break;
case IrpNotCompleted:
// This irp has not been completed, but has been fully processed.
// we will complete it now
IoCompleteRequest(Irp, IO_NO_INCREMENT);
break;
case IrpForward: case IrpNotWmi:
// This irp is either not a WMI irp or is a WMI irp targetted
// at a device lower in the stack.
IoSkipCurrentIrpStacktocation(Irp);
status = IoCallDriver(dx->NextStackDevice, Irp);
break;
default:
DebugPrint("Wdm3SystemControl bad disposition %d",disposition);
// ASSERT(FALSE);
}
return status;
}
At a minimum, you should write QueryWmiRegInfo, QueryWmiDataBlock, SetWmiDataBlock, and SetWmiDataItem callbacks. Even though the last two callbacks are optional, it is best to implement them to ensure that WmiCompleteRequest is called with an appropriate error code.
The Wdm3 QueryWmiRegInfo callback routine shown in Listing 12.5 provides further information about the WMI blocks you are registering, in addition to the GuidList provided in your WMILIB_CONTEXT.
Return your MOF resource name and your registry path.
If you are writing a WDM driver, you will usually set the WMIREG_FLAG_INSTANCE_PD0 flag and pass the device's PDO. See the DDK documentation for details of other flags that you can set.
Listing 12.5 QueryWmiRegInfo routine
#define MofResourceNameText L"MofResource"
NTSTATUS QueryWmiRegInfo(IN PDEVICE_OBJECT fdo, OUT PULONG PRegFlags, OUT PUNICODE_STRING PInstanceName, OUT PUNICODE_STRING *PRegistryPath, OUT PUNICODE_STRING MofResourceName, OUT PDEVICE_OBJECT *Pdo) {
DebugPrintMsg("QueryWmiRegInfo");
PWDM3_DEVICE_EXTENSION dx = (PWDM3_DEVICE_EXTENSION)fdo->DeviceExtension;
*PRegFlags = WMIREG_FLAG_INSTANCE_PDO;
*PRegistryPath = &Wdm3RegistryPath;
RtlInitUnicodeString( MofResourceName, MofResourceNameText);
*Pdo = dx->pdo;
return STATUS_SUCCESS;
}
A QueryWmiDataBlock callback routine returns one or more instances of a data block. When finished, it should call WmiCompleteRequest. A driver can return STATUS_PENDING if the IRP cannot be completed immediately.
QueryWmiDataBlock is passed GuidIndex, the relevant index into your GuidList array, the InstanceIndex, and the InstanceCount required. You have to fill the Buffer (that has a maximum size of BufferAvail). Fill in InstanceLengthArray, an array of ULONGs giving the length of each instance.
In the buffer, each instance's data should be aligned on an 8-byte boundary. The actual data should correspond to the class MOF definition. A MOF string should be returned as a counted Unicode, MOF Boolean returned as a BOOLEAN, MOF uint32 returned as a ULONG, MOF uint64 as a ULONGLONG, etc.
Listing 12.6 shows how the Wdm3 QueryWmiDataBlock routine handles requests for the Wdm3Information and MSPower_DeviceEnable WMI data blocks. For Wdm3Information, QueryWmiDataBlock first works out the size of buffer required and checks that the given output buffer is large enough. It then stores the BufferLen, BufferFirstWord, and SymbolicLinkName property values. The SymbolicLinkName string is stored in counted Unicode (i.e., a USHORT count followed by the wide char characters). Finally, the number of bytes set is stored. Only one instance of a Wdm3Information data block is ever processed, so this size is stored in the first ULONG pointed to by InstanceLengthArray.
QueryWmiDataBlock handles a MSPower_DeviceEnable WMI data block request in a similar way.
Listing 12.6 QueryWmiDataBlock routine
NTSTATUS QueryWmiDataBlock(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN ULONG GuidIndex, IN ULONG InstanceIndex, IN ULONG InstanceCount, IN OUT PULONG InstanceLengthArray, IN ULONG OutBufferSize, OUT PUCHAR PBuffer) {
DebugPrint("QueryWmiDataBlock: GuidIndex %d, InstanceIndex %d, "
"InstanceCount %d, OutBufferSize %d",
GuidIndex,InstanceIndex, InstanceCount, OutBufferSize);
PWDM3_DEVICE_EXTENSI0N dx = (PWDK3_DEVICE_EXTENSION)fdo->DeviceExtension;
NTSTATUS status;
ULONG size =0;
switch( GuidIndex) {
case WDM3_WMI_GUID_INDEX: // Wdm3Information
{
ULONG SymLinkNameLen = dx->ifSymLinkName.Length;
size = sizeof(ULONG)+sizeof(ULONG)+SymLinkNameLen+sizeof(USHORT);
// Check output buffer size
if (OutBufferSize<size) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}
// Store uint32 BufferLen
*(ULONG *)PBuffer = BufferSize;
PBuffer +-= sizeof (ULONG);
// Store uint32 BufferFirstWord
ULONG FirstWord = 0;
if (Buffer!=NULL && BufferSize>=4) FirstWord = *(ULONG*)Buffer;
*(ULONG *)PBuffer = FirstWord;
PBuffer += sizeof(ULONG);
// Store string SymbolicLinkName as counted Unicode
*(USHORT *)PBuffer = (USHORT)SymLinkNameLen;
PBuffer += sizeof(USHORT);
RtlCopyMemory(PBuffer, dx->ifSymLinkName.Buffer, SymLinkNameLen);
// Store total size
*InstanceLengthArray = size;
status = STATUS_SUCCESS;
break;
}
case GUID_POWER_DEVICE_ENABLE_INDEX: // MSPower_DeviceEnable
{
size = sizeof(BOOLEAN);
// Check output buffer size
if (OutBufferSize<size) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}
// Store boolean IdlePowerDownEnable in Enable property
*(BOOLEAN*)PBuffer = dx->IdlePowerDownEnable;
// Store total size
*InstanceLengthArray = size;
status = STATUS_SUCCESS;
break;
}
default:
DebugPrintMsg("QueryWmiDataBlock: Bad GUID index");
status = STATUS_WMI_GUID_NOT_FOUND;
break;
}
return WmiCompleteRequest(fdo, Irp, status, size, IO_NO_INCREMENT);
}
The Wdm3 SetWmiDataBlock routine shown in Listing 12.7 lets a user change the device extension IdlePowerDownEnable settings. If the MSPower_DeviceEnable GUID index is given and the input buffer size is large enough, the BOOLEAN at the start of Pbuffer is stored in IdlePowerDownEnable.
SetWmiDataBlock then acts on the new power down enable setting. If powering down is now enabled, PoRegisterDeviceForIdleDetection is called, if necessary. If now disabled, PoRegisterDeviceForIdleDetection is called to turn off idle detection; if the Wdm3 device is powered down, SendDeviceSetPower is called to power the device up.
Listing 12.7 SetWmiDataBlock routine
NTSTATUS SetWmiDataBlock(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN ULONG GuidIndex, IN ULONG InstanceIndex, IN ULONG BufferSize, IN PUCHAR PBuffer) {
DebugPrint("SetWmiDataBlock: GuidIndex %d, InstanceIndex %d, BufferSize %d",
GuidIndex,Instancelndex,BufferSize);
PWDM3_DEVICE_EXTENSION dx = (PWDM3_DEVICE_EXTENSION)fdo->DeviceExterision;
if (GuidIndex==GUID_POWER_DEVICE_ENABLE_INDEX) // MSPower_DeviceEnable
{
if (BufferSize<sizeof(BOOLEAN))
return WmiCompleteRequest(fdo, Irp, STATUS_BUFFER_TOO_SMALL, 0, IO_NO_INCREMENT);
// Get Enable property into IdlePowerDownEnable
dx->IdlePowerDownEnable = *(BOOLEAN*)PBuffer;
// Action IdlePowerDownEnable
if (dx->IdlePowerDownEnable) {
DebugPrintMsg("SetWmiDataBlock: Enabling power down");
// Enable power down idling
if (dx->PowerIdleCounter==NULL) dx->PowerIdleCounter = PoRegisterDeviceForIdleDetection(dx->pdo, 30, 60, PowerDeviceD3);
} else {
DebugPrintMsg("SetWmiDataBlock: Disabling power down");
// Disable power down idling
if (dx->PowerIdleCounter!=NULL) dx->PowerIdleCounter = PoRegisterDeviceForIdleDetection(dx->pdo, 0, 0, PowerDeviceD3);
if (dx->PowerState>PowerDeviceD0) {
DebugPrintMsg("SetWmiDataBlock: Disabling power down: power up");
SendDeviceSetPower(dx, PowerDeviceD0);
}
}
return WmiCompleteRequest( fdo, Irp, STATUS_SUCCESS, 0, IO_NO_INCREMENT);
}
return FailWMIRequest(fdo, Irp, GuidIndex);
}
A SetWmiDataItem callback handles the setting of one data item in an instance. This routine is not called in Wdm3. Listing 12.8 shows how SetWmiDataItem calls FailWMIRequest to reject the WMI IRP. FailWMIRequest fails the IRP with STATUS_WMI_GUID_NOT_FOUND if an invalid GUID was given, or STATUS_INVALID_DEVICE_REQUEST otherwise.
Listing 12.8 SetWmiDataItem routine
NTSTATUS SetWmiDataItem(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN ULONG GuidIndex, IN ULONG InstanceIndex, IN ULONG DataltemId, IN ULONG BufferSize, IN PUCHAR PBuffer) {
DebugPrint("SetWmiDataItem: GuidIndex %d, InstanceIndex %d, DataItemId %d, BufferSize %d",
GuidIndex.InstanceIndex, DataItemId, BufferSize);
return FailWMIRequest(fdo, Irp, GuidIndex);
}
NTSTATUS FailWMIRequest(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN ULONG GuidIndex) {
DebugPrint("FailWMIRequest: GuidIndex %d",GuidIndex);
NTSTATUS status;
if (GuidIndex<0 || GuidIndex>=GUID_COUNT) status = STATUS_WMI_GUID_NOT_FOUND;
else status = STATUS_INVALID_DEVICE_REQUEST;
status = WmiCompleteRequest(fdo, Irp, status, 0, IO_NO_INCREMENT);
return status;
}
Listing 12.9 shows how I think the optional ExecuteWmiMethod routine should be implemented. As I stated earlier, I could not get mofcomp to compile the PowerDown method in the Wdm3Information WMI data block. Therefore, I have not so far been able to test this method.
Listing 12.9 ExecuteWmiMethod routine
NTSTATUS ExecuteWmiMethod(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN ULONG GuidIndex, IN ULONG InstanceIndex, IN ULONG MethodId, IN ULONG InBufferSize, IN ULONG OutBufferSize, IN OUT PUCHAR Buffer) {
DebugPrint("ExecuteWmiMethod: GuidIndex %d, InstanceIndex %d, "
"MethodId %d, InBufferSize %d OutBufferSize %d",
GuidIndex, InstanceIndex, MethodId, InBufferSize, OutBufferSize);
PWOM3_DEVICE_EXTENSION dx = (PWDM3_DEVICE_EXTENSION)fdo->DeviceExtension;
if (GuidIndex==WDM3_WMI_GUID_INDEX && MethodId==0) {
DebugPrintMsg("ExecuteWmiMethod: PowerDown method");
// Power Down
if (dx->PowerState<PowerDeviceD3) SendDeviceSetPower(dx, PowerDeviceD3);
return WmiCompleteRequest(fdo, Irp, STATUS_SUCCESS, 0, IO_NO_INCREMENT);
}
return FailWMIRequest(fdo, Irp, GuidIndex);
}
To fire a WMI event, simply call WmiFireEvent, passing the relevant WMI event block GUID, the InstanceIndex, and any event data. WmiFireEvent makes the appropriate call to IoWmiWriteEvent.
The Wdm3 driver provides a helper function Wdm3FireEvent, shown in Listing 12.10. This takes a NULL-terminated wide string, formats it into a Wdm3Event message, and calls WmiFireEvent.
A user application must ask for events first by setting an Enable flag. This request arrives at the driver in its WmiFunctionControl optional callback routine. If the Function parameter is WmiEventControl, the new Enable setting is stored in the WMIEventEnabled flag in the device extension. WmiFunctionControl is also called to ask a driver to collect WMI data blocks that were marked as being expensive to collect.
The WMIEventEnabled flag is initially FALSE, which means that no events are generated. As I have found no way of registering for events, the Wdm3FireEvent and WmiFunctionControl functions have not been tested.
Listing 12.10 Wdm3FireEvent and WmiFunctionControl routines
void Wdm3FireEvent(IN PDEVICE_OBJECT fdo, wchar_t* Msg) {
DebugPrint("Wdm3FireEvent: Msg %S", Msg);
PWDM3_DEVICE_EXTENSION dx = (PWDM3_DEVICE_EXTENSION)fdo->DeviceExtension;
if (!dx->WMIEventEnabled) return;
// Get MsgLen in bytes
int MsgLen = 0;
wchar_t* Msg2 = Msg;
while (*Msg2++!=0) MsgLen += sizeof(wchar_t);
// Allocate event memory
PUSHORT pData = (PUSHORT)ExAllocatePool(NonPagedPool, MsgLen+2);
if (pData==NULL) return;
PUSHORT pData2 = pData;
*pData2++ = MsgLen;
RtlMoveMemory(pData2, Msg, MsgLen);
WmiFireEvent(fdo, (LPGUID)&WDM3_WMI_EVENT_GUID, 0, MsgLen+2, pData);
}
NTSTATUS WmiFunctionControl(IN PDEVICE_OBJECT fdo, IN PIRP Irp, IN ULONG GuidIndex, IN WMIENABLEDISABLECONTROL Function, IN BOOLEAN Enable) {
DebugPrint("WmiFunctionControl: GuidIndex %d, Function %d, Enable %d",
GuidIndex, Function, Enable);
PWDM3_DEVICE_EXTENSION dx = (PWDM3_DEVICE_EXTENSION)fdo->DeviceExtension;
if (GuidIndex==WDM3_WMI_EVENT_GUID_INDEX && Function==WmiEventControl) {
DebugPrint("WmiFunctionControl: Event enable %d", Enable);
dx->WMIEventEnabled = Enable;
return WmiCompleteRequest(fdo, Irp, STATUS_SUCCESS, 0, IO_NO_INCREMENT);
}
return FailWMIRequest(fdo, Irp, GuidIndex);
}
The WMl features of the Wdm3 driver can be tested in three ways. In all cases, the DebugPrint output of the checked build gives copious trace information about the requests as they happen.
The first thing I must say is that I could not get the driver to compile or run in Windows 98. I am sure that I could have got the Wdm3 driver to compile if I had copied the correct headers and libraries into the W98 DDK, but that did not seem appropriate. It is possible that the one test Windows 98 computer available had its WBEM/WMI runtime corrupted. In the Windows 2000 beta 2, I obtained an update to the WBEM/WMI SDK. Perhaps this update was not meant for Windows 98 and therefore messed up the runtime environment. Anyway, I was able to do the rest of my testing only in Windows 2000.
The MSPower_DeviceEnable WMI data block is used by the Device Manager to display the Power Management tab in the device properties box, as shown in Figure 10.3 in Chapter 10. It retrieves the Enable property, eventually in a call to QueryWmiDataBlock. If the user changes the setting, it is changed in SetWmiDataBlock when the device properties box is closed.
Alternatively, the standard WBEM Object Browser can be used to inspect the MSPower_ DeviceEnable or Wdm3Information WMI data blocks. Logon to the \Root\WMI namespace as the current user, select the Wdm3Information class, for instance, and select one of the Wdm3 device instances.
Figure 12.1 shows the Wdm3Information display for the Wdm3 device that has an instance name of Root\\Unknown\\0004_0 as its key property. The Wdm3Test application has been run, so the current buffer length is 4. The BufferFirstWord property has a decimal value of 2882400001, which is hex 0xabcdef01.
W2000 Beta 3 includes the WBEMTest tool (System32\WBEM\wbemtest.exe) that can also be used to inspect WBEM classes and instances.
By the way, I found that the WBEM Object Browser seemed a bit sluggish. This is possibly because it is using WQL to query the database. In contrast, the Device Manager's use of WMI when dealing with a device's Power Management tab seems to run quickly.
Figure 12.1 WBEM Object Browser inspecting a Wdm3Information instance
You can use the WBEM CIM Studio to look at the class definitions. If you change a WMI data block, use WBEM CIM Studio to delete the old class definition. A reboot of the computer seems to be necessary to reregister the class correctly.
I could not work out how to use the WBEM Event Registration utility to enable events in the Wdm3 driver.
This chapter has shown that it is reasonably straightforward to add administrative control facilities to a driver using the Windows Management Instrumentation extensions for WDM. Despite claims to the contrary, I could not get WMI to run in Windows 98, reducing its usefulness. I also could not get WMI events and methods to work in the Windows 2000 beta 2.
The next chapter looks at NT events, another way of reporting important events to NT 3.51, NT 4, and Windows 2000 administrators. It also concludes my look at the features of the Wdm3 example driver.
In Windows 98, select the Control Panel "Add/Remove Programs" applet. Click the Windows Setup tab. Highlight the Internet Tools options. Click on Details. Check the "Web-based Enterprise Mgmt" box. Click OK to proceed with the installation. You will need the WBEM core kit and possibly other components for NT 4 and Windows 95.