Passing parameters to a WMI method turns out to be considerably more complicated than calling a method without any parameters.
So that I could test it, I've written the code for a WMI method that exists on every Windows PC, and which normal users can't call so I can't break things and I can check error handling. This is SetForwardBufferMemory
on Win32_NetworkAdapterConfiguration
. I found a program called "WMI Explorer" helpful while figuring this out.
use anyhow::{anyhow, Context, Result};
use windows::core::{w, BSTR, VARIANT};
use windows::Win32::System::Com::{
CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CLSCTX_INPROC_SERVER, COINIT_MULTITHREADED, EOAC_NONE,
RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE,
};
use windows::Win32::System::Wmi::{
IWbemClassObject, IWbemLocator, WbemLocator, WBEM_FLAG_RETURN_WBEM_COMPLETE, WBEM_GENERIC_FLAG_TYPE,
};
fn main() -> Result<()> {
unsafe {
CoInitializeEx(None, COINIT_MULTITHREADED).ok().context("Failed to initialize COM")?;
CoInitializeSecurity(None, -1, None, None, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, None, EOAC_NONE, None)
.context("Failed to initialize COM security")?;
}
let loc: IWbemLocator =
unsafe { CoCreateInstance(&WbemLocator, None, CLSCTX_INPROC_SERVER).context("Failed to get WbemLocator")? };
let svc = unsafe {
loc.ConnectServer(&BSTR::from("ROOT\\CIMV2"), None, None, None, 0, None, None)
.context("Failed to open namespace ROOT\\CIMV2")?
};
let obj_name = BSTR::from("Win32_NetworkAdapterConfiguration");
let mut cls: Option<IWbemClassObject> = None;
unsafe {
svc.GetObject(&obj_name, WBEM_FLAG_RETURN_WBEM_COMPLETE, None, Some(&mut cls), None)
.context("Failed to get object Win32_NetworkAdapterConfiguration")?;
}
let cls = cls.ok_or_else(|| anyhow!("Missing Win32_NetworkAdapterConfiguration class"))?;
let method_name = BSTR::from("SetForwardBufferMemory");
let mut in_cls: Option<IWbemClassObject> = None;
let mut out_cls: Option<IWbemClassObject> = None;
unsafe {
cls.GetMethod(&method_name, 0, &mut in_cls, &mut out_cls).context("Failed to get method SetForwardBufferMemory")?;
}
let in_params = unsafe {
in_cls.ok_or_else(|| anyhow!("Missing input params class"))?.SpawnInstance(0).context("Failed to create input params")?
};
let param = VARIANT::from(74240i32);
unsafe {
in_params.Put(w!("ForwardBufferMemory"), 0, ¶m, 0).context("Failed to set value")?;
}
let mut out_params: Option<IWbemClassObject> = None;
unsafe {
svc.ExecMethod(&obj_name, &method_name, WBEM_GENERIC_FLAG_TYPE(0), None, &in_params, Some(&mut out_params), None)
.context("Failed to call SetForwardBufferMemory")?;
}
let out_params = out_params.ok_or_else(|| anyhow!("Missing output parameters"))?;
let mut return_value = VARIANT::new();
unsafe {
out_params.Get(w!("ReturnValue"), 0, &mut return_value, None, None)?;
}
println!("Return value = {return_value}.");
Ok(())
}
This is a lot like the sample at Calling a Provider Method - Win32 apps | Microsoft Learn except that it actually handles errors. I'm using anyhow
, which is where .context("...")?
comes from.
Note that although the ForwardBufferMemory
parameter is defined as a uint32
, I had to set the parameter from a signed i32
. This will probably be the case for GetSetULong
as well, for Data
instead of ForwardBufferMemory
.
Also, one result I found with Google for GetSetULong
used uint64
instead, which is ULONGLONG
on Windows rather than ULONG
, and might want i64
or u64
to work.