Skip to content

Commit 0d3a87f

Browse files
[3.13] gh-152680: Detect virtualization on Windows in pythoninfo (GH-152824) (#152888)
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 a1cb073 commit 0d3a87f

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
@@ -450,6 +450,12 @@ def format_attr(attr, value):
450450

451451
def run_command(cmd, check=True, **kwargs):
452452
import subprocess
453+
from test.support import has_subprocess_support
454+
455+
if not has_subprocess_support:
456+
# subprocess is not supported by the current platform
457+
return ''
458+
453459
timeout = COMMAND_TIMEOUT
454460

455461
cmd_str = ' '.join(cmd)
@@ -949,6 +955,24 @@ def winreg_query(path):
949955
return None
950956

951957

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

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

10081024
# windows.ver: "ver" command
10091025
output = run_command(["ver"], shell=True)
@@ -1121,7 +1137,97 @@ def get_machine_id():
11211137
return None
11221138

11231139

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

1182-
virt = detect_virt()
1288+
virt = detect_virt(info_add)
11831289
if virt:
11841290
info_add('system.virt', virt)
11851291

0 commit comments

Comments
 (0)