I'm trying to make WMI method calls (to the xeniface driver). The WMI crate does not yet wrap ExecMethod
so we have to get down to windows-rs
calls, and I'm having trouble getting it right.
Note, current wmi-rs 0.13.1 still uses windows-rs 0.48.
For reference, the Python equivalent of the code below would be the last line in:
wmi_connection = wmi.WMI(namespace=r"root\wmi")
xs_base = wmi_connection.CitrixXenStoreBase()[0]
sid = xs_base.AddSession("MyNewSession")[0]
This function accepts the CitrixXenStoreBase
object retrieved using the wmi
crate.
Following this post I skipped the introspection call for in/out parameters (but essentially because my GetMethod
call fails with WBEM_E_ILLEGAL_OPERATION, any clue why? It could be useful for introspection and finding out how to get nte next steps right).
I still don't see how I should pass in_params
to ExecMethod
(and will likely have to use this to pass the session name)
I can't see how to extract info from out_params
either, current attempt to extract ReturnValue
fails with WBEM_E_NOT_FOUND. I'd like to be able to introspect that IWbemClassObject
, but could not find any details on how to do that
use windows::core::{BSTR, w};
use windows::Win32::System::{Com::VARIANT, Wmi::{IWbemClassObject, WBEM_FLAG_RETURN_WBEM_COMPLETE}};
use wmi::{Variant, WMIConnection, WMIError, WMIResult};
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct CitrixXenStoreBase {
#[serde(rename = "__Path")]
__path: String,
instance_name: String,
}
pub fn add_session(cnx: &WMIConnection,
object: CitrixXenStoreBase) -> WMIResult<usize>
{
let mut wmi_object = None;
let object_path: &str = object.__path.as_str();
let object_path = BSTR::from(object_path);
unsafe {
let ret = cnx.svc.GetObject(&object_path,
WBEM_FLAG_RETURN_WBEM_COMPLETE.0 as _,
None,
Some(&mut wmi_object),
None);
eprintln!("GetObject -> {:?}", ret);
ret.expect("GetObject failure");
}
let wmi_object: IWbemClassObject = wmi_object.ok_or(WMIError::NullPointerResult)?;
//let mut in_params: Option<IWbemClassObject> = None;
let mut out_params: Option<IWbemClassObject> = None;
// unsafe {
// let ret = wmi_object.GetMethod(w!("AddSession"), 0, &mut in_params, &mut out_params);
// eprintln!("GetMethod -> {:?}", ret);
// ret.expect("GetMethod failure");
// }
unsafe {
let ret = cnx.svc.ExecMethod(
&BSTR::from(&object.__path),
&BSTR::from("AddSession"),
0,
None,
None, //in_params,
Some(&mut out_params),
None,
);
eprintln!("ExecMethod -> {:?}", ret);
ret.expect("ExecMethod failure");
}
eprintln!("out_params: {:?}", out_params);
let out_params = out_params.expect("AddSession should have output params");
let mut addsession_ret = VARIANT::default();
unsafe {
let ret = out_params.Get(w!("ReturnValue"), 0, &mut addsession_ret, None, None);
eprintln!("Get -> {:?}", ret);
ret.expect("Get failure");
}
let sid = Variant::from_variant(&addsession_ret)?;
eprintln!("sid: {:#?}", sid);
Ok(0)
}
Current output goes like:
GetObject -> Ok(())
ExecMethod -> Ok(())
out_params: Some(IWbemClassObject(IUnknown(0x129ecc1cd10)))
Get -> Err(Error { code: HRESULT(0x80041002), message: "" })
It turns out this particular case, which requires calling an instance method, requires more work than the official example from Microsoft, which showcases calling a class method. This left quite some work "as an exercise for the reader".
Intuitive handling of the VARIANT
type also requires this windows-rs update which comes post-0.52 (don't get fooled by its summary, it does bring code changes in addition to extending the example), so until 0.53 is out you'll need to fetch the windows
crate from git. Which in turns cannot work today with wmi-rs, which still works with 0.48.
The full process must then go this route:
Wmi::IWbemServices
handle (essentially copied from wmi-rs
and lifted to 0.52 API) - wmi_init()
belowGetObject("CitrixXenStoreBase")
on the service to get the class object to get the CitrixXenStoreBase
class object (a Wmi::IWbemClassObject
) - wmi_get_object()
belowExecQuery ("SELECT __Path FROM CitrixXenStoreBase")
on the service to get the instances of that class (other Wmi::IWbemClassObject
s), only collecting their __Path
attribute, which is apparently all we need. Since this class is a singleton, we only have to care about the first element returned by that enumerator. Inside main()
below.GetMethod("AddSession")
on the CitrixXenStoreBase
class object, to get a class object (yet another Wmi::IWbemClassObject
) describing the input parameters for that methodSpawnInstance()
method to get an instance object (a... did you follow? Wmi::IWbemClassObject
too) suitable to hold the actual input parameters for the method callPut()
on this object to set the sole input parameter as a named property, wrapping first the actual (string) value in a VARIANT
ExecMethod
on the service (passing it the instance object's __Path
and not the instance object itself) and get output parameters as a last Wmi::IWbemClassObject
Get
on that output parameters object to extract the named property for the return value we're after.As noticed by @iinspectable, yes it was quite a hike. Thanks @kenny-kerr for hinting me to the right direction and providing windows-rs
updates!
This is just a starting point obviously, this code will evolve on GitHub, and maybe at some point we'll find the right level of abstraction to add to wmi-rs
.
pub fn wmi_init(path: &str) -> Result<Wmi::IWbemServices, WinError> {
unsafe { Com::CoInitializeEx(None, Com::COINIT_MULTITHREADED) }?;
unsafe { Com::CoInitializeSecurity(None,
-1, // let COM choose.
None,
None,
Com::RPC_C_AUTHN_LEVEL_DEFAULT,
Com::RPC_C_IMP_LEVEL_IMPERSONATE,
None,
Com::EOAC_NONE,
None,
) }?;
// COM locator
let loc: Wmi::IWbemLocator = unsafe {
Com::CoCreateInstance(&Wmi::WbemLocator, None, Com::CLSCTX_INPROC_SERVER)
}?;
// connection
let svc = unsafe {
loc.ConnectServer(
&BSTR::from(path),
&BSTR::new(),
&BSTR::new(),
&BSTR::new(),
Wmi::WBEM_FLAG_CONNECT_USE_MAX_WAIT.0,
&BSTR::new(),
None,
)
}?;
// "set proxy"
unsafe {
Com::CoSetProxyBlanket(
&svc,
Rpc::RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
Rpc::RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
None,
Com::RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
Com::RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
None, // client identity
Com::EOAC_NONE, // proxy capabilities
)
}?;
Ok(svc)
}
pub fn wmi_get_object(svc: &Wmi::IWbemServices, name: &str)
-> Result<Wmi::IWbemClassObject, WinError> {
let mut wmi_object = None;
unsafe { svc.GetObject(&BSTR::from(name),
Wmi::WBEM_FLAG_RETURN_WBEM_COMPLETE,
None,
Some(&mut wmi_object),
None) }?;
// FIXME can this unwrap fail? if missing driver?
Ok(wmi_object.unwrap())
}
pub fn add_session(svc: &Wmi::IWbemServices,
xs_base_class: &Wmi::IWbemClassObject,
xs_base_path: &BSTR) -> Result<u32, WinError>
{
// get input params def
let mut in_params_class: Option<Wmi::IWbemClassObject> = None;
unsafe { xs_base_class.GetMethod(w!("AddSession"), 0,
&mut in_params_class, std::ptr::null_mut()) }?;
let in_params_class = in_params_class.unwrap();
// fill input params
let in_params = unsafe { in_params_class.SpawnInstance(0) }?;
let var_session_name = VARIANT::from("MySession");
unsafe { in_params.Put(w!("Id"), 0, &var_session_name, 0) }?;
// method call
let mut out_params = None;
unsafe { svc.ExecMethod(
xs_base_path,
&BSTR::from("AddSession"),
Default::default(),
None,
&in_params,
Some(&mut out_params),
None,
) }?;
let out_params = out_params.unwrap();
// output params
let mut sid = VARIANT::default();
unsafe { out_params.Get(w!("SessionId"), 0, &mut sid, None, None) }?;
let sid = u32::try_from(&sid)?;
eprintln!("sid: {:#?}", sid);
Ok(sid)
}
main() {
let wmi_service = wmi_extra::wmi_init(r#"root\wmi"#)?;
// get all instances of .CitrixXenStoreBase
let enumerator = unsafe {
wmi_service.ExecQuery(
&BSTR::from("WQL"),
&BSTR::from("SELECT __Path FROM CitrixXenStoreBase"),
Wmi::WBEM_FLAG_FORWARD_ONLY | Wmi::WBEM_FLAG_RETURN_IMMEDIATELY,
None,
)
}?;
let mut objs = [None; 1];
let res = {
let mut return_value = 0;
unsafe { enumerator.Next(Wmi::WBEM_INFINITE, &mut objs, &mut return_value) }
};
if let Err(e) = res.ok() {
return Err(e.into());
}
// get the singleton instance
let xenstore_base = objs.into_iter().next().unwrap().unwrap();
let xenstore_base_class = wmi_extra::wmi_get_object(&wmi_service, "CitrixXenStoreBase")?;
let mut xenstore_base_path = VARIANT::default();
unsafe { xenstore_base.Get(&BSTR::from("__Path"), 0, &mut xenstore_base_path, None, None) }?;
let xenstore_base_path = BSTR::try_from(&xenstore_base_path)?;
// ret ...> session id
let ret = wmi_extra::add_session(&wmi_service, &xenstore_base_class, &xenstore_base_path)?;
}