mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			371 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			371 lines
		
	
	
	
		
			10 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)
 | 
						|
 | 
						|
// 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;
 | 
						|
    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) && !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)) {
 | 
						|
        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;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
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.
 | 
						|
    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);
 | 
						|
    }
 | 
						|
 | 
						|
    // Allow the thread some time to clean up
 | 
						|
    switch (WaitForSingleObject(hThread, 100)) {
 | 
						|
    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);
 | 
						|
    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 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);
 | 
						|
    }
 | 
						|
}
 |