I'm trying to learn how the WMI works, but the default examples given have so far been atrocious.
Here's the example for calling the Create
method of the Win32_Process
class:
https://learn.microsoft.com/en-us/windows/win32/wmisdk/example--calling-a-provider-method
I have added proper error handling to this, we store the HRESULT of each call in a variable hres
and check if the calls failed. As such:
hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);
if (FAILED(hres))
{
wprintf("Failed to get class. Error code = 0x%lx\n", hres);
return hres;
}
The code executes correctly right up until here:
// Create the values for the in parameters
VARIANT varCommand;
varCommand.vt = VT_BSTR;
varCommand.bstrVal = _bstr_t(L"notepad.exe");
// Store the value for the in parameters
hres = pClassInstance->Put(L"CommandLine", 0,
&varCommand, 0);
wprintf(L"The command is: %s\n", V_BSTR(&varCommand));
Where the pClassInstance->Put
throws 'ol c5.
At this point, hres
is S_OK
for the call to SpawnInstance
but these are the pointers we have for the class instances:
pClass 0x000001c04e73fca0 IWbemClassObject *
- pClassInstance 0x000001c04e749d60 IWbemClassObject *
- IUnknown {...} IUnknown
- __vfptr 0x00007ff9f8d0ee98 {fastprox.dll!const CWbemInstance::`vftable'{for `_IWmiObject'}} {0x00007ff9f8c6f450 {fastprox.dll!CWbemObject::QueryInterface(void)}, ...} void * *
[0x00000000] 0x00007ff9f8c6f450 {fastprox.dll!CWbemObject::QueryInterface(void)} void *
[0x00000001] 0x00007ff9f8c907d0 {fastprox.dll!CWbemObject::AddRef(void)} void *
[0x00000002] 0x00007ff9f8c8ffd0 {fastprox.dll!CWbemObject::Release(void)} void *
pInParamsDefinition 0x000001c04e743ca0 IWbemClassObject *
And varCommand
:
varCommand BSTR = 0x000001c04e74ffe8 tagVARIANT
The call stack:
oleaut32.dll!SysAllocString()
vfbasics.dll!AVrfpSysAllocString()
wbemcomn.dll!CVar::SetVariant()
fastprox.dll!CWbemInstance::Put()
> Ele.exe!WMIConnection::InvokeMethod()
So it appears that bstrVal isn't being properly set, I think? I tried initializing it first with VariantInit
, and I also tried dynamically allocating it on the heap instead. Neither resolved the issue:
VARIANT varCommand;
VariantInit(&varCommand);
varCommand.vt = VT_BSTR;
varCommand.bstrVal = _bstr_t(L"notepad.exe");
I also tried manually zeroing out the Variant buffer, to no effect. This is what we have for bstrVal
in the memory dump when the access violation occurs:
bstrVal 0x000001c04e74ffe8 <Error reading characters of string.> wchar_t *
<Unable to read memory> wchar_t
CodePudding user response:
I figured it out. There are several forum posts on the internet asking about this example, with no solutions given, so I am very happy to provide this now.
The Microsoft example is using the incorrect classes.
In the Microsoft example, they attempt to call the Put
method on a class instance of Win32_Process
to set the parameters.
This is incorrect. We need to set the parameters via first getting the class method definition for Win32_Process::Create
and then setting its parameters inside a new instance of Win32_Process::Create
.
We additionally need to construct an instance of the Win32_ProcessStartup
class object as it's a required input parameter for Win32_Process::Create
.
In the example below I will populate one field of the Win32_ProcessStartup
class instance, you can figure out the rest.
So this code from the Microsoft example:
IWbemClassObject* pClass = NULL;
hres = pSvc->GetObject(ClassName, 0, NULL, &pClass, NULL);
IWbemClassObject* pInParamsDefinition = NULL;
hres = pClass->GetMethod(MethodName, 0,
&pInParamsDefinition, NULL);
IWbemClassObject* pClassInstance = NULL;
hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);
// Create the values for the in parameters
VARIANT varCommand;
varCommand.vt = VT_BSTR;
varCommand.bstrVal = _bstr_t(L"notepad.exe");
// Store the value for the in parameters
hres = pClassInstance->Put(L"CommandLine", 0,
&varCommand, 0);
wprintf(L"The command is: %s\n", V_BSTR(&varCommand));
Becomes (sans error handling for readability):
// Get the class object
hres = pClass->GetMethod(lpwMethodName, 0,
&pInParamsDefinition, NULL);
hres = pInParamsDefinition->SpawnInstance(0, &pClassInstance);
// Get the Win32_ProcessStartup class object
IWbemClassObject* pStartupObject = NULL;
hres = pSvc->GetObject(_bstr_t(L"Win32_ProcessStartup"), 0, NULL, &pStartupObject, NULL);
// Create an instance of the Win32_ProcessStartup class object
IWbemClassObject* pStartupInstance = NULL;
hres = pStartupObject->SpawnInstance(0, &pStartupInstance);
// Create the value for the ShowWindow variable of the Win32_ProcessStartup class
VARIANT varParams;
VariantInit(&varParams);
varParams.vt = VT_I2;
varParams.intVal = SW_SHOW;
// And populate it
hres = pStartupInstance->Put(_bstr_t(L"ShowWindow"), 0, &varParams, 0);
// Get the method definition for Win32_Process::Create and store it in pInParamsDefinition
hres = pClass->GetMethod(_bstr_t(lpwMethodName), 0, &pInParamsDefinition, NULL);
// Spawn an instance of the Create method and store it in pParamsInstance
IWbemClassObject* pParamsInstance = NULL;
hres = pInParamsDefinition->SpawnInstance(0, &pParamsInstance);
// Now we can set the parameters without error
hres = pParamsInstance->Put(_bstr_t(L"CurrentDirectory"), 0, pvCurrentDirectory, 0);
Please note that all of these hres
returns should be checked for failure.
The definition for Win32_ProcessStartup
is provided here:
https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-processstartup
And the definition for the Create
method of Win32_Process
:
https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process
CodePudding user response:
varCommand.bstrVal = _bstr_t(L"notepad.exe");
This line of code creates a temporary _bstr_t
object that goes out of scope, destroying the allocated BSTR
memory, after varCommand.bstrVal
has been assigned to. Thus, varCommand.bstrVal
is left dangling, pointing at invalid memory, when varCommand
is passed to pClassInstance->Put()
. That is undefined behavior.
Use this instead to keep the BSTR
alive until you are actually done using it:
_bstr_t str(L"notepad.exe");
varCommand.bstrVal = str;
Otherwise, use _variant_t
instead:
// Create the values for the in parameters
_variant_t varCommand(L"notepad.exe");
// Store the value for the in parameters
hres = pClassInstance->Put(L"CommandLine", 0, &varCommand, 0);
wprintf(L"The command is: %s\n", V_BSTR(&varCommand));