Skip to content

Commit

Permalink
Merge pull request #104 from TechnoPorg/wmi-methods
Browse files Browse the repository at this point in the history
Add support for executing WMI methods
  • Loading branch information
ohadravid authored Dec 29, 2024
2 parents 2f610f3 + 1edeaa9 commit 83b1a60
Show file tree
Hide file tree
Showing 8 changed files with 1,029 additions and 35 deletions.
13 changes: 12 additions & 1 deletion src/de/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,20 @@ where

forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf option unit unit_struct seq tuple
byte_buf option unit seq tuple
tuple_struct map enum identifier ignored_any
}

fn deserialize_unit_struct<V>(
self,
name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
self.deserialize_struct(name, &[], visitor)
}
}

let mut name = None;
Expand Down
2 changes: 1 addition & 1 deletion src/de/variant_de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl<'de> serde::Deserializer<'de> for Variant {
{
match self {
Variant::Null => visitor.visit_none(),
Variant::Empty => visitor.visit_none(),
Variant::Empty => visitor.visit_unit(),
Variant::String(s) => visitor.visit_string(s),
Variant::I1(n) => visitor.visit_i8(n),
Variant::I2(n) => visitor.visit_i16(n),
Expand Down
9 changes: 8 additions & 1 deletion src/de/wbem_class_de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,16 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer {
visitor.visit_string(class_name)
}

fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}

forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf option unit unit_struct seq tuple
byte_buf option unit_struct seq tuple
tuple_struct ignored_any
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@
//! [WMI Code Creator]: https://www.microsoft.com/en-us/download/details.aspx?id=8572
//! [`notification`]: connection/struct.WMIConnection.html#method.notification
//!
//! # Executing Methods
//!
//! The crate also offers support for executing WMI methods on classes and instances.
//!
//! See [`WMIConnection::exec_class_method`], [`WMIConnection::exec_instance_method`] and [`WMIConnection::exec_method_native_wrapper`]
//! for detailed examples.
//!
//! # Internals
//!
//! [`WMIConnection`](WMIConnection) is used to create and execute a WMI query, returning
Expand Down Expand Up @@ -277,9 +284,11 @@ mod datetime_time;
pub mod context;
pub mod de;
pub mod duration;
pub mod method;
pub mod query;
pub mod result_enumerator;
pub mod safearray;
pub mod ser;
pub mod utils;
pub mod variant;

Expand Down
305 changes: 305 additions & 0 deletions src/method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
use std::collections::HashMap;

use serde::{de, Serialize};
use windows_core::{BSTR, HSTRING, VARIANT};

use crate::{
de::meta::struct_name_and_fields, result_enumerator::IWbemClassWrapper,
ser::variant_ser::VariantStructSerializer, Variant, WMIConnection, WMIError, WMIResult,
};

impl WMIConnection {
/// Wrapper for WMI's [ExecMethod](https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemservices-execmethod) function.
///
/// This function is used internally by [`WMIConnection::exec_class_method`] and [`WMIConnection::exec_instance_method`],
/// which are a higher-level abstraction, dealing with Rust data types instead of raw Variants, that should be preferred to use.
///
/// In the case of a class ("static") method, `object_path` should be the same as `method_class`.
///
/// Returns `None` if the method has no out parameters and a `void` return type, and an [`IWbemClassWrapper`] containing the output otherwise.
/// A method with a return type other than `void` will always have a generic property named `ReturnValue` in the output class wrapper with the return value of the WMI method call.
///
/// ```edition2021
/// # use wmi::{COMLibrary, Variant, WMIConnection, WMIResult};
/// # fn main() -> WMIResult<()> {
/// # let wmi_con = WMIConnection::new(COMLibrary::new()?)?;
/// let in_params = [
/// ("CommandLine".to_string(), Variant::from("systeminfo".to_string()))
/// ].into_iter().collect();
///
/// // Because Create has a return value and out parameters, the Option returned will never be None.
/// // Note: The Create call can be unreliable, so consider using another means of starting processes.
/// let out = wmi_con.exec_method_native_wrapper("Win32_Process", "Win32_Process", "Create", in_params)?.unwrap();
/// println!("The return code of the Create call is {:?}", out.get_property("ReturnValue")?);
/// # Ok(())
/// # }
/// ```
pub fn exec_method_native_wrapper(
&self,
method_class: impl AsRef<str>,
object_path: impl AsRef<str>,
method: impl AsRef<str>,
in_params: HashMap<String, Variant>,
) -> WMIResult<Option<IWbemClassWrapper>> {
let method_class = BSTR::from(method_class.as_ref());
let object_path = BSTR::from(object_path.as_ref());
let method = BSTR::from(method.as_ref());

// See https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemclassobject-getmethod
// GetMethod can only be called on a class definition, so we retrieve that before retrieving a specific object
let mut class_definition = None;
unsafe {
self.svc.GetObject(
&method_class,
Default::default(),
&self.ctx.0,
Some(&mut class_definition),
None,
)?;
}
let class_definition = class_definition.ok_or(WMIError::ResultEmpty)?;
// Retrieve the input signature of the WMI method.
// The fields of the resulting IWbemClassObject will have the names and types of the WMI method's input parameters
let mut input_signature = None;
unsafe {
class_definition.GetMethod(
&method,
Default::default(),
&mut input_signature,
std::ptr::null_mut(),
)?;
}

// The method may have no input parameters, such as in this case: https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/reboot-method-in-class-win32-operatingsystem
let in_params = match input_signature {
Some(input) => {
let inst;
unsafe {
inst = input.SpawnInstance(Default::default())?;
};
// Set every field of the input object to the corresponding input parameter passed to this function
for (wszname, value) in in_params {
let wszname = HSTRING::from(wszname);
let value = TryInto::<VARIANT>::try_into(value)?;

// See https://learn.microsoft.com/en-us/windows/win32/api/wbemcli/nf-wbemcli-iwbemclassobject-put
// Note that the example shows the variant is expected to be cleared (dropped) after the call to Put,
// so passing &value is acceptable here
unsafe {
inst.Put(&wszname, Default::default(), &value, 0)?;
}
}
Some(inst)
}
None => None,
};

// In the case of a method with no out parameters and a VOID return type, there will be no out-parameters object
let mut output = None;
unsafe {
self.svc.ExecMethod(
&object_path,
&method,
Default::default(),
&self.ctx.0,
in_params.as_ref(),
Some(&mut output),
None,
)?;
}

Ok(output.map(IWbemClassWrapper::new))
}

/// Executes a method of a WMI class not tied to any specific instance. Examples include
/// [Create](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process) of `Win32_Process`
/// and [AddPrinterConnection](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/addprinterconnection-method-in-class-win32-printer) of `Win32_Printer`.
///
/// `MethodClass` should have the name of the class on which the method is being invoked.
/// `In` and `Out` can be `()` or any custom structs supporting (de)serialization containing the input and output parameters of the function.
///
/// A method with a return type other than `void` will always try to populate a generic property named `ReturnValue` in the output object with the return value of the WMI method call.
/// If the method call has a `void` return type and no out parameters, the only acceptable type for `Out` is `()`.
///
/// Arrays, Options, unknowns, and nested objects cannot be passed as input parameters due to limitations in how variants are constructed by `windows-rs`.
///
/// This function uses [`WMIConnection::exec_instance_method`] internally, with the name of the method class being the instance path, as is expected by WMI.
///
/// ```edition2021
/// # use serde::{Deserialize, Serialize};
/// # use wmi::{COMLibrary, Variant, WMIConnection, WMIResult};
/// #[derive(Serialize)]
/// # #[allow(non_snake_case)]
/// struct CreateInput {
/// CommandLine: String
/// }
///
/// #[derive(Deserialize)]
/// # #[allow(non_snake_case)]
/// struct CreateOutput {
/// ReturnValue: u32,
/// ProcessId: u32
/// }
///
/// #[derive(Deserialize)]
/// # #[allow(non_camel_case_types)]
/// struct Win32_Process;
///
/// # fn main() -> WMIResult<()> {
/// # let wmi_con = WMIConnection::new(COMLibrary::new()?)?;
/// // Note: The Create call can be unreliable, so consider using another means of starting processes.
/// let input = CreateInput {
/// CommandLine: "systeminfo".to_string()
/// };
/// let output: CreateOutput = wmi_con.exec_class_method::<Win32_Process, _, _>("Create", input)?;
///
/// println!("The return code of the Create call is {}", output.ReturnValue);
/// println!("The ID of the created process is: {}", output.ProcessId);
/// # Ok(())
/// # }
/// ```
pub fn exec_class_method<MethodClass, In, Out>(
&self,
method: impl AsRef<str>,
in_params: In,
) -> WMIResult<Out>
where
MethodClass: de::DeserializeOwned,
In: Serialize,
Out: de::DeserializeOwned,
{
let (method_class, _) = struct_name_and_fields::<MethodClass>()?;
self.exec_instance_method::<MethodClass, In, Out>(method, method_class, in_params)
}

/// Executes a WMI method on a specific instance of a class. Examples include
/// [GetSupportedSize](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-Volume-getsupportedsizes) of `MSFT_Volume`
/// and [Pause](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/pause-method-in-class-win32-printer) of `Win32_Printer`.
///
/// `MethodClass` should have the name of the class on which the method is being invoked.
/// `In` and `Out` can be `()` or any custom structs supporting (de)serialization containing the input and output parameters of the function.
/// `object_path` is the `__Path` variable of the class instance on which the method is being called, which can be obtained from a WMI query.
///
/// A method with a return type other than `void` will always try to populate a generic property named `ReturnValue` in the output object with the return value of the WMI method call.
/// If the method call has a `void` return type and no out parameters, the only acceptable type for `Out` is `()`.
///
/// Arrays, Options, unknowns, and nested objects cannot be passed as input parameters due to limitations in how variants are constructed by `windows-rs`.
///
/// ```edition2021
/// # use serde::{Deserialize, Serialize};
/// # use wmi::{COMLibrary, FilterValue, Variant, WMIConnection, WMIResult};
/// #[derive(Deserialize)]
/// # #[allow(non_snake_case)]
/// struct PrinterOutput {
/// ReturnValue: u32
/// }
///
/// #[derive(Deserialize)]
/// # #[allow(non_camel_case_types, non_snake_case)]
/// struct Win32_Printer {
/// __Path: String
/// }
///
/// # fn main() -> WMIResult<()> {
/// # let wmi_con = WMIConnection::new(COMLibrary::new()?)?;
/// let printers: Vec<Win32_Printer> = wmi_con.query()?;
///
/// for printer in printers {
/// let output: PrinterOutput = wmi_con.exec_instance_method::<Win32_Printer, _, _>("Pause", &printer.__Path, ())?;
/// println!("Pausing the printer returned {}", output.ReturnValue);
///
/// let output: PrinterOutput = wmi_con.exec_instance_method::<Win32_Printer, _, _>("Resume", &printer.__Path, ())?;
/// println!("Resuming the printer returned {}", output.ReturnValue);
/// }
/// # Ok(())
/// # }
/// ```
pub fn exec_instance_method<MethodClass, In, Out>(
&self,
method: impl AsRef<str>,
object_path: impl AsRef<str>,
in_params: In,
) -> WMIResult<Out>
where
MethodClass: de::DeserializeOwned,
In: Serialize,
Out: de::DeserializeOwned,
{
let (method_class, _) = struct_name_and_fields::<MethodClass>()?;
let serializer = VariantStructSerializer::new();
match in_params.serialize(serializer) {
Ok(field_map) => {
let output =
self.exec_method_native_wrapper(method_class, object_path, method, field_map)?;

match output {
Some(class_wrapper) => Ok(class_wrapper.into_desr()?),
None => Out::deserialize(Variant::Empty),
}
}
Err(e) => Err(WMIError::ConvertVariantError(e.to_string())),
}
}
}

#[cfg(test)]
mod tests {
use crate::tests::fixtures::wmi_con;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Win32_Process {
__Path: String,
}

#[derive(Serialize)]
struct CreateParams {
CommandLine: String,
}

#[derive(Deserialize)]
#[allow(non_snake_case)]
struct CreateOutput {
ReturnValue: u32,
ProcessId: u32,
}

#[test]
fn it_exec_methods() {
// Create powershell instance
let wmi_con = wmi_con();
let in_params = CreateParams {
CommandLine: "powershell.exe".to_string(),
};
let out = wmi_con
.exec_class_method::<Win32_Process, CreateParams, CreateOutput>("Create", in_params)
.unwrap();

assert_eq!(out.ReturnValue, 0);

let process = wmi_con
.raw_query::<Win32_Process>(format!(
"SELECT * FROM Win32_Process WHERE ProcessId = {}",
out.ProcessId
))
.unwrap()
.into_iter()
.next()
.unwrap();

wmi_con
.exec_instance_method::<Win32_Process, (), ()>("Terminate", process.__Path, ())
.unwrap();

assert!(
wmi_con
.raw_query::<Win32_Process>(format!(
"SELECT * FROM Win32_Process WHERE ProcessId = {}",
out.ProcessId
))
.unwrap()
.len()
== 0
);
}
}
1 change: 1 addition & 0 deletions src/ser/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod variant_ser;
Loading

0 comments on commit 83b1a60

Please sign in to comment.