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