Skip to content

Commit a1a9e6f

Browse files
[3.15] gh-152680: Detect virtualization on Windows in pythoninfo (GH-152824) (#152890)
gh-152680: Detect virtualization on Windows in pythoninfo (GH-152824) Use WMI to detect virtualization 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. (cherry picked from commit 4e4869b) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 9317d52 commit a1a9e6f

1 file changed

Lines changed: 124 additions & 18 deletions

File tree

Lib/test/pythoninfo.py

Lines changed: 124 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,12 @@ def format_attr(attr, value):
452452

453453
def run_command(cmd, check=True, **kwargs):
454454
import subprocess
455+
from test.support import has_subprocess_support
456+
457+
if not has_subprocess_support:
458+
# subprocess is not supported by the current platform
459+
return ''
460+
455461
timeout = COMMAND_TIMEOUT
456462

457463
cmd_str = ' '.join(cmd)
@@ -963,6 +969,24 @@ def winreg_query(path):
963969
return None
964970

965971

972+
def wmi_query(query):
973+
try:
974+
import _wmi
975+
except ImportError:
976+
return {}
977+
978+
try:
979+
data = _wmi.exec_query(query)
980+
except OSError:
981+
return {}
982+
983+
dict_data = {}
984+
for item in data.split("\0"):
985+
key, _, value = item.partition("=")
986+
dict_data[key] = value
987+
return dict_data
988+
989+
966990
def collect_windows(info_add):
967991
if not MS_WINDOWS:
968992
# Code specific to Windows
@@ -1009,22 +1033,14 @@ def collect_windows(info_add):
10091033
call_func(info_add, 'windows.ansi_code_page', _winapi, 'GetACP')
10101034
call_func(info_add, 'windows.oem_code_page', _winapi, 'GetOEMCP')
10111035

1012-
# windows.version_caption: "wmic os get Caption,Version /value" command
1013-
output = run_command(["wmic", "os", "get", "Caption,Version", "/value"],
1014-
# When wmic.exe output is redirected to a pipe,
1015-
# it uses the OEM code page
1016-
encoding="oem")
1017-
if output:
1018-
for line in output.splitlines():
1019-
line = line.strip()
1020-
if line.startswith('Caption='):
1021-
line = line.removeprefix('Caption=').strip()
1022-
if line:
1023-
info_add('windows.version_caption', line)
1024-
elif line.startswith('Version='):
1025-
line = line.removeprefix('Version=').strip()
1026-
if line:
1027-
info_add('windows.version', line)
1036+
# Get operating system caption and version using WMI
1037+
data = wmi_query("SELECT Caption, Version FROM Win32_OperatingSystem")
1038+
caption = data.get('Caption', '')
1039+
if caption:
1040+
info_add('windows.version_caption', caption)
1041+
version = data.get('Version', '')
1042+
if version:
1043+
info_add('windows.version', version)
10281044

10291045
# windows.ver: "ver" command
10301046
output = run_command(["ver"], shell=True)
@@ -1142,7 +1158,97 @@ def get_machine_id():
11421158
return None
11431159

11441160

1145-
def detect_virt():
1161+
def detect_virt_windows(info_add):
1162+
# On Windows, use WMI to detect the virtualization.
1163+
#
1164+
# Microsoft Hyper-V:
1165+
# - Win32_Bios.Version = 'VRTUAL - 12001807'
1166+
# - Win32_Bios.Manufacturer = 'American Megatrends Inc.'
1167+
# - Win32_ComputerSystem.Model = 'Virtual Machine'
1168+
# - Win32_ComputerSystem.Manufacturer = 'Microsoft Corporation'
1169+
#
1170+
# VMware:
1171+
# - Win32_ComputerSystem.Model = 'VMware'
1172+
# - Win32_ComputerSystem.Manufacturer = 'VMWare' (uppercase W in Ware)
1173+
# - Win32_Bios.SerialNumber starts with 'VMware-'
1174+
#
1175+
# QEMU:
1176+
# - Win32_ComputerSystem.Manufacturer = 'QEMU'
1177+
# - Win32_ComputerSystem.Model = 'Standard PC (Q35 + ICH9, 2009)'
1178+
# - Win32_Bios.Version = 'BOCHS - 1'
1179+
# - Win32_Bios.Manufacturer = 'EDK II'
1180+
#
1181+
# Parallels:
1182+
# - Win32_Bios.Version = 'PARALLELS'
1183+
#
1184+
# VirtualBox:
1185+
# - Win32_Bios.Version = 'VBOX'
1186+
# - Win32_ComputerSystem.Model = 'VirtualBox'
1187+
# - Win32_ComputerSystem.Manufacturer = 'innotek GmbH'
1188+
#
1189+
# Amazon EC2:
1190+
# - Win32_Bios.Version = 'AMAZON - 1'
1191+
# - Win32_Bios.Manufacturer = 'Amazon EC2'
1192+
# - Win32_ComputerSystem.Model = 'm7i.4xlarge'
1193+
# - Win32_ComputerSystem.Manufacturer = 'Amazon EC2'
1194+
1195+
KNOWN_VIRT = (
1196+
'Amazon EC2',
1197+
'QEMU',
1198+
'VMware',
1199+
'VirtualBox',
1200+
'Xen',
1201+
'oVirt',
1202+
)
1203+
KNOWN_BIOS_VERSIONS = {
1204+
'PARALLELS': 'Parallels',
1205+
'VBOX': 'VirtualBox',
1206+
}
1207+
1208+
computer = wmi_query('SELECT Model, Manufacturer FROM Win32_ComputerSystem')
1209+
computer_model = computer.get('Model', '')
1210+
computer_manufacturer = computer.get('Manufacturer', '')
1211+
if computer_manufacturer == 'Amazon EC2':
1212+
# Log the VM model (ex: 'm7i.4xlarge')
1213+
info_add('system.computer.model', computer_model)
1214+
return computer_manufacturer
1215+
if computer_model in KNOWN_VIRT:
1216+
return computer_model
1217+
if computer_manufacturer in KNOWN_VIRT:
1218+
return computer_manufacturer
1219+
1220+
bios = wmi_query('SELECT Version, Manufacturer FROM Win32_Bios')
1221+
1222+
bios_version = bios.get('Version', '')
1223+
if bios_version in KNOWN_VIRT:
1224+
return bios_version
1225+
if (bios_version.startswith('VRTUAL - ')
1226+
and computer_manufacturer == 'Microsoft Corporation'):
1227+
return 'Microsoft Hyper-V'
1228+
try:
1229+
return KNOWN_BIOS_VERSIONS[bios_version]
1230+
except KeyError:
1231+
pass
1232+
1233+
bios_manufacturer = bios.get('Manufacturer', '')
1234+
if bios_manufacturer in KNOWN_VIRT:
1235+
return bios_manufacturer
1236+
1237+
# Log the values to update the code if a new VM is discovered
1238+
if computer_model:
1239+
info_add('system.computer.model', computer_model)
1240+
if computer_manufacturer:
1241+
info_add('system.computer.manufacturer', computer_manufacturer)
1242+
if bios_version:
1243+
info_add('system.bios.version', bios_version)
1244+
if bios_manufacturer:
1245+
info_add('system.bios.manufacturer', bios_manufacturer)
1246+
1247+
1248+
def detect_virt(info_add):
1249+
if MS_WINDOWS:
1250+
return detect_virt_windows(info_add)
1251+
11461252
# Run systemd-detect-virt command
11471253
virt = run_command(["systemd-detect-virt"], check=False)
11481254
if virt and virt != "none":
@@ -1200,7 +1306,7 @@ def collect_system(info_add):
12001306
uptime = f'{uptime} sec'
12011307
info_add('system.uptime', uptime)
12021308

1203-
virt = detect_virt()
1309+
virt = detect_virt(info_add)
12041310
if virt:
12051311
info_add('system.virt', virt)
12061312

0 commit comments

Comments
 (0)