diff --git a/AsBuiltReport.Microsoft.AD.json b/AsBuiltReport.Microsoft.AD.json index 3ecc136..9820e62 100644 --- a/AsBuiltReport.Microsoft.AD.json +++ b/AsBuiltReport.Microsoft.AD.json @@ -31,6 +31,7 @@ "DiagramType": { "CertificateAuthority": true, "Forest": true, + "Replication": true, "Sites": true, "SitesInventory": true, "Trusts": true diff --git a/Language/en-US/MicrosoftAD.psd1 b/Language/en-US/MicrosoftAD.psd1 index 717f320..2801683 100644 --- a/Language/en-US/MicrosoftAD.psd1 +++ b/Language/en-US/MicrosoftAD.psd1 @@ -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 '@ } \ No newline at end of file diff --git a/Language/es-ES/MicrosoftAD.psd1 b/Language/es-ES/MicrosoftAD.psd1 index 5f41505..d64f862 100644 --- a/Language/es-ES/MicrosoftAD.psd1 +++ b/Language/es-ES/MicrosoftAD.psd1 @@ -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 + replEnabled = Habilitado + replYes = Si + replNo = No + replUnknownSite = Sitio desconocido '@ } \ No newline at end of file diff --git a/Src/Private/Get-AbrADReplicationInfo.ps1 b/Src/Private/Get-AbrADReplicationInfo.ps1 new file mode 100644 index 0000000..26cab99 --- /dev/null +++ b/Src/Private/Get-AbrADReplicationInfo.ps1 @@ -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 ',.*', '' } + $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' } + + $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 + 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 {} +} diff --git a/Src/Private/Get-AbrDiagReplication.ps1 b/Src/Private/Get-AbrDiagReplication.ps1 new file mode 100644 index 0000000..b22f7dc --- /dev/null +++ b/Src/Private/Get-AbrDiagReplication.ps1 @@ -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 } + } + } + } + } + } 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 {} +} diff --git a/Src/Private/Get-AbrDiagrammer.ps1 b/Src/Private/Get-AbrDiagrammer.ps1 index 802630e..48b48bc 100644 --- a/Src/Private/Get-AbrDiagrammer.ps1 +++ b/Src/Private/Get-AbrDiagrammer.ps1 @@ -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, diff --git a/Src/Private/New-AbrADDiagram.ps1 b/Src/Private/New-AbrADDiagram.ps1 index 44022c9..a7b3922 100644 --- a/Src/Private/New-AbrADDiagram.ps1 +++ b/Src/Private/New-AbrADDiagram.ps1 @@ -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 } } } }