Virtualization
banshee/virtualization-collection/virtualization/hyperv/winrm/
vms
The state, health and heartbeat of each virtual machine of a Hyper-V host, by WinRM and the Hyper-V WMI classes.
by banshee1 installation1 version
Published on October 27, 2026. First version.
// The virtual machines of a Hyper-V host, by WinRM: the state and the health of each, and, for those that
// run, the heartbeat their integration services send to the host and the state of their applications. The
// watches of the expected state are not checked by default: only the administrator knows which machines must
// run and which must stay off, and says it by the selection of the probe, one probe for each.
// Runs one PowerShell script that only reads two WMI classes of Hyper-V, whose properties and codes Microsoft
// documents (root\virtualization\v2): Msvm_ComputerSystem (ElementName, the name; Name, the id of the
// machine; EnabledState; HealthState), and Msvm_HeartbeatComponent (SystemName, the id of its machine;
// OperationalStatus). The host itself is an Msvm_ComputerSystem too: a machine is told by its id, a GUID.
// With $ErrorActionPreference set to Stop, a right missing on Hyper-V ends the script with no answer, rather
// than an empty list.
need('protocol:winrm');
per('vm', 'Virtual machines');
output('state', 'text', array('label' => 'State', 'per' => 'vm'));
output('health', 'text', array('label' => 'Health', 'per' => 'vm'));
output('heartbeat', 'text', array('label' => 'Heartbeat', 'per' => 'vm'));
output('applications', 'text', array('label' => 'Applications', 'per' => 'vm'));
watch.state('unhealthy', 'Virtual machine unhealthy', 'health', array(
'warning' => array('major-failure'),
'critical' => array('critical-failure'),
));
watch.state('heartbeat-lost', 'Heartbeat lost', 'heartbeat', array(
'critical' => array('lost-communication', 'non-recoverable-error'),
'for' => '2 runs',
));
watch.state('no-heartbeat', 'No heartbeat', 'heartbeat', array('warning' => array('no-contact'), 'enabled' => false));
watch.state('application-critical', 'Application in a critical state', 'applications', array('warning' => array('application-critical')));
watch.expect('not-running', 'Virtual machine not running', 'state', array(
'help' => 'For a selection of the machines that must run: critical when one is off, saved or paused.',
'critical' => array('running'),
'enabled' => false,
));
watch.expect('not-off', 'Virtual machine not off', 'state', array(
'help' => 'For a selection of the machines that must stay off: warning when one runs.',
'warning' => array('off'),
'enabled' => false,
));
watch.missing('missing', 'Virtual machine missing', 'state', array('enabled' => false));
$read = winrm.run("$ErrorActionPreference = 'Stop'; "
. "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding; "
. "ConvertTo-Json -Compress -Depth 4 -InputObject @{"
. "vms = @(Get-CimInstance -Namespace root\\virtualization\\v2 -ClassName Msvm_ComputerSystem"
. " | Where-Object Name -match '^[0-9A-Fa-f]{8}-'"
. " | Select-Object ElementName, Name, EnabledState, HealthState); "
. "heartbeats = @(Get-CimInstance -Namespace root\\virtualization\\v2 -ClassName Msvm_HeartbeatComponent"
. " | Select-Object SystemName, OperationalStatus)}");
$answer = text.trim($read['output']);
if (list.count(text.match($answer, '^\{')) == 0) {
fail('misconfigured', 'PowerShell did not return the virtual machines', $answer . $read['errors']);
}
$read_vms = json.read($answer);
// The codes Microsoft documents for Msvm_ComputerSystem: EnabledState, HealthState.
$states = array(
0 => 'unknown', 2 => 'running', 3 => 'off', 4 => 'shutting-down', 6 => 'enabled-but-offline',
9 => 'quiesce', 10 => 'starting', 32768 => 'paused', 32769 => 'suspended',
);
$healths = array(5 => 'ok', 20 => 'major-failure', 25 => 'critical-failure');
// And for Msvm_HeartbeatComponent: OperationalStatus[0], the heartbeat; OperationalStatus[1], the state of
// the applications of the machine.
$heartbeats = array(
2 => 'ok', 3 => 'degraded', 7 => 'non-recoverable-error', 12 => 'no-contact', 13 => 'lost-communication',
15 => 'paused',
);
$applications = array(2 => 'ok', 32775 => 'protocol-mismatch', 32782 => 'application-critical', 32783 => 'communication-timed-out');
// {"heartbeats": [{"SystemName": "0B8C5A1E-...", "OperationalStatus": [2, 2]}, ...]}, by the id of the machine.
$beat_of = array();
foreach ($read_vms['heartbeats'] as $beat) {
$beat_of[$beat['SystemName']] = $beat['OperationalStatus'];
}
// {"vms": [{"ElementName": "web01", "Name": "0B8C5A1E-...", "EnabledState": 2, "HealthState": 5}, ...]}. A code
// outside the documented ones reads "state" or "health" and the code. Two machines of one name each keep their
// own, the second carrying its id.
$seen = array();
foreach ($read_vms['vms'] as $vm) {
$name = $vm['ElementName'];
if (list.has($seen, $name)) {
$name = $name . ' #' . $vm['Name'];
}
$seen[$name] = true;
$state = list.get($states, $vm['EnabledState'], 'state ' . $vm['EnabledState']);
value('state', $state, $name);
value('health', list.get($healths, $vm['HealthState'], 'health ' . $vm['HealthState']), $name);
if ($state != 'running' || !list.has($beat_of, $vm['Name'])) {
continue;
}
$status = $beat_of[$vm['Name']];
if (list.count($status) > 0) {
value('heartbeat', list.get($heartbeats, $status[0], 'status ' . $status[0]), $name);
}
if (list.count($status) > 1) {
value('applications', list.get($applications, $status[1], 'status ' . $status[1]), $name);
}
}