Domain Replication Health

I have been doing a fair amount of work upgrading/removing/building domain controllers for a client, and wanted an easy way to check replication health and topology, and came up with this script.

It has a GUI and allows for export of results to CSV

cls
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$Form1 = New-Object -TypeName System.Windows.Forms.Form
[System.Windows.Forms.DataGridView]$DataGridView1 = $null
[System.Windows.Forms.Button]$Button1 = $null
[System.Windows.Forms.Button]$ButtonExport = $null
[System.Windows.Forms.ProgressBar]$ProgressBar1 = $null
[System.Windows.Forms.StatusStrip]$StatusStrip1 = $null
[System.Windows.Forms.ToolStripStatusLabel]$ToolStripStatusLabel1 = $null

function InitializeComponent {
    $DataGridView1 = (New-Object -TypeName System.Windows.Forms.DataGridView)
    $Button1 = (New-Object -TypeName System.Windows.Forms.Button)
    $ButtonExport = (New-Object -TypeName System.Windows.Forms.Button)
    $ProgressBar1 = (New-Object -TypeName System.Windows.Forms.ProgressBar)
    $StatusStrip1 = (New-Object -TypeName System.Windows.Forms.StatusStrip)
    $ToolStripStatusLabel1 = (New-Object -TypeName System.Windows.Forms.ToolStripStatusLabel)

    ([System.ComponentModel.ISupportInitialize]$DataGridView1).BeginInit()
    $StatusStrip1.SuspendLayout()
    $Form1.SuspendLayout()

    # DataGridView1
    $DataGridView1.Anchor = [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
    $DataGridView1.ColumnHeadersHeightSizeMode = [System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode]::AutoSize
    $DataGridView1.Location = New-Object -TypeName System.Drawing.Point -ArgumentList 12, 12
    $DataGridView1.Name = "DataGridView1"
    $DataGridView1.RowHeadersWidthSizeMode = [System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode]::AutoSizeToAllHeaders
    $DataGridView1.RowTemplate.Height = 24
    $DataGridView1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList 826, 403
    $DataGridView1.TabIndex = 0

    # Button1
    $Button1.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right
    $Button1.Location = New-Object -TypeName System.Drawing.Point -ArgumentList 763, 421
    $Button1.Name = "Button1"
    $Button1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList 75, 32
    $Button1.TabIndex = 1
    $Button1.Text = "Check"
    $Button1.UseVisualStyleBackColor = $true
    $Button1.Add_Click($button1_Click)

    # ButtonExport
    $ButtonExport.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right
    $ButtonExport.Location = New-Object -TypeName System.Drawing.Point -ArgumentList 672, 421
    $ButtonExport.Name = "ButtonExport"
    $ButtonExport.Size = New-Object -TypeName System.Drawing.Size -ArgumentList 85, 32
    $ButtonExport.TabIndex = 2
    $ButtonExport.Text = "Export"
    $ButtonExport.UseVisualStyleBackColor = $true
    $ButtonExport.Add_Click($buttonExport_Click)

    # ProgressBar1
    $ProgressBar1.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left -bor [System.Windows.Forms.AnchorStyles]::Right
    $ProgressBar1.Location = New-Object -TypeName System.Drawing.Point -ArgumentList 12, 459
    $ProgressBar1.Name = "ProgressBar1"
    $ProgressBar1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList 826, 23
    $ProgressBar1.TabIndex = 2

    # StatusStrip1
    $StatusStrip1.Items.AddRange(@($ToolStripStatusLabel1))
    $StatusStrip1.Location = New-Object -TypeName System.Drawing.Point -ArgumentList 0, 506
    $StatusStrip1.Name = "StatusStrip1"
    $StatusStrip1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList 850, 25
    $StatusStrip1.TabIndex = 3
    $StatusStrip1.Text = "StatusStrip1"

    # ToolStripStatusLabel1
    $ToolStripStatusLabel1.Name = "ToolStripStatusLabel1"
    $ToolStripStatusLabel1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList 153, 20
    $ToolStripStatusLabel1.Text = "Ready"

    # Form1
    $Form1.ClientSize = New-Object -TypeName System.Drawing.Size -ArgumentList 850, 531
    $Form1.Controls.Add($StatusStrip1)
    $Form1.Controls.Add($ProgressBar1)
    $Form1.Controls.Add($Button1)
    $Form1.Controls.Add($ButtonExport)
    $Form1.Controls.Add($DataGridView1)
    $Form1.Text = "AD Replication Health Checker"
    ([System.ComponentModel.ISupportInitialize]$DataGridView1).EndInit()
    $StatusStrip1.ResumeLayout($false)
    $Form1.ResumeLayout($false)
    $Form1.PerformLayout()

    Add-Member -InputObject $Form1 -Name DataGridView1 -Value $DataGridView1 -MemberType NoteProperty
    Add-Member -InputObject $Form1 -Name Button1 -Value $Button1 -MemberType NoteProperty
    Add-Member -InputObject $Form1 -Name ButtonExport -Value $ButtonExport -MemberType NoteProperty
    Add-Member -InputObject $Form1 -Name ProgressBar1 -Value $ProgressBar1 -MemberType NoteProperty
    Add-Member -InputObject $Form1 -Name StatusStrip1 -Value $StatusStrip1 -MemberType NoteProperty
    Add-Member -InputObject $Form1 -Name ToolStripStatusLabel1 -Value $ToolStripStatusLabel1 -MemberType NoteProperty
}

function Export-ResultsToCSV {
    $saveFileDialog = New-Object -TypeName System.Windows.Forms.SaveFileDialog
    $saveFileDialog.Filter = "CSV Files (*.csv)|*.csv"

    $saveFileDialog.Title = "Export Results"

    if ($saveFileDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
        $filePath = $saveFileDialog.FileName
        $dataGridView1.DataSource | Export-Csv -Path $filePath -NoTypeInformation
        Write-Host "Results exported successfully to: $filePath"
    }
}

$button1_Click = {
    ## Active Directory Domain Controller Replication Status##
    Import-Module ActiveDirectory

    $domainControllers = Get-ADDomainController -Filter * | Select-Object -ExpandProperty Name
    $total = $domainControllers.Count
    $i = 0

    # Create a DataTable to store the reports
    $reportsTable = New-Object System.Data.DataTable
    $reportsTable.Columns.Add("Name") | Out-Null
    $reportsTable.Columns.Add("ReplicationPartners") | Out-Null
    $reportsTable.Columns.Add("LastReplication") | Out-Null
    $reportsTable.Columns.Add("FailureCount") | Out-Null
    $reportsTable.Columns.Add("FailureType") | Out-Null
    $reportsTable.Columns.Add("FirstFailure") | Out-Null
    $reportsTable.Columns.Add("Status") | Out-Null
    $reportsTable.Columns.Add("CurrentTime") | Out-Null
    $reportsTable.Columns.Add("TimeZone") | Out-Null

    foreach ($dc in $domainControllers) {
        $i++
        ## Progress ##
        $progressBar1.Value = ($i / $total) * 100
        $toolStripStatusLabel1.Text = "Checking $($dc)..."
        $Form1.Refresh()

        ## Define Row ##
        $reportRow = $reportsTable.NewRow()

        ## Check if AD WS is reachable ##
        $tcpClient = New-Object System.Net.Sockets.TcpClient
        $connect = $tcpClient.BeginConnect($dc, 9389, $null, $null)
        $success = $connect.AsyncWaitHandle.WaitOne(5000, $true)  ## Wait 5 seconds

        if ($success) {
            $tcpClient.EndConnect($connect)
            ## Replication Partners ##
            $replicationPartners = (Get-ADReplicationPartnerMetadata -Target $dc).Partner -replace '^.+?,CN=([^,]+).+$', '$1'
            $reportRow["ReplicationPartners"] = if ($replicationPartners) { $replicationPartners -join ", " } else { "" }
            ## Last Replication ##
            $LastReplication = (Get-ADReplicationPartnerMetadata -Target $dc).LastReplicationSuccess
            $reportRow["LastReplication"] = if ($LastReplication -ne $null) { $LastReplication -join ", " } else { "" }

            $replicationFailures = Get-ADReplicationFailure -Target $dc
            $reportRow["FailureCount"] = if ($FailureCount) { $FailureCount.ToString() } else { "" }
            $reportRow["FailureType"] = if ($replicationFailures) { $replicationFailures.FailureType -join ", " } else { "" }
            $firstFailure = if ($replicationFailures) { $replicationFailures | Select-Object -ExpandProperty FirstFailureTime }
            $reportRow["FirstFailure"] = if ($firstFailure -ne $null) { $firstFailure -join ", " } else { "" }
            $reportRow["Status"] = "Reachable"

            ## Get Current Time and Timezone ##
            try {
                $currentTime = Get-CimInstance -ClassName Win32_LocalTime -ComputerName $dc -ErrorAction Stop -TimeoutSec 5 | Select-Object -ExpandProperty LocalDateTime
                $timeZone = Get-CimInstance -ClassName Win32_TimeZone -ComputerName $dc -ErrorAction Stop -TimeoutSec 5 | Select-Object -ExpandProperty StandardName
                if (-not $currentTime) {
                    throw "Error"
                }
                $reportRow["CurrentTime"] = $currentTime.ToString("yyyy-MM-dd HH:mm:ss")
                $reportRow["TimeZone"] = $timeZone
            }
           catch {
    try {
        $currentTime = Invoke-Command -ComputerName $dc -ScriptBlock { Get-Date }
        $timeZone = Invoke-Command -ComputerName $dc -ScriptBlock { tzutil /g }
        
        if ($currentTime -and $timeZone) {
            $reportRow["CurrentTime"] = $currentTime.ToString("yyyy-MM-dd HH:mm:ss")
            $reportRow["TimeZone"] = $timeZone.Trim()
        } else {
            $reportRow["CurrentTime"] = "N/A"
            $reportRow["TimeZone"] = "N/A"
        }
    }
    catch {
        $reportRow["CurrentTime"] = "N/A"
        $reportRow["TimeZone"] = "N/A"
    }
}



        }
        else {
            $reportRow["Status"] = "Unreachable"
            $tcpClient.Close()
        }

        ## Set the Name value ##
        $reportRow["Name"] = $dc

        ## Add the row to the DataTable ##
        $reportsTable.Rows.Add($reportRow)

        ## Update the DataGridView data source and refresh it ##
        $dataGridView1.DataSource = $reportsTable
        $dataGridView1.Refresh()
    }

    $toolStripStatusLabel1.Text = "Ready"
    $progressBar1.Value = 0
}

$buttonExport_Click = {
    Export-ResultsToCSV
}

. InitializeComponent

# Show the form
$Form1.ShowDialog()

This requires powershell remoting to be enabled, and a powershell version of 5.1 or above

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.