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/

replication

The health, state and lag of the Hyper-V replication of each virtual machine of a host, by WinRM and PowerShell.

  • virtualization
  • hyper-v
  • windows
  • winrm
  • powershell
  • replication
  • replica

by banshee1 installation1 version

v1Latest

Published on October 27, 2026. First version.

Keen 1.0.0 · 86 lines
// The Hyper-V replication of the virtual machines of a host, by WinRM: for each replication relationship of a
// machine, from this host or to it, the health Hyper-V gives it, its state, and the time since its last
// replication.
// 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, a GUID that tells it from the host) and Msvm_ReplicationRelationship (InstanceID,
// "Microsoft:<id of the machine>\HVR\<0 or 1>", 1 for an extended replication; ReplicationState;
// ReplicationHealth; LastReplicationTime, turned into minutes on the host, by its own clock). 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('health', 'text', array('label' => 'Replication health', 'per' => 'vm'));
output('state', 'text', array('label' => 'Replication state', 'per' => 'vm'));
output('lag', 'number', array('label' => 'Time since the last replication', 'unit' => 'min', 'per' => 'vm'));

watch.state('health', 'Replication unhealthy', 'health', array('warning' => array('warning'), 'critical' => array('critical')));
watch.state('stopped', 'Replication stopped', 'state', array(
    'warning'  => array('suspended', 'resynchronization-suspended'),
    'critical' => array('critical', 'disk-update-critical'),
));
watch.above('late', 'Replication late', 'lag', array('warning' => 60, 'critical' => 240));
watch.missing('missing', 'Replication missing', 'health', array('enabled' => false));

// A replication that never ran has no time, or one before 2000: Lag is then -1.
$read = winrm.run("$ErrorActionPreference = 'Stop'; "
    . "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding; "
    . "$now = Get-Date; "
    . "ConvertTo-Json -Compress -Depth 3 -InputObject @{"
    . "vms = @(Get-CimInstance -Namespace root\\virtualization\\v2 -ClassName Msvm_ComputerSystem"
    . " | Where-Object Name -match '^[0-9A-Fa-f]{8}-' | Select-Object ElementName, Name); "
    . "relationships = @(Get-CimInstance -Namespace root\\virtualization\\v2 -ClassName Msvm_ReplicationRelationship"
    . " | Select-Object InstanceID, ReplicationState, ReplicationHealth, "
    . "@{Name = 'Lag'; Expression = {if ($_.LastReplicationTime -and $_.LastReplicationTime.Year -gt 2000)"
    . " {[math]::Round(($now - $_.LastReplicationTime).TotalMinutes)} else {-1}}})}");
$answer = text.trim($read['output']);
if (list.count(text.match($answer, '^\{')) == 0) {
    fail('misconfigured', 'PowerShell did not return the replication of the virtual machines', $answer . $read['errors']);
}
$read_replication = json.read($answer);

// The codes Microsoft documents for Msvm_ReplicationRelationship: ReplicationState, ReplicationHealth. A code
// outside them reads "state" or "health" and the code.
$states = array(
    0 => 'disabled', 1 => 'ready-for-replication', 2 => 'waiting-to-complete-initial-replication',
    3 => 'replicating', 4 => 'synced-replication-complete', 5 => 'recovered', 6 => 'committed', 7 => 'suspended',
    8 => 'critical', 9 => 'waiting-to-start-resynchronization', 10 => 'resynchronizing',
    11 => 'resynchronization-suspended', 12 => 'failover-in-progress', 13 => 'failback-in-progress',
    14 => 'failback-complete', 15 => 'disk-update-in-progress', 16 => 'disk-update-critical', 17 => 'unknown',
    18 => 'repurpose-replication-in-progress', 19 => 'prepared-for-sync-replication',
    20 => 'prepared-for-group-reverse-replication', 21 => 'firedrill-in-progress',
);
$healths = array(0 => 'not-applicable', 1 => 'ok', 2 => 'warning', 3 => 'critical');

// {"vms": [{"ElementName": "db01", "Name": "0B8C5A1E-..."}, ...]}: the name of each machine by its id; two
// machines of one name each keep their own, the second carrying its id, as in hyperv/winrm/vms.
$name_of = array();
$seen = array();
foreach ($read_replication['vms'] as $vm) {
    $name = $vm['ElementName'];
    if (list.has($seen, $name)) {
        $name = $name . ' #' . $vm['Name'];
    }
    $seen[$name] = true;
    $name_of[text.lower($vm['Name'])] = $name;
}

// {"relationships": [{"InstanceID": "Microsoft:0B8C5A1E-...\HVR\0", "ReplicationState": 3,
// "ReplicationHealth": 1, "Lag": 4}]}. The extended replication of a machine is named after it, followed by
// "extended".
foreach ($read_replication['relationships'] as $relationship) {
    $m = text.match($relationship['InstanceID'], '^Microsoft:([0-9A-Fa-f-]+).HVR.([0-9]+)$');
    if (list.count($m) == 0) {
        fail('misconfigured', 'A replication relationship has an InstanceID out of the documented form', $relationship['InstanceID']);
    }
    $name = list.get($name_of, text.lower($m[1]), $m[1]);
    if ($m[2] != '0') {
        $name = $name . ' extended';
    }
    value('health', list.get($healths, $relationship['ReplicationHealth'], 'health ' . $relationship['ReplicationHealth']), $name);
    value('state', list.get($states, $relationship['ReplicationState'], 'state ' . $relationship['ReplicationState']), $name);
    if ($relationship['Lag'] >= 0) {
        value('lag', $relationship['Lag'], $name);
    }
}