| // | 
 | // 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 <Windows.h> | 
 | #include <comdef.h> | 
 | #include <Wbemidl.h> | 
 | #include <propvarutil.h> | 
 |  | 
 | #include <Python.h> | 
 |  | 
 |  | 
 | #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; | 
 |     _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] | 
 | @permit_long_docstring_body | 
 | _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=621f5c50c56d06d0]*/ | 
 |  | 
 | /*[clinic end generated code]*/ | 
 | { | 
 |     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); | 
 |     } | 
 | } |