Implementing MI Provider (5) - Implement
As discussed in Implementing MI Provider (4) - Generate Code (continute), Convert-MofToProvider.exe tool generates <class name>.c file for each target class, which contains stub functions to place the business logic of each operation, such as enumerate/get/delete/modify/new/<extrinsic methods>.
This blog discusses how to implement stub functions of normal (instance) classes, while association and indication class will be discussed separately in future. Let's first introduce a set of frequently used MI APIs (when implementing MI provider) and then discuss how to implement the business logic of each generated stub function using those APIs.
MI Context API
The name of provider APIs are suffixed by MI_Context, which means the operation performed by the API was against the MI_Context object, while MI_Context defined as below, the MI_ContextFT structure definition could be found in mi.h from windows 8 SDK.
typedef struct _MI_Context MI_Context;
struct _MI_Context
{
/* Function table */
const MI_ContextFT* ft;
/* Reserved for internal use */
ptrdiff_t reserved[3];
};
All MI_Context_* APIs are just a wrapper function that calls into one function inside MI_ContextFT structure.
MI_Context_PostInstance is used to post a MI_Instance back to server. Usually a MI_Instance could be constructed by calling generated constructor API defined in a class header file. Normally, provider calls into this API to delivery prepared instances. For example, client send an enumeration request to omi server, then the provider generates N number of instances, and should call this API N times to post them back to server.
MI_Context_PostError is used to post an error code with error message back to server.
MI_Context_PostCimError is used to post a CimError instance back to server.
MI_Context_PostResult is used to post the final operation result back to server.
NOTE: All Post APIs listed above are posting data to server, while server will deliver the data to client. Any operation might post arbitrary number of instances back to server, however if without any specific clarification, all provider stub functions should call one of the following APIs once and only once (One exception is indication class's stub function EnableIndications, which may be covered by future blog). MI_Context_PostError MI_Context_PostCimError MI_Context_PostResult |
MI_Context_GetLocalSession gets local MI_Session object, which can be used to communicate back to the local CIMOM server for local operations, for example a provider need to query instances of another class on the local machine. The provider must *NOT* call MI_Session_Close on the local session object since the life cycle of the session object is managed by the host process.
MI Powershell Semantics API
Powershell Semantics APIs are designed to enable MI provider to interact with Powershell Console efficiently, such as MI_Context_PromptUser, be used by MI provider to send a prompt message to the client application (powershell for example) to confirm whether to continue the operation or not.
MI_Context_PromptUser
MI_Context_ShouldContinue
MI_Context_ShouldProcess
MI_Context_WriteCimError
MI_Context_WriteError
MI_Context_WriteProgress
MI_Context_WriteMessage
MI_Context_WriteVerbose
MI_Context_WriteWarning
MI_Context_WriteDebug
MI Client API
A MI provider may need MI client API to perform specific operation to a different CIM class, such as querying instances of another CIM class or invoking an extrinsic method of another CIM class. Following are several frequently used client APIs, for a complete list of APIs, please refer to windows 8 SDK mi.h
MI_Application_Initialize is used to initialize and create a MI_Application object.
MI_Application_Close is used to close a MI_Application object, which will cancel all sessions and operations related to this application object.
MI_Application_NewSession creates a new MI_Session object, which is used to kick off any operation towards the CIMOM server.
MI_Session_Close closes the session, all operations have to be closed before session close finished.
MI_Session_EnumerateInstances start an operation to enumerate instances of a target class under specific namespace from target server, which will return a MI_Operation object.
MI_Operation_GetInstance retrieves MI_Instance from a started MI_Operation object synchronously.
MI_Operation_Close closes the operation.
Taking MSFT_WindowsProcess class example, remaining of this blog explains the general process of implementing enumerate instance, get instance, modify instance, delete instance, create instance, and extrinsic method.
(1) Implement EnumerateInstances
Generally speaking, there are six steps to implement enumerate instance operation for a class,
1) Firstly construct an instance of the target class by invoking <class name>_Construct API,
2) Set properties value of the instance. One important note is that all key properties of the instance must be set, and the combination of the key properties value should be unique, which identifies the instance uniquely.
3) Post the instances back by invoking <class_name>_Post API.
4) Destruct the constructed instances by calling <class name>_Destruct API.
5) Post final result to client by calling one of the post API. Call MI_Context_PostResult if succeed.
6) If there is any failure happened during above step which is considered blocking the current operation, provider should call MI_Context_PostError or MI_Context_PostCimError API to notify the client the final result.
Sometime, it may take time to complete the enumerating operation if there are quite a few instances to be posted back to client, and it makes sense to use ps-semantic API MI_Context_WriteProgress to notify client the current percentage of the operation.
Following diagram is the implementation diagram of MSFT_WindowsProcess_EnumerateInstances, see example code as well. Refer to MI API sample for complete implementation.
NOTE: For any MI_Instance constructed successfully by calling generated API <class name>_Construct, it should be destructed always once and only once by calling <class name>_Destruct.For any MI_Instance created by MI_Context_NewInstance or generated API such as MSFT_WindowsProcess_Clone, it should be deleted by calling MI_Instance_Delete once and only once. |
void MI_CALL MSFT_WindowsProcess_EnumerateInstances(
_In_opt_ MSFT_WindowsProcess_Self* self,
_In_ MI_Context* context,
_In_opt_z_ const MI_Char* nameSpace,
_In_opt_z_ const MI_Char* className,
_In_opt_ const MI_PropertySet* propertySet,
_In_ MI_Boolean keysOnly,
_In_opt_ const MI_Filter* filter)
{
MI_Result result = MI_RESULT_OK;
MI_UNREFERENCED_PARAMETER(self);
MI_UNREFERENCED_PARAMETER(nameSpace);
MI_UNREFERENCED_PARAMETER(className);
MI_UNREFERENCED_PARAMETER(propertySet);
MI_UNREFERENCED_PARAMETER(filter);
result = EnumerateProcesses(context, keysOnly);
if (result == MI_RESULT_ACCESS_DENIED)
{
MI_Context_PostError(context,
result,
MI_RESULT_TYPE_MI,
MI_T("Access denied. Please try to run client process with elevated priviledge."));
}
else if (result != MI_RESULT_OK)
{
MI_Context_PostError(context,
result,
MI_RESULT_TYPE_MI,
MI_T(""));
}
else
{
MI_Context_PostResult(context, result);
}
}
(2) Implement GetInstance
Get instance operation requires that all key properties of a MI_instance are sent to MI provider. There are several steps to implement get instance operation for a class,
1) Firstly check the key properties value of the given instance is valid or not.
2) If it is valid, then construct an instance of the target class by invoking <class name>_Construct API,
3) Set properties' value of the instance. One important note is that all key properties of the instance must be set, which are equal to the input instance, and the combination of the key properties value should be unique, which identifies the instance uniquely.
4) Post the instances back by invoking <class_name>_Post API.
5) Destruct the constructed instance by calling <class name>_Destruct API.
6) Post final result to client by calling MI_Context_PostResult API.
7) If there is any failure happened during above step which is considered blocking the current operation, provider should call MI_Context_PostError or MI_Context_PostCimError API to notify the client the final result.
Refer to MI API sample for complete implementation of MSFT_WindowsProcess_GetInstance.
NOTE: If a MI provider does not support Get instance operation, i.e., keep generated code <class name>_GetInstance unchanged (i.e., post MI_RESULT_NOT_SUPPORTED via one of the post API), then the get instance request will be redirected to <class name>_EnumerateInstance function, and returned instances from EnumerateInstances function will be filtered based on the key properties value of the incoming requested instance, and then CIMOM server post back result full instance back to client. But this behavior is inefficient and might cause performance issue in case the class has too many instances. |
(3) Implement CreateInstance
Not all classes support CreateInstance operation. The confusing part of the intrinsic create method is that it is hard for client user to figure out which properties value are mandatory to create an instance of a target class, and the supported format of each properties value, especially for key properties, it kind of different for client user to specify a valid key value to create a new instance, sometime, the key properties value might be generated by MI provider automatically. So commonly, defining an extrinsic method to create instance makes more sense, the method could define a set of input parameters and might the format of each parameter through parameter qualifiers. One example is Create method of MSFT_WindowsProcess class. However, if a MI provider does support intrinsic CreateInstance operation, following are the steps to implement it,
1) Firstly check the properties’ value of the given instance is valid or not.
2) If it is valid, then construct an instance of the target class by invoking <class name>_Construct API,
3) Set all of the properties’ value of the instance. Some properties’ value might come from input instance; other might be calculated by the provider. One important note is that all key properties of the instance must be set, which are equal to the input instance, and the combination of the key properties value should be unique, which identifies the instance uniquely.
4) Post the newly created instance back by invoking <class_name>_Post API. This step is mandatory due to client relies on this to get the key values of the newly created instance.
5) Destruct the constructed instance by calling <class name>_Destruct API.
6) Post final result to client by calling MI_Context_PostResult API.
7) If there is any failure happened during above step which is considered blocking the current operation, provider should call MI_Context_PostError or MI_Context_PostCimError API to notify the client the final result.
Refer to MI API sample for complete implementation of MSFT_WindowsProcess_CreateInstance.
(4) Implement ModifyInstance
ModifyInstance operation faces the similar issue with CreateInstance due to it is hard for client user to figure out which property could be modified and which property could not. Someone might argue that Read and Write qualifiers are the flag to tell which property could be modified, but in reality, not all of the class schema definition follows Read/Write qualifiers strictly. If schema does define Read/Write qualifier appropriately, powershell console as a client does prevent modifying the value of Read only properties.
For ModifyInstance operation, it is also challenging to implement. Provider developer needs to figure out the set of properties to be modified, unfortunately there is no straight forward approach to tell the exactly list of properties to be modified, the PropertySet parameter of following method is always set to NULL in windows 8.
void MI_CALL MSFT_WindowsProcess_ModifyInstance(
_In_opt_ MSFT_WindowsProcess_Self* self,
_In_ MI_Context* context,
_In_opt_z_ const MI_Char* nameSpace,
_In_opt_z_ const MI_Char* className,
_In_ const MSFT_WindowsProcess* modifiedInstance,
_In_opt_ const MI_PropertySet* propertySet)
{
MI_UNREFERENCED_PARAMETER(self);
MI_UNREFERENCED_PARAMETER(nameSpace);
MI_UNREFERENCED_PARAMETER(className);
MI_UNREFERENCED_PARAMETER(propertySet);
MI_Context_PostResult(context, MI_RESULT_NOT_SUPPORTED);
}
There is one workaround of the problem,
- The provider could query the latest value of the instance.
- Comparing the instance latest value with given instance to be modified, for any property that has a different value, it might need modification.
- One important note is that if the property value is NULL, then it needs another technique to tell should the property be modified or not.
The following utility method could tell whether a specific property was explicitly modified by client application or not.
MI_Result MI_CALL MI_Instance_IsElementModified(
_In_ const MI_Instance* self,
_In_z_ const MI_Char* name,
_Out_opt_ MI_Boolean* modified)
{
MI_Uint32 flags = 0;
MI_Result r;
if (!modified)
{
return MI_RESULT_INVALID_PARAMETER;
}
r = MI_Instance_GetElement(self, name, NULL, NULL, &flags, NULL);
if (r == MI_RESULT_OK)
{
if ((flags & MI_FLAG_NOT_MODIFIED) == 0)
{
*modified = MI_TRUE;
}
else
{
*modified = MI_FALSE;
}
}
return r;
}
NOTE: MI_FLAG_NOT_MODIFIED is only applicable to top level property, which was modified by MI_Instance_SetElementAt or MI_Instance_SetElement APIs or equivalent MI.net APIs. Since a MI Instance could have embedded instance property, if a sub property value of an embedded instance property was modified, since there is no MI_Instance_SetElementAt or MI_Instance_SetElement API call to notify the parent instance of the change of the embedded property, then above utility method won’t work as expected for this property. |
Given the above limitations, it might make more sense to define a specific extrinsic method to modify a specific property of an instance. One example is SetPriority method of MSFT_WindowsProcess class. However, some classes may still need to implement intrinsic ModifyInstance method following below steps,
1) Firstly check the key properties value of the given instance is valid or not.
2) If it is valid, before modifying the instance properties’ value, it might make sense to request confirmation from client application by calling MI_Context_PromptUser or …… to confirm that user really wants the modification.
3) If get positive answer from client user, then modify all of the requested non-key properties value of the instance by figuring out which properties are requested to be modified based on above techniques. How to modify the instance value is really depends on the provider’s business logic, it could modify the registry key; could store the value into file or database; could modify the value in memory cache, etc. There is NO need to post any instance back.
4) Post final result to client by calling MI_Context_PostResult API.
5) If there is any failure happened during above step which is considered blocking the current operation, provider should call MI_Context_PostError or MI_Context_PostCimError API to notify the client the final result.
Refer to MI API sample for complete implementation of MSFT_WindowsProcess_ModifyInstance.
(5) Implement DeleteInstance
DeleteInstance may have different meaning to different class, for MSFT_WindowsProcess class, it means kill the process. To implement delete instance,
1) Firstly check the key properties value of the given instance is valid or not.
2) If it is valid, then delete the instance. For MSFT_WindowsProcess class, it terminates the process corresponding to the given MI Instance.
3) Post final result to client by calling MI_Context_PostResult API.
4) If there is any failure happened during above step which is considered blocking the current operation, provider should call MI_Context_PostError or MI_Context_PostCimError API to notify the client the final result.
Refer to MI API sample for complete implementation of MSFT_WindowsProcess_DeleteInstance.
(6) Implement Extrinsic Method
Extrinsic methods include instance method and static method. The difference between static and instance method is that static method has [static] qualifier defined; while instance method does not have. For static method, the parameter [instanceName] is always set to NULL, while instance method has a valid instanceName parameter to tell provider on which instance should the method be invoked.
_In_ const MSFT_WindowsProcess* instanceName,
For any method, there is a structure defined for it, used to store all input and output parameters' value, and return value as well. There are several steps to implement method invoking operation for a class,
1) For instance method, firstly check the key properties value of the given instance is valid or not. If valid then continue,
2) Construct an instance of the target method by invoking <class name>_<method name>_Construct API,
3) Implement the method logic base on the input parameters and setting all of the output parameters and return value of the output instance. One important note is that ….
4) Post the instances back by invoking <class_name>_<method name>_Post API.
5) Destruct the constructed instance by calling <class name>_<method name>_Destruct API.
6) Post final result to client by calling MI_Context_PostResult API.
7) If there is any failure happened during above step which is considered blocking the current operation, provider should call MI_Context_PostError or MI_Context_PostCimError API to notify the client the final result.
Please look at MI API sample for an example of extrinsic method implementation: MSFT_WindowsProcess_Invoke_SetPriority.
Happy Holidays! Next blog will discuss how to build, register and debug MI provider.
Haowei Qin
Senior SDE
Standards Based Management