Active Directory
banshee/active-directory-collection/active-directory/winrm/
fsmo
The holder of each of the five FSMO roles and whether it answers on the LDAP port, by WinRM 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 WinRM: 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 script 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 a WinRM session authenticated by NTLM cannot.
// The script is the one of active-directory/ssh/fsmo, without the powershell -Command "..." that cmd.exe
// needs there.
need('protocol:winrm');
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 = winrm.run("$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);
}