Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AsBuiltReport.Microsoft.AD.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"DiagramType": {
"CertificateAuthority": true,
"Forest": true,
"Replication": true,
"Sites": true,
"SitesInventory": true,
"Trusts": true
Expand Down
12 changes: 12 additions & 0 deletions Language/en-US/MicrosoftAD.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,17 @@
DomainControllers = Domain Controllers
Sites = Sites
Subnets = Subnets

replicationDiagramLabel = Active Directory Replication Topology
NoReplication = No Replication Topology
emptyReplication = No Replication topology available to diagram
connectingReplication = Collecting Microsoft AD Replication information from {0}.
buildingReplication = Building Microsoft AD Replication diagram from {0}.
replTransportProtocol = Protocol
replAutoGenerated = Auto Generated
replEnabled = Enabled
replYes = Yes
replNo = No
replUnknownSite = Unknown Site
'@
}
12 changes: 12 additions & 0 deletions Language/es-ES/MicrosoftAD.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,17 @@
DomainControllers = Controlador de dominio
Sites = Sitios
Subnets = Subred

replicationDiagramLabel = Topologia de replicacion de Active Directory
NoReplication = No hay topologia de replicacion
emptyReplication = No hay topologia de replicacion disponible para diagramar
connectingReplication = Recopilando informacion de replicacion de Microsoft AD desde {0}.
buildingReplication = Construyendo diagrama de replicacion de Microsoft AD desde {0}.
Comment on lines +153 to +154
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spanish strings here omit accents ("informacion", "replicacion"), while earlier entries use accented forms. Consider updating to "Recopilando información de replicación de Microsoft AD desde {0}." for consistency/correctness.

Copilot uses AI. Check for mistakes.
replTransportProtocol = Protocolo
replAutoGenerated = Generado automaticamente
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spanish value "Generado automaticamente" is missing the accent on "automáticamente"; consider correcting to "Generado automáticamente" for proper Spanish and consistency with other accented strings in this file.

Suggested change
replAutoGenerated = Generado automaticamente
replAutoGenerated = Generado automáticamente

Copilot uses AI. Check for mistakes.
replEnabled = Habilitado
replYes = Si
Comment on lines +150 to +158
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spanish translation string is missing expected accents (the rest of the es-ES file uses accented forms like "información"). Consider correcting this label for proper Spanish: "Topología de replicación de Active Directory".

Suggested change
replicationDiagramLabel = Topologia de replicacion de Active Directory
NoReplication = No hay topologia de replicacion
emptyReplication = No hay topologia de replicacion disponible para diagramar
connectingReplication = Recopilando informacion de replicacion de Microsoft AD desde {0}.
buildingReplication = Construyendo diagrama de replicacion de Microsoft AD desde {0}.
replTransportProtocol = Protocolo
replAutoGenerated = Generado automaticamente
replEnabled = Habilitado
replYes = Si
replicationDiagramLabel = Topología de replicación de Active Directory
NoReplication = No hay topología de replicación
emptyReplication = No hay topología de replicación disponible para diagramar
connectingReplication = Recopilando información de replicación de Microsoft AD desde {0}.
buildingReplication = Construyendo diagrama de replicación de Microsoft AD desde {0}.
replTransportProtocol = Protocolo
replAutoGenerated = Generado automáticamente
replEnabled = Habilitado
replYes =

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spanish value "Si" should be "Sí" (accent) in this context; the rest of the locale file already uses accented forms (e.g., "información").

Suggested change
replYes = Si
replYes =

Copilot uses AI. Check for mistakes.
replNo = No
replUnknownSite = Sitio desconocido
'@
}
73 changes: 73 additions & 0 deletions Src/Private/Get-AbrADReplicationInfo.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
function Get-AbrADReplicationInfo {
<#
.SYNOPSIS
Function to extract microsoft active directory replication information.
.DESCRIPTION
Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph.
.NOTES
Version: 0.9.12
Author: Jonathan Colon
Twitter: @jcolonfzenpr
Github: rebelinux
.LINK
https://github.com/rebelinux/Diagrammer.Microsoft.AD
#>
[CmdletBinding()]
[OutputType([System.Collections.ArrayList])]

param()

begin {
}

process {
Write-Verbose -Message ($reportTranslate.NewADDiagram.buildingReplication -f $($ForestRoot))
try {

$ReplInfo = [System.Collections.ArrayList]::new()
foreach ($Domain in $OrderedDomains) {
$DomainControllers = Invoke-CommandWithTimeout -Session $DiagramTempPssSession -ScriptBlock { Get-ADDomainController -Filter * -Server $using:Domain }
foreach ($DC in $DomainControllers) {
$Connections = Invoke-CommandWithTimeout -Session $DiagramTempPssSession -ScriptBlock { Get-ADReplicationConnection -Server $using:DC.HostName -Filter * -Properties * }
foreach ($Conn in $Connections) {
$FromServer = try { ConvertTo-ADObjectName $Conn.ReplicateFromDirectoryServer.Split(',', 2)[1] -Session $DiagramTempPssSession -DC $System } catch { $Conn.ReplicateFromDirectoryServer.Split(',')[1] -replace 'CN=', '' }
$ToServer = try { ConvertTo-ADObjectName $Conn.ReplicateToDirectoryServer -Session $DiagramTempPssSession -DC $System } catch { $Conn.ReplicateToDirectoryServer -replace 'CN=NTDS Settings,CN=', '' -replace ',.*', '' }
Comment on lines +33 to +34
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConvertTo-ADObjectName is called with -DC $System while iterating over per-domain/per-DC connections. This can resolve names against the wrong domain controller (or fail for other domains); consider using the current $DC.HostName (or a domain-specific valid DC) as the -DC argument, consistent with Get-AbrADSiteReplication.

Suggested change
$FromServer = try { ConvertTo-ADObjectName $Conn.ReplicateFromDirectoryServer.Split(',', 2)[1] -Session $DiagramTempPssSession -DC $System } catch { $Conn.ReplicateFromDirectoryServer.Split(',')[1] -replace 'CN=', '' }
$ToServer = try { ConvertTo-ADObjectName $Conn.ReplicateToDirectoryServer -Session $DiagramTempPssSession -DC $System } catch { $Conn.ReplicateToDirectoryServer -replace 'CN=NTDS Settings,CN=', '' -replace ',.*', '' }
$FromServer = try { ConvertTo-ADObjectName $Conn.ReplicateFromDirectoryServer.Split(',', 2)[1] -Session $DiagramTempPssSession -DC $DC.HostName } catch { $Conn.ReplicateFromDirectoryServer.Split(',')[1] -replace 'CN=', '' }
$ToServer = try { ConvertTo-ADObjectName $Conn.ReplicateToDirectoryServer -Session $DiagramTempPssSession -DC $DC.HostName } catch { $Conn.ReplicateToDirectoryServer -replace 'CN=NTDS Settings,CN=', '' -replace ',.*', '' }

Copilot uses AI. Check for mistakes.
$FromSite = try { $Conn.fromserver.Split(',')[3].SubString($Conn.fromserver.Split(',')[3].IndexOf('=') + 1) } catch { 'Unknown' }
$ToSite = try { $Conn.ReplicateToDirectoryServer.Split(',')[2].SubString($Conn.ReplicateToDirectoryServer.Split(',')[2].IndexOf('=') + 1) } catch { 'Unknown' }
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ToSite extraction uses Split(',')[2], which typically yields CN=Servers ("Servers") rather than the AD site name. This will mis-group DCs and color edges incorrectly; it should parse the same DN component as FromSite (usually index 3) or otherwise reliably resolve the site from the DN.

Suggested change
$ToSite = try { $Conn.ReplicateToDirectoryServer.Split(',')[2].SubString($Conn.ReplicateToDirectoryServer.Split(',')[2].IndexOf('=') + 1) } catch { 'Unknown' }
$ToSite = try { $Conn.ReplicateToDirectoryServer.Split(',')[3].SubString($Conn.ReplicateToDirectoryServer.Split(',')[3].IndexOf('=') + 1) } catch { 'Unknown' }

Copilot uses AI. Check for mistakes.

$AditionalInfo = [PSCustomObject] [ordered] @{
$reportTranslate.NewADDiagram.replTransportProtocol = $Conn.InterSiteTransportProtocol
$reportTranslate.NewADDiagram.replAutoGenerated = & {
if ($Conn.AutoGenerated) { $reportTranslate.NewADDiagram.replYes } else { $reportTranslate.NewADDiagram.replNo }
}
$reportTranslate.NewADDiagram.replEnabled = & {
if ($Conn.enabledConnection) { $reportTranslate.NewADDiagram.replYes } else { $reportTranslate.NewADDiagram.replNo }
}
}

$TempReplInfo = [PSCustomObject]@{
FromServer = $FromServer
ToServer = $ToServer
FromSite = $FromSite
ToSite = $ToSite
Domain = $Domain
AditionalInfo = $AditionalInfo
Comment on lines +38 to +54
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$AditionalInfo/AditionalInfo is built and stored on each replication record but is never consumed by Get-AbrDiagReplication (nor elsewhere in this PR). Keeping it adds overhead and makes the data shape harder to reason about; either remove it or use it to enrich edge/node labels (e.g., enabled/autogenerated).

Suggested change
$AditionalInfo = [PSCustomObject] [ordered] @{
$reportTranslate.NewADDiagram.replTransportProtocol = $Conn.InterSiteTransportProtocol
$reportTranslate.NewADDiagram.replAutoGenerated = & {
if ($Conn.AutoGenerated) { $reportTranslate.NewADDiagram.replYes } else { $reportTranslate.NewADDiagram.replNo }
}
$reportTranslate.NewADDiagram.replEnabled = & {
if ($Conn.enabledConnection) { $reportTranslate.NewADDiagram.replYes } else { $reportTranslate.NewADDiagram.replNo }
}
}
$TempReplInfo = [PSCustomObject]@{
FromServer = $FromServer
ToServer = $ToServer
FromSite = $FromSite
ToSite = $ToSite
Domain = $Domain
AditionalInfo = $AditionalInfo
$TempReplInfo = [PSCustomObject]@{
FromServer = $FromServer
ToServer = $ToServer
FromSite = $FromSite
ToSite = $ToSite
Domain = $Domain

Copilot uses AI. Check for mistakes.
TransportProtocol = switch ([string]::IsNullOrEmpty($Conn.InterSiteTransportProtocol)) {
$true { 'Unknown' }
$false { $Conn.InterSiteTransportProtocol }
default { 'Unknown' }
}
AutoGenerated = $Conn.AutoGenerated
Enabled = $Conn.enabledConnection
}
$ReplInfo.Add($TempReplInfo) | Out-Null
}
}
}
$ReplInfo
} catch {
Write-Verbose $_.Exception.Message
}
}
end {}
}
120 changes: 120 additions & 0 deletions Src/Private/Get-AbrDiagReplication.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
function Get-AbrDiagReplication {
<#
.SYNOPSIS
Function to diagram Microsoft Active Directory Replication.
.DESCRIPTION
Build a diagram of the configuration of Microsoft Active Directory to a supported formats using Psgraph.
.NOTES
Version: 0.9.12
Author: Jonathan Colon
Twitter: @jcolonfzenpr
Github: rebelinux
.LINK
https://github.com/rebelinux/Diagrammer.Microsoft.AD
#>
[CmdletBinding()]
[OutputType([System.Object[]])]

param
(

)

begin {
Write-Verbose ($reportTranslate.NewADDiagram.gereratingDiag -f 'Replication')
}

process {
Write-Verbose -Message ($reportTranslate.NewADDiagram.connectingReplication -f $($ForestRoot))
try {
if ($ForestRoot) {

$ReplInfo = Get-AbrADReplicationInfo

if ($ReplInfo) {
SubGraph ForestSubGraph -Attributes @{Label = (Add-DiaHtmlLabel -ImagesObj $Images -Label $ForestRoot -IconType 'ForestRoot' -IconDebug $IconDebug -SubgraphLabel -IconWidth 50 -IconHeight 50 -Fontsize 22 -FontName 'Segoe UI' -FontColor $Fontcolor -FontBold) ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style ; color = $SubGraphDebug.color } {
# SubGraph MainSubGraph -Attributes @{Label = 'Prueba' ; fontsize = 24; penwidth = 1.5; labelloc = 't'; style = $SubGraphDebug.style; color = $SubGraphDebug.color } {
# }
# Collect unique sites from replication data
$Sites = ($ReplInfo | Select-Object -ExpandProperty FromSite) + ($ReplInfo | Select-Object -ExpandProperty ToSite) | Select-Object -Unique | Where-Object { $_ -ne 'Unknown' }

# Collect unique DCs from replication data
$AllDCs = ($ReplInfo | Select-Object -ExpandProperty FromServer) + ($ReplInfo | Select-Object -ExpandProperty ToServer) | Select-Object -Unique

if ($Sites -and ($Sites | Measure-Object).Count -gt 0) {

# Group DCs by site and draw site subgraphs
foreach ($Site in $Sites) {
$SiteDCsFrom = $ReplInfo | Where-Object { $_.FromSite -eq $Site } | Select-Object -ExpandProperty FromServer -Unique
$SiteDCsTo = $ReplInfo | Where-Object { $_.ToSite -eq $Site } | Select-Object -ExpandProperty ToServer -Unique
$SiteDCs = ($SiteDCsFrom + $SiteDCsTo) | Select-Object -Unique

$SiteNodeName = Remove-SpecialChar -String "$($Site)Site" -SpecialChars '\-. '
SubGraph $SiteNodeName -Attributes @{Label = (Add-DiaHtmlLabel -ImagesObj $Images -Label $Site -IconType 'AD_Site' -IconDebug $IconDebug -SubgraphLabel -IconWidth 35 -IconHeight 35 -Fontsize 18 -FontName 'Segoe UI' -FontColor $Fontcolor); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded'; color = 'gray' } {
foreach ($DC in $SiteDCs) {
$DCNodeName = Remove-SpecialChar -String $DC -SpecialChars '\-. '
Node -Name $DCNodeName -Attributes @{Label = (Add-DiaNodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18); shape = 'plain'; fillColor = 'transparent' }
}
}
}

# Draw DCs with unknown site affiliation
$UnknownSiteDCs = $AllDCs | Where-Object {
$DC = $_
-not ($ReplInfo | Where-Object { ($_.FromServer -eq $DC -and $_.FromSite -ne 'Unknown') -or ($_.ToServer -eq $DC -and $_.ToSite -ne 'Unknown') })
}
if ($UnknownSiteDCs) {
$UnknownSiteNodeName = 'UnknownSite'
SubGraph $UnknownSiteNodeName -Attributes @{Label = (Add-DiaHtmlLabel -ImagesObj $Images -Label $reportTranslate.NewADDiagram.replUnknownSite -IconType 'AD_Site' -IconDebug $IconDebug -SubgraphLabel -IconWidth 35 -IconHeight 35 -Fontsize 18 -FontName 'Segoe UI' -FontColor $Fontcolor); fontsize = 18; penwidth = 1.5; labelloc = 't'; style = 'dashed,rounded'; color = 'gray' } {
foreach ($DC in $UnknownSiteDCs) {
$DCNodeName = Remove-SpecialChar -String $DC -SpecialChars '\-. '
Node -Name $DCNodeName -Attributes @{Label = (Add-DiaNodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18); shape = 'plain'; fillColor = 'transparent' }
}
}
}

} else {
# No site information - draw all DCs flat
foreach ($DC in $AllDCs) {
$DCNodeName = Remove-SpecialChar -String $DC -SpecialChars '\-. '
Node -Name $DCNodeName -Attributes @{Label = (Add-DiaNodeIcon -Name ($DC.Split('.')[0].ToUpper()) -IconType 'AD_DC' -Align 'Center' -ImagesObj $Images -IconDebug $IconDebug -FontSize 18); shape = 'plain'; fillColor = 'transparent' }
}
}

# Draw replication edges between DCs
$DrawnEdges = [System.Collections.Generic.HashSet[string]]::new()
foreach ($Repl in $ReplInfo) {
$FromNodeName = Remove-SpecialChar -String $Repl.FromServer -SpecialChars '\-. '
$ToNodeName = Remove-SpecialChar -String $Repl.ToServer -SpecialChars '\-. '

if ($FromNodeName -and $ToNodeName -and $FromNodeName -ne $ToNodeName) {
$EdgeKey = "$FromNodeName->$ToNodeName"
if (-not $DrawnEdges.Contains($EdgeKey)) {
$DrawnEdges.Add($EdgeKey) | Out-Null
$EdgeLabel = & {
if ($Repl.TransportProtocol) {
$Repl.TransportProtocol
} else {
' '
}
}
if ($Repl.FromSite -eq $Repl.ToSite) {
$EdgeColor = 'darkgreen'
} else {
$EdgeColor = 'darkblue'
}
Edge -From $FromNodeName -To $ToNodeName @{minlen = 2; label = $EdgeLabel; fontsize = 16; fontname = 'Segoe UI'; color = $EdgeColor; penwidth = 1.5 }
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replication edges are intended to be directional, but the diagram’s global edge defaults set dir = 'both' (and arrowtail = 'dot'), and this Edge call doesn’t override them. As a result replication connections will render with arrows at both ends; override edge attributes here (e.g., dir='forward' and an appropriate arrowtail/arrowhead) to make direction unambiguous.

Suggested change
Edge -From $FromNodeName -To $ToNodeName @{minlen = 2; label = $EdgeLabel; fontsize = 16; fontname = 'Segoe UI'; color = $EdgeColor; penwidth = 1.5 }
Edge -From $FromNodeName -To $ToNodeName @{minlen = 2; label = $EdgeLabel; fontsize = 16; fontname = 'Segoe UI'; color = $EdgeColor; penwidth = 1.5; dir = 'forward'; arrowhead = 'normal'; arrowtail = 'none' }

Copilot uses AI. Check for mistakes.
}
}
}
}
} else {
Node -Name NoReplication @{Label = $reportTranslate.NewADDiagram.NoReplication; shape = 'rectangle'; labelloc = 'c'; fixedsize = $true; width = '3'; height = '2'; fillColor = 'transparent'; penwidth = 1.5; style = 'dashed'; color = 'gray' }
}
}
} catch {
Write-Verbose $_.Exception.Message
}
}
end {}
}
2 changes: 1 addition & 1 deletion Src/Private/Get-AbrDiagrammer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function Get-AbrDiagrammer {
HelpMessage = 'Please provide diagram type to generate'
)]
[ValidateNotNullOrEmpty()]
[ValidateSet('Forest', 'CertificateAuthority', 'Sites', 'SitesInventory', 'Trusts', 'All')]
[ValidateSet('Forest', 'CertificateAuthority', 'Sites', 'SitesInventory', 'Trusts', 'Replication', 'All')]
[string]$DiagramType,
[Parameter(
Mandatory = $false,
Expand Down
9 changes: 8 additions & 1 deletion Src/Private/New-AbrADDiagram.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ function New-AbrADDiagram {
'Sites'
'SitesInventory'
'Trusts'
'CertificateAuthority'
'Replication'
.PARAMETER Target
Specifies the IP/FQDN of the system to connect.
Multiple targets may be specified, separated by a comma.
Expand Down Expand Up @@ -234,7 +236,7 @@ function New-AbrADDiagram {
Mandatory = $true,
HelpMessage = 'Controls type of Active Directory generated diagram'
)]
[ValidateSet('Forest', 'Sites', 'SitesInventory', 'Trusts', 'CertificateAuthority')]
[ValidateSet('Forest', 'Sites', 'SitesInventory', 'Trusts', 'CertificateAuthority', 'Replication')]
[string] $DiagramType,

[Parameter(
Expand Down Expand Up @@ -333,6 +335,7 @@ function New-AbrADDiagram {
'SitesInventory' { $reportTranslate.NewADDiagram.sitesinventorygraphlabel }
'Trusts' { $reportTranslate.NewADDiagram.trustsDiagramLabel }
'CertificateAuthority' { $reportTranslate.NewADDiagram.caDiagramLabel }
'Replication' { $reportTranslate.NewADDiagram.replicationDiagramLabel }
}

$Verbose = if ($PSBoundParameters.ContainsKey('Verbose')) {
Expand Down Expand Up @@ -535,6 +538,10 @@ function New-AbrADDiagram {
if ($TrustsInfo = Get-AbrDiagTrust | Select-String -Pattern '"([A-Z])\w+"\s\[label="";style="invis";shape="point";]' -NotMatch) {
$TrustsInfo
} else { Write-Warning $reportTranslate.NewADDiagram.emptyTrusts }
} elseif ($DiagramType -eq 'Replication') {
if ($ReplInfo = Get-AbrDiagReplication | Select-String -Pattern '"([A-Z])\w+"\s\[label="";style="invis";shape="point";]' -NotMatch) {
$ReplInfo
} else { Write-Warning $reportTranslate.NewADDiagram.emptyReplication }
}
Comment on lines +541 to 545
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions enabling the Graphviz concentrate attribute for the Replication diagram (like Sites), but New-AbrADDiagram currently only sets concentrate when $DiagramType -eq 'Sites'. If Replication also needs edge concentration, extend that condition (or make it a per-diagram setting).

Copilot uses AI. Check for mistakes.
}
}
Expand Down