@@ -452,6 +452,12 @@ def format_attr(attr, value): |
452 | 452 |
|
453 | 453 | def run_command(cmd, check=True, **kwargs): |
454 | 454 | 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 | + |
455 | 461 | timeout = COMMAND_TIMEOUT |
456 | 462 |
|
457 | 463 | cmd_str = ' '.join(cmd) |
@@ -951,6 +957,24 @@ def winreg_query(path): |
951 | 957 | return None |
952 | 958 |
|
953 | 959 |
|
| 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 | + |
954 | 978 | def collect_windows(info_add): |
955 | 979 | if not MS_WINDOWS: |
956 | 980 | # Code specific to Windows |
@@ -990,22 +1014,14 @@ def collect_windows(info_add): |
990 | 1014 | except (ImportError, AttributeError): |
991 | 1015 | pass |
992 | 1016 |
|
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) |
1009 | 1025 |
|
1010 | 1026 | # windows.ver: "ver" command |
1011 | 1027 | output = run_command(["ver"], shell=True) |
@@ -1123,7 +1139,97 @@ def get_machine_id(): |
1123 | 1139 | return None |
1124 | 1140 |
|
1125 | 1141 |
|
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 | + |
1127 | 1233 | # Run systemd-detect-virt command |
1128 | 1234 | virt = run_command(["systemd-detect-virt"], check=False) |
1129 | 1235 | if virt and virt != "none": |
@@ -1181,7 +1287,7 @@ def collect_system(info_add): |
1181 | 1287 | uptime = f'{uptime} sec' |
1182 | 1288 | info_add('system.uptime', uptime) |
1183 | 1289 |
|
1184 | | - virt = detect_virt() |
| 1290 | + virt = detect_virt(info_add) |
1185 | 1291 | if virt: |
1186 | 1292 | info_add('system.virt', virt) |
1187 | 1293 |
|
|