winapirustwmiwindows-rs

Using WMI `IWbemServices::ExecMethod` from Rust


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.

  1. 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).

  2. 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)

  3. 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: "" })

Solution

  • 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:

    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)?;
    }