// // Helper library for querying WMI using its COM-based query API. // // Copyright (c) Microsoft Corporation // Licensed to PSF under a contributor agreement // // Version history // 2022-08: Initial contribution (Steve Dower) // clinic/_wmimodule.cpp.h uses internal pycore_modsupport.h API #ifndef Py_BUILD_CORE_BUILTIN # define Py_BUILD_CORE_MODULE 1 #endif #define _WIN32_DCOM #include #include #include #include #include #if _MSVC_LANG >= 202002L // We can use clinic directly when the C++ compiler supports C++20 #include "clinic/_wmimodule.cpp.h" #else // Cannot use clinic because of missing C++20 support, so create a simpler // API instead. This won't impact releases, so fine to omit the docstring. static PyObject *_wmi_exec_query_impl(PyObject *module, PyObject *query); #define _WMI_EXEC_QUERY_METHODDEF {"exec_query", _wmi_exec_query_impl, METH_O, NULL}, #endif /*[clinic input] module _wmi [clinic start generated code]*/ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=7ca95dad1453d10d]*/ struct _query_data { LPCWSTR query; HANDLE writePipe; HANDLE readPipe; HANDLE initEvent; HANDLE connectEvent; }; static DWORD WINAPI _query_thread(LPVOID param) { IWbemLocator *locator = NULL; IWbemServices *services = NULL; IEnumWbemClassObject* enumerator = NULL; HRESULT hr = S_OK; BSTR bstrQuery = NULL; struct _query_data *data = (struct _query_data*)param; // gh-125315: Copy the query string first, so that if the main thread gives // up on waiting we aren't left with a dangling pointer (and a likely crash) bstrQuery = SysAllocString(data->query); if (!bstrQuery) { hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); } if (SUCCEEDED(hr)) { hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); } if (FAILED(hr)) { CloseHandle(data->writePipe); if (bstrQuery) { SysFreeString(bstrQuery); } return (DWORD)hr; } hr = CoInitializeSecurity( NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL ); // gh-96684: CoInitializeSecurity will fail if another part of the app has // already called it. Hopefully they passed lenient enough settings that we // can complete the WMI query, so keep going. if (hr == RPC_E_TOO_LATE) { hr = 0; } if (SUCCEEDED(hr)) { hr = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&locator ); } if (SUCCEEDED(hr) && !SetEvent(data->initEvent)) { hr = HRESULT_FROM_WIN32(GetLastError()); } if (SUCCEEDED(hr)) { hr = locator->ConnectServer( bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &services ); } if (SUCCEEDED(hr) && !SetEvent(data->connectEvent)) { 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)) { hr = services->ExecQuery( bstr_t("WQL"), bstrQuery, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &enumerator ); } // Okay, after all that, at this stage we should have an enumerator // to the query results and can start writing them to the pipe! IWbemClassObject *value = NULL; int startOfEnum = TRUE; int endOfEnum = FALSE; while (SUCCEEDED(hr) && !endOfEnum) { ULONG got = 0; DWORD written; hr = enumerator->Next(WBEM_INFINITE, 1, &value, &got); if (hr == WBEM_S_FALSE) { // Could be at the end, but still got a result this time endOfEnum = TRUE; hr = 0; break; } if (FAILED(hr) || got != 1 || !value) { continue; } if (!startOfEnum && !WriteFile(data->writePipe, (LPVOID)L"\0", 2, &written, NULL)) { hr = HRESULT_FROM_WIN32(GetLastError()); break; } startOfEnum = FALSE; // Okay, now we have each resulting object it's time to // enumerate its members hr = value->BeginEnumeration(0); if (FAILED(hr)) { value->Release(); break; } while (SUCCEEDED(hr)) { BSTR propName; VARIANT propValue; long flavor; hr = value->Next(0, &propName, &propValue, NULL, &flavor); if (hr == WBEM_S_NO_MORE_DATA) { hr = 0; break; } if (SUCCEEDED(hr) && (flavor & WBEM_FLAVOR_MASK_ORIGIN) != WBEM_FLAVOR_ORIGIN_SYSTEM) { WCHAR propStr[8192]; hr = VariantToString(propValue, propStr, sizeof(propStr) / sizeof(propStr[0])); if (SUCCEEDED(hr)) { DWORD cbStr1, cbStr2; cbStr1 = (DWORD)(wcslen(propName) * sizeof(propName[0])); cbStr2 = (DWORD)(wcslen(propStr) * sizeof(propStr[0])); if (!WriteFile(data->writePipe, propName, cbStr1, &written, NULL) || !WriteFile(data->writePipe, (LPVOID)L"=", 2, &written, NULL) || !WriteFile(data->writePipe, propStr, cbStr2, &written, NULL) || !WriteFile(data->writePipe, (LPVOID)L"\0", 2, &written, NULL) ) { hr = HRESULT_FROM_WIN32(GetLastError()); } } VariantClear(&propValue); SysFreeString(propName); } } value->EndEnumeration(); value->Release(); } if (bstrQuery) { SysFreeString(bstrQuery); } if (enumerator) { enumerator->Release(); } if (services) { services->Release(); } if (locator) { locator->Release(); } CoUninitialize(); CloseHandle(data->writePipe); return (DWORD)hr; } static DWORD wait_event(HANDLE event, DWORD timeout) { DWORD err = 0; switch (WaitForSingleObject(event, timeout)) { case WAIT_OBJECT_0: break; case WAIT_TIMEOUT: err = WAIT_TIMEOUT; break; default: err = GetLastError(); break; } return err; } /*[clinic input] _wmi.exec_query query: unicode Runs a WMI query against the local machine. This returns a single string with 'name=value' pairs in a flat array separated by null characters. [clinic start generated code]*/ static PyObject * _wmi_exec_query_impl(PyObject *module, PyObject *query) /*[clinic end generated code: output=a62303d5bb5e003f input=48d2d0a1e1a7e3c2]*/ /*[clinic end generated code]*/ { PyObject *result = NULL; HANDLE hThread = NULL; int err = 0; WCHAR buffer[8192]; DWORD offset = 0; DWORD bytesRead; struct _query_data data = {0}; if (PySys_Audit("_wmi.exec_query", "O", query) < 0) { return NULL; } data.query = PyUnicode_AsWideCharString(query, NULL); if (!data.query) { return NULL; } if (0 != _wcsnicmp(data.query, L"select ", 7)) { PyMem_Free((void *)data.query); PyErr_SetString(PyExc_ValueError, "only SELECT queries are supported"); return NULL; } Py_BEGIN_ALLOW_THREADS data.initEvent = CreateEvent(NULL, TRUE, FALSE, NULL); data.connectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!data.initEvent || !data.connectEvent || !CreatePipe(&data.readPipe, &data.writePipe, NULL, 0)) { err = GetLastError(); } else { hThread = CreateThread(NULL, 0, _query_thread, (LPVOID*)&data, 0, NULL); if (!hThread) { err = GetLastError(); // Normally the thread proc closes this handle, but since we never started // we need to close it here. CloseHandle(data.writePipe); } } // gh-112278: If current user doesn't have permission to query the WMI, the // function IWbemLocator::ConnectServer will hang for 5 seconds, and there // is no way to specify the timeout. So we use an Event object to simulate // a timeout. The initEvent will be set after COM initialization, it will // take a longer time when first initialized. The connectEvent will be set // after connected to WMI. if (!err) { err = wait_event(data.initEvent, 1000); if (!err) { err = wait_event(data.connectEvent, 100); } } while (!err) { if (ReadFile( data.readPipe, (LPVOID)&buffer[offset / sizeof(buffer[0])], sizeof(buffer) - offset, &bytesRead, NULL )) { offset += bytesRead; if (offset >= sizeof(buffer)) { err = ERROR_MORE_DATA; } } else { err = GetLastError(); } } if (data.readPipe) { CloseHandle(data.readPipe); } if (hThread) { // Allow the thread some time to clean up int thread_err; switch (WaitForSingleObject(hThread, 100)) { case WAIT_OBJECT_0: // Thread ended cleanly if (!GetExitCodeThread(hThread, (LPDWORD)&thread_err)) { thread_err = GetLastError(); } break; case WAIT_TIMEOUT: // Probably stuck - there's not much we can do, unfortunately thread_err = WAIT_TIMEOUT; break; default: thread_err = GetLastError(); break; } // An error on our side is more likely to be relevant than one from // the thread, but if we don't have one on our side we'll take theirs. if (err == 0 || err == ERROR_BROKEN_PIPE) { err = thread_err; } CloseHandle(hThread); } CloseHandle(data.initEvent); CloseHandle(data.connectEvent); hThread = NULL; Py_END_ALLOW_THREADS PyMem_Free((void *)data.query); if (err == ERROR_MORE_DATA) { PyErr_Format(PyExc_OSError, "Query returns more than %zd characters", Py_ARRAY_LENGTH(buffer)); return NULL; } else if (err) { PyErr_SetFromWindowsErr(err); return NULL; } if (!offset) { return PyUnicode_FromStringAndSize(NULL, 0); } return PyUnicode_FromWideChar(buffer, offset / sizeof(buffer[0]) - 1); } static PyMethodDef wmi_functions[] = { _WMI_EXEC_QUERY_METHODDEF { NULL, NULL, 0, NULL } }; static PyModuleDef_Slot wmi_slots[] = { {Py_mod_gil, Py_MOD_GIL_NOT_USED}, {0, NULL}, }; static PyModuleDef wmi_def = { PyModuleDef_HEAD_INIT, "_wmi", NULL, // doc 0, // m_size wmi_functions, // m_methods wmi_slots, // m_slots }; extern "C" { PyMODINIT_FUNC PyInit__wmi(void) { return PyModuleDef_Init(&wmi_def); } }