#![allow(non_snake_case)]
pub(crate) use _wmi::module_def;
mod wmi_ffi {
#![allow(unsafe_op_in_unsafe_fn)]
use core::ffi::c_void;
pub type HRESULT = i32;
#[repr(C)]
pub struct GUID {
pub data1: u32,
pub data2: u16,
pub data3: u16,
pub data4: [u8; 8],
}
#[repr(C, align(8))]
pub struct VARIANT([u64; 3]);
impl VARIANT {
pub fn zeroed() -> Self {
VARIANT([0u64; 3])
}
}
pub const CLSID_WBEM_LOCATOR: GUID = GUID {
data1: 0x4590F811,
data2: 0x1D3A,
data3: 0x11D0,
data4: [0x89, 0x1F, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24],
};
pub const IID_IWBEM_LOCATOR: GUID = GUID {
data1: 0xDC12A687,
data2: 0x737F,
data3: 0x11CF,
data4: [0x88, 0x4D, 0x00, 0xAA, 0x00, 0x4B, 0x2E, 0x24],
};
pub const COINIT_APARTMENTTHREADED: u32 = 0x2;
pub const CLSCTX_INPROC_SERVER: u32 = 0x1;
pub const RPC_C_AUTHN_LEVEL_DEFAULT: u32 = 0;
pub const RPC_C_IMP_LEVEL_IMPERSONATE: u32 = 3;
pub const RPC_C_AUTHN_LEVEL_CALL: u32 = 3;
pub const RPC_C_AUTHN_WINNT: u32 = 10;
pub const RPC_C_AUTHZ_NONE: u32 = 0;
pub const EOAC_NONE: u32 = 0;
pub const RPC_E_TOO_LATE: HRESULT = 0x80010119_u32 as i32;
pub const WBEM_FLAG_FORWARD_ONLY: i32 = 0x20;
pub const WBEM_FLAG_RETURN_IMMEDIATELY: i32 = 0x10;
pub const WBEM_S_FALSE: HRESULT = 1;
pub const WBEM_S_NO_MORE_DATA: HRESULT = 0x40005;
pub const WBEM_INFINITE: i32 = -1;
pub const WBEM_FLAVOR_MASK_ORIGIN: i32 = 0x60;
pub const WBEM_FLAVOR_ORIGIN_SYSTEM: i32 = 0x40;
#[link(name = "ole32")]
unsafe extern "system" {
pub fn CoInitializeEx(pvReserved: *mut c_void, dwCoInit: u32) -> HRESULT;
pub fn CoUninitialize();
pub fn CoInitializeSecurity(
pSecDesc: *const c_void,
cAuthSvc: i32,
asAuthSvc: *const c_void,
pReserved1: *const c_void,
dwAuthnLevel: u32,
dwImpLevel: u32,
pAuthList: *const c_void,
dwCapabilities: u32,
pReserved3: *const c_void,
) -> HRESULT;
pub fn CoCreateInstance(
rclsid: *const GUID,
pUnkOuter: *mut c_void,
dwClsContext: u32,
riid: *const GUID,
ppv: *mut *mut c_void,
) -> HRESULT;
pub fn CoSetProxyBlanket(
pProxy: *mut c_void,
dwAuthnSvc: u32,
dwAuthzSvc: u32,
pServerPrincName: *const u16,
dwAuthnLevel: u32,
dwImpLevel: u32,
pAuthInfo: *const c_void,
dwCapabilities: u32,
) -> HRESULT;
}
#[link(name = "oleaut32")]
unsafe extern "system" {
pub fn SysAllocString(psz: *const u16) -> *mut u16;
pub fn SysFreeString(bstrString: *mut u16);
pub fn VariantClear(pvarg: *mut VARIANT) -> HRESULT;
}
#[link(name = "propsys")]
unsafe extern "system" {
pub fn VariantToString(varIn: *const VARIANT, pszBuf: *mut u16, cchBuf: u32) -> HRESULT;
}
pub unsafe fn com_release(this: *mut c_void) {
if !this.is_null() {
let vtable = *(this as *const *const usize);
let release: unsafe extern "system" fn(*mut c_void) -> u32 =
core::mem::transmute(*vtable.add(2));
release(this);
}
}
#[allow(clippy::too_many_arguments)]
pub unsafe fn locator_connect_server(
this: *mut c_void,
network_resource: *const u16,
user: *const u16,
password: *const u16,
locale: *const u16,
security_flags: i32,
authority: *const u16,
ctx: *mut c_void,
services: *mut *mut c_void,
) -> HRESULT {
let vtable = *(this as *const *const usize);
let method: unsafe extern "system" fn(
*mut c_void,
*const u16,
*const u16,
*const u16,
*const u16,
i32,
*const u16,
*mut c_void,
*mut *mut c_void,
) -> HRESULT = core::mem::transmute(*vtable.add(3));
method(
this,
network_resource,
user,
password,
locale,
security_flags,
authority,
ctx,
services,
)
}
pub unsafe fn services_exec_query(
this: *mut c_void,
query_language: *const u16,
query: *const u16,
flags: i32,
ctx: *mut c_void,
enumerator: *mut *mut c_void,
) -> HRESULT {
let vtable = *(this as *const *const usize);
let method: unsafe extern "system" fn(
*mut c_void,
*const u16,
*const u16,
i32,
*mut c_void,
*mut *mut c_void,
) -> HRESULT = core::mem::transmute(*vtable.add(20));
method(this, query_language, query, flags, ctx, enumerator)
}
pub unsafe fn enum_next(
this: *mut c_void,
timeout: i32,
count: u32,
objects: *mut *mut c_void,
returned: *mut u32,
) -> HRESULT {
let vtable = *(this as *const *const usize);
let method: unsafe extern "system" fn(
*mut c_void,
i32,
u32,
*mut *mut c_void,
*mut u32,
) -> HRESULT = core::mem::transmute(*vtable.add(4));
method(this, timeout, count, objects, returned)
}
pub unsafe fn object_begin_enumeration(this: *mut c_void, enum_flags: i32) -> HRESULT {
let vtable = *(this as *const *const usize);
let method: unsafe extern "system" fn(*mut c_void, i32) -> HRESULT =
core::mem::transmute(*vtable.add(8));
method(this, enum_flags)
}
pub unsafe fn object_next(
this: *mut c_void,
flags: i32,
name: *mut *mut u16,
val: *mut VARIANT,
cim_type: *mut i32,
flavor: *mut i32,
) -> HRESULT {
let vtable = *(this as *const *const usize);
let method: unsafe extern "system" fn(
*mut c_void,
i32,
*mut *mut u16,
*mut VARIANT,
*mut i32,
*mut i32,
) -> HRESULT = core::mem::transmute(*vtable.add(9));
method(this, flags, name, val, cim_type, flavor)
}
pub unsafe fn object_end_enumeration(this: *mut c_void) -> HRESULT {
let vtable = *(this as *const *const usize);
let method: unsafe extern "system" fn(*mut c_void) -> HRESULT =
core::mem::transmute(*vtable.add(10));
method(this)
}
}
#[pymodule]
mod _wmi {
use super::wmi_ffi::*;
use crate::builtins::PyStrRef;
use crate::convert::ToPyException;
use crate::{PyResult, VirtualMachine};
use core::ffi::c_void;
use core::ptr::{null, null_mut};
use windows_sys::Win32::Foundation::{
CloseHandle, ERROR_BROKEN_PIPE, ERROR_MORE_DATA, ERROR_NOT_ENOUGH_MEMORY, GetLastError,
HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT,
};
use windows_sys::Win32::Storage::FileSystem::{ReadFile, WriteFile};
use windows_sys::Win32::System::Pipes::CreatePipe;
use windows_sys::Win32::System::Threading::{
CreateEventW, CreateThread, GetExitCodeThread, SetEvent, WaitForSingleObject,
};
const BUFFER_SIZE: usize = 8192;
fn hresult_from_win32(err: u32) -> HRESULT {
if err == 0 {
0
} else {
((err & 0xFFFF) | 0x80070000) as HRESULT
}
}
fn succeeded(hr: HRESULT) -> bool {
hr >= 0
}
fn failed(hr: HRESULT) -> bool {
hr < 0
}
fn wide_str(s: &str) -> Vec<u16> {
s.encode_utf16().chain(core::iter::once(0)).collect()
}
unsafe fn wcslen(s: *const u16) -> usize {
unsafe {
let mut len = 0;
while *s.add(len) != 0 {
len += 1;
}
len
}
}
unsafe fn wait_event(event: HANDLE, timeout: u32) -> u32 {
unsafe {
match WaitForSingleObject(event, timeout) {
WAIT_OBJECT_0 => 0,
WAIT_TIMEOUT => WAIT_TIMEOUT,
_ => GetLastError(),
}
}
}
struct QueryThreadData {
query: Vec<u16>,
write_pipe: HANDLE,
init_event: HANDLE,
connect_event: HANDLE,
}
unsafe impl Send for QueryThreadData {}
unsafe extern "system" fn query_thread(param: *mut c_void) -> u32 {
unsafe { query_thread_impl(param) }
}
unsafe fn query_thread_impl(param: *mut c_void) -> u32 {
unsafe {
let data = Box::from_raw(param as *mut QueryThreadData);
let write_pipe = data.write_pipe;
let init_event = data.init_event;
let connect_event = data.connect_event;
let mut locator: *mut c_void = null_mut();
let mut services: *mut c_void = null_mut();
let mut enumerator: *mut c_void = null_mut();
let mut hr: HRESULT = 0;
let bstr_query = SysAllocString(data.query.as_ptr());
if bstr_query.is_null() {
hr = hresult_from_win32(ERROR_NOT_ENOUGH_MEMORY);
}
drop(data);
if succeeded(hr) {
hr = CoInitializeEx(null_mut(), COINIT_APARTMENTTHREADED);
}
if failed(hr) {
CloseHandle(write_pipe);
if !bstr_query.is_null() {
SysFreeString(bstr_query);
}
return hr as u32;
}
hr = CoInitializeSecurity(
null(),
-1,
null(),
null(),
RPC_C_AUTHN_LEVEL_DEFAULT,
RPC_C_IMP_LEVEL_IMPERSONATE,
null(),
EOAC_NONE,
null(),
);
if hr == RPC_E_TOO_LATE {
hr = 0;
}
if succeeded(hr) {
hr = CoCreateInstance(
&CLSID_WBEM_LOCATOR,
null_mut(),
CLSCTX_INPROC_SERVER,
&IID_IWBEM_LOCATOR,
&mut locator,
);
}
if succeeded(hr) && SetEvent(init_event) == 0 {
hr = hresult_from_win32(GetLastError());
}
if succeeded(hr) {
let root_cimv2 = wide_str("ROOT\\CIMV2");
let bstr_root = SysAllocString(root_cimv2.as_ptr());
hr = locator_connect_server(
locator,
bstr_root,
null(),
null(),
null(),
0,
null(),
null_mut(),
&mut services,
);
if !bstr_root.is_null() {
SysFreeString(bstr_root);
}
}
if succeeded(hr) && SetEvent(connect_event) == 0 {
hr = hresult_from_win32(GetLastError());
}
if succeeded(hr) {
hr = CoSetProxyBlanket(
services,
RPC_C_AUTHN_WINNT,
RPC_C_AUTHZ_NONE,
null(),
RPC_C_AUTHN_LEVEL_CALL,
RPC_C_IMP_LEVEL_IMPERSONATE,
null(),
EOAC_NONE,
);
}
if succeeded(hr) {
let wql = wide_str("WQL");
let bstr_wql = SysAllocString(wql.as_ptr());
hr = services_exec_query(
services,
bstr_wql,
bstr_query,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
null_mut(),
&mut enumerator,
);
if !bstr_wql.is_null() {
SysFreeString(bstr_wql);
}
}
let mut value: *mut c_void;
let mut start_of_enum = true;
let null_sep: u16 = 0;
let eq_sign: u16 = b'=' as u16;
while succeeded(hr) {
let mut got: u32 = 0;
let mut written: u32 = 0;
value = null_mut();
hr = enum_next(enumerator, WBEM_INFINITE, 1, &mut value, &mut got);
if hr == WBEM_S_FALSE {
hr = 0;
break;
}
if failed(hr) || got != 1 || value.is_null() {
continue;
}
if !start_of_enum
&& WriteFile(
write_pipe,
&null_sep as *const u16 as *const _,
2,
&mut written,
null_mut(),
) == 0
{
hr = hresult_from_win32(GetLastError());
com_release(value);
break;
}
start_of_enum = false;
hr = object_begin_enumeration(value, 0);
if failed(hr) {
com_release(value);
break;
}
while succeeded(hr) {
let mut prop_name: *mut u16 = null_mut();
let mut prop_value = VARIANT::zeroed();
let mut flavor: i32 = 0;
hr = object_next(
value,
0,
&mut prop_name,
&mut prop_value,
null_mut(),
&mut flavor,
);
if hr == WBEM_S_NO_MORE_DATA {
hr = 0;
break;
}
if succeeded(hr)
&& (flavor & WBEM_FLAVOR_MASK_ORIGIN) != WBEM_FLAVOR_ORIGIN_SYSTEM
{
let mut prop_str = [0u16; BUFFER_SIZE];
hr =
VariantToString(&prop_value, prop_str.as_mut_ptr(), BUFFER_SIZE as u32);
if succeeded(hr) {
let cb_str1 = (wcslen(prop_name) * 2) as u32;
let cb_str2 = (wcslen(prop_str.as_ptr()) * 2) as u32;
if WriteFile(
write_pipe,
prop_name as *const _,
cb_str1,
&mut written,
null_mut(),
) == 0
|| WriteFile(
write_pipe,
&eq_sign as *const u16 as *const _,
2,
&mut written,
null_mut(),
) == 0
|| WriteFile(
write_pipe,
prop_str.as_ptr() as *const _,
cb_str2,
&mut written,
null_mut(),
) == 0
|| WriteFile(
write_pipe,
&null_sep as *const u16 as *const _,
2,
&mut written,
null_mut(),
) == 0
{
hr = hresult_from_win32(GetLastError());
}
}
VariantClear(&mut prop_value);
SysFreeString(prop_name);
}
}
object_end_enumeration(value);
com_release(value);
}
if !bstr_query.is_null() {
SysFreeString(bstr_query);
}
if !enumerator.is_null() {
com_release(enumerator);
}
if !services.is_null() {
com_release(services);
}
if !locator.is_null() {
com_release(locator);
}
CoUninitialize();
CloseHandle(write_pipe);
hr as u32
}
}
#[pyfunction]
fn exec_query(query: PyStrRef, vm: &VirtualMachine) -> PyResult<String> {
let query_str = query.expect_str();
if !query_str
.get(..7)
.is_some_and(|s| s.eq_ignore_ascii_case("select "))
{
return Err(vm.new_value_error("only SELECT queries are supported"));
}
let query_wide = wide_str(query_str);
let mut h_thread: HANDLE = null_mut();
let mut err: u32 = 0;
let mut buffer = [0u16; BUFFER_SIZE];
let mut offset: u32 = 0;
let mut bytes_read: u32 = 0;
let mut read_pipe: HANDLE = null_mut();
let mut write_pipe: HANDLE = null_mut();
unsafe {
let init_event = CreateEventW(null(), 1, 0, null());
let connect_event = CreateEventW(null(), 1, 0, null());
if init_event.is_null()
|| connect_event.is_null()
|| CreatePipe(&mut read_pipe, &mut write_pipe, null(), 0) == 0
{
err = GetLastError();
} else {
let thread_data = Box::new(QueryThreadData {
query: query_wide,
write_pipe,
init_event,
connect_event,
});
let thread_data_ptr = Box::into_raw(thread_data);
h_thread = CreateThread(
null(),
0,
Some(query_thread),
thread_data_ptr as *const _ as *mut _,
0,
null_mut(),
);
if h_thread.is_null() {
err = GetLastError();
let data = Box::from_raw(thread_data_ptr);
CloseHandle(data.write_pipe);
}
}
if err == 0 {
err = wait_event(init_event, 1000);
if err == 0 {
err = wait_event(connect_event, 100);
}
}
while err == 0 {
let buf_ptr = (buffer.as_mut_ptr() as *mut u8).add(offset as usize);
let buf_remaining = (BUFFER_SIZE * 2) as u32 - offset;
if ReadFile(
read_pipe,
buf_ptr as *mut _,
buf_remaining,
&mut bytes_read,
null_mut(),
) != 0
{
offset += bytes_read;
if offset >= (BUFFER_SIZE * 2) as u32 {
err = ERROR_MORE_DATA;
}
} else {
err = GetLastError();
}
}
if !read_pipe.is_null() {
CloseHandle(read_pipe);
}
if !h_thread.is_null() {
let thread_err: u32;
match WaitForSingleObject(h_thread, 100) {
WAIT_OBJECT_0 => {
let mut exit_code: u32 = 0;
if GetExitCodeThread(h_thread, &mut exit_code) == 0 {
thread_err = GetLastError();
} else {
thread_err = exit_code;
}
}
WAIT_TIMEOUT => {
thread_err = WAIT_TIMEOUT;
}
_ => {
thread_err = GetLastError();
}
}
if err == 0 || err == ERROR_BROKEN_PIPE {
err = thread_err;
}
CloseHandle(h_thread);
}
CloseHandle(init_event);
CloseHandle(connect_event);
}
if err == ERROR_MORE_DATA {
return Err(vm.new_os_error(format!(
"Query returns more than {} characters",
BUFFER_SIZE,
)));
} else if err != 0 {
return Err(std::io::Error::from_raw_os_error(err as i32).to_pyexception(vm));
}
if offset == 0 {
return Ok(String::new());
}
let char_count = (offset as usize) / 2 - 1;
Ok(String::from_utf16_lossy(&buffer[..char_count]))
}
}