Managing WMI on Windows

I need to implement the following PowerShell command to change wmi

$obj = Get-CimInstance -Namespace root/wmi -ClassName AcpiTest_MULong | Select-Object -First 1
Invoke-CimMethod -InputObject $obj -MethodName GetSetULong -Arguments @{Data = 0x000001000000ABCD}

I can't find how to achieve it

A Rust program using CIM or WMI will look like the C++ equivalent using COM, like:

Unfortunately I don't seem to have a CIM class called AcpiTest_MULong on my system so I can't really see what the program would look like, but I wrote a short example that uses WMI with the DNS cache a while ago which might get you started:

1 Like

How can I input 0x000001000000ABCD?

fn clear_dns_cache() -> windows::core::Result<()> {
    let namespace = BSTR::from(r"root/wmi");
    let dns_client_cache = BSTR::from("AcpiTest_MULong");
    let clear = BSTR::from("GetSetULong");
    unsafe {
        let loc: IWbemLocator = CoCreateInstance(&WbemLocator, None, CLSCTX_INPROC_SERVER)?;
        let svc = loc.ConnectServer(&namespace, None, None, None, 0, None, None)?;
        println!("{:?}", svc);
        svc.ExecMethod(&dns_client_cache, &clear, windows::Win32::System::Wmi::WBEM_GENERIC_FLAG_TYPE(0x000001000000ABCD), None, None, None, None)
    }
}

fn main() {
    unsafe {
        let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
        CoInitializeSecurity(
            None,
            -1,
            None,
            None,
            RPC_C_AUTHN_LEVEL_DEFAULT,
            RPC_C_IMP_LEVEL_IMPERSONATE,
            None,
            EOAC_NONE,
            None,
        )
        .expect("CoInitializeSecurity failed unexpectedly");
    }
    if let Err(e) = clear_dns_cache() {
        eprintln!("WMI error: {}", e);
    }
}

out:

IWbemServices(0x279d47569b0)
WMI error: 0x80041008

I can't understand how to make it work next

[dependencies]
wmi = "0.14"
serde = { version = "1.0", features = ["derive"] }

[dependencies.windows]
version = "0.58.0"
features = [
    "Data_Xml_Dom",
    "Win32_Foundation",
    "Win32_Security",
    "Win32_System_Threading",
    "Win32_UI_WindowsAndMessaging",
]

At the same time, due to possible differences in versions, I modified part of your code so that it can run

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, &param, 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.

VARIANT { type: 21, value: 1099511628901 }
Error: Failed to set value
let param = VARIANT::from(0x0000010000000464 as u64);
    println!("{:?}", param);
    unsafe {
        in_params.Put(w!("Data"), 0, &param, 0).context("Failed to set value")?;
    }

I don't know what's wrong

I found this documentation about passing different numbers to WMI:

While unsigned numbers like uint16 and uint32 have to be passed as i32 VARIANTs, it looks like uint64 has to be passed as a string (what the ‽). I guess this would be:

let param = VARIANT::from("0x0000010000000464");

According to this document, the values printed by println are indeed automatically converted to hexadecimal, but the error is still 0x80041005. I don't know why this is happening

If BSTR isn't correct:

VARIANT { type: 8, value: 0x0000010000000464 }

then a signed 64-bit integer (i64) might work:

VARIANT { type: 20, value: 1099511628900 }

I've also just realised now that GetSetULong isn't a static method, and that's what the sample code I copied is for. I don't know what that would look like, but I'll have a look later.

VARIANT { type: 20, value: 1099511628901 }
Error: Failed to set value
VARIANT { type: 21, value: 1099511628901 }
Error: Failed to set value
VARIANT { type: 8, value: 0x0000010000000465 }
Error: Failed to set value

The part of the documentation that says

This type follows hexadecimal or decimal format according to ANSI C rules.

doesn't seem to be correct; after trying a lot of different things, I've only been able to use a string containing a decimal number.

Here's some better-commented code that tries to create a very large partition on the first physical disk on the system. This should be similar to what you're trying to do, calling GetSetULong on the first ACPITest_MULong on the system.

use anyhow::{anyhow, Context, Result};
use windows::core::{w, BSTR, VARIANT};
use windows::Win32::System::Com::*;
use windows::Win32::System::Wmi::*;

fn main() -> Result<()> {
    // IWbemServices uses DCOM so COM security must be initialized correctly.
    unsafe {
        CoInitializeEx(None, COINIT_MULTITHREADED).ok().context("Initializing COM")?;
        CoInitializeSecurity(None, -1, None, None, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, None, EOAC_NONE, None)
            .context("Initializing COM security")?;
    }

    // Connect to the required namespace on the local DCOM server.
    let loc: IWbemLocator =
        unsafe { CoCreateInstance(&WbemLocator, None, CLSCTX_INPROC_SERVER).context("Failed to get WbemLocator")? };
    let svc = unsafe {
        loc.ConnectServer(&BSTR::from(r"ROOT\Microsoft\Windows\Storage"), None, None, None, 0, None, None)
            .context("Connecting to server")?
    };

    // Allocate null-terminated 16-bit character strings for the object class name and method name.
    let cls_name = BSTR::from("MSFT_Disk");
    let method_name = BSTR::from("CreatePartition");

    // List instances of the requested object by name, and get the path of the first.
    let object_enum = unsafe {
        svc.CreateInstanceEnum(&cls_name, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_ERROR_OBJECT, None)
            .context("Listing instances of MSFT_Disk")?
    };
    let mut objects = [None; 1];
    let mut count: u32 = 0;
    unsafe {
        object_enum.Next(WBEM_INFINITE, &mut objects, &mut count).ok().context("Retrieving first MSFT_Disk")?;
    }
    let mut obj_path = VARIANT::new();
    unsafe {
        objects[0]
            .as_ref()
            .ok_or_else(|| anyhow!("Missing MSFT_Disk object"))?
            .Get(w!("__RELPATH"), 0, &mut obj_path, None, None)
            .context("Retrieving object path")?;
    }
    let obj_path = BSTR::try_from(&obj_path).context("Converting object path to string")?;
    drop(objects);
    drop(object_enum);
    println!("Instance: {obj_path}");

    // Get an input parameter object from the object class.
    let mut cls: Option<IWbemClassObject> = None;
    unsafe {
        svc.GetObject(&cls_name, WBEM_FLAG_RETURN_WBEM_COMPLETE, None, Some(&mut cls), None).context("Getting class MSFT_Disk")?;
    }
    let cls = cls.ok_or_else(|| anyhow!("Missing MSFT_Disk class"))?;
    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("Getting method definition")?;
    }
    let in_params =
        unsafe { in_cls.ok_or_else(|| anyhow!("Missing input params class"))?.SpawnInstance(0).context("Creating input params")? };

    // Set the desired parameters on the input parameter object.
    let size = 0x0000010000000465u64.to_string();
    unsafe {
        in_params.Put(&BSTR::from("AssignDriveLetter"), 0, &VARIANT::from(true), 0).context("Setting AssignDriveLetter")?;
        in_params.Put(&BSTR::from("Size"), 0, &VARIANT::from(size.as_str()), 0).context("Setting Size")?;
    }

    // Call the method and check the return value.
    println!("Calling method with {}", unsafe { in_params.GetObjectText(0)? });
    let mut out_params: Option<IWbemClassObject> = None;
    unsafe {
        svc.ExecMethod(&obj_path, &method_name, WBEM_FLAG_RETURN_WBEM_COMPLETE, None, &in_params, Some(&mut out_params), None)
            .context("Failed to call CreatePartition")?;
    }
    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(())
}

Very successful! The program successfully returns the correct value

I have discovered a problem that I may not be able to solve. When using this code in Tauri2, it involves threading issues. It does not allow it to run outside of one thread or initialize multiple times, which is very troublesome when trying to pass global parametersGlobal variables involve types that do not support send and sync

As I understand it, the UI thread of a Windows application always uses COINIT_APARTMENTTHREADED instead of COINIT_MULTITHREADED for COM, including Tauri when it uses WebView2. This means you can't call CoInitializeEx(0, COINIT_MULTITHREADED) on the same thread as anything related to the UI, and you can't pass WMI references between threads.

WMI itself seems to work fine with COINIT_APARTMENTTHREADED, so you may be able to solve some issues by just using that instead from Tauri's main() function:

CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok().context("Initializing COM")?;
CoInitializeSecurity(None, -1, None, None, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, None, EOAC_NONE, None)
    .context("Initializing COM security")?;

Apart from that, the main thing I would try is to spawn an entirely new thread when you want to use WMI, call CoInitializeEx again and all the other WMI functions on the new thread (but not CoInitializeSecurity), and only pass in and return native Rust values like u64 and String. It may be that Tauri does some of that for you.

I have already solved it. Yesterday, I tried to initialize it in the channel and it worked very well. Although I don't understand, I feel lucky because my goal is to have it all work in the channel, but just initialize it in it and it can work elsewhere

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.