Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/PC/_wmimodule.cpp
12 views
1
//
2
// Helper library for querying WMI using its COM-based query API.
3
//
4
// Copyright (c) Microsoft Corporation
5
// Licensed to PSF under a contributor agreement
6
//
7
8
// Version history
9
// 2022-08: Initial contribution (Steve Dower)
10
11
#define _WIN32_DCOM
12
#include <Windows.h>
13
#include <comdef.h>
14
#include <Wbemidl.h>
15
#include <propvarutil.h>
16
17
#include <Python.h>
18
19
20
#if _MSVC_LANG >= 202002L
21
// We can use clinic directly when the C++ compiler supports C++20
22
#include "clinic/_wmimodule.cpp.h"
23
#else
24
// Cannot use clinic because of missing C++20 support, so create a simpler
25
// API instead. This won't impact releases, so fine to omit the docstring.
26
static PyObject *_wmi_exec_query_impl(PyObject *module, PyObject *query);
27
#define _WMI_EXEC_QUERY_METHODDEF {"exec_query", _wmi_exec_query_impl, METH_O, NULL},
28
#endif
29
30
31
/*[clinic input]
32
module _wmi
33
[clinic start generated code]*/
34
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=7ca95dad1453d10d]*/
35
36
37
38
struct _query_data {
39
LPCWSTR query;
40
HANDLE writePipe;
41
HANDLE readPipe;
42
};
43
44
45
static DWORD WINAPI
46
_query_thread(LPVOID param)
47
{
48
IWbemLocator *locator = NULL;
49
IWbemServices *services = NULL;
50
IEnumWbemClassObject* enumerator = NULL;
51
BSTR bstrQuery = NULL;
52
struct _query_data *data = (struct _query_data*)param;
53
54
HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
55
if (FAILED(hr)) {
56
CloseHandle(data->writePipe);
57
return (DWORD)hr;
58
}
59
60
hr = CoInitializeSecurity(
61
NULL, -1, NULL, NULL,
62
RPC_C_AUTHN_LEVEL_DEFAULT,
63
RPC_C_IMP_LEVEL_IMPERSONATE,
64
NULL, EOAC_NONE, NULL
65
);
66
// gh-96684: CoInitializeSecurity will fail if another part of the app has
67
// already called it. Hopefully they passed lenient enough settings that we
68
// can complete the WMI query, so keep going.
69
if (hr == RPC_E_TOO_LATE) {
70
hr = 0;
71
}
72
if (SUCCEEDED(hr)) {
73
hr = CoCreateInstance(
74
CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER,
75
IID_IWbemLocator, (LPVOID *)&locator
76
);
77
}
78
if (SUCCEEDED(hr)) {
79
hr = locator->ConnectServer(
80
bstr_t(L"ROOT\\CIMV2"),
81
NULL, NULL, 0, NULL, 0, 0, &services
82
);
83
}
84
if (SUCCEEDED(hr)) {
85
hr = CoSetProxyBlanket(
86
services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
87
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE,
88
NULL, EOAC_NONE
89
);
90
}
91
if (SUCCEEDED(hr)) {
92
bstrQuery = SysAllocString(data->query);
93
if (!bstrQuery) {
94
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
95
}
96
}
97
if (SUCCEEDED(hr)) {
98
hr = services->ExecQuery(
99
bstr_t("WQL"),
100
bstrQuery,
101
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
102
NULL,
103
&enumerator
104
);
105
}
106
107
// Okay, after all that, at this stage we should have an enumerator
108
// to the query results and can start writing them to the pipe!
109
IWbemClassObject *value = NULL;
110
int startOfEnum = TRUE;
111
int endOfEnum = FALSE;
112
while (SUCCEEDED(hr) && !endOfEnum) {
113
ULONG got = 0;
114
DWORD written;
115
hr = enumerator->Next(WBEM_INFINITE, 1, &value, &got);
116
if (hr == WBEM_S_FALSE) {
117
// Could be at the end, but still got a result this time
118
endOfEnum = TRUE;
119
hr = 0;
120
break;
121
}
122
if (FAILED(hr) || got != 1 || !value) {
123
continue;
124
}
125
if (!startOfEnum && !WriteFile(data->writePipe, (LPVOID)L"\0", 2, &written, NULL)) {
126
hr = HRESULT_FROM_WIN32(GetLastError());
127
break;
128
}
129
startOfEnum = FALSE;
130
// Okay, now we have each resulting object it's time to
131
// enumerate its members
132
hr = value->BeginEnumeration(0);
133
if (FAILED(hr)) {
134
value->Release();
135
break;
136
}
137
while (SUCCEEDED(hr)) {
138
BSTR propName;
139
VARIANT propValue;
140
long flavor;
141
hr = value->Next(0, &propName, &propValue, NULL, &flavor);
142
if (hr == WBEM_S_NO_MORE_DATA) {
143
hr = 0;
144
break;
145
}
146
if (SUCCEEDED(hr) && (flavor & WBEM_FLAVOR_MASK_ORIGIN) != WBEM_FLAVOR_ORIGIN_SYSTEM) {
147
WCHAR propStr[8192];
148
hr = VariantToString(propValue, propStr, sizeof(propStr) / sizeof(propStr[0]));
149
if (SUCCEEDED(hr)) {
150
DWORD cbStr1, cbStr2;
151
cbStr1 = (DWORD)(wcslen(propName) * sizeof(propName[0]));
152
cbStr2 = (DWORD)(wcslen(propStr) * sizeof(propStr[0]));
153
if (!WriteFile(data->writePipe, propName, cbStr1, &written, NULL) ||
154
!WriteFile(data->writePipe, (LPVOID)L"=", 2, &written, NULL) ||
155
!WriteFile(data->writePipe, propStr, cbStr2, &written, NULL) ||
156
!WriteFile(data->writePipe, (LPVOID)L"\0", 2, &written, NULL)
157
) {
158
hr = HRESULT_FROM_WIN32(GetLastError());
159
}
160
}
161
VariantClear(&propValue);
162
SysFreeString(propName);
163
}
164
}
165
value->EndEnumeration();
166
value->Release();
167
}
168
169
if (bstrQuery) {
170
SysFreeString(bstrQuery);
171
}
172
if (enumerator) {
173
enumerator->Release();
174
}
175
if (services) {
176
services->Release();
177
}
178
if (locator) {
179
locator->Release();
180
}
181
CoUninitialize();
182
CloseHandle(data->writePipe);
183
return (DWORD)hr;
184
}
185
186
187
/*[clinic input]
188
_wmi.exec_query
189
190
query: unicode
191
192
Runs a WMI query against the local machine.
193
194
This returns a single string with 'name=value' pairs in a flat array separated
195
by null characters.
196
[clinic start generated code]*/
197
198
static PyObject *
199
_wmi_exec_query_impl(PyObject *module, PyObject *query)
200
/*[clinic end generated code: output=a62303d5bb5e003f input=48d2d0a1e1a7e3c2]*/
201
202
/*[clinic end generated code]*/
203
{
204
PyObject *result = NULL;
205
HANDLE hThread = NULL;
206
int err = 0;
207
WCHAR buffer[8192];
208
DWORD offset = 0;
209
DWORD bytesRead;
210
struct _query_data data = {0};
211
212
if (PySys_Audit("_wmi.exec_query", "O", query) < 0) {
213
return NULL;
214
}
215
216
data.query = PyUnicode_AsWideCharString(query, NULL);
217
if (!data.query) {
218
return NULL;
219
}
220
221
if (0 != _wcsnicmp(data.query, L"select ", 7)) {
222
PyMem_Free((void *)data.query);
223
PyErr_SetString(PyExc_ValueError, "only SELECT queries are supported");
224
return NULL;
225
}
226
227
Py_BEGIN_ALLOW_THREADS
228
229
if (!CreatePipe(&data.readPipe, &data.writePipe, NULL, 0)) {
230
err = GetLastError();
231
} else {
232
hThread = CreateThread(NULL, 0, _query_thread, (LPVOID*)&data, 0, NULL);
233
if (!hThread) {
234
err = GetLastError();
235
// Normally the thread proc closes this handle, but since we never started
236
// we need to close it here.
237
CloseHandle(data.writePipe);
238
}
239
}
240
241
while (!err) {
242
if (ReadFile(
243
data.readPipe,
244
(LPVOID)&buffer[offset / sizeof(buffer[0])],
245
sizeof(buffer) - offset,
246
&bytesRead,
247
NULL
248
)) {
249
offset += bytesRead;
250
if (offset >= sizeof(buffer)) {
251
err = ERROR_MORE_DATA;
252
}
253
} else {
254
err = GetLastError();
255
}
256
}
257
258
if (data.readPipe) {
259
CloseHandle(data.readPipe);
260
}
261
262
// Allow the thread some time to clean up
263
switch (WaitForSingleObject(hThread, 1000)) {
264
case WAIT_OBJECT_0:
265
// Thread ended cleanly
266
if (!GetExitCodeThread(hThread, (LPDWORD)&err)) {
267
err = GetLastError();
268
}
269
break;
270
case WAIT_TIMEOUT:
271
// Probably stuck - there's not much we can do, unfortunately
272
if (err == 0 || err == ERROR_BROKEN_PIPE) {
273
err = WAIT_TIMEOUT;
274
}
275
break;
276
default:
277
if (err == 0 || err == ERROR_BROKEN_PIPE) {
278
err = GetLastError();
279
}
280
break;
281
}
282
283
CloseHandle(hThread);
284
hThread = NULL;
285
286
Py_END_ALLOW_THREADS
287
288
PyMem_Free((void *)data.query);
289
290
if (err == ERROR_MORE_DATA) {
291
PyErr_Format(PyExc_OSError, "Query returns more than %zd characters", Py_ARRAY_LENGTH(buffer));
292
return NULL;
293
} else if (err) {
294
PyErr_SetFromWindowsErr(err);
295
return NULL;
296
}
297
298
if (!offset) {
299
return PyUnicode_FromStringAndSize(NULL, 0);
300
}
301
return PyUnicode_FromWideChar(buffer, offset / sizeof(buffer[0]) - 1);
302
}
303
304
305
static PyMethodDef wmi_functions[] = {
306
_WMI_EXEC_QUERY_METHODDEF
307
{ NULL, NULL, 0, NULL }
308
};
309
310
static PyModuleDef wmi_def = {
311
PyModuleDef_HEAD_INIT,
312
"_wmi",
313
NULL, // doc
314
0, // m_size
315
wmi_functions
316
};
317
318
extern "C" {
319
PyMODINIT_FUNC PyInit__wmi(void)
320
{
321
return PyModuleDef_Init(&wmi_def);
322
}
323
}
324
325