Skip to content

Commit 4324eb2

Browse files
[3.14] gh-152680: Detect virtualization on Windows in pythoninfo (GH-152824) (#152889)
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 8b3b764 commit 4324eb2

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)
@@ -951,6 +957,24 @@ def winreg_query(path):
951957
return None
952958

953959

960+
def wmi_query(query):
961+
try:
962+
import _wmi
963+
except ImportError:
964+
return {}
965+
966+
try:
967+
data = _wmi.exec_query(query)
968+
except OSError:
969+
return {}
970+
971+
dict_data = {}
972+
for item in data.split("\0"):
973+
key, _, value = item.partition("=")
974+
dict_data[key] = value
975+
return dict_data
976+
977+
954978
def collect_windows(info_add):
955979
if not MS_WINDOWS:
956980
# Code specific to Windows
@@ -990,22 +1014,14 @@ def collect_windows(info_add):
9901014
except (ImportError, AttributeError):
9911015
pass
9921016

993-
# windows.version_caption: "wmic os get Caption,Version /value" command
994-
output = run_command(["wmic", "os", "get", "Caption,Version", "/value"],
995-
# When wmic.exe output is redirected to a pipe,
996-
# it uses the OEM code page
997-
encoding="oem")
998-
if output:
999-
for line in output.splitlines():
1000-
line = line.strip()
1001-
if line.startswith('Caption='):
1002-
line = line.removeprefix('Caption=').strip()
1003-
if line:
1004-
info_add('windows.version_caption', line)
1005-
elif line.startswith('Version='):
1006-
line = line.removeprefix('Version=').strip()
1007-
if line:
1008-
info_add('windows.version', line)
1017+
# Get operating system caption and version using WMI
1018+
data = wmi_query("SELECT Caption, Version FROM Win32_OperatingSystem")
1019+
caption = data.get('Caption', '')
1020+
if caption:
1021+
info_add('windows.version_caption', caption)
1022+
version = data.get('Version', '')
1023+
if version:
1024+
info_add('windows.version', version)
10091025

10101026
# windows.ver: "ver" command
10111027
output = run_command(["ver"], shell=True)
@@ -1123,7 +1139,97 @@ def get_machine_id():
11231139
return None
11241140

11251141

1126-
def detect_virt():
1142+
def detect_virt_windows(info_add):
1143+
# On Windows, use WMI to detect the virtualization.
1144+
#
1145+
# Microsoft Hyper-V:
1146+
# - Win32_Bios.Version = 'VRTUAL - 12001807'
1147+
# - Win32_Bios.Manufacturer = 'American Megatrends Inc.'
1148+
# - Win32_ComputerSystem.Model = 'Virtual Machine'
1149+
# - Win32_ComputerSystem.Manufacturer = 'Microsoft Corporation'
1150+
#
1151+
# VMware:
1152+
# - Win32_ComputerSystem.Model = 'VMware'
1153+
# - Win32_ComputerSystem.Manufacturer = 'VMWare' (uppercase W in Ware)
1154+
# - Win32_Bios.SerialNumber starts with 'VMware-'
1155+
#
1156+
# QEMU:
1157+
# - Win32_ComputerSystem.Manufacturer = 'QEMU'
1158+
# - Win32_ComputerSystem.Model = 'Standard PC (Q35 + ICH9, 2009)'
1159+
# - Win32_Bios.Version = 'BOCHS - 1'
1160+
# - Win32_Bios.Manufacturer = 'EDK II'
1161+
#
1162+
# Parallels:
1163+
# - Win32_Bios.Version = 'PARALLELS'
1164+
#
1165+
# VirtualBox:
1166+
# - Win32_Bios.Version = 'VBOX'
1167+
# - Win32_ComputerSystem.Model = 'VirtualBox'
1168+
# - Win32_ComputerSystem.Manufacturer = 'innotek GmbH'
1169+
#
1170+
# Amazon EC2:
1171+
# - Win32_Bios.Version = 'AMAZON - 1'
1172+
# - Win32_Bios.Manufacturer = 'Amazon EC2'
1173+
# - Win32_ComputerSystem.Model = 'm7i.4xlarge'
1174+
# - Win32_ComputerSystem.Manufacturer = 'Amazon EC2'
1175+
1176+
KNOWN_VIRT = (
1177+
'Amazon EC2',
1178+
'QEMU',
1179+
'VMware',
1180+
'VirtualBox',
1181+
'Xen',
1182+
'oVirt',
1183+
)
1184+
KNOWN_BIOS_VERSIONS = {
1185+
'PARALLELS': 'Parallels',
1186+
'VBOX': 'VirtualBox',
1187+
}
1188+
1189+
computer = wmi_query('SELECT Model, Manufacturer FROM Win32_ComputerSystem')
1190+
computer_model = computer.get('Model', '')
1191+
computer_manufacturer = computer.get('Manufacturer', '')
1192+
if computer_manufacturer == 'Amazon EC2':
1193+
# Log the VM model (ex: 'm7i.4xlarge')
1194+
info_add('system.computer.model', computer_model)
1195+
return computer_manufacturer
1196+
if computer_model in KNOWN_VIRT:
1197+
return computer_model
1198+
if computer_manufacturer in KNOWN_VIRT:
1199+
return computer_manufacturer
1200+
1201+
bios = wmi_query('SELECT Version, Manufacturer FROM Win32_Bios')
1202+
1203+
bios_version = bios.get('Version', '')
1204+
if bios_version in KNOWN_VIRT:
1205+
return bios_version
1206+
if (bios_version.startswith('VRTUAL - ')
1207+
and computer_manufacturer == 'Microsoft Corporation'):
1208+
return 'Microsoft Hyper-V'
1209+
try:
1210+
return KNOWN_BIOS_VERSIONS[bios_version]
1211+
except KeyError:
1212+
pass
1213+
1214+
bios_manufacturer = bios.get('Manufacturer', '')
1215+
if bios_manufacturer in KNOWN_VIRT:
1216+
return bios_manufacturer
1217+
1218+
# Log the values to update the code if a new VM is discovered
1219+
if computer_model:
1220+
info_add('system.computer.model', computer_model)
1221+
if computer_manufacturer:
1222+
info_add('system.computer.manufacturer', computer_manufacturer)
1223+
if bios_version:
1224+
info_add('system.bios.version', bios_version)
1225+
if bios_manufacturer:
1226+
info_add('system.bios.manufacturer', bios_manufacturer)
1227+
1228+
1229+
def detect_virt(info_add):
1230+
if MS_WINDOWS:
1231+
return detect_virt_windows(info_add)
1232+
11271233
# Run systemd-detect-virt command
11281234
virt = run_command(["systemd-detect-virt"], check=False)
11291235
if virt and virt != "none":
@@ -1181,7 +1287,7 @@ def collect_system(info_add):
11811287
uptime = f'{uptime} sec'
11821288
info_add('system.uptime', uptime)
11831289

1184-
virt = detect_virt()
1290+
virt = detect_virt(info_add)
11851291
if virt:
11861292
info_add('system.virt', virt)
11871293

0 commit comments

Comments
 (0)