Public/activedirectory/Get-ADReplicationStatus.ps1

#Requires -Version 5.1
function Get-ADReplicationStatus {
    <#
    .SYNOPSIS
        Retrieves Active Directory replication status for one or more domain controllers
 
    .DESCRIPTION
        Queries replication partner metadata and failure information for Active Directory
        domain controllers. When no Server is specified, automatically discovers all DCs
        in the current domain via Get-ADDomainController. Returns one object per DC/partner/
        partition combination with health status derived from replication result codes and
        consecutive failure counts.
 
    .PARAMETER Server
        One or more domain controller names or FQDNs to query. When omitted, all domain
        controllers in the current domain are discovered automatically.
        Accepts pipeline input by value and by property name.
 
    .PARAMETER Credential
        The PSCredential object used to authenticate against Active Directory.
 
    .EXAMPLE
        Get-ADReplicationStatus
 
        Discovers all domain controllers and returns replication status for each.
 
    .EXAMPLE
        Get-ADReplicationStatus -Server 'DC01.contoso.com'
 
        Returns replication status for a specific domain controller.
 
    .EXAMPLE
        'DC01', 'DC02' | Get-ADReplicationStatus -Credential (Get-Credential)
 
        Returns replication status for multiple DCs via pipeline with alternate credentials.
 
    .OUTPUTS
        PSWinOps.ADReplicationStatus
        Returns objects with Server, Partner, Partition, LastAttempt, LastSuccess,
        LastResult, ConsecutiveFailures, Status, and Timestamp properties.
 
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-04-04
        Requires: PowerShell 5.1+ / Windows only
        Requires: ActiveDirectory module (RSAT)
        Requires: Domain Admin or equivalent for cross-DC queries
 
    .LINK
        https://github.com/k9fr4n/PSWinOps
 
    .LINK
        https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-adreplicationpartnermetadata
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('ComputerName', 'HostName')]
        [ValidateNotNullOrEmpty()]
        [string[]]$Server,

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

    begin {
        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Starting"

        $moduleAvailable = $false
        try {
            Import-Module -Name 'ActiveDirectory' -ErrorAction Stop -Verbose:$false
            $moduleAvailable = $true
        }
        catch {
            Write-Error -Message "[$($MyInvocation.MyCommand)] ActiveDirectory module is not available: $_"
        }

        $adSplat = @{}
        if ($PSBoundParameters.ContainsKey('Credential')) {
            $adSplat['Credential'] = $Credential
        }

        $timestamp = Get-Date -Format 'o'
        $dcList = [System.Collections.Generic.List[string]]::new()
        $serverProvided = $false
    }

    process {
        if (-not $moduleAvailable) { return }

        if ($PSBoundParameters.ContainsKey('Server')) {
            $serverProvided = $true
            foreach ($dc in $Server) {
                $dcList.Add($dc)
            }
        }
    }

    end {
        if (-not $moduleAvailable) { return }

        if (-not $serverProvided) {
            Write-Verbose -Message "[$($MyInvocation.MyCommand)] No Server specified, discovering domain controllers"
            try {
                $discoveredDCs = Get-ADDomainController -Filter * -ErrorAction Stop @adSplat
                foreach ($dc in $discoveredDCs) {
                    $dcList.Add($dc.HostName)
                }
                Write-Verbose -Message "[$($MyInvocation.MyCommand)] Discovered $($dcList.Count) domain controller(s)"
            }
            catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed to discover domain controllers: $_"
                return
            }
        }

        if ($dcList.Count -eq 0) {
            Write-Warning -Message "[$($MyInvocation.MyCommand)] No domain controllers to query"
            return
        }

        foreach ($targetDC in $dcList) {
            try {
                Write-Verbose -Message "[$($MyInvocation.MyCommand)] Querying replication metadata on $targetDC"

                $replPartners = Get-ADReplicationPartnerMetadata -Target $targetDC -ErrorAction Stop @adSplat

                if (-not $replPartners) {
                    Write-Warning -Message "[$($MyInvocation.MyCommand)] No replication partners found for $targetDC"
                    continue
                }

                $failureMap = @{}
                try {
                    $failures = Get-ADReplicationFailure -Target $targetDC -ErrorAction Stop @adSplat
                    foreach ($failure in $failures) {
                        $failureMap[$failure.Partner] = $failure
                    }
                }
                catch {
                    Write-Verbose -Message "[$($MyInvocation.MyCommand)] Could not retrieve failure data for ${targetDC}: $_"
                }

                foreach ($partner in $replPartners) {
                    $partnerName = if ($partner.Partner) {
                        ($partner.Partner -split ',')[0] -replace '^CN='
                    }
                    else {
                        'Unknown'
                    }

                    $partitionShort = if ($partner.Partition) {
                        ($partner.Partition -split ',')[0] -replace '^DC=|^CN='
                    }
                    else {
                        'Unknown'
                    }

                    $consecutiveFailures = $partner.ConsecutiveReplicationFailures
                    $lastResult = $partner.LastReplicationResult

                    $status = if ($lastResult -eq 0 -and $consecutiveFailures -eq 0) {
                        'Healthy'
                    }
                    elseif ($consecutiveFailures -gt 0 -and $consecutiveFailures -le 5) {
                        'Warning'
                    }
                    else {
                        'Critical'
                    }

                    [PSCustomObject]@{
                        PSTypeName          = 'PSWinOps.ADReplicationStatus'
                        Server              = $targetDC
                        Partner             = $partnerName
                        Partition           = $partitionShort
                        PartitionDN         = $partner.Partition
                        LastAttempt         = $partner.LastReplicationAttempt
                        LastSuccess         = $partner.LastReplicationSuccess
                        LastResult          = $lastResult
                        ConsecutiveFailures = $consecutiveFailures
                        Status              = $status
                        Timestamp           = $timestamp
                    }
                }
            }
            catch {
                Write-Error -Message "[$($MyInvocation.MyCommand)] Failed to query replication on ${targetDC}: $_"
            }
        }

        Write-Verbose -Message "[$($MyInvocation.MyCommand)] Completed"
    }
}