Active Directory
banshee/active-directory-collection/active-directory/ssh/
fsmo
The holder of each of the five FSMO roles and whether it answers on the LDAP port, by SSH and PowerShell
by banshee1 installation2 versions
v2Latest
Published on October 28, 2026. One naming convention across collections; documentation completed.
// The five FSMO roles of the forest and of the domain of a domain controller, by SSH: the domain controller
// that holds each one, as this domain controller knows it, and whether that holder accepts a connection on
// the LDAP port, 389, from this domain controller.
// Runs one PowerShell command that only reads, through the classes of .NET that Microsoft documents:
// DomainController.GetDomainController, then Forest.SchemaRoleOwner and NamingRoleOwner, Domain.PdcRoleOwner,
// RidRoleOwner and InfrastructureRoleOwner. A holder is reached by a TCP connection that sends nothing, given
// up after 3 seconds: a bind with the account would also depend on whether the remote session can hand its
// credentials on to another server, which it often cannot.
need('protocol:ssh');
per('role', 'Roles');
output('holder', 'text', array('label' => 'Holder', 'per' => 'role'));
output('reachable', 'text', array('label' => 'Reachable', 'per' => 'role'));
watch.state('unreachable', 'Role holder unreachable', 'reachable', array('critical' => array('no')));
$read = ssh.run("powershell -NoProfile -NonInteractive -Command \""
. "$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); "
. "$holders = [ordered]@{"
. "schema = $dc.Forest.SchemaRoleOwner.Name; "
. "'domain-naming' = $dc.Forest.NamingRoleOwner.Name; "
. "pdc = $dc.Domain.PdcRoleOwner.Name; "
. "rid = $dc.Domain.RidRoleOwner.Name; "
. "infrastructure = $dc.Domain.InfrastructureRoleOwner.Name}; "
. "$roles = @(foreach ($role in $holders.Keys) {"
. " $reachable = $false;"
. " try { $tcp = New-Object Net.Sockets.TcpClient;"
. " $reachable = $tcp.BeginConnect($holders[$role], 389, $null, $null).AsyncWaitHandle.WaitOne(3000) -and $tcp.Connected;"
. " $tcp.Close() } catch { };"
. " @{role = $role; holder = $holders[$role]; reachable = $reachable} }); "
. "ConvertTo-Json -Compress -Depth 4 -InputObject @{roles = $roles}\"");
$answer = text.trim($read['output']);
if (list.count(text.match($answer, '^\{')) == 0) {
fail('misconfigured', 'PowerShell did not return the FSMO roles', $answer . $read['errors']);
}
$fsmo = json.read($answer);
$roles = 0;
foreach ($fsmo['roles'] as $role) {
$roles = $roles + 1;
value('holder', $role['holder'], $role['role']);
if ($role['reachable']) {
value('reachable', 'yes', $role['role']);
} else {
value('reachable', 'no', $role['role']);
}
}
if ($roles != 5) {
fail('misconfigured', 'PowerShell did not return the five FSMO roles', $answer);
}