-
-
Notifications
You must be signed in to change notification settings - Fork 32
Add Active Directory Replication Relationship Diagram #239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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}. | ||||||||||||||||||||||||||||||||||||||||||
| replTransportProtocol = Protocolo | ||||||||||||||||||||||||||||||||||||||||||
| replAutoGenerated = Generado automaticamente | ||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||
| replAutoGenerated = Generado automaticamente | |
| replAutoGenerated = Generado automáticamente |
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
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".
| 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 = Sí |
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
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").
| replYes = Si | |
| replYes = Sí |
| 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
|
||||||||||||||||||||||||||||||||||||||||||||||||
| $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
AI
Mar 3, 2026
There was a problem hiding this comment.
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.
| $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
AI
Mar 3, 2026
There was a problem hiding this comment.
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).
| $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 |
| 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 } | ||||||
|
||||||
| 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' } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
@@ -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( | ||
|
|
@@ -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')) { | ||
|
|
@@ -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
|
||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
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.