banshee is under active development: collectors will keep changing, and a version you publish will likely need adjustments. For stable use, wait for version 1.0.0.

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.

  • virtualization
  • hyper-v
  • windows
  • winrm
  • powershell
  • vm
  • heartbeat

by banshee1 installation1 version

v1Latest

Published on October 27, 2026. First version.

Keen 1.0.0 · 98 lines
// 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);
    }
}