| indowsÂ® Management Instrumentation (WMI) is a MicrosoftÂ® technology that simplifies system and hardware management by providing one consistent, standardized interface to application and device management data. WMI is built on standards set by the Desktop Management Task Force (DMTF) to make it useable across systems and platforms. For a broader view of WMI, see Jeffrey Coopersteinï¿½s article, "Windows Management Instrumentation: Administering Windows and Applications across Your Enterprise," in this issue.|
In this article we will focus on building a WMI provider. A WMI provider is an object-oriented database that provides data about apps, devices, and other sources of system information. Unlike a standard relational database, it supports classes, instances of those classes, and inheritance relationships between classes. Many of the terms used by WMI map fairly well to the relational database concepts youï¿½re probably already familiar with (see Figure 1). Remember, however, that these are rough approximationsâ"WMI offers features that a relational database does not, and vice versa.
Unlike standard relational databases, WMI can retrieve instances of classes either by reading the repository (a persisted description of the objects WMI manages), or by calling a COM object to retrieve the information. This architecture offers significantly more flexibility in the types of information that can be managed. These COM objects are referred to as providers.
Conceptually, WMI looks like the illustration shown in Figure 2. Here you can see a client application calling into WMI for information. WMI examines its repository to determine whether the information is stored statically or is provided by one or more providers. If a provider supplies the information, WMI loads the provider and requests the information. The provider will then do whatever it needs to doâ"perform IOCTL calls, read registry keys, make Win32Â® API calls, and so onâ"to retrieve the information and send it back to WMI. When WMI has the information, it responds to the clientï¿½s request.
|Figure 2 WMI Architecture |
WMI also supports events. Using WMI, it is possible for a client application to be notified when certain events occur. The runtime services supported by WMI operate through the WinMgmt service (WINMGMT.EXE), also referred to as the Common Information Model Object Manager (CIMOM). These services provide certain built-in event types, but custom types can also be defined and fired by event providers. This capability makes it possible, for example, for devices to report when they are overheating, or applications to report when they are running out of resources.
The fact that WMI supports inheritance allows for a general schema that defines the relationships between the manageable components of a computer. The DMTF has defined a schema that does just that. Their schema (known as the Common Information Model, or CIM) describes many of the common classes needed to define a computer. A (very) small extract of the CIM schema is shown in Figure 3. However, the DMTF only defines where in the schema classes should be, as well as a very general set of properties. It is up to individual hardware and software manufacturers to add classes at the very lowest levels describing the capabilities of their specific components. Additionally, the DMTF only defines the schema. They donï¿½t create the providers that report the dataâ"this is a task for you, the provider developer.
|Figure 3 CIM Schema Excerpt |
The complexity of your provider will primarily depend on how much information you have available. Information for a single device will probably require just a few classes and perhaps an association or two. The total management of an e-mail or database server could require well over a hundred classes. Once the schema is defined, writing a provider for a single class is generally easy. Itï¿½s just a matter of calling the API for your component, taking the data from your structures, and passing it to WinMgmt.
Why Use WMI? You may well be asking yourself why you should use WMI. After all, you already have an API that communicates with your component. Why should you add another layer on top of it? You may be surprised to learn that there are compelling reasons to do so.
First, consider that every operating system component requires learning some new paradigm. As a simple example, consider the differences between enumerating files and enumerating users. Who is responsible for allocating memory? Who is responsible for freeing it? Do you use CloseHandle or CloseFile? There are just about as many answers to these questions as there are APIs. With WMI, client applications interact with these objects indirectly via consistent, well-defined, and immutable COM interfaces.
Second, consider the fact that enumeration, getting and setting data, deleting instances, and creating instances are all functions common to essentially every API set, yet every API set has some unique way to handle them. For instance, there are four different ways to represent dates and times within a single API grouping. Not only is there an overlap in functionality, but there is often precious little documentation on the relationships between API sets. Can the third element in this network API structure be passed as the second parameter to that RAS function? Well, maybe, sometimes; other times you just have to try it and find out. With WMI, client applications use the standard WMI calls, and leave it up to the provider to figure out the details.
WMI provides a single, consistent interface that is well-documented. This interface allows for scripting, remoting, describing the relationships between classes, and inheritance to extend existing classes with more specific data. Combine this with the fact that WMI supports events, and you have a very powerful tool. Indeed, Microsoft has chosen WMI as the standard for the management of its operating systems and applications, and Iï¿½m sure you will soon see many popular programs employing this technology.
So if you want to use the WMI interface, does that mean you have to publish your proprietary interface in addition to the WMI interface? Not at all. Just publish one interface, and make it WMI.
Getting Started Letï¿½s begin developing WMI framework providers. In particular, we will write providers using the WMI provider framework in conjunction with certain tools available through the WMI SDK (http://msdn.microsoft.com/downloads/default.asp?URL=/code/topic.asp?URL=/MSDN-FILES/028/000/015/topic.xml) that allow you to write providers quickly and easily using C++ classes instead of the COM interfaces for providers. We will focus on which methods your provider needs to support, and how to implement those methods to achieve the greatest possible performance.
Prior to writing your provider, you must make a number of design decisions, most of which are dictated by what you want your provider to do, and by the role the provider will play relative to the overall schema. When defining your schema, you are really defining the specification of your provider. The schema defines what information is to be made available, what operations can be performed on that data, and relationships between different groups of data. Not only is this the first step you will take when defining your provider, it is probably the most important.
When designing your provider, you should decide which class (the schema class, not any C++ class youï¿½re using) if any to derive it from. If your schema is not related to any other schema on the machine, you should consider creating your own namespace rather than deriving your schema classes from an existing class. However, if you have an existing namespace in which other providers and classes are defined (such as the CIMV2 namespace, in which all the Win32 system classes reside), you can add your provider and classes to that namespace. You can also derive your classes from existing classes. This establishes a schema relationship between your class and others defined in the schema. The SDK contains a file named SCHEMA.TXT that will help with these decisions.
Implementing a Provider We have chosen to illustrate provider development using classes that represent Windows NTÂ® user accounts (MSJ_User) and groups (MSJ_Group) and the relationship between them (MSJ_GroupMembership). You should note that, because users and groups are only defined on Windows NT and Windows 2000, our provider will not operate on Windows 95 or Windows 98. In general, however, a single WMI provider can support multiple platforms.
We used the WMI CIM Studio to create the namespace and schema classes to be used by our provider. To export the schema classes to a Managed Object Format (MOF) file (a standardized means of representing a schema, defined by the DMTF), we used the WMI MOF Generation Wizard. We then used the WMI Code Generator Wizard (also available through the WMI CIM Studio) to create all the files necessary to get the provider up and running. For details on how to use these tools, please see the Framework Tutorial in the WMI SDK.
The files produced by the code generator include the provider MOF file, a MAINDLL.CPP file (containing provider self-registration code and the standard COM in-proc server exported functions), a makefile, a .def file, and default implementation header and .cpp files. The skeletons of the functions produced by the code generator must be filled in, but this is a good start.
At this point we have created two separate MOFs: one from the WMI MOF Generation Wizard to define the classes, and a second from the WMI Code Generator Wizard that registers your provider and describes what type of provider it is. These MOFs should be combined into a single MOF for ease in distribution. Figure 4 shows these sections merged into a single MOF. You will also see some code at the top of the MOF that creates the namespace.
Throughout the remainder of the article weï¿½ll be examining the code for the sample provider. Due to the size of the listings, we recommend downloading the code from the link at the top of this article. The code is found in three flavors: raw from the code generator, basic implementation, and advanced implementation. The class MSJ_User is located in MSJ_User.cpp. As you can see from the heavily commented code produced, we now have function placeholders for GetObject, EnumerateInstances, ExecQuery, PutInstance, DeleteInstance, and ExecMethod. These six functions are the guts of your provider. They are the only entry points to your provider that the framework will call. However, you need not support them all. For an instance provider (a provider that dynamically supplies instances of a class), it is sufficient to support only GetObject and EnumerateInstances; for a method provider (a type of provider that supports the use of provider-declared functions), you need only implement ExecMethod. For our sample combination provider, however, we have included them all to illustrate how each is implemented.
Before discussing these functions in further detail, a couple of additional comments about the files generated by the code generator are in order. First, look at this line from MSJ_User:
This declares a static instance of your class. This instance, which is created when your provider loads, allows the framework to map the class internally as one that it interacts with. All calls from WinMgmt involving MSJ_User will be routed by the framework through this instance. This is important when you consider that WinMgmt is a multithreaded application. It is possible that multiple calls will be made into your class concurrently, and each will be routed through this instance.
CMSJ_User MyMSJ_UserSet (PROVIDER_NAME_MSJ_USER, L"NameSpace") ;
The direct implication of this functionality is that the implementation of framework classes must be thread-safe. For this reason, we do not recommend the use of member or global variables to store data. Because you are building a dynamic provider, this is generally not a problem; you will want to regenerate volatile data in response to each call. For instance, the class MSJ_GroupMembership should query the OS for group membership each time the class is enumerated, as group membership may have changed since the last time this class enumerated. Storing this data in a member array is tempting from a performance standpoint, but it is not the appropriate choice unless you are modeling truly static data, in which case you can create static instances of the class rather than writing a dynamic instance provider.
Itï¿½s now time to implement each of the six functions mentioned previously. We will describe both basic and advanced implementations of GetObject and EnumerateInstances. Because implementations of ExecQuery, ExecMethod, PutInstance, and DeleteInstance are not required, we will describe each of these functions as part of an advanced implementation only.
Implementing GetObject The purpose of GetObject is to locate a specific instance of a class. When GetObject is called, the keys to a specific instance are passed in. If our schema was designed correctly, the keys uniquely identify an instance of the actual object modeled by the corresponding schema class. It is the providerï¿½s job to assess whether an actual corresponding instance of the modeled object exists and, if it does, to fully populate all of the instanceï¿½s properties. By the time your providerï¿½s implementation of GetObject is called, the framework will have created a new instance of the class and filled in its key properties based on the object path specified by the client application making the call to GetObjectAsync.
The dynamic providerï¿½s job is to attempt to find the actual object represented by your class. If the object is found, the function should populate the nonkey properties and return WBEM_S_ NO_ERROR; if the object cannot be found, the function should return WBEM_E_NOT_FOUND; and if some other sort of error occurs, the function should return WBEM_E_PROVIDER_FAILURE (for a generic error), or any valid Win32 HRESULT.
This code illustrates a basic instrumentation of GetObject.
In this example, and in those that follow for the other methods that our provider instruments, we will attempt to separate the structure of the provider from the implementation details specific to the particular class and/or properties used in the examples. We do this to more clearly highlight those aspects of provider writing that will be common to most of the providers you write. You can see that the work of GetObject itself is straightforward. It attempts to find the instance requested. If the instance is found, it sets the nonkey properties for that instance.
HRESULT CMSJ_User::GetObject(CInstance* pInstance, long lFlags,
HRESULT hr = WBEM_E_NOT_FOUND;
hr = FindSingleInstance(pInstance, sLogonScriptPath,
hr = LoadPropertyValues(pInstance, sLogonScriptPath,
return hr ;
The two functions used in GetObjectâ"FindSingleInstance and LoadPropertyValues, or functions similar to theseâ"commonly appear in providers. FindSingleInstance takes as its arguments a pointer to the CInstance passed into GetObject and parameters used to hold data associated with that object. The advantage of this design is that we will be able to use the same LoadPropertyValues function in both our ExecQuery and our EnumerateInstances functions. This approach helps us avoid coding errors introduced by filling in different sets of properties in response to a GetObject versus an ExecQuery or EnumerateInstances.
In FindSingleInstance, we use the framework property extraction function GetCHString to extract the value of the instance key properties (Name and Domain in this case). Based on this key value, we find the matching user account. If the user is found, we store the properties we will use to populate the MSJ_User instance, and return WBEM_S_ NO_ERROR to indicate that we actually did find the instance. Note again that for our sample provider, which only supports local instances of user accounts, the Domain property is not requiredâ"its value will always be the name of the local machine. We have chosen to keep two keys for this class, however, for illustrative purposes.
It is important to note that you may not be able, in all cases, to retrieve all the properties of a particular instance. For such properties, simply do not set them. They will be presented to the client with a value of NULL, which by convention means no information is available. Do not, for instance, set a string property to an empty string in such a case. That would indicate that the property value was in fact obtained and was found to be empty. In cases where not all the properties could be obtained, you should still return WBEM_S_NO_ERROR or WBEM_S_PARTIAL_RESULTS from GetObject, as long as the object was found.
The function LoadPropertyValues takes the CInstance pointer passed into GetObject, as well as parameters containing the data we will set for the new instance. LoadPropertyValues simply uses CInstance functions to set the instanceï¿½s properties.
GetObject for Association Classes When instrumenting GetObject for association classes (such as MSJ_GroupMembership), your goal is the same, but the steps are different. To say that the association exists, you need to verify two things. First, do the association classï¿½s endpoints exist? In this case, do instances of MSJ_User and MSJ_Group really exist as specified in the association instance path? Second, is there a real association between the elements your association class models? In this example, is the specified user really a member of the specified group? Figure 5 contains the code used to make these tests. Only if both tests are true should our association class report WBEM_S_NO_ ERROR from GetObject.
In implementing GetObject for MSJ_GroupMembership, we have employed the function GetInstanceKeysByPath, which is supplied to us by the framework. This function performs a GetObject on the specified class, only retrieving its key properties, thereby avoiding the overhead of obtaining unnecessary, and possibly expensive, properties. This function is one of a number of framework functions that call back into WinMgmt (see Figure 6).
It is convenient to use WMI itself to instrument your classes in this manner, and it keeps our sample code clean, but you should be aware that extra overhead is generated as a result of calls back into WinMgmt. Given that the code to obtain group and user information is available, we could have cut and pasted that code directly into MSJ_GroupMembership, thereby improving our performance. This trade-off between performance and tidiness in your providers is one you will have to weigh when using GetInstanceKeysByPath or any other WinMgmt callback function.
A Better GetObject Although the work of GetObject seems simple enough, it is still a very important function to code efficiently. You might be lulled into thinking that this function, which returns only a single instance, is not the best candidate to spend your time optimizing. However, the class may contain certain expensive properties that may not be required by the caller. If you can avoid filling in these properties, you should.
Second, WinMgmt will call this function in response to a number of different client requests such as ExecMethod or Associator queries (which relate instances of the user class to instances of other classes). In such cases, WinMgmt only needs to confirm that the object exists. Fortunately, there are simple techniques for getting the best performance from your GetObject routine.
Figure 7 contains a revised version of GetObject that has been optimized to take advantage of these two situations. The first difference you will note is that the function signature for GetObject is different from the basic implementation. A new parameter, CFrameworkQuery& Query, has been included. The framework calls this overload of GetObject if the provider has implemented it; otherwise, the original GetObject is called. The CFrameworkQuery reference (which we will discuss in more detail later) can be used to obtain information regarding what the client caller really is interested in obtaining from the GetObject function call.
We have also defined a DWORD, dwRequestedProperties, which will contain a bitmask of the properties requested by the client. Values for the bitmask are defined in each classï¿½s header. We set this value in a new member function, GetRequestedProps:
GetRequestedProps is a helper function we use to encapsulate calls to the CFrameworkQuery function IsPropertyRequired, which returns true if the caller requested the property supplied as its argument. If the caller did not explicitly request the property, the function returns false. We use this function to modify a DWORD bitmask of the requested properties.
DWORD CMSJ_User::GetRequestedProps(CFrameworkQuery& Query)
DWORD dwReqProps = PROP_NONE_REQUIRED;
dwReqProps |= PROP_NAME;
dwReqProps |= PROP_DOMAIN;
dwReqProps |= PROP_SID;
DwReqProps |= PROP_LOGSCRIPTPATH;
The benefit of doing this extra work is realized in the FindSingleInstance and LoadPropertyValues functions. The SID property, which is a string representation of the security identifier associated with the particular user account, is a relatively expensive property to obtain. In this simple sample class, which only provides for accounts defined on the local machine, the overhead of determining the userï¿½s SID is not terribly onerous. However, if the user account were located on another domain, this account name resolution would involve costly network user lookup calls. For this reason, we would like to avoid filling this property if at all possible. In LoadPropertyValues, we only resolve the user account to its SID if this property was required.
Implementing EnumerateInstances As you might have guessed from the name, the job of the EnumerateInstances function is to return all the properties of all instances of the specified class. The implementation of the EnumerateInstances function is a whopping three lines:
We construct the calls this way to take advantage of the fact that certain variations of ExecQuery (which we will discuss later) can call the same Enumerate function.
(MethodContext* pMethodContext, long lFlags)
HRESULT hr = WBEM_S_NO_ERROR;
Hr = Enumerate(pMethodContext, PROP_ALL_REQUIRED);
The basic implementation of the Enumerate function in Figure 8 is where the real work takes place. Although much of the code in this example is specific to obtaining instances of user accounts, the overall function flow used here will appear in most of the providers you write. Essentially, for each instance of a Win32 user account, we need to create a new CInstance, which we do via the framework function CreateNewInstance. We then must set the key values for the instance (we make use of the creatively named framework function SetWCHARSplat to set the Name and Domain key properties). We then set the rest of the properties using the same helper function used in our GetObject routine: LoadPropertyValues. Finally, we commit the instance, release the pointer to CInstance, and return.
Note that we check the return value of the Commit function and add it as a condition to our inner loop. This is important, as it allows our Enumerate function to terminate quickly in response to a failed Commit, which can happen due to the clientï¿½s request to abort the enumeration. Without this check, the client would wait (if they were calling our provider synchronously) until our provider had finished processing all instances.
Improving EnumerateInstances From a performance perspective, not much can be done to improve the basic implementation of the Enumerate function shown in Figure 8. We must return all the instances of the class, and we must fill in all the properties of each instance. However, we would still recommend two changes to this routine. They are enhancements that can be employed in the other functions we will be discussing later. Both of these changes have to do with error recovery. As the code generator-supplied comment says, the function CreateNewInstance just might throw an exception. If this happened in the basic implementation, two problems arise: we would leak the buffer pInfo, and we might fail to release pInstance.
To prevent this, we have revised the code as shown in Figure 9 in the advanced implementation of MSJ_User.cpp. There youï¿½ll see two changes: a try/catch block, and the use of a smartpointer version of CInstance, CInstancePtr. CInstancePtr is defined in the header via the standard macro _COM_SMARTPTR_TYPEDEF, which results in Release being called automatically on the encapsulated interface whenever the object goes out of scope. This includes going out of scope if an exception is thrown. Also note that the memory allocated via the call to NetUserEnum is freed in the catch block, before we throw the exception again.
Advanced Implementation of ExecQuery The best way to get the most out of your provider is to implement query support by instrumenting the framework provider function ExecQuery. Improved performance is the sole reason for the use of ExecQuery. By specifying a query, clients (and WinMgmt itself) hope to evoke optimized instance retrieval from your provider by specifying which instances they want and what properties they are interested in. Because ExecQuery is all about performance, we will not describe a basic and an improved flavor of this function; the version we will describe is representative of any ExecQuery you should write.
If you do not implement this function and a client issues a query against an object supported by your provider, WinMgmt will call your EnumerateInstances function, thereby obtaining every property of every instance of the class in question. WinMgmt will then post-filter those instances and properties that do not fulfill the request. Take the example of a file system provider. You can imagine just how intolerable the performance of this approach would be. If a client were interested in obtaining a resultset containing just the names of the files on the root of the C: drive, for example, the provider would enumerate all files on all drives, including mapped network shares, and attempt to obtain all possible properties for every instance.
Fortunately, supporting queries is easy using the framework. You do not need to write extensive WMI Query Language (WQL) query parsers. Easy-to-use functions are available to help you process the WHERE clause (which specifies which instances are returned) and the set of properties requested of each instance.
In our sample implementation of ExecQuery we will first consider how this routine optimizes on WHERE clause expressions. The framework function GetValuesForProp is used to determine which instances of the specified class are explicitly requested by the query.
Hence, the WHERE clause
HRESULT GetValuesForProp(LPCWSTR wszPropName,
would result in GetValuesForProp returning an array of two strings: LocalComputer and Redmond. GetValuesForProp operators other than the equal sign will not result in GetValuesForProp returning any elements.
WHERE Domain = "LocalComputer" OR
Domain = "Redmond"
We use the results from GetValuesForProp to limit which domains we look at (only the local computer) and which accounts we attempt to find. If specific users were requested in the query, we use the helper FindSingleInstance; if no specific users were requested, we enumerate all local user accounts using the helper function Enumerate. If a particular domain is specified, we filter the request to look only for accounts defined locally.
As was the case in our instrumentation of GetObject, we also optimize our queries to obtain only those properties required to satisfy the query request. Hence, we use the helper function GetRequestedProps and pass the values it returns along to both FindSingleInstance and Enumerate.
ExecQuery for Association Classes As was the case in the implementation of GetObject for our sample association class, certain aspects of ExecQuery routines are unique to association class instance providers and bear further discussion. Note that implementing ExecQuery for association classes is a relatively advanced exercise, so we will include a brief discussion of our implementation for completeness. We recommend that you become comfortable with the other concepts discussed here before you ponder the intricacies of this code.
MSJ_GroupMembership.cpp illustrates how to instrument ExecQuery for the association class MSJ_GroupMembership. The MSJ_GroupMembership association class lists the groups to which various users belongâ"and, by implication, which users belong to various groups.
The MSJ_GroupMembership association class consists of only the object paths (a string consisting of the key properties that uniquely identify that instance) to the user and the group. We chose to instrument ExecQuery for this class rather than relying on WinMgmt to postprocess the results of our EnumerateInstances function. This way we can optimize this classï¿½s providerï¿½s performance for certain types of queries. Specifically, if the client issues WQL queries requesting the members of a specific group or the group membership of a specific user, we can reduce the work our provider has to do to satisfy the request. In our simple example schema in which we only provide instances for local groups, the optimization is not overly significant. However, if the classes in our schema provided instances for all users and groups on all domains visible to the machine executing the query, optimization on queries such as these would be critical to our performance. Without such optimization, every query would result in all group membership instances being enumerated, potentially having all but one instance thrown away.
In our instrumentation of ExecQuery, we determine what was specified in the queryï¿½s WHERE clause and decide what approach will optimally resolve it. If both users and groups have been specified, we get the users indicated by the query and check to see if each user belongs to any of the specified groups. If it does, we create a new instance of the association class, fill in its properties, and commit the instance (returning it to WinMgmt).
Another type of query is where one or more groups were specified. For each such group we get its members, create instances of the association class, and commit each new instance. If one or more users were specified, we get the groups each user belongs to, create new association class instances, and commit each instance.
If the query was of a form other than these three, we simply do an enumeration of the association class and allow WinMgmt to post-filter the resultset such that the query is satisfied.
Advanced Implementation of ExecMethod In representing software or hardware using a WMI schema you define, you are not limited to reporting instances of the objects you are modelingâ"WMI also allows you to execute methods against the objects you define. For instance, a class that modeled CD-ROM drives might have an Eject method defined.
Methods come in two flavors: those that operate on instances of a class (instance methods), and those that operate against the class itself (static methods). Unlike instance methods, static methods contain the "static" qualifier in the classï¿½s MOF. An example of a static method would be a method called CountDirectories defined on a class that models file system directories (such as the class Win32_Directory) that could return a count of all the directories on the machine. This involves the class in general, not a particular instance of the class.
In our sample MSJ_User class we have included an instance method called Rename, which allows you to change the name of a user account. MSJ_User.cpp contains the code for our implementation of MSJ_Userï¿½s ExecMethod function. The first thing to note about the implementation of ExecMethod is that it is the single entry point called by the framework for all methods your class supports. Therefore, your first task in implementing ExecMethod is to determine exactly which method the client called.
Also potentially confusing to first-time provider writers are the return values from methods. The MOF description of the Rename method is as follows:
The uint32 return code is used to indicate whether the method performed as expected. What doesnï¿½t appear in the MOF, however, is the HRESULT value returned to the client for the ExecMethod call. This value is used to indicate whether the provider successfully managed to call our method.
[Description("The Rename method allows one to change
the name of a user account"): ToInstanceToSubClass]
uint32 Rename([IN] string NewName);
You should also note that methods can have in parameters, out parameters (our sample does not have any out parameters), both, or neither. The return value can be any valid CIM datatype. Whether or not you specify any out parameters in your function definition in the MOF, you will always have one invisible out parameter called ReturnValue. ReturnValue holds the result of the method called (the uint32 value in our Rename method). In the sample you can see that we set this out parameter just prior to returning from ExecMethod.
PutInstance and DeleteInstance For instance providers, the methods GetObject and EnumerateInstances are the only functions that your provider is required to support. For method providers, ExecMethod is the only function the provider must support. As you have seen, ExecQuery, although not absolutely required, offers the greatest potential for improving the performance of your provider.
There are still two functions to discuss: PutInstance and DeleteInstance. These functions are not required for any provider, although they do add functionality that your clients may expect.
PutInstance actually serves two purposes: creating new instances of a class and modifying the (nonkey) properties of an existing instance. The action taken is based on the lFlags parameter of the PutInstance call:
The lFlags parameter can be one of several values. WBEM_ FLAG_CREATE_OR_UPDATE signals the provider that the caller would like to create a new object (as specified by the key properties of the Instance parameter), if such an object does not already exist. If an object already exists as specified by the key properties, this signals that the caller would like to modify the properties of that object. WBEM_FLAG_UPDATE_ONLY specifies that the caller wants to update the properties of an existing instance. It returns an error if the specified instance does not already exist. WBEM_ FLAG_CREATE_ONLY requests that a new instance should be created as specified. An error should be returned if such an instance already exists.
PutInstance(const CInstance& Instance, long lFlags);
MSJ_User.cpp also contains the sample code for the PutInstance function of the class MSJ_User. You need not support all three options specified by the lFlags parameter, although we do here. If you do not support a particular lFlag value, you should return WBEM_E_UNSUPPORTED_PARAMETER. If lFlags has been specified by a value other than the three shown here, your PutInstance function should return WBEM_E_INVALID_PARAMETER. If everything has been specified properly and in a manner your provider supports, and if the object is created or updated successfully, you should return WBEM_S_NO_ERROR.
As you can see, we condition the behavior of our PutInstance implementation on the value of lFlags. We also make use of our helper function FindSingleInstance (from our discussion of GetObject) and the new helper functions Update and Create. The latter two functions update an existing instance with new property values (really only the LogonScriptPath property) and create a new instance with the property values supplied in the CInstance parameter, respectively.
Just about the easiest function to implement is DeleteInstance. As shown in MSJ_User.cpp, the process is merely to find the actual object specified by the CInstance parameter and, if it exists, delete it. If it existed, and was deleted successfully, you should return WBEM_S_NO_ERROR. If it does not exist, you should return WBEM_E_NOT_FOUND. If an error is encountered, return the appropriate HRESULT.
Testing the Provider After implementing the methods you have decided to support in your provider, you are ready to test it. To do so, be sure you have first done the following:
Your provider should now be installed, and your classes and the provider should be in the CIM Repository. Now you can use CIM Studio to connect to the namespace your providerï¿½s classes reside in and enumerate the namespaceï¿½s classes. You should see the classes defined in your MOF in the list of classes returned.
- Compile your MOF files.
- Build a debug version of your provider, and link it to the debug version of the framework (FRAMEDYD.DLL), not the release version (FRAMEDYN.DLL). This enables additional error checking in the framework.
- Register your provider using regsvr32. If you used the code generator to produce the skeleton of your provider, everything you need for provider self-registration and unregistration has been automatically generated for you and appears in the file MAINDLL.CPP.
- Enable detailed logging support for WinMgmt. To do this, go to the registry key HKEY_LOCAL_MACHINE\SOFTWARE\ Microsoft\WBEM\CIMOM and change the Logging entry from 1 (the default) to 2. The log file that you will be interested in examining is located in %WINDIR%\System32\ WBEM\Logs, and is called FRAMEWORK.LOG.
If you note any missing instances, extra instances, invalid or missing data, assertions, or other problems, you will need to debug your provider. As an in-process provider (which is what the code generator creates), you are in-proc to WinMgmt. Therefore, WINMGMT.EXE is the image associated with the process you should be debugging. It is a good idea to be logged in as an administrator on the machine you will be debugging on because WinMgmt runs as a system service. As an administrator, you will have the permissions to debug that process. By default, only administrators are granted full access to WMI.
If you are debugging using Visual Studio, fire it up, then attach to WINMGMT.EXE. In the Project/Settings dialog, add your provider as well as FRAMEDYD.DLL to the list of additional DLLs you will be debugging. Attaching to WINMGMT.EXE this way attaches you to the process as it runs under the local system account. When you halt your debug session, you will stop the WinMgmt service. To start debugging again, you should first restart the service (you can issue the command "net start Winmgmt" from a command prompt), then reattach.
You can also debug your provider by starting WinMgmt under the debugger (with the F5 key in Visual Studio). This will start WinMgmt under the context of the user currently logged on rather than as a system service. This may be fine for you as the debugger, but be aware that this is not how WinMgmt will be running on most machines that will be using your provider. As another variation on debugging, if you elect to start WinMgmt from the debugger, you can specify the command-line switch /EXE, which will cause WinMgmt to run as an executable rather than as a service.
You can now set breakpoints where appropriate in your modules, most likely at the entry points to GetObject, EnumerateInstances, ExecQuery, and the other functions we have discussed. Your first task in testing your provider should probably be to run an enumeration of your classï¿½s instances, which you would request by clicking the Enumerate Instances button on CIM Studio. When you do this, you should break into the debugger at your EnumerateInstances function. If this does not happen, it is likely due to one of the following:
You should be aware that your provider can be called on multiple threads from WinMgmt. For this reason, you may see your execution point jumping about in your module. You can use Visual StudioÂ® to suspend threads to prevent this from happening. You may also see your execution point jumping from your code into the framework exception routines. This will happen in the event of a general protection fault or C++ exception, as the framework catches these automatically and converts them into error codes.
- You didnï¿½t register your provider with COM using regsvr32
- Your provider is not registered correctly with CIMOM (you need to run MOFCOMP.EXE). Make sure that it is listed in your MOF as being either an instance of __InstanceProviderRegistration or __MethodProviderRegistration, or both, depending on your implementation. Also make sure that your provider is correctly specified as an instance of __Win32Provider. The correct provider name and its classid should be specified. Examples of these settings appear in Figure 4.
- You linked to the release version of the Framework, FRAMEDYN.DLL for your debug build.
- The debug version of the Visual C++Â® runtime library, MSVCRTD.DLL, is missing.
- You linked statically to the C runtime library rather than to MSVCRTD.DLL. The framework requires that you use this rather than the static C runtime library.
- Your MOF file is either missing the dynamic qualifier on the class you are calling, or the provider qualifier is missing or incorrect. It should give the name of your providerâ"in the case of our sample, MSJSampProv.
- You may have a provider class ID mismatch between the MOF and MAINDLL.CPP.
If you successfully see all the instances of your class, you should attempt to do a GetObject on each instance. You can do this using your own client code or a tool such as WBEMDUMP.EXE, which ships with the WMI SDK. With WBEMDUMP.EXE, using the /G option when requesting an enumeration will perform a GetObject on each enumerated instance. You should make sure that you cannot retrieve bogus instances from GetObject by specifying the path of an object that does not really exist.
You should also test case sensitivity in your GetObject code. In general, object paths should be case-insensitive, so you should be able to specify any combination of upper or lower case in your object path and still retrieve the proper instance. In the case of classes with complex keys (that is, more than one key, such as our MSJ_Group and MSJ_User classes, which each have two key properties, Domain and Name), you should test that the order in which the keys are specified does not affect the providerï¿½s behavior.
To test ExecQuery, you should specify as many combinations of WHERE clauses and properties as you have provided optimized support for in your provider, and confirm that your code follows the optimized code path when it should rather than falling into an enumeration. Testing PutInstance, DeleteInstance, and ExecMethod are all very straightforward, and follow the same pattern. Test that they work for valid data, and that they fail for invalid data, returning the proper error codes in each case. For instance, attempt to execute a method that doesnï¿½t existâ"you canï¿½t use CIM Studio to do this, but you could use a Visual BasicÂ® script launched from the command line, for instanceâ"and make sure that your provider fails gracefully with the proper error. Attempt to execute a valid method with invalid method parameters, and check for the same behavior.
Finally, prior to shipping your provider, be sure to recompile it as a release build, and link to FRAMEDYN.DLL (the release version of the framework). You should then install everything, including your MOF, on a clean machine and retest.
Conclusion WMI provides a means for client applications, whether they are scripts written in VBScript or applications written in C++, to interact with software and hardware elements using a published, formalized schema. WMI providers, in combination with the WinMgmt service, make this interaction possible.
It is easy to write a WMI provider using the tools available through the WMI SDKâ"namely, the CIM Studio (to define your classes), the CIM Studio MOF Generator (to produce the document that allows you to install your schema on other machines), and the WMI Code Generator. With the information and samples provided in this article, in combination with the WMI SDK, you are well equipped to use this exciting new tool in a wide array of applications.