Public/Get-ADLocalDC.ps1

Function Get-ADLocalDC() {
    <#
        .SYNOPSIS
        This function will attempt to return an available domain controller in the same Active Directory site as the computer calling the function.
 
        It provides functionality to use either .net calls (default) or the ActiveDirectory module for Powershell cmdlet to validate the availability of the Domain Controller
 
        IMPORTANT: This function relies on the function Get-ADComputerSite which is available in this module
 
        .PARAMETER UsePsAdCommand
        Optional. Switch. Default = false. This will use the PowerShell module for ActiveDirectory command "Get-ADDomain" to validate the domain controller availability. This is useful for testing AD WebServices availability. If this parameter is not specified the domain controller will be validated using the .net System.DirectoryServices.DirectoryEntry class.
 
        .PARAMETER Verbose
        Optional. This will print the steps taken to discover the DC in the local site
 
        .DESCRIPTION
        This function uses the .net class System.DirectoryServices.ActiveDirectory.DomainController to discover the domain controllers in the AD site. Each DC is tested in turn until a valid Domain Controller has been discovered.
 
        .INPUTS
        See parameters.
 
        .OUTPUTS
        This function will return a string containing an available domain controller located in the same AD site as the computer calling the function.
 
        .EXAMPLE
        Import-Module ps_Module_ActiveDirectoryTools
 
        $sDomainController = Get-ADLocalDC
 
        .NOTES
        MVogwell - October 2022 - Version 1.0
 
        Version history:
            1.0 - Initial tested release
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$false)][switch]$UsePsAdCommand
    )

    # dotsource private functions required for operation
    $sPathADDomainControllerDirectoryEntry = ($PSScriptRoot).Replace("Public","Private") + "\Get-ADDomainControllerDirectoryEntry.ps1"
    $sPathADDomainControllersBySite = ($PSScriptRoot).Replace("Public","Private") + "\Get-ADDomainControllersBySite.ps1"

    Write-Verbose $sPathADDomainControllerDirectoryEntry
    Write-Verbose $sPathADDomainControllersBySite

    . $sPathADDomainControllersBySite
    . $sPathADDomainControllerDirectoryEntry

    $ErrorActionPreference = "Stop"

    # This function attempts to discover an available domain controller in the local AD site
    # The UsePsADCommand

    $sRtnDcName = ""

    # Get local AD site name - call function to get this. This could trigger an error
    $sAdSiteName = Get-ADComputerSite

    # Search for the domain controllers on the local site
    $arrDomainControllers = Get-ADDomainControllersBySite -sADSiteName $sAdSiteName

    # Confirm at least one DC has been returned
    if ($null -eq $arrDomainControllers) {
        throw "Failed to discover any domain controllers"
    }

    # Test the availability of the domain controllers
    foreach ($objDc in $arrDomainControllers) {
        Write-Verbose "`t=== Testing DC: $($objDc.Name)"

        $objAd = $null

        try {
            if ($UsePsAdCommand -eq $true) {
                $objAd = Get-ADDomain -Server $objDc.Name -ErrorAction "Stop"

                # If the search was successful
                if (!($null -eq $objAD)) {
                    $sRtnDcName = $objDc.name

                    Write-Verbose "`t`t+++ Successfully tested $($objDc.Name)"

                    break   # escape the AD loop
                }
                else {
                    Write-Verbose "`t`t--- DC $($objDc.Name) did not respond to Get-ADDomain"
                }
            }
            else {
                # Validate the domain controller availabl
                $sDirectoryEntryPath = "LDAP://" + $objDc.Name + "/RootDSE"

                # Call a function (in private in this module) to request RootDSE directory entry info from
                # the specified domain controller - this proves whether the DC is avaialble
                $objAd = Get-ADDomainControllerDirectoryEntry -sDirectoryEntryPath $sDirectoryEntryPath

                if (!($null -eq $objAd)) {
                    if ((($objAd | Get-Member -MemberType Properties | Select-Object -ExpandProperty name) -contains "defaultNamingContext") -eq $true) {
                        $sRtnDcName = $objDc.name

                        Write-Verbose "`t`t+++ SUCCESS: DC '$($objDc.Name)' has been validated"

                        break   # escape the AD loop
                    }
                    else {
                        Write-Verbose "`t`t--- DC '$($objDc.Name)' did not respond with a valid defaultNamingContext value"
                    }
                }
                else {
                    Write-Verbose "`t`t--- DC '$($objDc.Name)' did not response with any valid data"
                }

                Remove-Variable sDirectoryEntryPath,objAd -ErrorAction "SilentlyContinue"
            }
        }
        catch {
            $objAd = $null

            $sErrMsg = ("Domain Controller '$($objDc.Name)' not available. Error: " + (($Global:Error[0].exception).toString()).replace("`r"," ").replace("`n"," "))
            Write-Verbose "`t`t--- $sErrMsg"

            throw $sErrMsg
        }
    }

    # Check that a DC was discovered - throw error if not found
    if ([string]::IsNullOrEmpty($sRtnDcName) -eq $true) {
        throw "Failed to discover an available Domain Controller in the local site"
    }

    return $sRtnDcName
}