mirror of https://github.com/python/cpython.git
393 lines
11 KiB
C++
393 lines
11 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;
|
|
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);
|
|
}
|
|
}
|