Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit de33df2

Browse files
authored
gh-89545: Updates platform module to use new internal _wmi module on Windows to directly query OS properties (GH-96289)
1 parent 4114bcc commit de33df2

17 files changed

+859
-91
lines changed

Include/internal/pycore_global_strings.h

+1
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ struct _Py_global_strings {
527527
STRUCT_FOR_ID(protocol)
528528
STRUCT_FOR_ID(ps1)
529529
STRUCT_FOR_ID(ps2)
530+
STRUCT_FOR_ID(query)
530531
STRUCT_FOR_ID(quotetabs)
531532
STRUCT_FOR_ID(r)
532533
STRUCT_FOR_ID(raw)

Include/internal/pycore_runtime_init_generated.h

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/ntpath.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -732,9 +732,8 @@ def realpath(path, *, strict=False):
732732
return path
733733

734734

735-
# Win9x family and earlier have no Unicode filename support.
736-
supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and
737-
sys.getwindowsversion()[3] >= 2)
735+
# All supported version have Unicode filename support.
736+
supports_unicode_filenames = True
738737

739738
def relpath(path, start=None):
740739
"""Return a relative version of a path"""

Lib/platform.py

+105-43
Original file line numberDiff line numberDiff line change
@@ -309,34 +309,52 @@ def _syscmd_ver(system='', release='', version='',
309309
version = _norm_version(version)
310310
return system, release, version
311311

312-
_WIN32_CLIENT_RELEASES = {
313-
(5, 0): "2000",
314-
(5, 1): "XP",
315-
# Strictly, 5.2 client is XP 64-bit, but platform.py historically
316-
# has always called it 2003 Server
317-
(5, 2): "2003Server",
318-
(5, None): "post2003",
319-
320-
(6, 0): "Vista",
321-
(6, 1): "7",
322-
(6, 2): "8",
323-
(6, 3): "8.1",
324-
(6, None): "post8.1",
325-
326-
(10, 0): "10",
327-
(10, None): "post10",
328-
}
329-
330-
# Server release name lookup will default to client names if necessary
331-
_WIN32_SERVER_RELEASES = {
332-
(5, 2): "2003Server",
333-
334-
(6, 0): "2008Server",
335-
(6, 1): "2008ServerR2",
336-
(6, 2): "2012Server",
337-
(6, 3): "2012ServerR2",
338-
(6, None): "post2012ServerR2",
339-
}
312+
try:
313+
import _wmi
314+
except ImportError:
315+
def _wmi_query(*keys):
316+
raise OSError("not supported")
317+
else:
318+
def _wmi_query(table, *keys):
319+
table = {
320+
"OS": "Win32_OperatingSystem",
321+
"CPU": "Win32_Processor",
322+
}[table]
323+
data = _wmi.exec_query("SELECT {} FROM {}".format(
324+
",".join(keys),
325+
table,
326+
)).split("\0")
327+
split_data = (i.partition("=") for i in data)
328+
dict_data = {i[0]: i[2] for i in split_data}
329+
return (dict_data[k] for k in keys)
330+
331+
332+
_WIN32_CLIENT_RELEASES = [
333+
((10, 1, 0), "post11"),
334+
((10, 0, 22000), "11"),
335+
((6, 4, 0), "10"),
336+
((6, 3, 0), "8.1"),
337+
((6, 2, 0), "8"),
338+
((6, 1, 0), "7"),
339+
((6, 0, 0), "Vista"),
340+
((5, 2, 3790), "XP64"),
341+
((5, 2, 0), "XPMedia"),
342+
((5, 1, 0), "XP"),
343+
((5, 0, 0), "2000"),
344+
]
345+
346+
_WIN32_SERVER_RELEASES = [
347+
((10, 1, 0), "post2022Server"),
348+
((10, 0, 20348), "2022Server"),
349+
((10, 0, 17763), "2019Server"),
350+
((6, 4, 0), "2016Server"),
351+
((6, 3, 0), "2012ServerR2"),
352+
((6, 2, 0), "2012Server"),
353+
((6, 1, 0), "2008ServerR2"),
354+
((6, 0, 0), "2008Server"),
355+
((5, 2, 0), "2003Server"),
356+
((5, 0, 0), "2000Server"),
357+
]
340358

341359
def win32_is_iot():
342360
return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS')
@@ -359,22 +377,40 @@ def win32_edition():
359377

360378
return None
361379

362-
def win32_ver(release='', version='', csd='', ptype=''):
380+
def _win32_ver(version, csd, ptype):
381+
# Try using WMI first, as this is the canonical source of data
382+
try:
383+
(version, product_type, ptype, spmajor, spminor) = _wmi_query(
384+
'OS',
385+
'Version',
386+
'ProductType',
387+
'BuildType',
388+
'ServicePackMajorVersion',
389+
'ServicePackMinorVersion',
390+
)
391+
is_client = (int(product_type) == 1)
392+
if spminor and spminor != '0':
393+
csd = f'SP{spmajor}.{spminor}'
394+
else:
395+
csd = f'SP{spmajor}'
396+
return version, csd, ptype, is_client
397+
except OSError:
398+
pass
399+
400+
# Fall back to a combination of sys.getwindowsversion and "ver"
363401
try:
364402
from sys import getwindowsversion
365403
except ImportError:
366-
return release, version, csd, ptype
404+
return version, csd, ptype, True
367405

368406
winver = getwindowsversion()
407+
is_client = (getattr(winver, 'product_type', 1) == 1)
369408
try:
370-
major, minor, build = map(int, _syscmd_ver()[2].split('.'))
409+
version = _syscmd_ver()[2]
410+
major, minor, build = map(int, version.split('.'))
371411
except ValueError:
372412
major, minor, build = winver.platform_version or winver[:3]
373-
version = '{0}.{1}.{2}'.format(major, minor, build)
374-
375-
release = (_WIN32_CLIENT_RELEASES.get((major, minor)) or
376-
_WIN32_CLIENT_RELEASES.get((major, None)) or
377-
release)
413+
version = '{0}.{1}.{2}'.format(major, minor, build)
378414

379415
# getwindowsversion() reflect the compatibility mode Python is
380416
# running under, and so the service pack value is only going to be
@@ -386,12 +422,6 @@ def win32_ver(release='', version='', csd='', ptype=''):
386422
if csd[:13] == 'Service Pack ':
387423
csd = 'SP' + csd[13:]
388424

389-
# VER_NT_SERVER = 3
390-
if getattr(winver, 'product_type', None) == 3:
391-
release = (_WIN32_SERVER_RELEASES.get((major, minor)) or
392-
_WIN32_SERVER_RELEASES.get((major, None)) or
393-
release)
394-
395425
try:
396426
try:
397427
import winreg
@@ -407,6 +437,18 @@ def win32_ver(release='', version='', csd='', ptype=''):
407437
except OSError:
408438
pass
409439

440+
return version, csd, ptype, is_client
441+
442+
def win32_ver(release='', version='', csd='', ptype=''):
443+
is_client = False
444+
445+
version, csd, ptype, is_client = _win32_ver(version, csd, ptype)
446+
447+
if version:
448+
intversion = tuple(map(int, version.split('.')))
449+
releases = _WIN32_CLIENT_RELEASES if is_client else _WIN32_SERVER_RELEASES
450+
release = next((r for v, r in releases if v <= intversion), release)
451+
410452
return release, version, csd, ptype
411453

412454

@@ -725,6 +767,21 @@ def _get_machine_win32():
725767
# http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM
726768

727769
# WOW64 processes mask the native architecture
770+
try:
771+
[arch, *_] = _wmi_query('CPU', 'Architecture')
772+
except OSError:
773+
pass
774+
else:
775+
try:
776+
arch = ['x86', 'MIPS', 'Alpha', 'PowerPC', None,
777+
'ARM', 'ia64', None, None,
778+
'AMD64', None, None, 'ARM64',
779+
][int(arch)]
780+
except (ValueError, IndexError):
781+
pass
782+
else:
783+
if arch:
784+
return arch
728785
return (
729786
os.environ.get('PROCESSOR_ARCHITEW6432', '') or
730787
os.environ.get('PROCESSOR_ARCHITECTURE', '')
@@ -738,7 +795,12 @@ def get(cls):
738795
return func() or ''
739796

740797
def get_win32():
741-
return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32())
798+
try:
799+
manufacturer, caption = _wmi_query('CPU', 'Manufacturer', 'Caption')
800+
except OSError:
801+
return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32())
802+
else:
803+
return f'{caption}, {manufacturer}'
742804

743805
def get_OpenVMS():
744806
try:

Lib/test/audit-tests.py

+11
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,17 @@ def hook(event, args):
419419
sys._getframe()
420420

421421

422+
def test_wmi_exec_query():
423+
import _wmi
424+
425+
def hook(event, args):
426+
if event.startswith("_wmi."):
427+
print(event, args[0])
428+
429+
sys.addaudithook(hook)
430+
_wmi.exec_query("SELECT * FROM Win32_OperatingSystem")
431+
432+
422433
if __name__ == "__main__":
423434
from test.support import suppress_msvcrt_asserts
424435

Lib/test/test_audit.py

+15
Original file line numberDiff line numberDiff line change
@@ -185,5 +185,20 @@ def test_sys_getframe(self):
185185

186186
self.assertEqual(actual, expected)
187187

188+
189+
def test_wmi_exec_query(self):
190+
import_helper.import_module("_wmi")
191+
returncode, events, stderr = self.run_python("test_wmi_exec_query")
192+
if returncode:
193+
self.fail(stderr)
194+
195+
if support.verbose:
196+
print(*events, sep='\n')
197+
actual = [(ev[0], ev[2]) for ev in events]
198+
expected = [("_wmi.exec_query", "SELECT * FROM Win32_OperatingSystem")]
199+
200+
self.assertEqual(actual, expected)
201+
202+
188203
if __name__ == "__main__":
189204
unittest.main()

Lib/test/test_platform.py

+28-13
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ def test_uname(self):
229229
self.assertEqual(res[-1], res.processor)
230230
self.assertEqual(len(res), 6)
231231

232+
@unittest.skipUnless(sys.platform.startswith('win'), "windows only test")
233+
def test_uname_win32_without_wmi(self):
234+
def raises_oserror(*a):
235+
raise OSError()
236+
237+
with support.swap_attr(platform, '_wmi_query', raises_oserror):
238+
self.test_uname()
239+
232240
def test_uname_cast_to_tuple(self):
233241
res = platform.uname()
234242
expected = (
@@ -289,20 +297,27 @@ def test_uname_win32_ARCHITEW6432(self):
289297
# on 64 bit Windows: if PROCESSOR_ARCHITEW6432 exists we should be
290298
# using it, per
291299
# http://blogs.msdn.com/david.wang/archive/2006/03/26/HOWTO-Detect-Process-Bitness.aspx
292-
try:
300+
301+
# We also need to suppress WMI checks, as those are reliable and
302+
# overrule the environment variables
303+
def raises_oserror(*a):
304+
raise OSError()
305+
306+
with support.swap_attr(platform, '_wmi_query', raises_oserror):
293307
with os_helper.EnvironmentVarGuard() as environ:
294-
if 'PROCESSOR_ARCHITEW6432' in environ:
295-
del environ['PROCESSOR_ARCHITEW6432']
296-
environ['PROCESSOR_ARCHITECTURE'] = 'foo'
297-
platform._uname_cache = None
298-
system, node, release, version, machine, processor = platform.uname()
299-
self.assertEqual(machine, 'foo')
300-
environ['PROCESSOR_ARCHITEW6432'] = 'bar'
301-
platform._uname_cache = None
302-
system, node, release, version, machine, processor = platform.uname()
303-
self.assertEqual(machine, 'bar')
304-
finally:
305-
platform._uname_cache = None
308+
try:
309+
if 'PROCESSOR_ARCHITEW6432' in environ:
310+
del environ['PROCESSOR_ARCHITEW6432']
311+
environ['PROCESSOR_ARCHITECTURE'] = 'foo'
312+
platform._uname_cache = None
313+
system, node, release, version, machine, processor = platform.uname()
314+
self.assertEqual(machine, 'foo')
315+
environ['PROCESSOR_ARCHITEW6432'] = 'bar'
316+
platform._uname_cache = None
317+
system, node, release, version, machine, processor = platform.uname()
318+
self.assertEqual(machine, 'bar')
319+
finally:
320+
platform._uname_cache = None
306321

307322
def test_java_ver(self):
308323
res = platform.java_ver()

0 commit comments

Comments
 (0)