From 2a3ce9ac0c4df679b9f2b3563bcc5e9a08437ce4 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 1 Jul 2026 22:54:26 +0200 Subject: [PATCH 1/9] Use the _wmi module in test.pythoninfo On Windows, replace wmic command with _wmi module to get the operating system caption and version. The wmic tool is deprecated since January 2024: https://techcommunity.microsoft.com/blog/windows-itpro-blog/wmi-command-line-wmic-utility-deprecation-next-steps/4039242 For example, it's no longer installed in Windows images on GitHub Action. Fix also run_command(): no longer try to spawn a subprocess if the platform doesn't support subprocess. It avoids logging run_command() errors. --- Lib/test/pythoninfo.py | 50 ++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index d7fd98a8d4311d4..1ee83d12ad38799 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -452,6 +452,12 @@ def format_attr(attr, value): def run_command(cmd, check=True, **kwargs): import subprocess + from test.support import has_subprocess_support + + if not has_subprocess_support: + # subprocess is not supported by the current platform + return '' + timeout = COMMAND_TIMEOUT cmd_str = ' '.join(cmd) @@ -963,6 +969,25 @@ def winreg_query(path): return None +def wmi_get_os(): + try: + import _wmi + except ImportError: + return + + query = "SELECT Caption, Version FROM Win32_OperatingSystem" + try: + data = _wmi.exec_query(query) + except OSError: + return + + dict_data = {} + for item in data.split("\0"): + key, _, value = item.partition("=") + dict_data[key] = value + return dict_data + + def collect_windows(info_add): if not MS_WINDOWS: # Code specific to Windows @@ -1009,22 +1034,15 @@ def collect_windows(info_add): call_func(info_add, 'windows.ansi_code_page', _winapi, 'GetACP') call_func(info_add, 'windows.oem_code_page', _winapi, 'GetOEMCP') - # windows.version_caption: "wmic os get Caption,Version /value" command - output = run_command(["wmic", "os", "get", "Caption,Version", "/value"], - # When wmic.exe output is redirected to a pipe, - # it uses the OEM code page - encoding="oem") - if output: - for line in output.splitlines(): - line = line.strip() - if line.startswith('Caption='): - line = line.removeprefix('Caption=').strip() - if line: - info_add('windows.version_caption', line) - elif line.startswith('Version='): - line = line.removeprefix('Version=').strip() - if line: - info_add('windows.version', line) + # Get operating system caption and version using WMI + data = wmi_get_os() + if data: + caption = data.get('Caption', '') + if caption: + info_add('windows.version_caption', caption) + version = data.get('Version', '') + if version: + info_add('windows.version', version) # windows.ver: "ver" command output = run_command(["ver"], shell=True) From 42b54e7f39c3c9a1824f0761442b774d0278fe60 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 1 Jul 2026 23:26:15 +0200 Subject: [PATCH 2/9] Detect QEMU on Windows --- Lib/test/pythoninfo.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 1ee83d12ad38799..46bc505b17aa1ab 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -969,17 +969,16 @@ def winreg_query(path): return None -def wmi_get_os(): +def wmi_query(query): try: import _wmi except ImportError: - return + return {} - query = "SELECT Caption, Version FROM Win32_OperatingSystem" try: data = _wmi.exec_query(query) except OSError: - return + return {} dict_data = {} for item in data.split("\0"): @@ -1035,14 +1034,13 @@ def collect_windows(info_add): call_func(info_add, 'windows.oem_code_page', _winapi, 'GetOEMCP') # Get operating system caption and version using WMI - data = wmi_get_os() - if data: - caption = data.get('Caption', '') - if caption: - info_add('windows.version_caption', caption) - version = data.get('Version', '') - if version: - info_add('windows.version', version) + data = wmi_query("SELECT Caption, Version FROM Win32_OperatingSystem") + caption = data.get('Caption', '') + if caption: + info_add('windows.version_caption', caption) + version = data.get('Version', '') + if version: + info_add('windows.version', version) # windows.ver: "ver" command output = run_command(["ver"], shell=True) @@ -1218,7 +1216,16 @@ def collect_system(info_add): uptime = f'{uptime} sec' info_add('system.uptime', uptime) - virt = detect_virt() + virt = None + if MS_WINDOWS: + data = wmi_query("SELECT Manufacturer FROM Win32_ComputerSystem") + manufacturer = data.get('Manufacturer', '') + if manufacturer == 'QEMU': + virt = manufacturer + elif manufacturer: + info_add('system.manufacturer', manufacturer) + if not virt: + virt = detect_virt() if virt: info_add('system.virt', virt) From fe39732c77dfda3543080a09a8d1595801ad5d50 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 1 Jul 2026 23:54:56 +0200 Subject: [PATCH 3/9] Get also the BIOS version --- Lib/test/pythoninfo.py | 41 ++++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 46bc505b17aa1ab..c1d34a9cd7b728e 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1158,7 +1158,35 @@ def get_machine_id(): return None -def detect_virt(): +def detect_virt(info_add): + # On Windows, use WMI to get the computer manufacturer and the BIOS version + if MS_WINDOWS: + data = wmi_query("SELECT Manufacturer FROM Win32_ComputerSystem") + manufacturer = data.get('Manufacturer', '') + patterns = ( + 'QEMU', + 'VMWare', + 'Virtual', + ) + if any(pattern in manufacturer for pattern in patterns): + return manufacturer + elif manufacturer: + # Log the value to update patterns on new VM + info_add('system.manufacturer', manufacturer) + + data = wmi_query("SELECT Version FROM Win32_Bios") + bios_version = data.get('Version', '') + patterns = ( + 'VIRTUAL', + 'VMware', + 'Xen', + ) + if any(pattern in bios_version for pattern in patterns): + return bios_version + elif bios_version: + # Log the value to update patterns on new VM + info_add('system.bios_version', bios_version) + # Run systemd-detect-virt command virt = run_command(["systemd-detect-virt"], check=False) if virt and virt != "none": @@ -1216,16 +1244,7 @@ def collect_system(info_add): uptime = f'{uptime} sec' info_add('system.uptime', uptime) - virt = None - if MS_WINDOWS: - data = wmi_query("SELECT Manufacturer FROM Win32_ComputerSystem") - manufacturer = data.get('Manufacturer', '') - if manufacturer == 'QEMU': - virt = manufacturer - elif manufacturer: - info_add('system.manufacturer', manufacturer) - if not virt: - virt = detect_virt() + virt = detect_virt() if virt: info_add('system.virt', virt) From 896b4208df3c8c2e0409d7d98efdba65188bf3c7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 1 Jul 2026 23:58:41 +0200 Subject: [PATCH 4/9] Log BIOS serial number --- Lib/test/pythoninfo.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index c1d34a9cd7b728e..bc44163c2dc66c0 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1174,18 +1174,24 @@ def detect_virt(info_add): # Log the value to update patterns on new VM info_add('system.manufacturer', manufacturer) - data = wmi_query("SELECT Version FROM Win32_Bios") + data = wmi_query("SELECT SerialNumber,Version FROM Win32_Bios") + bios_serial_number = data.get('SerialNumber', '') bios_version = data.get('Version', '') patterns = ( 'VIRTUAL', 'VMware', 'Xen', ) - if any(pattern in bios_version for pattern in patterns): + if any(pattern in bios_serial_number for pattern in patterns): + return bios_serial_number + elif any(pattern in bios_version for pattern in patterns): return bios_version - elif bios_version: - # Log the value to update patterns on new VM - info_add('system.bios_version', bios_version) + else: + # Log the values to update patterns on new VM + if bios_version: + info_add('system.bios_version', bios_version) + if bios_serial_number: + info_add('system.bios_serial_number', bios_serial_number) # Run systemd-detect-virt command virt = run_command(["systemd-detect-virt"], check=False) From a0787630ee9dc823d6212c1f59826d345effb80d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 1 Jul 2026 23:59:51 +0200 Subject: [PATCH 5/9] Fix detect_virt() call --- Lib/test/pythoninfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index bc44163c2dc66c0..42d9eaf266a32b0 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1250,7 +1250,7 @@ def collect_system(info_add): uptime = f'{uptime} sec' info_add('system.uptime', uptime) - virt = detect_virt() + virt = detect_virt(info_add) if virt: info_add('system.virt', virt) From 947d5a8ef1754e8eebdc1a880b3a542ad24dc8b3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 2 Jul 2026 00:15:28 +0200 Subject: [PATCH 6/9] Get Model --- Lib/test/pythoninfo.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 42d9eaf266a32b0..f93edd5db5a44a0 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1161,20 +1161,27 @@ def get_machine_id(): def detect_virt(info_add): # On Windows, use WMI to get the computer manufacturer and the BIOS version if MS_WINDOWS: - data = wmi_query("SELECT Manufacturer FROM Win32_ComputerSystem") + data = wmi_query("SELECT Manufacturer, Model FROM Win32_ComputerSystem") manufacturer = data.get('Manufacturer', '') + model = data.get('Model', '') patterns = ( 'QEMU', + 'KVM', 'VMWare', 'Virtual', ) if any(pattern in manufacturer for pattern in patterns): return manufacturer - elif manufacturer: - # Log the value to update patterns on new VM - info_add('system.manufacturer', manufacturer) + elif any(pattern in model for pattern in patterns): + return model + else: + # Log the values to update patterns on new VM + if manufacturer and manufacturer != "Microsoft Corporation": + info_add('system.manufacturer', manufacturer) + if model: + info_add('system.model', model) - data = wmi_query("SELECT SerialNumber,Version FROM Win32_Bios") + data = wmi_query("SELECT SerialNumber, Version FROM Win32_Bios") bios_serial_number = data.get('SerialNumber', '') bios_version = data.get('Version', '') patterns = ( From 0b41228383f07181d94c513f83d7d7707cb34690 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 2 Jul 2026 00:39:29 +0200 Subject: [PATCH 7/9] Add detect_virt_windows() --- Lib/test/pythoninfo.py | 96 +++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index f93edd5db5a44a0..4812c766719d0da 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1158,47 +1158,65 @@ def get_machine_id(): return None -def detect_virt(info_add): +def detect_virt_windows(info_add): # On Windows, use WMI to get the computer manufacturer and the BIOS version + # + # VMware: + # - Win32_Bios.SerialNumber starts with "VMware-" + # - Win32_ComputerSystem.Model = "VMware" + # - Win32_ComputerSystem.Manufacturer = "VMware" + # + # QEMU: + # - Win32_ComputerSystem.Manufacturer = "QEMU" + # + # Parallels: + # - Win32_Bios.Version = "PARALLELS" + # + # VirtualBox: + # - Win32_Bios.Version = "VBOX" + # - Win32_ComputerSystem.Model = "VirtualBox" + # - Win32_ComputerSystem.Manufacturer = "innotek GmbH" + + data = wmi_query("SELECT Model, Manufacturer FROM Win32_ComputerSystem") + known_virt = ( + 'QEMU', + 'VMware', + 'VirtualBox', + ) + model = data.get('Model', '') + if model in known_virt: + return model + manufacturer = data.get('Manufacturer', '') + if manufacturer in known_virt: + return manufacturer + + data = wmi_query("SELECT Version FROM Win32_Bios") + bios_version = data.get('Version', '') + if bios_version in known_virt: + return bios_version + known_bios_versions = { + "PARALLELS": "Parallels", + "VBOX": "VirtualBox", + } + try: + return known_bios_versions[bios_version] + except KeyError: + pass + + # Log the values to update patterns on new VM + if model: + info_add('system.model', model) + if manufacturer: + info_add('system.manufacturer', manufacturer) + if bios_version: + info_add('system.bios_version', bios_version) + + +def detect_virt(info_add): if MS_WINDOWS: - data = wmi_query("SELECT Manufacturer, Model FROM Win32_ComputerSystem") - manufacturer = data.get('Manufacturer', '') - model = data.get('Model', '') - patterns = ( - 'QEMU', - 'KVM', - 'VMWare', - 'Virtual', - ) - if any(pattern in manufacturer for pattern in patterns): - return manufacturer - elif any(pattern in model for pattern in patterns): - return model - else: - # Log the values to update patterns on new VM - if manufacturer and manufacturer != "Microsoft Corporation": - info_add('system.manufacturer', manufacturer) - if model: - info_add('system.model', model) - - data = wmi_query("SELECT SerialNumber, Version FROM Win32_Bios") - bios_serial_number = data.get('SerialNumber', '') - bios_version = data.get('Version', '') - patterns = ( - 'VIRTUAL', - 'VMware', - 'Xen', - ) - if any(pattern in bios_serial_number for pattern in patterns): - return bios_serial_number - elif any(pattern in bios_version for pattern in patterns): - return bios_version - else: - # Log the values to update patterns on new VM - if bios_version: - info_add('system.bios_version', bios_version) - if bios_serial_number: - info_add('system.bios_serial_number', bios_serial_number) + virt = detect_virt_windows(info_add) + if virt: + return virt # Run systemd-detect-virt command virt = run_command(["systemd-detect-virt"], check=False) From bb6099d66893b59e4549385732e6df7286931607 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 2 Jul 2026 01:07:27 +0200 Subject: [PATCH 8/9] Detect Hyper-V --- Lib/test/pythoninfo.py | 69 ++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 4812c766719d0da..2adaad0bb8a33d5 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1159,15 +1159,23 @@ def get_machine_id(): def detect_virt_windows(info_add): - # On Windows, use WMI to get the computer manufacturer and the BIOS version + # On Windows, use WMI to detect the virtualization. + # + # Microsoft Hyper-V: + # - Win32_Bios.Version = "VRTUAL - 1" + # - Win32_ComputerSystem.Model = "Virtual Machine" + # - Win32_ComputerSystem.Manufacturer = "Microsoft Corporation" # # VMware: - # - Win32_Bios.SerialNumber starts with "VMware-" # - Win32_ComputerSystem.Model = "VMware" # - Win32_ComputerSystem.Manufacturer = "VMware" + # - Win32_Bios.SerialNumber starts with "VMware-" # # QEMU: # - Win32_ComputerSystem.Manufacturer = "QEMU" + # - Win32_ComputerSystem.Model = "Standard PC (Q35 + ICH9, 2009)" + # - Win32_Bios.Version = "BOCHS - 1" + # - Win32_Bios.Manufacturer = "EDK II" # # Parallels: # - Win32_Bios.Version = "PARALLELS" @@ -1177,46 +1185,55 @@ def detect_virt_windows(info_add): # - Win32_ComputerSystem.Model = "VirtualBox" # - Win32_ComputerSystem.Manufacturer = "innotek GmbH" - data = wmi_query("SELECT Model, Manufacturer FROM Win32_ComputerSystem") - known_virt = ( + KNOWN_VIRT = ( 'QEMU', 'VMware', 'VirtualBox', + 'Xen', + 'oVirt', ) - model = data.get('Model', '') - if model in known_virt: - return model - manufacturer = data.get('Manufacturer', '') - if manufacturer in known_virt: - return manufacturer - - data = wmi_query("SELECT Version FROM Win32_Bios") - bios_version = data.get('Version', '') - if bios_version in known_virt: - return bios_version - known_bios_versions = { + KNOWN_BIOS_VERSIONS = { + "VRTUAL - 1": "Microsoft Hyper-V", "PARALLELS": "Parallels", "VBOX": "VirtualBox", } + + computer = wmi_query("SELECT Model, Manufacturer FROM Win32_ComputerSystem") + computer_model = computer.get('Model', '') + if computer_model in KNOWN_VIRT: + return computer_model + computer_manufacturer = computer.get('Manufacturer', '') + if computer_manufacturer in KNOWN_VIRT: + return computer_manufacturer + + bios = wmi_query("SELECT Version, Manufacturer FROM Win32_Bios") + + bios_version = bios.get('Version', '') + if bios_version in KNOWN_VIRT: + return bios_version try: - return known_bios_versions[bios_version] + return KNOWN_BIOS_VERSIONS[bios_version] except KeyError: pass - # Log the values to update patterns on new VM - if model: - info_add('system.model', model) - if manufacturer: - info_add('system.manufacturer', manufacturer) + bios_manufacturer = bios.get('Manufacturer', '') + if bios_manufacturer in KNOWN_VIRT: + return bios_manufacturer + + # Log the values to update the code if a new VM is discovered + if computer_model: + info_add('system.computer.model', computer_model) + if computer_manufacturer: + info_add('system.computer.manufacturer', computer_manufacturer) if bios_version: - info_add('system.bios_version', bios_version) + info_add('system.bios.version', bios_version) + if bios_manufacturer: + info_add('system.bios.manufacturer', bios_manufacturer) def detect_virt(info_add): if MS_WINDOWS: - virt = detect_virt_windows(info_add) - if virt: - return virt + return detect_virt_windows(info_add) # Run systemd-detect-virt command virt = run_command(["systemd-detect-virt"], check=False) From 1761ea855dc48171046df7c6f6e1708defc1de5d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 2 Jul 2026 12:58:14 +0200 Subject: [PATCH 9/9] Detect Amazon EC2 --- Lib/test/pythoninfo.py | 54 ++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 2adaad0bb8a33d5..3227b91bd82a863 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1162,30 +1162,38 @@ def detect_virt_windows(info_add): # On Windows, use WMI to detect the virtualization. # # Microsoft Hyper-V: - # - Win32_Bios.Version = "VRTUAL - 1" - # - Win32_ComputerSystem.Model = "Virtual Machine" - # - Win32_ComputerSystem.Manufacturer = "Microsoft Corporation" + # - Win32_Bios.Version = 'VRTUAL - 12001807' + # - Win32_Bios.Manufacturer = 'American Megatrends Inc.' + # - Win32_ComputerSystem.Model = 'Virtual Machine' + # - Win32_ComputerSystem.Manufacturer = 'Microsoft Corporation' # # VMware: - # - Win32_ComputerSystem.Model = "VMware" - # - Win32_ComputerSystem.Manufacturer = "VMware" - # - Win32_Bios.SerialNumber starts with "VMware-" + # - Win32_ComputerSystem.Model = 'VMware' + # - Win32_ComputerSystem.Manufacturer = 'VMWare' (uppercase W in Ware) + # - Win32_Bios.SerialNumber starts with 'VMware-' # # QEMU: - # - Win32_ComputerSystem.Manufacturer = "QEMU" - # - Win32_ComputerSystem.Model = "Standard PC (Q35 + ICH9, 2009)" - # - Win32_Bios.Version = "BOCHS - 1" - # - Win32_Bios.Manufacturer = "EDK II" + # - Win32_ComputerSystem.Manufacturer = 'QEMU' + # - Win32_ComputerSystem.Model = 'Standard PC (Q35 + ICH9, 2009)' + # - Win32_Bios.Version = 'BOCHS - 1' + # - Win32_Bios.Manufacturer = 'EDK II' # # Parallels: - # - Win32_Bios.Version = "PARALLELS" + # - Win32_Bios.Version = 'PARALLELS' # # VirtualBox: - # - Win32_Bios.Version = "VBOX" - # - Win32_ComputerSystem.Model = "VirtualBox" - # - Win32_ComputerSystem.Manufacturer = "innotek GmbH" + # - Win32_Bios.Version = 'VBOX' + # - Win32_ComputerSystem.Model = 'VirtualBox' + # - Win32_ComputerSystem.Manufacturer = 'innotek GmbH' + # + # Amazon EC2: + # - Win32_Bios.Version = 'AMAZON - 1' + # - Win32_Bios.Manufacturer = 'Amazon EC2' + # - Win32_ComputerSystem.Model = 'm7i.4xlarge' + # - Win32_ComputerSystem.Manufacturer = 'Amazon EC2' KNOWN_VIRT = ( + 'Amazon EC2', 'QEMU', 'VMware', 'VirtualBox', @@ -1193,24 +1201,30 @@ def detect_virt_windows(info_add): 'oVirt', ) KNOWN_BIOS_VERSIONS = { - "VRTUAL - 1": "Microsoft Hyper-V", - "PARALLELS": "Parallels", - "VBOX": "VirtualBox", + 'PARALLELS': 'Parallels', + 'VBOX': 'VirtualBox', } - computer = wmi_query("SELECT Model, Manufacturer FROM Win32_ComputerSystem") + computer = wmi_query('SELECT Model, Manufacturer FROM Win32_ComputerSystem') computer_model = computer.get('Model', '') + computer_manufacturer = computer.get('Manufacturer', '') + if computer_manufacturer == 'Amazon EC2': + # Log the VM model (ex: 'm7i.4xlarge') + info_add('system.computer.model', computer_model) + return computer_manufacturer if computer_model in KNOWN_VIRT: return computer_model - computer_manufacturer = computer.get('Manufacturer', '') if computer_manufacturer in KNOWN_VIRT: return computer_manufacturer - bios = wmi_query("SELECT Version, Manufacturer FROM Win32_Bios") + bios = wmi_query('SELECT Version, Manufacturer FROM Win32_Bios') bios_version = bios.get('Version', '') if bios_version in KNOWN_VIRT: return bios_version + if (bios_version.startswith('VRTUAL - ') + and computer_manufacturer == 'Microsoft Corporation'): + return 'Microsoft Hyper-V' try: return KNOWN_BIOS_VERSIONS[bios_version] except KeyError: