Providers/AD/Private/Select-ADTestServer.ps1

function Select-ADTestServer {
    <#
    .SYNOPSIS
        Picks a domain controller that is actually answering, rather than whichever one DNS names first

    .DESCRIPTION
        A domain can have more than one domain controller registered and fewer than that
        running. Automatic discovery resolves the `_ldap._tcp.dc._msdcs` records and will
        happily hand back one that has been switched off for a month; the caller then gets
        "unable to find a default server with Active Directory Web Services running" from
        whichever command asked next. Worse, discovery is cached and re-resolved, so a run
        can work for twenty minutes and then start failing halfway through, leaving a
        half-built directory behind.

        This picks one that answers and returns its name, so every later call can be pinned
        to it. The order is deliberate:

        1. A server the caller named. If it does not answer, that is an error rather than a
           reason to look elsewhere - somebody who names a domain controller means it.
        2. The PDC emulator, which is the one this provider would have used anyway.
        3. Every other domain controller the domain knows, in the order the domain lists
           them.

        Each candidate is tried with a real call, because a name in DNS, a service that is
        listening and a directory that will answer a query are three different things, and
        only the third one matters.

    .PARAMETER Server
        A specific domain controller to use. Tried first, and its failure is fatal.

    .PARAMETER Credential
        The credential to test with, if the caller supplied one.

    .OUTPUTS
        System.String. The name of a domain controller that answered.

    .EXAMPLE
        PS> Select-ADTestServer

        DESCRIPTION: Finds a working domain controller in a domain with a stale registration
        OUTPUT: 'DC01.ad.contoso.com'
        USE CASE: Called by Connect-ADEnvironment, which pins every later call to the result

    .NOTES
        Author: Jeffrey Stuhr
        Blog: https://www.techbyjeff.net
        LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/
    #>


    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]$Server,

        [Parameter()]
        [System.Management.Automation.PSCredential]$Credential
    )

    $common = @{ ErrorAction = 'Stop' }
    if ($Credential) { $common['Credential'] = $Credential }

    # One real query. Get-ADDomain goes through the same web service every other command in
    # this provider uses, so a candidate that satisfies it will satisfy them.
    $answers = {
        param($candidate)
        try {
            $null = Get-ADDomain -Server $candidate @common
            return $true
        }
        catch {
            Write-Verbose "Domain controller '$candidate' did not answer: $($_.Exception.Message)"
            return $false
        }
    }

    if ($Server) {
        if (& $answers $Server) { return $Server }
        throw ("The domain controller '$Server' did not answer. It was named explicitly, so no " +
            'other one was tried; omit -Server to let the provider choose one that is running.')
    }

    # Whatever discovery currently believes, so the common case costs one call and the
    # candidate list below is only built when that fails.
    $candidates = [System.Collections.Generic.List[string]]::new()
    try {
        $discovered = Get-ADDomain @common
        if ($discovered.PDCEmulator) { $candidates.Add([string]$discovered.PDCEmulator) }
        foreach ($name in @($discovered.ReplicaDirectoryServers) + @($discovered.ReadOnlyReplicaDirectoryServers)) {
            if ($name -and -not $candidates.Contains([string]$name)) { $candidates.Add([string]$name) }
        }
    }
    catch {
        Write-Verbose "Discovery did not answer, so the domain controllers are read from DNS: $($_.Exception.Message)"
    }

    # Discovery itself failed, which is the case this function exists for. Ask DNS directly
    # for the domain controllers and try them one at a time.
    if ($candidates.Count -eq 0) {
        $domainName = $env:USERDNSDOMAIN
        if ($domainName) {
            try {
                $records = Resolve-DnsName -Name "_ldap._tcp.dc._msdcs.$domainName" -Type SRV -ErrorAction Stop
                foreach ($record in @($records | Where-Object { $_.NameTarget })) {
                    $name = [string]$record.NameTarget
                    if ($name -and -not $candidates.Contains($name)) { $candidates.Add($name) }
                }
            }
            catch {
                Write-Verbose "Could not read the domain controller records from DNS: $($_.Exception.Message)"
            }
        }
    }

    # Insertion order, not alphabetical: the PDC emulator was put first on purpose.
    foreach ($candidate in $candidates) {
        if (& $answers $candidate) {
            Write-Verbose "Using domain controller '$candidate'"
            return $candidate
        }
    }

    if ($candidates.Count -eq 0) {
        throw ('No domain controller could be found at all. Check that this host is domain joined ' +
            'and that DNS resolves the domain.')
    }

    throw ("None of the $($candidates.Count) domain controller(s) the domain knows about answered: " +
        "$($candidates -join ', '). One of them may be registered in DNS but switched off; start it, " +
        'or remove its registration, or name a working one with -Server.')
}