gh-89545: Updates platform module to use new internal _wmi module on Windows to directly query OS properties (GH-96289)

This commit is contained in:
Steve Dower 2022-09-07 21:09:20 +01:00 committed by GitHub
parent 4114bcc9ef
commit de33df27aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 856 additions and 88 deletions

View File

@ -527,6 +527,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(protocol) STRUCT_FOR_ID(protocol)
STRUCT_FOR_ID(ps1) STRUCT_FOR_ID(ps1)
STRUCT_FOR_ID(ps2) STRUCT_FOR_ID(ps2)
STRUCT_FOR_ID(query)
STRUCT_FOR_ID(quotetabs) STRUCT_FOR_ID(quotetabs)
STRUCT_FOR_ID(r) STRUCT_FOR_ID(r)
STRUCT_FOR_ID(raw) STRUCT_FOR_ID(raw)

View File

@ -1036,6 +1036,7 @@ extern "C" {
INIT_ID(protocol), \ INIT_ID(protocol), \
INIT_ID(ps1), \ INIT_ID(ps1), \
INIT_ID(ps2), \ INIT_ID(ps2), \
INIT_ID(query), \
INIT_ID(quotetabs), \ INIT_ID(quotetabs), \
INIT_ID(r), \ INIT_ID(r), \
INIT_ID(raw), \ INIT_ID(raw), \
@ -2377,6 +2378,8 @@ _PyUnicode_InitStaticStrings(void) {
PyUnicode_InternInPlace(&string); PyUnicode_InternInPlace(&string);
string = &_Py_ID(ps2); string = &_Py_ID(ps2);
PyUnicode_InternInPlace(&string); PyUnicode_InternInPlace(&string);
string = &_Py_ID(query);
PyUnicode_InternInPlace(&string);
string = &_Py_ID(quotetabs); string = &_Py_ID(quotetabs);
PyUnicode_InternInPlace(&string); PyUnicode_InternInPlace(&string);
string = &_Py_ID(r); string = &_Py_ID(r);
@ -6680,6 +6683,10 @@ _PyStaticObjects_CheckRefcnt(void) {
_PyObject_Dump((PyObject *)&_Py_ID(ps2)); _PyObject_Dump((PyObject *)&_Py_ID(ps2));
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT"); Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
}; };
if (Py_REFCNT((PyObject *)&_Py_ID(query)) < _PyObject_IMMORTAL_REFCNT) {
_PyObject_Dump((PyObject *)&_Py_ID(query));
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
};
if (Py_REFCNT((PyObject *)&_Py_ID(quotetabs)) < _PyObject_IMMORTAL_REFCNT) { if (Py_REFCNT((PyObject *)&_Py_ID(quotetabs)) < _PyObject_IMMORTAL_REFCNT) {
_PyObject_Dump((PyObject *)&_Py_ID(quotetabs)); _PyObject_Dump((PyObject *)&_Py_ID(quotetabs));
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT"); Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");

View File

@ -732,9 +732,8 @@ else:
return path return path
# Win9x family and earlier have no Unicode filename support. # All supported version have Unicode filename support.
supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and supports_unicode_filenames = True
sys.getwindowsversion()[3] >= 2)
def relpath(path, start=None): def relpath(path, start=None):
"""Return a relative version of a path""" """Return a relative version of a path"""

View File

@ -309,34 +309,52 @@ def _syscmd_ver(system='', release='', version='',
version = _norm_version(version) version = _norm_version(version)
return system, release, version return system, release, version
_WIN32_CLIENT_RELEASES = { try:
(5, 0): "2000", import _wmi
(5, 1): "XP", except ImportError:
# Strictly, 5.2 client is XP 64-bit, but platform.py historically def _wmi_query(*keys):
# has always called it 2003 Server raise OSError("not supported")
(5, 2): "2003Server", else:
(5, None): "post2003", def _wmi_query(table, *keys):
table = {
"OS": "Win32_OperatingSystem",
"CPU": "Win32_Processor",
}[table]
data = _wmi.exec_query("SELECT {} FROM {}".format(
",".join(keys),
table,
)).split("\0")
split_data = (i.partition("=") for i in data)
dict_data = {i[0]: i[2] for i in split_data}
return (dict_data[k] for k in keys)
(6, 0): "Vista",
(6, 1): "7",
(6, 2): "8",
(6, 3): "8.1",
(6, None): "post8.1",
(10, 0): "10", _WIN32_CLIENT_RELEASES = [
(10, None): "post10", ((10, 1, 0), "post11"),
} ((10, 0, 22000), "11"),
((6, 4, 0), "10"),
((6, 3, 0), "8.1"),
((6, 2, 0), "8"),
((6, 1, 0), "7"),
((6, 0, 0), "Vista"),
((5, 2, 3790), "XP64"),
((5, 2, 0), "XPMedia"),
((5, 1, 0), "XP"),
((5, 0, 0), "2000"),
]
# Server release name lookup will default to client names if necessary _WIN32_SERVER_RELEASES = [
_WIN32_SERVER_RELEASES = { ((10, 1, 0), "post2022Server"),
(5, 2): "2003Server", ((10, 0, 20348), "2022Server"),
((10, 0, 17763), "2019Server"),
(6, 0): "2008Server", ((6, 4, 0), "2016Server"),
(6, 1): "2008ServerR2", ((6, 3, 0), "2012ServerR2"),
(6, 2): "2012Server", ((6, 2, 0), "2012Server"),
(6, 3): "2012ServerR2", ((6, 1, 0), "2008ServerR2"),
(6, None): "post2012ServerR2", ((6, 0, 0), "2008Server"),
} ((5, 2, 0), "2003Server"),
((5, 0, 0), "2000Server"),
]
def win32_is_iot(): def win32_is_iot():
return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS') return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS')
@ -359,22 +377,40 @@ def win32_edition():
return None return None
def win32_ver(release='', version='', csd='', ptype=''): def _win32_ver(version, csd, ptype):
# Try using WMI first, as this is the canonical source of data
try:
(version, product_type, ptype, spmajor, spminor) = _wmi_query(
'OS',
'Version',
'ProductType',
'BuildType',
'ServicePackMajorVersion',
'ServicePackMinorVersion',
)
is_client = (int(product_type) == 1)
if spminor and spminor != '0':
csd = f'SP{spmajor}.{spminor}'
else:
csd = f'SP{spmajor}'
return version, csd, ptype, is_client
except OSError:
pass
# Fall back to a combination of sys.getwindowsversion and "ver"
try: try:
from sys import getwindowsversion from sys import getwindowsversion
except ImportError: except ImportError:
return release, version, csd, ptype return version, csd, ptype, True
winver = getwindowsversion() winver = getwindowsversion()
is_client = (getattr(winver, 'product_type', 1) == 1)
try: try:
major, minor, build = map(int, _syscmd_ver()[2].split('.')) version = _syscmd_ver()[2]
major, minor, build = map(int, version.split('.'))
except ValueError: except ValueError:
major, minor, build = winver.platform_version or winver[:3] major, minor, build = winver.platform_version or winver[:3]
version = '{0}.{1}.{2}'.format(major, minor, build) version = '{0}.{1}.{2}'.format(major, minor, build)
release = (_WIN32_CLIENT_RELEASES.get((major, minor)) or
_WIN32_CLIENT_RELEASES.get((major, None)) or
release)
# getwindowsversion() reflect the compatibility mode Python is # getwindowsversion() reflect the compatibility mode Python is
# running under, and so the service pack value is only going to be # running under, and so the service pack value is only going to be
@ -386,12 +422,6 @@ def win32_ver(release='', version='', csd='', ptype=''):
if csd[:13] == 'Service Pack ': if csd[:13] == 'Service Pack ':
csd = 'SP' + csd[13:] csd = 'SP' + csd[13:]
# VER_NT_SERVER = 3
if getattr(winver, 'product_type', None) == 3:
release = (_WIN32_SERVER_RELEASES.get((major, minor)) or
_WIN32_SERVER_RELEASES.get((major, None)) or
release)
try: try:
try: try:
import winreg import winreg
@ -407,6 +437,18 @@ def win32_ver(release='', version='', csd='', ptype=''):
except OSError: except OSError:
pass pass
return version, csd, ptype, is_client
def win32_ver(release='', version='', csd='', ptype=''):
is_client = False
version, csd, ptype, is_client = _win32_ver(version, csd, ptype)
if version:
intversion = tuple(map(int, version.split('.')))
releases = _WIN32_CLIENT_RELEASES if is_client else _WIN32_SERVER_RELEASES
release = next((r for v, r in releases if v <= intversion), release)
return release, version, csd, ptype return release, version, csd, ptype
@ -725,6 +767,21 @@ def _get_machine_win32():
# http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM
# WOW64 processes mask the native architecture # WOW64 processes mask the native architecture
try:
[arch, *_] = _wmi_query('CPU', 'Architecture')
except OSError:
pass
else:
try:
arch = ['x86', 'MIPS', 'Alpha', 'PowerPC', None,
'ARM', 'ia64', None, None,
'AMD64', None, None, 'ARM64',
][int(arch)]
except (ValueError, IndexError):
pass
else:
if arch:
return arch
return ( return (
os.environ.get('PROCESSOR_ARCHITEW6432', '') or os.environ.get('PROCESSOR_ARCHITEW6432', '') or
os.environ.get('PROCESSOR_ARCHITECTURE', '') os.environ.get('PROCESSOR_ARCHITECTURE', '')
@ -738,7 +795,12 @@ class _Processor:
return func() or '' return func() or ''
def get_win32(): def get_win32():
return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32()) try:
manufacturer, caption = _wmi_query('CPU', 'Manufacturer', 'Caption')
except OSError:
return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32())
else:
return f'{caption}, {manufacturer}'
def get_OpenVMS(): def get_OpenVMS():
try: try:

View File

@ -419,6 +419,17 @@ def test_sys_getframe():
sys._getframe() sys._getframe()
def test_wmi_exec_query():
import _wmi
def hook(event, args):
if event.startswith("_wmi."):
print(event, args[0])
sys.addaudithook(hook)
_wmi.exec_query("SELECT * FROM Win32_OperatingSystem")
if __name__ == "__main__": if __name__ == "__main__":
from test.support import suppress_msvcrt_asserts from test.support import suppress_msvcrt_asserts

View File

@ -185,5 +185,20 @@ class AuditTest(unittest.TestCase):
self.assertEqual(actual, expected) self.assertEqual(actual, expected)
def test_wmi_exec_query(self):
import_helper.import_module("_wmi")
returncode, events, stderr = self.run_python("test_wmi_exec_query")
if returncode:
self.fail(stderr)
if support.verbose:
print(*events, sep='\n')
actual = [(ev[0], ev[2]) for ev in events]
expected = [("_wmi.exec_query", "SELECT * FROM Win32_OperatingSystem")]
self.assertEqual(actual, expected)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -229,6 +229,14 @@ class PlatformTest(unittest.TestCase):
self.assertEqual(res[-1], res.processor) self.assertEqual(res[-1], res.processor)
self.assertEqual(len(res), 6) self.assertEqual(len(res), 6)
@unittest.skipUnless(sys.platform.startswith('win'), "windows only test")
def test_uname_win32_without_wmi(self):
def raises_oserror(*a):
raise OSError()
with support.swap_attr(platform, '_wmi_query', raises_oserror):
self.test_uname()
def test_uname_cast_to_tuple(self): def test_uname_cast_to_tuple(self):
res = platform.uname() res = platform.uname()
expected = ( expected = (
@ -289,20 +297,27 @@ class PlatformTest(unittest.TestCase):
# on 64 bit Windows: if PROCESSOR_ARCHITEW6432 exists we should be # on 64 bit Windows: if PROCESSOR_ARCHITEW6432 exists we should be
# using it, per # using it, per
# http://blogs.msdn.com/david.wang/archive/2006/03/26/HOWTO-Detect-Process-Bitness.aspx # http://blogs.msdn.com/david.wang/archive/2006/03/26/HOWTO-Detect-Process-Bitness.aspx
try:
# We also need to suppress WMI checks, as those are reliable and
# overrule the environment variables
def raises_oserror(*a):
raise OSError()
with support.swap_attr(platform, '_wmi_query', raises_oserror):
with os_helper.EnvironmentVarGuard() as environ: with os_helper.EnvironmentVarGuard() as environ:
if 'PROCESSOR_ARCHITEW6432' in environ: try:
del environ['PROCESSOR_ARCHITEW6432'] if 'PROCESSOR_ARCHITEW6432' in environ:
environ['PROCESSOR_ARCHITECTURE'] = 'foo' del environ['PROCESSOR_ARCHITEW6432']
platform._uname_cache = None environ['PROCESSOR_ARCHITECTURE'] = 'foo'
system, node, release, version, machine, processor = platform.uname() platform._uname_cache = None
self.assertEqual(machine, 'foo') system, node, release, version, machine, processor = platform.uname()
environ['PROCESSOR_ARCHITEW6432'] = 'bar' self.assertEqual(machine, 'foo')
platform._uname_cache = None environ['PROCESSOR_ARCHITEW6432'] = 'bar'
system, node, release, version, machine, processor = platform.uname() platform._uname_cache = None
self.assertEqual(machine, 'bar') system, node, release, version, machine, processor = platform.uname()
finally: self.assertEqual(machine, 'bar')
platform._uname_cache = None finally:
platform._uname_cache = None
def test_java_ver(self): def test_java_ver(self):
res = platform.java_ver() res = platform.java_ver()

67
Lib/test/test_wmi.py Normal file
View File

@ -0,0 +1,67 @@
# Test the internal _wmi module on Windows
# This is used by the platform module, and potentially others
import re
import sys
import unittest
from test.support import import_helper
# Do this first so test will be skipped if module doesn't exist
_wmi = import_helper.import_module('_wmi', required_on=['win'])
class WmiTests(unittest.TestCase):
def test_wmi_query_os_version(self):
r = _wmi.exec_query("SELECT Version FROM Win32_OperatingSystem").split("\0")
self.assertEqual(1, len(r))
k, eq, v = r[0].partition("=")
self.assertEqual("=", eq, r[0])
self.assertEqual("Version", k, r[0])
# Best we can check for the version is that it's digits, dot, digits, anything
# Otherwise, we are likely checking the result of the query against itself
self.assertTrue(re.match(r"\d+\.\d+.+$", v), r[0])
def test_wmi_query_repeated(self):
# Repeated queries should not break
for _ in range(10):
self.test_wmi_query_os_version()
def test_wmi_query_error(self):
# Invalid queries fail with OSError
try:
_wmi.exec_query("SELECT InvalidColumnName FROM InvalidTableName")
except OSError as ex:
if ex.winerror & 0xFFFFFFFF == 0x80041010:
# This is the expected error code. All others should fail the test
return
self.fail("Expected OSError")
def test_wmi_query_repeated_error(self):
for _ in range(10):
self.test_wmi_query_error()
def test_wmi_query_not_select(self):
# Queries other than SELECT are blocked to avoid potential exploits
with self.assertRaises(ValueError):
_wmi.exec_query("not select, just in case someone tries something")
def test_wmi_query_overflow(self):
# Ensure very big queries fail
# Test multiple times to ensure consistency
for _ in range(2):
with self.assertRaises(OSError):
_wmi.exec_query("SELECT * FROM CIM_DataFile")
def test_wmi_query_multiple_rows(self):
# Multiple instances should have an extra null separator
r = _wmi.exec_query("SELECT ProcessId FROM Win32_Process WHERE ProcessId < 1000")
self.assertFalse(r.startswith("\0"), r)
self.assertFalse(r.endswith("\0"), r)
it = iter(r.split("\0"))
try:
while True:
self.assertTrue(re.match(r"ProcessId=\d+", next(it)))
self.assertEqual("", next(it))
except StopIteration:
pass

View File

@ -0,0 +1 @@
Updates :mod:`platform` code getting the Windows version to use native Windows Management Instrumentation (WMI) queries to determine OS version, type, and architecture.

307
PC/_wmimodule.cpp Normal file
View File

@ -0,0 +1,307 @@
//
// 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>
#include "clinic/_wmimodule.cpp.h"
/*[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
);
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);
}
}

View File

@ -0,0 +1,75 @@
/*[clinic input]
preserve
[clinic start generated code]*/
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
# include "pycore_gc.h" // PyGC_Head
# include "pycore_runtime.h" // _Py_ID()
#endif
PyDoc_STRVAR(_wmi_exec_query__doc__,
"exec_query($module, /, query)\n"
"--\n"
"\n"
"Runs a WMI query against the local machine.\n"
"\n"
"This returns a single string with \'name=value\' pairs in a flat array separated\n"
"by null characters.");
#define _WMI_EXEC_QUERY_METHODDEF \
{"exec_query", _PyCFunction_CAST(_wmi_exec_query), METH_FASTCALL|METH_KEYWORDS, _wmi_exec_query__doc__},
static PyObject *
_wmi_exec_query_impl(PyObject *module, PyObject *query);
static PyObject *
_wmi_exec_query(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
#define NUM_KEYWORDS 1
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
PyObject *ob_item[NUM_KEYWORDS];
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_item = { &_Py_ID(query), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
#else // !Py_BUILD_CORE
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
static const char * const _keywords[] = {"query", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "exec_query",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
PyObject *argsbuf[1];
PyObject *query;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
if (!args) {
goto exit;
}
if (!PyUnicode_Check(args[0])) {
_PyArg_BadArgument("exec_query", "argument 'query'", "str", args[0]);
goto exit;
}
if (PyUnicode_READY(args[0]) == -1) {
goto exit;
}
query = args[0];
return_value = _wmi_exec_query_impl(module, query);
exit:
return return_value;
}
/*[clinic end generated code: output=7fdf0c0579ddb566 input=a9049054013a1b77]*/

119
PCbuild/_wmi.vcxproj Normal file
View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|ARM">
<Configuration>PGInstrument</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|ARM64">
<Configuration>PGInstrument</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|Win32">
<Configuration>PGInstrument</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGInstrument|x64">
<Configuration>PGInstrument</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|ARM">
<Configuration>PGUpdate</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|ARM64">
<Configuration>PGUpdate</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|Win32">
<Configuration>PGUpdate</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="PGUpdate|x64">
<Configuration>PGUpdate</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{54B1431F-B86B-4ACB-B28C-88BCF93191D8}</ProjectGuid>
<RootNamespace>_wmi</RootNamespace>
<Keyword>Win32Proj</Keyword>
<SupportPGO>false</SupportPGO>
</PropertyGroup>
<Import Project="python.props" />
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<TargetExt>.pyd</TargetExt>
</PropertyGroup>
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="pyproject.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalOptions>/std:c++20 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalDependencies>wbemuuid.lib;propsys.lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>ole32.dll</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\PC\_wmimodule.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="pythoncore.vcxproj">
<Project>{cf7ac3d1-e2df-41d2-bea6-1e2556cdea26}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{4fa4dbfa-e069-4ab4-86a6-ad389b2ec407}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\PC\_wmimodule.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View File

@ -64,7 +64,7 @@
<!-- pyshellext.dll --> <!-- pyshellext.dll -->
<Projects Include="pyshellext.vcxproj" /> <Projects Include="pyshellext.vcxproj" />
<!-- Extension modules --> <!-- Extension modules -->
<ExtensionModules Include="_asyncio;_zoneinfo;_decimal;_elementtree;_msi;_multiprocessing;_overlapped;pyexpat;_queue;select;unicodedata;winsound;_uuid" /> <ExtensionModules Include="_asyncio;_zoneinfo;_decimal;_elementtree;_msi;_multiprocessing;_overlapped;pyexpat;_queue;select;unicodedata;winsound;_uuid;_wmi" />
<ExtensionModules Include="_ctypes" Condition="$(IncludeCTypes)" /> <ExtensionModules Include="_ctypes" Condition="$(IncludeCTypes)" />
<!-- Extension modules that require external sources --> <!-- Extension modules that require external sources -->
<ExternalModules Include="_bz2;_lzma;_sqlite3" /> <ExternalModules Include="_bz2;_lzma;_sqlite3" />

View File

@ -108,6 +108,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pythonw_uwp", "pythonw_uwp.
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_uuid", "_uuid.vcxproj", "{CB435430-EBB1-478B-8F4E-C256F6838F55}" Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_uuid", "_uuid.vcxproj", "{CB435430-EBB1-478B-8F4E-C256F6838F55}"
EndProject EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_wmi", "_wmi.vcxproj", "{54B1431F-B86B-4ACB-B28C-88BCF93191D8}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM Debug|ARM = Debug|ARM
@ -1503,6 +1505,38 @@ Global
{CB435430-EBB1-478B-8F4E-C256F6838F55}.Release|Win32.Build.0 = Release|Win32 {CB435430-EBB1-478B-8F4E-C256F6838F55}.Release|Win32.Build.0 = Release|Win32
{CB435430-EBB1-478B-8F4E-C256F6838F55}.Release|x64.ActiveCfg = Release|x64 {CB435430-EBB1-478B-8F4E-C256F6838F55}.Release|x64.ActiveCfg = Release|x64
{CB435430-EBB1-478B-8F4E-C256F6838F55}.Release|x64.Build.0 = Release|x64 {CB435430-EBB1-478B-8F4E-C256F6838F55}.Release|x64.Build.0 = Release|x64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Debug|ARM.ActiveCfg = Debug|ARM
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Debug|ARM.Build.0 = Debug|ARM
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Debug|ARM64.ActiveCfg = Debug|ARM64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Debug|ARM64.Build.0 = Debug|ARM64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Debug|Win32.ActiveCfg = Debug|Win32
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Debug|Win32.Build.0 = Debug|Win32
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Debug|x64.ActiveCfg = Debug|x64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Debug|x64.Build.0 = Debug|x64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGInstrument|ARM.Build.0 = PGInstrument|ARM
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGInstrument|Win32.Build.0 = PGInstrument|Win32
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGInstrument|x64.ActiveCfg = PGInstrument|x64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGInstrument|x64.Build.0 = PGInstrument|x64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGUpdate|ARM.Build.0 = PGUpdate|ARM
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGUpdate|Win32.Build.0 = PGUpdate|Win32
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGUpdate|x64.ActiveCfg = PGUpdate|x64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.PGUpdate|x64.Build.0 = PGUpdate|x64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Release|ARM.ActiveCfg = Release|ARM
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Release|ARM.Build.0 = Release|ARM
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Release|ARM64.ActiveCfg = Release|ARM64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Release|ARM64.Build.0 = Release|ARM64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Release|Win32.ActiveCfg = Release|Win32
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Release|Win32.Build.0 = Release|Win32
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Release|x64.ActiveCfg = Release|x64
{54B1431F-B86B-4ACB-B28C-88BCF93191D8}.Release|x64.Build.0 = Release|x64
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1487,6 +1487,44 @@ static PyStructSequence_Desc windows_version_desc = {
via indexing, the rest are name only */ via indexing, the rest are name only */
}; };
static PyObject *
_sys_getwindowsversion_from_kernel32()
{
HANDLE hKernel32;
wchar_t kernel32_path[MAX_PATH];
LPVOID verblock;
DWORD verblock_size;
VS_FIXEDFILEINFO *ffi;
UINT ffi_len;
DWORD realMajor, realMinor, realBuild;
Py_BEGIN_ALLOW_THREADS
hKernel32 = GetModuleHandleW(L"kernel32.dll");
Py_END_ALLOW_THREADS
if (!hKernel32 || !GetModuleFileNameW(hKernel32, kernel32_path, MAX_PATH)) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
verblock_size = GetFileVersionInfoSizeW(kernel32_path, NULL);
if (!verblock_size) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
verblock = PyMem_RawMalloc(verblock_size);
if (!verblock ||
!GetFileVersionInfoW(kernel32_path, 0, verblock_size, verblock) ||
!VerQueryValueW(verblock, L"", (LPVOID)&ffi, &ffi_len)) {
PyErr_SetFromWindowsErr(0);
return NULL;
}
realMajor = HIWORD(ffi->dwProductVersionMS);
realMinor = LOWORD(ffi->dwProductVersionMS);
realBuild = HIWORD(ffi->dwProductVersionLS);
PyMem_RawFree(verblock);
return Py_BuildValue("(kkk)", realMajor, realMinor, realBuild);
}
/* Disable deprecation warnings about GetVersionEx as the result is /* Disable deprecation warnings about GetVersionEx as the result is
being passed straight through to the caller, who is responsible for being passed straight through to the caller, who is responsible for
using it correctly. */ using it correctly. */
@ -1516,11 +1554,13 @@ sys_getwindowsversion_impl(PyObject *module)
PyObject *version; PyObject *version;
int pos = 0; int pos = 0;
OSVERSIONINFOEXW ver; OSVERSIONINFOEXW ver;
DWORD realMajor, realMinor, realBuild;
HANDLE hKernel32; version = PyObject_GetAttrString(module, "_cached_windows_version");
wchar_t kernel32_path[MAX_PATH]; if (version && PyObject_TypeCheck(version, &WindowsVersionType)) {
LPVOID verblock; return version;
DWORD verblock_size; }
Py_XDECREF(version);
PyErr_Clear();
ver.dwOSVersionInfoSize = sizeof(ver); ver.dwOSVersionInfoSize = sizeof(ver);
if (!GetVersionExW((OSVERSIONINFOW*) &ver)) if (!GetVersionExW((OSVERSIONINFOW*) &ver))
@ -1540,41 +1580,34 @@ sys_getwindowsversion_impl(PyObject *module)
PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wSuiteMask)); PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wSuiteMask));
PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wProductType)); PyStructSequence_SET_ITEM(version, pos++, PyLong_FromLong(ver.wProductType));
realMajor = ver.dwMajorVersion;
realMinor = ver.dwMinorVersion;
realBuild = ver.dwBuildNumber;
// GetVersion will lie if we are running in a compatibility mode. // GetVersion will lie if we are running in a compatibility mode.
// We need to read the version info from a system file resource // We need to read the version info from a system file resource
// to accurately identify the OS version. If we fail for any reason, // to accurately identify the OS version. If we fail for any reason,
// just return whatever GetVersion said. // just return whatever GetVersion said.
Py_BEGIN_ALLOW_THREADS PyObject *realVersion = _sys_getwindowsversion_from_kernel32();
hKernel32 = GetModuleHandleW(L"kernel32.dll"); if (!realVersion) {
Py_END_ALLOW_THREADS PyErr_Clear();
if (hKernel32 && GetModuleFileNameW(hKernel32, kernel32_path, MAX_PATH) && realVersion = Py_BuildValue("(kkk)",
(verblock_size = GetFileVersionInfoSizeW(kernel32_path, NULL)) && ver.dwMajorVersion,
(verblock = PyMem_RawMalloc(verblock_size))) { ver.dwMinorVersion,
VS_FIXEDFILEINFO *ffi; ver.dwBuildNumber
UINT ffi_len; );
}
if (GetFileVersionInfoW(kernel32_path, 0, verblock_size, verblock) &&
VerQueryValueW(verblock, L"", (LPVOID)&ffi, &ffi_len)) { if (realVersion) {
realMajor = HIWORD(ffi->dwProductVersionMS); PyStructSequence_SET_ITEM(version, pos++, realVersion);
realMinor = LOWORD(ffi->dwProductVersionMS);
realBuild = HIWORD(ffi->dwProductVersionLS);
}
PyMem_RawFree(verblock);
} }
PyStructSequence_SET_ITEM(version, pos++, Py_BuildValue("(kkk)",
realMajor,
realMinor,
realBuild
));
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
Py_DECREF(version); Py_DECREF(version);
return NULL; return NULL;
} }
if (PyObject_SetAttrString(module, "_cached_windows_version", version) < 0) {
Py_DECREF(version);
return NULL;
}
return version; return version;
} }

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_msi;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_uuid;_zoneinfo ?> <?define exts=pyexpat;select;unicodedata;winsound;_bz2;_elementtree;_socket;_ssl;_msi;_ctypes;_hashlib;_multiprocessing;_lzma;_decimal;_overlapped;_sqlite3;_asyncio;_queue;_uuid;_wmi;_zoneinfo ?>
<Fragment> <Fragment>
<DirectoryRef Id="Lib_venv_scripts_nt" /> <DirectoryRef Id="Lib_venv_scripts_nt" />