Active Directory
banshee/active-directory-collection/active-directory/winrm/
time
The clock of a domain controller against a time server, and the time source its role calls for, by WinRM and PowerShell
by banshee1 installation2 versions
// The clock of a domain controller, by WinRM: its offset from a time server, and the time source the Windows
// Time service is set to follow. Microsoft sets the domain hierarchy (NT5DS) on every domain controller but
// one: the PDC emulator of the root domain of the forest, the source of the whole forest, which follows an
// external time server (NTP). Watch that one first.
// Runs one PowerShell script that only reads: [DateTime]::UtcNow.Ticks, read first; the PDC emulator of the
// root domain, through the classes of .NET that Microsoft documents (Forest.RootDomain.PdcRoleOwner); and the
// values Type and NtpServer under HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters, or under
// HKLM\SOFTWARE\Policies\Microsoft\W32Time\Parameters when a group policy sets them, as it then prevails.
// The offset is read as the clock collector of windows-collection reads it: to about a second.
// The script is the one of active-directory/ssh/time, without the powershell -Command "..." that cmd.exe
// needs there.
need('protocol:winrm');
need('protocol:ntp');
output('offset', 'number', array('label' => 'Offset from the time server', 'unit' => 's'));
output('drift', 'number', array('label' => 'Offset, without its sign', 'unit' => 's'));
output('role', 'text', array('label' => 'Role in the time hierarchy'));
output('type', 'text', array('label' => 'Time source type'));
output('peers', 'text', array('label' => 'Time servers set'));
watch.above('clock-drift', 'Clock off', 'drift', array('warning' => 5, 'critical' => 60));
watch.custom('source', 'Time source unexpected', array(
'help' => 'Warning when the PDC emulator of the root domain does not follow an external time server (NTP), or another domain controller does not follow the domain hierarchy (NT5DS).',
));
$asked = time.now();
$read = winrm.run("$ticks = [string][DateTime]::UtcNow.Ticks; "
. "$ErrorActionPreference = 'Stop'; "
. "[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding; "
. "$computer = Get-CimInstance Win32_ComputerSystem; "
. "$server = $computer.DNSHostName + '.' + $computer.Domain; "
. "$context = New-Object DirectoryServices.ActiveDirectory.DirectoryContext('DirectoryServer', $server); "
. "$dc = [DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($context); "
. "$role = 'other'; "
. "if ($dc.Name -eq $dc.Forest.RootDomain.PdcRoleOwner.Name) { $role = 'root-pdc' }; "
. "$config = Get-ItemProperty HKLM:\\SYSTEM\\CurrentControlSet\\Services\\W32Time\\Parameters; "
. "$policy = Get-ItemProperty HKLM:\\SOFTWARE\\Policies\\Microsoft\\W32Time\\Parameters -ErrorAction SilentlyContinue; "
. "$type = $config.Type; $peers = $config.NtpServer; "
. "if ($policy.Type) { $type = $policy.Type; $peers = $policy.NtpServer }; "
. "ConvertTo-Json -Compress -InputObject @{ticks = $ticks; role = $role; type = $type; peers = $peers}");
$answered = time.now();
$server = ntp.time(errors=$err);
$now = time.now();
if ($err['code'] != '') {
fail('unreachable', 'Time server unreachable', $err['text']);
}
$answer = text.trim($read['output']);
if (list.count(text.match($answer, '^\{')) == 0) {
fail('misconfigured', 'PowerShell did not return the time of the domain controller', $answer . $read['errors']);
}
$clock = json.read($answer);
if (!text.is_number($clock['ticks'])) {
fail('misconfigured', 'PowerShell did not return the time of the domain controller', $answer);
}
// The server's time at the middle of the WinRM call: its time, less the time elapsed since, measured by the
// poller's clock.
$reference = $server - ($now - ($asked + $answered) / 2);
// 621355968000000000 ticks separate 1 January of the year 1 from 1 January 1970; ten million make a second.
$offset = ($clock['ticks'] - 621355968000000000) / 10000000 - $reference;
$drift = $offset;
if ($drift < 0) {
$drift = -$drift;
}
value('offset', number.round($offset, 1));
value('drift', number.round($drift, 1));
value('role', $clock['role']);
value('type', $clock['type']);
value('peers', $clock['peers']);
$type = text.lower($clock['type']);
if ($clock['role'] == 'root-pdc' && $type != 'ntp') {
watch.raise('source', 'warning', 'PDC emulator of the root domain set to ' . $clock['type'] . ', not to an external time server (NTP)');
}
if ($clock['role'] == 'other' && $type != 'nt5ds') {
watch.raise('source', 'warning', 'Domain controller set to ' . $clock['type'] . ', not to the domain hierarchy (NT5DS)');
}