Samstag, 26. März 2011

Using WMI to call method on objects

Hello everyone

As the title states in this article we will have a look on how to call methods on objects we have got in WMI. In the example source code we will stick on printers and call the SetDefaultPrinter method on an instance of a printer.

Functions can be called as static functions or they can be called on an object. For the function mentioned above it may be pretty clear that we need to call it as part of an object (which actually indicates the printer to be the default one).

We will start with a part of the code presented in the last part of the WMI articles (See here). This actually just initializes WMI and connects to the CIMV2 namespace.
#include <Windows.h>
#include <iostream>
#include <WbemCli.h>

#pragma comment(lib, "wbemuuid.lib")

int main()
{
 using std::cout;
 using std::cin;
 using std::endl;

 HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
 if(FAILED(hRes))
 {
  cout << "Unable to launch COM: 0x" << std::hex << hRes << endl;
  return 1;
 }

 if((FAILED(hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0))))
 {
  cout << "Unable to initialize security: 0x" << std::hex << hRes << endl;
  return 1;
 }

 IWbemLocator* pLocator = NULL;
 if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
 {
  cout << "Unable to create a WbemLocator: " << std::hex << hRes << endl;
  return 1;
 }

 IWbemServices* pService = NULL;
 if(FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService)))
 {
  pLocator->Release();
  cout << "Unable to connect to \"CIMV2\": " << std::hex << hRes << endl;
  return 1;
 }
Now what we need to identify our printer is a value that identifies it. I used the Caption but you can also choose other keys. You can find the caption of your printers in the control panel under "Devices and Printers" (also linked when you press Start). This will look somehow like that but with your printers:


The tick in the green circle indicates that the printer is the default printer. In my case its the PDFCreator. Now i want to make the HP Deskjet my default printer. So i rember the name "HP Deskjet F4100 series" which will be the caption used to query it. To query a special object we can use the WHERE claue in the query. That gives this code
IEnumWbemClassObject* pEnumerator = NULL;
 if(FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_Printer WHERE Caption = 'HP Deskjet F4100 Series'", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator)))
 {
  pLocator->Release();
  pService->Release();
  cout << "Unable to retrive the printer: " << std::hex << hRes << endl;
  return 1;
 }
Now we have an enumerator for the printers which are named "HP Deskjet F4100 Series" (case insensitive). Even if there is only one printer matching the criteria you will still get enumerator to enumerate all of them (which is only one...). The next step we could do is actually not necessary for that particular function. When we call GetMethod in a IWbemClassObject we can query information about a method. This includes parameters that are used by the function as input and parameters that are output by the function. The second one (output) is pretty useless if you will call the function because the call actually will also return an IWbemClassObject containing all the output parameters of the function. The input parameter object is actually also not really useful because if you call a function you actually already know what input parameters it expects. Thats why the GetMethod method is actually more for informative purpose if you dont plan to call the function afterwards. In our case its even worse, we dont have any Input or Output parameters so we dont need that at all. So we can start enumerating the objects right on. Every instance of a certain object (in our case Win32_Printer) becomes it unique path. Also objects themselves have paths.

For example a path for the object would be: \\ADMIN-PC\root\CIMV2:Win32_Printer

A path for an instance of that object could be: \\ADMIN-PC\root\CIMV2:Win32_Printer.DeviceID="HP Deskjet F4100 series"

To obtain the path of our instance we can query the __PATH property of the object. So our loop starts like that:
IWbemClassObject* clsObj = NULL;
 int numElems;
 while((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE)
 {
  if(FAILED(hRes))
   break;

  VARIANT vtPath;
  VariantInit(&vtPath);
  if(FAILED(clsObj->Get(L"__Path", 0, &vtPath, NULL, NULL)))
  {
   cout << "Object has no __Path!" << endl;
   clsObj->Release();
   continue;
  }
To call a method we use the IWbemServices interface which exposes a method called ExecMethod. This method first expects the path of the object or instance the function should be called on. If you are calling a static function line Win32_Process.Create you should provide the path to the object and if you are calling a non-static method like SetDefaultPrinter you should give the path to an instance. The second parameter is pretty simple just the name of the function to call. The flags and context parameters are not important now but interesting are the parameters pInParams and ppOutParams. The first expects a pointer to a IWbemClassObject which has properties that holds the input parameters for that function. For example the function Win32_Process.Create has an input parameter called CommandLine. Therefore your IWbemClassObject which will be put as pInParams must define a property with the name CommandLine and its value should be the command line. Our function has no parameters so we can pass NULL. ppOutParams will hold a pointer to a IWbemClassObject after the function has completed. This object has a property for each output parameter and one special property called "ReturnValue". So we can call the function like that:

IWbemClassObject* pResult = NULL;
  if(FAILED(hRes = pService->ExecMethod(vtPath.bstrVal, L"SetDefaultPrinter", 0, NULL, NULL, &pResult, NULL)))
   cout << "Unable to set the default printer: " << std::hex << hRes << endl;

Next we want to extract the returned value if the function has succeeded. As i mentioned above this value is stored in the property "ReturnValue". So we query that property from the returned result object.
else
  {
   VARIANT vtRet;
   VariantInit(&vtRet);
   if(FAILED(hRes = pResult->Get(L"ReturnValue", 0, &vtRet, NULL, 0)))
    cout << "Unable to get return value of call: " << std::hex << hRes << endl;
   else
    cout << "Method returned: " << vtRet.intVal << endl;

   VariantClear(&vtRet);
   pResult->Release();
  }

Thats it! We have called the function and got its return value. All left now is releasing everything we dont need anymore and exit the program.
VariantClear(&vtPath);
  clsObj->Release();
 }

 pEnumerator->Release();
 pService->Release();
 pLocator->Release();
 cin.get();
 return 0;
}

And here is the complete source code again:
#include <Windows.h>
#include <iostream>
#include <WbemCli.h>

#pragma comment(lib, "wbemuuid.lib")

int main()
{
 using std::cout;
 using std::cin;
 using std::endl;

 HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
 if(FAILED(hRes))
 {
  cout << "Unable to launch COM: 0x" << std::hex << hRes << endl;
  return 1;
 }

 if((FAILED(hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0))))
 {
  cout << "Unable to initialize security: 0x" << std::hex << hRes << endl;
  return 1;
 }

 IWbemLocator* pLocator = NULL;
 if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
 {
  cout << "Unable to create a WbemLocator: " << std::hex << hRes << endl;
  return 1;
 }

 IWbemServices* pService = NULL;
 if(FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService)))
 {
  pLocator->Release();
  cout << "Unable to connect to \"CIMV2\": " << std::hex << hRes << endl;
  return 1;
 }

 IEnumWbemClassObject* pEnumerator = NULL;
 if(FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_Printer WHERE Caption = 'HP Deskjet F4100 Series'", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator)))
 {
  pLocator->Release();
  pService->Release();
  cout << "Unable to retrive the printer: " << std::hex << hRes << endl;
  return 1;
 }

 IWbemClassObject* clsObj = NULL;
 int numElems;
 while((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE)
 {
  if(FAILED(hRes))
   break;

  VARIANT vtPath;
  VariantInit(&vtPath);
  if(FAILED(clsObj->Get(L"__Path", 0, &vtPath, NULL, NULL)))
  {
   cout << "Object has no __Path!" << endl;
   clsObj->Release();
   continue;
  }

  IWbemClassObject* pResult = NULL;
  if(FAILED(hRes = pService->ExecMethod(vtPath.bstrVal, L"SetDefaultPrinter", 0, NULL, NULL, &pResult, NULL)))
   cout << "Unable to set the default printer: " << std::hex << hRes << endl;
  else
  {
   VARIANT vtRet;
   VariantInit(&vtRet);
   if(FAILED(hRes = pResult->Get(L"ReturnValue", 0, &vtRet, NULL, 0)))
    cout << "Unable to get return value of call: " << std::hex << hRes << endl;
   else
    cout << "Method returned: " << vtRet.intVal << endl;

   VariantClear(&vtRet);
   pResult->Release();
  }

  VariantClear(&vtPath);
  clsObj->Release();
 }

 pEnumerator->Release();
 pService->Release();
 pLocator->Release();
 return 0;
}

I hope you have learned how methods are called and when you run the above code you will see that your printer is now the default one!


Thanks for reading and bye
Yanick

1 Kommentar:

  1. Hi Yanick,

    Thank you for your post.

    One question please, what the ReturnValue indicates? I have got vtRet.intVal = 70.

    AntwortenLöschen