Microsoft Active Directory Replication
You can use repladmin to get information. I'll edit this to show that later.
For now, my gift to the internet is a script to get a matrix of latencies in replication and a quick-reference for FSMO assignments.
The result is something that looks like this:

Here's the full script:
<#
.SYNOPSIS
Shows Active Directory replication status as source/destination matrices.
.DESCRIPTION
Uses Get-ADReplicationPartnerMetadata to collect replication metadata,
then builds:
- One intra-site matrix per AD site
- One inter-site matrix showing only DCs participating in site-to-site replication
- FSMO role holder summary
Site-specific grids:
Rows = Destination DCs in the site
Columns = Source DCs in the site
Labels = DC display names only
Inter-site grid:
Rows = All DCs participating in inter-site replication
Columns = All DCs participating in inter-site replication
Labels = Site\DC display names
Cell = Time since last successful sync.
.NOTES
Requires:
- RSAT Active Directory PowerShell module
- Domain permissions to query replication metadata
.EXAMPLE
.\Show-ADReplicationMatrix.ps1
.EXAMPLE
.\Show-ADReplicationMatrix.ps1 -ShowDetails
.EXAMPLE
.\Show-ADReplicationMatrix.ps1 -ExportCsv C:\Temp\ADReplicationMatrix.csv
.EXAMPLE
.\Show-ADReplicationMatrix.ps1 -IncludeNamingContext "DC=bwicompanies,DC=com"
#>
param(
[string]$IncludeNamingContext,
[string]$ExportCsv,
[int]$WarningMinutes = 20,
[int]$CriticalMinutes = 61,
[switch]$ShowDetails
)
# ------------------------------------------------------------
# Optional display name mappings.
# If a value is not mapped, the raw value is displayed.
# ------------------------------------------------------------
$SiteNameMap = @{
"COR" = "Corporate"
}
$DcNameMap = @{
}
# ------------------------------------------------------------
# Helper functions
# ------------------------------------------------------------
function Get-ShortName {
param(
[AllowNull()]
[AllowEmptyString()]
[string]$Name
)
if ([string]::IsNullOrWhiteSpace($Name)) {
return ""
}
return (($Name.Trim() -split "\.")[0]).ToUpper()
}
function Get-SourceDcNameFromPartner {
param(
[AllowNull()]
[AllowEmptyString()]
[string]$Partner
)
if ([string]::IsNullOrWhiteSpace($Partner)) {
return ""
}
# Typical format:
# CN=NTDS Settings,CN=DC1,CN=Servers,CN=SITE,CN=Sites,CN=Configuration,...
if ($Partner -match 'CN=NTDS Settings,CN=([^,]+),CN=Servers') {
return $matches[1].ToUpper()
}
# Sometimes it may be a server DN directly.
if ($Partner -match 'CN=([^,]+),CN=Servers') {
return $matches[1].ToUpper()
}
# Fallback for FQDN-ish values.
return Get-ShortName -Name $Partner
}
function Get-SiteNameFromPartner {
param(
[AllowNull()]
[AllowEmptyString()]
[string]$Partner
)
if ([string]::IsNullOrWhiteSpace($Partner)) {
return ""
}
# Typical format:
# CN=NTDS Settings,CN=DC1,CN=Servers,CN=SITE,CN=Sites,CN=Configuration,...
if ($Partner -match 'CN=Servers,CN=([^,]+),CN=Sites') {
return $matches[1]
}
return ""
}
function Resolve-DisplayName {
param(
[AllowNull()]
[AllowEmptyString()]
[string]$Value,
[hashtable]$Map
)
if ([string]::IsNullOrWhiteSpace($Value)) {
return ""
}
if ($Map -and $Map.ContainsKey($Value)) {
return $Map[$Value]
}
return $Value
}
function Get-DcDisplayName {
param(
[string]$Name
)
return Resolve-DisplayName -Value $Name -Map $DcNameMap
}
function Get-SiteDisplayName {
param(
[string]$Site
)
return Resolve-DisplayName -Value $Site -Map $SiteNameMap
}
function Get-FullDisplayName {
param(
[AllowNull()]
[AllowEmptyString()]
[string]$Site,
[string]$Name
)
$dcDisplay = Get-DcDisplayName -Name $Name
if ([string]::IsNullOrWhiteSpace($Site)) {
return $dcDisplay
}
$siteDisplay = Get-SiteDisplayName -Site $Site
return "$siteDisplay\$dcDisplay"
}
function Convert-ToElapsedLabel {
param(
[AllowNull()]
[datetime]$LastSuccess
)
if ($null -eq $LastSuccess -or $LastSuccess -eq [datetime]::MinValue) {
return "Never"
}
$span = New-TimeSpan -Start $LastSuccess -End (Get-Date)
if ($span.TotalDays -ge 1) {
return "{0}d {1}h" -f [math]::Floor($span.TotalDays), $span.Hours
}
if ($span.TotalHours -ge 1) {
return "{0}h {1}m" -f [math]::Floor($span.TotalHours), $span.Minutes
}
return "{0}m" -f [math]::Max(0, [math]::Floor($span.TotalMinutes))
}
function Write-ColoredCell {
param(
[string]$Text,
$AgeMinutes,
[int]$Width,
[int]$WarningMinutes,
[int]$CriticalMinutes
)
if ([string]::IsNullOrWhiteSpace($Text)) {
$Text = "."
}
if ($Text.Length -gt $Width) {
$Text = $Text.Substring(0, $Width)
}
$padded = $Text.PadRight($Width)
if ($null -eq $AgeMinutes) {
Write-Host $padded -NoNewline -ForegroundColor DarkGray
return
}
$age = [double]$AgeMinutes
if ($age -ge $CriticalMinutes) {
Write-Host $padded -NoNewline -ForegroundColor Red
}
elseif ($age -ge $WarningMinutes) {
Write-Host $padded -NoNewline -ForegroundColor Yellow
}
else {
Write-Host $padded -NoNewline -ForegroundColor Green
}
}
function Write-DcLabel {
param(
[string]$Text,
[int]$Width,
[bool]$IsGlobalCatalog,
[bool]$IsReadOnly
)
if ([string]::IsNullOrWhiteSpace($Text)) {
$Text = ""
}
if ($Text.Length -gt $Width) {
$Text = $Text.Substring(0, $Width)
}
$padded = $Text.PadRight($Width)
# Normal DCs = White
# GCs = Cyan
# RODCs = Gray
#
# If both GC and RODC, RODC wins because operationally that matters more.
$foregroundColor = "White"
if ($IsGlobalCatalog) {
$foregroundColor = "Cyan"
}
if ($IsReadOnly) {
$foregroundColor = "Gray"
}
Write-Host $padded -NoNewline -ForegroundColor $foregroundColor
}
function New-CellLookup {
param(
[array]$Summary
)
$lookup = @{}
foreach ($cell in $Summary) {
$key = "$($cell.Destination)|$($cell.Source)"
$lookup[$key] = $cell
}
return $lookup
}
function New-DcEndpoint {
param(
[string]$Site,
[string]$Name,
[hashtable]$DcLookup
)
$isGlobalCatalog = $false
$isReadOnly = $false
if ($DcLookup -and $DcLookup.ContainsKey($Name)) {
$isGlobalCatalog = [bool]$DcLookup[$Name].IsGlobalCatalog
$isReadOnly = [bool]$DcLookup[$Name].IsReadOnly
}
[pscustomobject]@{
Site = $Site
Name = $Name
IsGlobalCatalog = $isGlobalCatalog
IsReadOnly = $isReadOnly
}
}
function Get-EndpointDisplayName {
param(
[object]$Endpoint,
[switch]$HideSiteInLabels
)
if ($HideSiteInLabels) {
return Get-DcDisplayName -Name $Endpoint.Name
}
return Get-FullDisplayName -Site $Endpoint.Site -Name $Endpoint.Name
}
function Write-ReplicationGrid {
param(
[string]$Title,
[array]$Rows,
[array]$Columns,
[hashtable]$CellLookup,
[int]$WarningMinutes,
[int]$CriticalMinutes,
[switch]$HideSiteInLabels
)
if (-not $Rows -or -not $Columns) {
return
}
$maxRowLength = 0
foreach ($row in $Rows) {
$displayName = Get-EndpointDisplayName -Endpoint $row -HideSiteInLabels:$HideSiteInLabels
if ($displayName.Length -gt $maxRowLength) {
$maxRowLength = $displayName.Length
}
}
$maxColumnLength = 0
foreach ($column in $Columns) {
$displayName = Get-EndpointDisplayName -Endpoint $column -HideSiteInLabels:$HideSiteInLabels
if ($displayName.Length -gt $maxColumnLength) {
$maxColumnLength = $displayName.Length
}
}
$cellWidth = [math]::Max(12, [math]::Min(24, $maxColumnLength + 2))
$rowHeaderWidth = [math]::Max(20, $maxRowLength + 2)
Write-Host ""
Write-Host $Title -ForegroundColor Cyan
Write-Host "Rows = Destination DCs, Columns = Source DCs" -ForegroundColor DarkCyan
if ($HideSiteInLabels) {
Write-Host "Format = DC" -ForegroundColor DarkCyan
}
else {
Write-Host "Format = SITE\DC" -ForegroundColor DarkCyan
}
Write-Host "Cyan names = Global Catalogs, Gray names = RODCs" -ForegroundColor DarkCyan
Write-Host ""
# Header
Write-Host "".PadRight($rowHeaderWidth) -NoNewline
foreach ($column in $Columns) {
$header = Get-EndpointDisplayName -Endpoint $column -HideSiteInLabels:$HideSiteInLabels
Write-DcLabel `
-Text $header `
-Width $cellWidth `
-IsGlobalCatalog $column.IsGlobalCatalog `
-IsReadOnly $column.IsReadOnly
}
Write-Host ""
# Separator
$totalWidth = $rowHeaderWidth + ($Columns.Count * $cellWidth)
Write-Host ("".PadRight($totalWidth, "-")) -ForegroundColor DarkGray
# Rows
foreach ($row in $Rows) {
$rowName = Get-EndpointDisplayName -Endpoint $row -HideSiteInLabels:$HideSiteInLabels
Write-DcLabel `
-Text $rowName `
-Width $rowHeaderWidth `
-IsGlobalCatalog $row.IsGlobalCatalog `
-IsReadOnly $row.IsReadOnly
foreach ($column in $Columns) {
$key = "$($row.Name)|$($column.Name)"
if ($CellLookup.ContainsKey($key)) {
$cell = $CellLookup[$key]
Write-ColoredCell `
-Text $cell.Cell `
-AgeMinutes $cell.AgeMinutes `
-Width $cellWidth `
-WarningMinutes $WarningMinutes `
-CriticalMinutes $CriticalMinutes
}
else {
Write-ColoredCell `
-Text "." `
-AgeMinutes $null `
-Width $cellWidth `
-WarningMinutes $WarningMinutes `
-CriticalMinutes $CriticalMinutes
}
}
Write-Host ""
}
}
function Convert-SummaryToExportRows {
param(
[array]$Rows,
[array]$Columns,
[hashtable]$CellLookup,
[string]$GridName,
[switch]$HideSiteInLabels
)
foreach ($row in $Rows) {
$destinationDisplay = Get-EndpointDisplayName -Endpoint $row -HideSiteInLabels:$HideSiteInLabels
$obj = [ordered]@{
Grid = $GridName
DestinationSite = $row.Site
DestinationSiteName = Get-SiteDisplayName -Site $row.Site
Destination = $row.Name
DestinationName = $destinationDisplay
DestinationIsGlobalCatalog = $row.IsGlobalCatalog
DestinationIsReadOnly = $row.IsReadOnly
}
foreach ($column in $Columns) {
$sourceDisplay = Get-EndpointDisplayName -Endpoint $column -HideSiteInLabels:$HideSiteInLabels
$key = "$($row.Name)|$($column.Name)"
if ($CellLookup.ContainsKey($key)) {
$obj[$sourceDisplay] = $CellLookup[$key].Cell
}
else {
$obj[$sourceDisplay] = ""
}
}
[pscustomobject]$obj
}
}
function Write-FsmoRoleSummary {
param(
[hashtable]$DcLookup
)
Write-Host ""
Write-Host "FSMO Role Holders" -ForegroundColor Cyan
try {
$domain = Get-ADDomain -ErrorAction Stop
$forest = Get-ADForest -ErrorAction Stop
}
catch {
Write-Warning "Failed to retrieve FSMO role holders: $($_.Exception.Message)"
return
}
$roleRows = @(
[pscustomobject]@{
Scope = "Forest"
Role = "Schema Master"
Server = Get-ShortName -Name $forest.SchemaMaster
}
[pscustomobject]@{
Scope = "Forest"
Role = "Domain Naming Master"
Server = Get-ShortName -Name $forest.DomainNamingMaster
}
[pscustomobject]@{
Scope = "Domain"
Role = "PDC Emulator"
Server = Get-ShortName -Name $domain.PDCEmulator
}
[pscustomobject]@{
Scope = "Domain"
Role = "RID Master"
Server = Get-ShortName -Name $domain.RIDMaster
}
[pscustomobject]@{
Scope = "Domain"
Role = "Infrastructure Master"
Server = Get-ShortName -Name $domain.InfrastructureMaster
}
)
$outputRows = foreach ($row in $roleRows) {
$site = ""
$isGlobalCatalog = $false
$isReadOnly = $false
if ($DcLookup -and $DcLookup.ContainsKey($row.Server)) {
$site = $DcLookup[$row.Server].Site
$isGlobalCatalog = [bool]$DcLookup[$row.Server].IsGlobalCatalog
$isReadOnly = [bool]$DcLookup[$row.Server].IsReadOnly
}
[pscustomobject]@{
Scope = $row.Scope
Role = $row.Role
Site = Get-SiteDisplayName -Site $site
Server = Get-DcDisplayName -Name $row.Server
IsGlobalCatalog = $isGlobalCatalog
IsReadOnly = $isReadOnly
RawSite = $site
RawServer = $row.Server
}
}
$scopeWidth = 8
$roleWidth = 24
$siteWidth = 18
$serverWidth = 18
Write-Host ""
Write-Host "Scope".PadRight($scopeWidth) -NoNewline -ForegroundColor White
Write-Host "Role".PadRight($roleWidth) -NoNewline -ForegroundColor White
Write-Host "Site".PadRight($siteWidth) -NoNewline -ForegroundColor White
Write-Host "Server".PadRight($serverWidth) -NoNewline -ForegroundColor White
Write-Host ""
Write-Host ("".PadRight($scopeWidth + $roleWidth + $siteWidth + $serverWidth, "-")) -ForegroundColor DarkGray
foreach ($row in ($outputRows | Sort-Object Scope, Role)) {
Write-Host $row.Scope.PadRight($scopeWidth) -NoNewline -ForegroundColor White
Write-Host $row.Role.PadRight($roleWidth) -NoNewline -ForegroundColor White
Write-Host $row.Site.PadRight($siteWidth) -NoNewline -ForegroundColor White
Write-DcLabel `
-Text $row.Server `
-Width $serverWidth `
-IsGlobalCatalog $row.IsGlobalCatalog `
-IsReadOnly $row.IsReadOnly
Write-Host ""
}
}
# ------------------------------------------------------------
# Load AD module
# ------------------------------------------------------------
Write-Host ""
Write-Host "Loading Active Directory module..." -ForegroundColor Cyan
try {
Import-Module ActiveDirectory -ErrorAction Stop
}
catch {
Write-Error "Could not load the ActiveDirectory PowerShell module. Install RSAT AD DS tools or run from a domain controller."
exit 1
}
# ------------------------------------------------------------
# Get domain controllers
# ------------------------------------------------------------
Write-Host "Getting domain controllers..." -ForegroundColor Cyan
try {
$domainControllers = Get-ADDomainController -Filter * |
Sort-Object Site, Name
}
catch {
Write-Error "Failed to retrieve domain controllers. Error: $($_.Exception.Message)"
exit 1
}
if (-not $domainControllers) {
Write-Error "No domain controllers were found. Which would be impressive, but not useful."
exit 1
}
# Build lookup table:
# DC short name -> site/name/role info
$dcLookup = @{}
foreach ($dc in $domainControllers) {
$shortName = Get-ShortName -Name $dc.HostName
if ([string]::IsNullOrWhiteSpace($shortName)) {
$shortName = Get-ShortName -Name $dc.Name
}
if ([string]::IsNullOrWhiteSpace($shortName)) {
continue
}
$dcLookup[$shortName] = [pscustomobject]@{
Name = $shortName
HostName = $dc.HostName
Site = $dc.Site
IsGlobalCatalog = [bool]$dc.IsGlobalCatalog
IsReadOnly = [bool]$dc.IsReadOnly
}
}
# ------------------------------------------------------------
# Collect replication metadata
# ------------------------------------------------------------
Write-Host "Collecting replication partner metadata..." -ForegroundColor Cyan
$metadata = foreach ($dc in $domainControllers) {
try {
Get-ADReplicationPartnerMetadata `
-Target $dc.HostName `
-Scope Server `
-ErrorAction Stop
}
catch {
Write-Warning "Failed to query replication metadata from $($dc.Name): $($_.Exception.Message)"
}
}
if (-not $metadata) {
Write-Error "No replication metadata was returned."
exit 1
}
# ------------------------------------------------------------
# Normalize records
# ------------------------------------------------------------
$records = foreach ($item in $metadata) {
if ($IncludeNamingContext -and $item.Partition -notlike "*$IncludeNamingContext*") {
continue
}
$destination = Get-ShortName -Name $item.Server
$source = Get-SourceDcNameFromPartner -Partner $item.Partner
if ([string]::IsNullOrWhiteSpace($destination) -or
[string]::IsNullOrWhiteSpace($source)) {
continue
}
$destinationSite = ""
$sourceSite = ""
$destinationIsGlobalCatalog = $false
$destinationIsReadOnly = $false
$sourceIsGlobalCatalog = $false
$sourceIsReadOnly = $false
if ($dcLookup.ContainsKey($destination)) {
$destinationSite = $dcLookup[$destination].Site
$destinationIsGlobalCatalog = [bool]$dcLookup[$destination].IsGlobalCatalog
$destinationIsReadOnly = [bool]$dcLookup[$destination].IsReadOnly
}
if ($dcLookup.ContainsKey($source)) {
$sourceSite = $dcLookup[$source].Site
$sourceIsGlobalCatalog = [bool]$dcLookup[$source].IsGlobalCatalog
$sourceIsReadOnly = [bool]$dcLookup[$source].IsReadOnly
}
if ([string]::IsNullOrWhiteSpace($sourceSite)) {
$sourceSite = Get-SiteNameFromPartner -Partner $item.Partner
}
$lastSuccess = $item.LastReplicationSuccess
$ageMinutes = $null
if ($null -ne $lastSuccess -and $lastSuccess -ne [datetime]::MinValue) {
$ageMinutes = ((Get-Date) - $lastSuccess).TotalMinutes
}
[pscustomobject]@{
Destination = $destination
DestinationSite = $destinationSite
DestinationIsGlobalCatalog = $destinationIsGlobalCatalog
DestinationIsReadOnly = $destinationIsReadOnly
Source = $source
SourceSite = $sourceSite
SourceIsGlobalCatalog = $sourceIsGlobalCatalog
SourceIsReadOnly = $sourceIsReadOnly
NamingContext = $item.Partition
LastSuccess = $lastSuccess
LastAttempt = $item.LastReplicationAttempt
FailureCount = $item.ConsecutiveReplicationFailures
LastReplicationResult = $item.LastReplicationResult
AgeMinutes = $ageMinutes
AgeLabel = Convert-ToElapsedLabel -LastSuccess $lastSuccess
}
}
if (-not $records) {
Write-Warning "No replication records found after filtering."
exit 0
}
# ------------------------------------------------------------
# Pair summary
#
# Multiple naming contexts can exist per source/destination pair.
# Show the oldest valid success time for the pair.
# Only show Never if there are no valid success times at all.
# ------------------------------------------------------------
$pairSummary = $records |
Group-Object Destination, Source |
ForEach-Object {
$items = $_.Group
$validItems = $items |
Where-Object {
$null -ne $_.LastSuccess -and
$_.LastSuccess -ne [datetime]::MinValue
}
if ($validItems) {
$oldest = $validItems |
Sort-Object LastSuccess |
Select-Object -First 1
}
else {
$oldest = $items | Select-Object -First 1
}
$failureCount = ($items | Measure-Object FailureCount -Sum).Sum
if ($null -eq $failureCount) {
$failureCount = 0
}
$label = $oldest.AgeLabel
if ($failureCount -gt 0) {
$label = "$label!"
}
[pscustomobject]@{
Destination = $oldest.Destination
DestinationSite = $oldest.DestinationSite
DestinationIsGlobalCatalog = $oldest.DestinationIsGlobalCatalog
DestinationIsReadOnly = $oldest.DestinationIsReadOnly
Source = $oldest.Source
SourceSite = $oldest.SourceSite
SourceIsGlobalCatalog = $oldest.SourceIsGlobalCatalog
SourceIsReadOnly = $oldest.SourceIsReadOnly
Cell = $label
AgeMinutes = $oldest.AgeMinutes
FailureCount = $failureCount
OldestNamingContext = $oldest.NamingContext
LastSuccess = $oldest.LastSuccess
LastReplicationResult = $oldest.LastReplicationResult
}
}
$cellLookup = New-CellLookup -Summary $pairSummary
# ------------------------------------------------------------
# Output heading
# ------------------------------------------------------------
Write-Host ""
Write-Host "Active Directory Replication Matrix" -ForegroundColor Cyan
Write-Host "Cell = Time since last successful replication" -ForegroundColor DarkCyan
Write-Host "'!' = One or more consecutive failures reported for that source/destination pair" -ForegroundColor Yellow
Write-Host "Cyan names = Global Catalogs, Gray names = RODCs" -ForegroundColor DarkCyan
$allExportRows = @()
# ------------------------------------------------------------
# Per-site grids
# Only show intra-site replication:
# DestinationSite == SourceSite
# Site is not shown in row/column labels.
# ------------------------------------------------------------
$siteNames = $pairSummary |
Where-Object {
-not [string]::IsNullOrWhiteSpace($_.DestinationSite)
} |
Select-Object -ExpandProperty DestinationSite -Unique |
Sort-Object
foreach ($siteName in $siteNames) {
$siteSummary = $pairSummary |
Where-Object {
$_.DestinationSite -eq $siteName -and
$_.SourceSite -eq $siteName
}
if (-not $siteSummary) {
continue
}
$siteRows = $siteSummary |
ForEach-Object {
New-DcEndpoint `
-Site $_.DestinationSite `
-Name $_.Destination `
-DcLookup $dcLookup
} |
Sort-Object Site, Name -Unique
$siteColumns = $siteSummary |
ForEach-Object {
New-DcEndpoint `
-Site $_.SourceSite `
-Name $_.Source `
-DcLookup $dcLookup
} |
Sort-Object Site, Name -Unique
$siteDisplayName = Get-SiteDisplayName -Site $siteName
Write-ReplicationGrid `
-Title "Site Replication Matrix: $siteDisplayName" `
-Rows $siteRows `
-Columns $siteColumns `
-CellLookup $cellLookup `
-WarningMinutes $WarningMinutes `
-CriticalMinutes $CriticalMinutes `
-HideSiteInLabels
$allExportRows += Convert-SummaryToExportRows `
-Rows $siteRows `
-Columns $siteColumns `
-CellLookup $cellLookup `
-GridName "Site: $siteDisplayName" `
-HideSiteInLabels
}
# ------------------------------------------------------------
# Inter-site grid
#
# Only show pairs where source and destination sites differ.
#
# Rows and columns are both built from the UNION of all DCs participating
# in inter-site replication, regardless of whether a DC appeared only as
# a source or only as a destination.
# ------------------------------------------------------------
$interSiteSummary = $pairSummary |
Where-Object {
-not [string]::IsNullOrWhiteSpace($_.DestinationSite) -and
-not [string]::IsNullOrWhiteSpace($_.SourceSite) -and
$_.DestinationSite -ne $_.SourceSite
}
if ($interSiteSummary) {
$interSiteParticipants = @()
$interSiteParticipants += $interSiteSummary |
ForEach-Object {
New-DcEndpoint `
-Site $_.DestinationSite `
-Name $_.Destination `
-DcLookup $dcLookup
}
$interSiteParticipants += $interSiteSummary |
ForEach-Object {
New-DcEndpoint `
-Site $_.SourceSite `
-Name $_.Source `
-DcLookup $dcLookup
}
$interSiteParticipants = $interSiteParticipants |
Sort-Object Site, Name -Unique
# Same participants on both axes, so source-only and destination-only
# bridgehead DCs are both visible. Revolutionary: seeing the data.
$interSiteRows = $interSiteParticipants
$interSiteColumns = $interSiteParticipants
Write-ReplicationGrid `
-Title "Inter-Site Replication Matrix" `
-Rows $interSiteRows `
-Columns $interSiteColumns `
-CellLookup $cellLookup `
-WarningMinutes $WarningMinutes `
-CriticalMinutes $CriticalMinutes
$allExportRows += Convert-SummaryToExportRows `
-Rows $interSiteRows `
-Columns $interSiteColumns `
-CellLookup $cellLookup `
-GridName "Inter-Site"
}
else {
Write-Host ""
Write-Host "No inter-site replication relationships found." -ForegroundColor Yellow
}
# ------------------------------------------------------------
# FSMO Role Holders
# ------------------------------------------------------------
Write-FsmoRoleSummary -DcLookup $dcLookup
# ------------------------------------------------------------
# Legend
# ------------------------------------------------------------
Write-Host ""
Write-Host "Legend:" -ForegroundColor Cyan
Write-Host " Green = under $WarningMinutes Minutes"
Write-Host " Yellow = $WarningMinutes-$CriticalMinutes Minutes"
Write-Host " Red = over $CriticalMinutes Minutes"
Write-Host " . = no direct replication relationship found"
Write-Host " ! = one or more consecutive replication failures reported"
Write-Host " Cyan name = Global Catalog"
Write-Host " Gray name = Read-only Domain Controller"
Write-Host ""
# ------------------------------------------------------------
# Optional details
# ------------------------------------------------------------
if ($ShowDetails) {
Write-Host ""
Write-Host "Detailed replication records:" -ForegroundColor Cyan
$records |
Sort-Object DestinationSite, Destination, SourceSite, Source, NamingContext |
Select-Object `
DestinationSite,
Destination,
DestinationIsGlobalCatalog,
DestinationIsReadOnly,
SourceSite,
Source,
SourceIsGlobalCatalog,
SourceIsReadOnly,
NamingContext,
AgeLabel,
LastSuccess,
LastAttempt,
FailureCount,
LastReplicationResult |
Format-Table -AutoSize
}
# ------------------------------------------------------------
# Optional CSV export
# ------------------------------------------------------------
if ($ExportCsv) {
try {
$allExportRows | Export-Csv -Path $ExportCsv -NoTypeInformation
Write-Host "Matrix exported to: $ExportCsv" -ForegroundColor Cyan
}
catch {
Write-Error "Failed to export CSV to '$ExportCsv'. Error: $($_.Exception.Message)"
}
}