Functions/Test-ADDomainControllerReplication.ps1

<#
    .SYNOPSIS
    Replication test of Active Directory Domain Controllers by using the
    build-in and trusted "repadmin.exe" command line tool.

    .DESCRIPTION
    This function uses Windows PowerShell Remoting to connect to all specified
    Active Directory Domain Controllers. Inside the session, repadmin is
    executed with the /showrepl and /csv parameter. The result is then parsed
    into Windows PowerShell custom objects.

    .PARAMETER ComputerName
    The list of Domain Controllers to test. Aliases are defined to accept
    pipeline input from the result of the Active Directory built-in cmdlets.

    .PARAMETER Credential
    Optionally, provide credentials to create the PowerShell Remoting session.

    .INPUTS
    System.String. You can pipe an array of strings representing the computer names.

    .OUTPUTS
    ActiveDirectoryFever.ReplicationResult. A collection of result objects. One object per test.

    .EXAMPLE
    C:\> Test-ADDomainControllerReplication -ComputerName LON-DC1.contoso.com
    Run the replication test against one Domain Controller.

    .EXAMPLE
    C:\> Test-ADDomainControllerReplication -ComputerName LON-DC1.contoso.com -Credential (Get-Credential)
    Run the replication test against one Domain Controller by using custom credentials.

    .EXAMPLE
    C:\> Test-ADDomainControllerReplication -ComputerName LON-DC1, LON-DC2 | Where-Object { -not $_.Result }
    Run the replication test against two Domain Controllers and filter failed tests.

    .EXAMPLE
    C:\> Get-ADDomainController | Test-ADDomainControllerReplication
    Run the replication test on the current enumerated Domain Controller.

    .EXAMPLE
    C:\> Get-ADDomain -Identity "corp.contoso.com" | Test-ADDomainControllerReplication
    Run the replication test on all writable and read-only Domain Controllers in the "corp.contoso.com" domain.

    .EXAMPLE
    C:\> Get-ADForest -Identity "contoso.com" | Test-ADDomainControllerReplication
    Run the replication test on all Global Catalog Domain Controllers in the "contoso.com" forest.

    .NOTES
    Author : Claudio Spizzi
    License : MIT License

    .LINK
    https://github.com/claudiospizzi/ActiveDirectoryFever
#>


function Test-ADDomainControllerReplication
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [Alias('HostName', 'DNSHostName', 'ReplicaDirectoryServers', 'GlobalCatalogs')]
        [String[]] $ComputerName,

        [Parameter(Mandatory=$false)]
        [PSCredential] $Credential = $null
    )

    begin
    {
        $Jobs = @()

        # Define the alternate credentials hash table
        $CredentialParameter = @{}
        if($Credential -ne $null) { $CredentialParameter['Credential'] = $Credential }
    }

    process
    {
        # For each provided computer name, use it as target to start the replsum
        # command inside a remoting session and as a job. This allows parallel
        # executing of all replsum commands.
        foreach ($ComputerNameTarget in $ComputerName)
        {
            Write-Verbose -Message "Start Job to execute 'repadmin.exe /showrepl /csv' on $ComputerNameTarget with PowerShell Remoting..."

            $Jobs += Invoke-Command -ComputerName $ComputerNameTarget -ScriptBlock { Write-Output (repadmin.exe /showrepl /csv) } @CredentialParameter -AsJob -JobName $ComputerNameTarget
        }
    }

    end
    {
        Write-Verbose -Message 'Wait for all Jobs to complete.'

        # Wait for all jobs to complete
        $Jobs = $Jobs | Wait-Job

        # Iterate all child jobs (one child job per domain controller)
        foreach ($Job in $Jobs)
        {
            try
            {
                if ($Job.State -eq 'Completed')
                {
                    # Fill name and domain with location
                    $Name   = $Job.Name.Split('.', 2)[0]
                    $Domain = try { $Job.Name.Split('.', 2)[1] } catch { '' }

                    # Receive result (and error, if occured)
                    $Result = $Job | Receive-Job -ErrorAction Stop

                    # Check if the result is empty
                    if ($Result -ne $null)
                    {
                        # Iterating all objects
                        foreach ($ChildResult in ($Result | ConvertFrom-Csv))
                        {
                            # Calculate last execution time
                            $LastTime = [DateTime]::MinValue
                            if (($ChildResult.'Last Failure Time' -ne '0') -and ($LastTime -lt ($LastFailureTime = [DateTime] $ChildResult.'Last Failure Time')))
                            {
                                $LastTime = $LastFailureTime
                            }
                            if (($ChildResult.'Last Success Time' -ne '0') -and ($LastTime -lt ($LastSuccessTime = [DateTime] $ChildResult.'Last Success Time')))
                            {
                                $LastTime = $LastSuccessTime
                            }

                            # Create a custom result object with custom type
                            $ReplicationResult = New-Object -TypeName PSObject -Property @{
                                Name           = $Name
                                Domain         = $Domain
                                NamingContext  = $ChildResult.'Naming Context'
                                SourceSite     = $ChildResult.'Source DSA Site'
                                SourceServer   = $ChildResult.'Source DSA'
                                TargetSite     = $ChildResult.'Destination DSA Site'
                                TargetServer   = $ChildResult.'Destination DSA'
                                ExecutionTime  = $LastTime
                                TransportType  = $ChildResult.'Transport Type'
                                FailureCount   = $ChildResult.'Number of Failures'
                                FailureStatus  = $ChildResult.'Last Failure Status'
                                Result         = $ChildResult.'Number of Failures' -eq '0'
                            }
                            $ReplicationResult.PSTypeNames.Insert(0, 'ActiveDirectoryFever.ReplicationResult')

                            Write-Output $ReplicationResult
                        }
                    }
                    else
                    {
                        Write-Warning "Replication test on $($Job.Location) does not return any data. This is expected, if the forest does only consist of one Domain Controller."
                    }
                }
                else
                {
                    $Job | Receive-Job -ErrorAction Stop | Out-Null
                }
            }
            catch
            {
                Write-Error -Message "Failed to execute diagnistic test.`r`n$_" -Exception $_.Exception -TargetObject $Job.Location
            }
        }

        # Cleanup jobs
        $Job | Remove-Job -ErrorAction SilentlyContinue
    }
}