mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 18:28:49 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			323 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //
 | |
| // 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)
 | |
| 
 | |
| #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;
 | |
| };
 | |
| 
 | |
| 
 | |
| static DWORD WINAPI
 | |
| _query_thread(LPVOID param)
 | |
| {
 | |
|     IWbemLocator *locator = NULL;
 | |
|     IWbemServices *services = NULL;
 | |
|     IEnumWbemClassObject* enumerator = NULL;
 | |
|     BSTR bstrQuery = NULL;
 | |
|     struct _query_data *data = (struct _query_data*)param;
 | |
| 
 | |
|     HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
 | |
|     if (FAILED(hr)) {
 | |
|         CloseHandle(data->writePipe);
 | |
|         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)) {
 | |
|         hr = locator->ConnectServer(
 | |
|             bstr_t(L"ROOT\\CIMV2"),
 | |
|             NULL, NULL, 0, NULL, 0, 0, &services
 | |
|         );
 | |
|     }
 | |
|     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)) {
 | |
|         bstrQuery = SysAllocString(data->query);
 | |
|         if (!bstrQuery) {
 | |
|             hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
 | |
|         }
 | |
|     }
 | |
|     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;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*[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
 | |
| 
 | |
|     if (!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);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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);
 | |
|     }
 | |
| 
 | |
|     // Allow the thread some time to clean up
 | |
|     switch (WaitForSingleObject(hThread, 1000)) {
 | |
|     case WAIT_OBJECT_0:
 | |
|         // Thread ended cleanly
 | |
|         if (!GetExitCodeThread(hThread, (LPDWORD)&err)) {
 | |
|             err = GetLastError();
 | |
|         }
 | |
|         break;
 | |
|     case WAIT_TIMEOUT:
 | |
|         // Probably stuck - there's not much we can do, unfortunately
 | |
|         if (err == 0 || err == ERROR_BROKEN_PIPE) {
 | |
|             err = WAIT_TIMEOUT;
 | |
|         }
 | |
|         break;
 | |
|     default:
 | |
|         if (err == 0 || err == ERROR_BROKEN_PIPE) {
 | |
|             err = GetLastError();
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     CloseHandle(hThread);
 | |
|     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 wmi_def = {
 | |
|     PyModuleDef_HEAD_INIT,
 | |
|     "_wmi",
 | |
|     NULL,   // doc
 | |
|     0,      // m_size
 | |
|     wmi_functions
 | |
| };
 | |
| 
 | |
| extern "C" {
 | |
|     PyMODINIT_FUNC PyInit__wmi(void)
 | |
|     {
 | |
|         return PyModuleDef_Init(&wmi_def);
 | |
|     }
 | |
| }
 | 
