run-rs 0.6.23

Run a subset of Rust as an interpreted script
//! WMI queries. A connection is stored as its namespace path and reopened per call, so nothing lands
//! in the `Native` enum. A query returns a vec of maps like `Get-CimInstance`. Off `Windows`
//! every call returns an error.

use anyhow::Result;

use super::bytecode::MethodName;
use super::value::{StructData, Value};

/// `WMIConnection::new()` defaults to `root\cimv2`. Neither takes a `COMLibrary`, the crate
/// initializes COM per thread.
pub(super) fn connection(args: &[Value], default_namespace: bool) -> Value {
    let ns = if default_namespace {
        r"root\cimv2".to_string()
    } else {
        args.first().map(Value::display).unwrap_or_default()
    };
    imp::connection(ns)
}

pub(super) fn wmi_method(s: &StructData, name: &MethodName, args: &[Value]) -> Result<Value> {
    imp::wmi_method(s, name, args)
}

#[cfg(windows)]
mod imp {
    use super::super::bytecode::{BuiltinId, MethodName};

    use std::collections::HashMap;

    use anyhow::{Result, bail};
    use indexmap::IndexMap;
    use wmi::{Variant, WMIConnection};

    use super::super::numeric::IntWidth;
    use super::super::value::{MapKey, StructData, Value};

    /// A bad namespace reports itself when the connection is opened.
    pub(super) fn connection(namespace: String) -> Value {
        Value::ok(Value::struct_of(
            "WmiConnection",
            [("namespace".into(), Value::str(namespace))],
        ))
    }

    fn connect(namespace: &str) -> Result<WMIConnection> {
        Ok(WMIConnection::with_namespace_path(namespace)?)
    }

    /// Values are returned bare, the map lookup already hands back an Option. A present null
    /// reads as None inside that outer Some.
    fn from_variant(v: &Variant) -> Value {
        match v {
            Variant::Empty | Variant::Null => Value::none(),
            Variant::String(s) => Value::str(s.clone()),
            Variant::Bool(b) => Value::Bool(*b),
            Variant::I1(n) => Value::Int(i64::from(*n)),
            Variant::I2(n) => Value::Int(i64::from(*n)),
            Variant::I4(n) => Value::Int(i64::from(*n)),
            Variant::I8(n) => Value::Int(*n),
            Variant::UI1(n) => Value::Int(i64::from(*n)),
            Variant::UI2(n) => Value::Int(i64::from(*n)),
            Variant::UI4(n) => Value::Int(i64::from(*n)),
            Variant::UI8(n) => Value::int_of_width(i128::from(*n), IntWidth::U64),
            Variant::R4(n) => Value::Float(f64::from(*n)),
            Variant::R8(n) => Value::Float(*n),
            Variant::Array(items) => Value::vec(items.iter().map(from_variant).collect()),
            other => Value::str(format!("{other:?}")),
        }
    }

    fn row_to_value(row: &HashMap<String, Variant>) -> Value {
        let mut names: Vec<&String> = row.keys().collect();
        // sorted for a stable result
        names.sort();
        let mut map = IndexMap::default();
        for name in names {
            let Some(v) = row.get(name) else { continue };
            map.insert(MapKey::Str(name.as_str().into()), from_variant(v));
        }
        Value::map_of(map)
    }

    pub(super) fn wmi_method(s: &StructData, name: &MethodName, args: &[Value]) -> Result<Value> {
        let namespace = s
            .get("namespace")
            .map_or_else(|| r"root\cimv2".to_string(), |v| v.display());
        Ok(match name.id {
            BuiltinId::RawQuery | BuiltinId::Query => {
                let q = args.first().map(Value::display).unwrap_or_default();
                match connect(&namespace)
                    .and_then(|c| Ok(c.raw_query::<HashMap<String, Variant>>(&q)?))
                {
                    Ok(rows) => Value::ok(Value::vec(rows.iter().map(row_to_value).collect())),
                    Err(e) => Value::err(Value::str(e.to_string())),
                }
            }
            _ => bail!("unknown method `{name}` on WmiConnection"),
        })
    }
}

#[cfg(not(windows))]
mod imp {
    use super::super::bytecode::MethodName;
    use anyhow::{Result, bail};

    use super::super::value::{StructData, Value};

    pub(super) fn connection(_namespace: String) -> Value {
        Value::err(Value::str("WMI does not exist on this platform"))
    }

    pub(super) fn wmi_method(_s: &StructData, name: &MethodName, _args: &[Value]) -> Result<Value> {
        bail!("WmiConnection::{name} is WMI, it does not exist on this platform")
    }
}