Src/Private/Get-AbrADSite.ps1

function Get-AbrADSite {
    <#
    .SYNOPSIS
    Used by As Built Report to retrieve Microsoft AD Domain Sites information.
    .DESCRIPTION
 
    .NOTES
        Version: 0.8.0
        Author: Jonathan Colon
        Twitter: @jcolonfzenpr
        Github: rebelinux
    .EXAMPLE
 
    .LINK
 
    #>

    [CmdletBinding()]
    param (
    )

    begin {
        Write-PscriboMessage "Discovering Active Directory Sites information of forest $ForestInfo"
    }

    process {
        try {
            $Site =  Invoke-Command -Session $TempPssSession {Get-ADReplicationSite -Filter * -Properties *}
            if ($Site) {
                Section -Style Heading3 'Sites' {
                    $OutObj = @()
                    foreach ($Item in $Site) {
                        try {
                            Write-PscriboMessage "Collecting '$($Item.Name)' Site"
                            $SubnetArray = @()
                            $Subnets = $Item.Subnets
                            foreach ($Object in $Subnets) {
                                $SubnetName =  Invoke-Command -Session $TempPssSession {Get-ADReplicationSubnet $using:Object}
                                $SubnetArray += $SubnetName.Name
                            }
                            $inObj = [ordered] @{
                                'Site Name' = $Item.Name
                                'Description' = ConvertTo-EmptyToFiller $Item.Description
                                'Subnets' = Switch (($SubnetArray).count) {
                                    0 {"No subnet assigned"}
                                    default {$SubnetArray}
                                }
                                'Domain Controllers' = &{
                                    $ServerArray = @()
                                    $Servers = try {Get-ADObjectSearch -DN "CN=Servers,$($Item.DistinguishedName)" -Filter { objectClass -eq "Server" } -Properties "DNSHostName" -SelectPrty 'DNSHostName','Name' -Session $TempPssSession} catch {'Unknown'}
                                    foreach ($Object in $Servers) {
                                        $ServerArray += $Object.Name
                                    }

                                    if ($ServerArray) {
                                        return $ServerArray
                                    } else {'No DC assigned'}
                                }
                            }
                            $OutObj += [pscustomobject]$inobj

                            if ($HealthCheck.Site.BestPractice) {
                                $OutObj | Where-Object { $_.'Subnets' -eq 'No subnet assigned'} | Set-Style -Style Warning -Property 'Subnets'
                                $OutObj | Where-Object { $_.'Description' -eq '--'} | Set-Style -Style Warning -Property 'Description'
                                $OutObj | Where-Object { $_.'Domain Controllers' -eq 'No DC assigned'} | Set-Style -Style Warning -Property 'Domain Controllers'
                            }
                        }
                        catch {
                            Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Domain Site)"
                        }
                    }

                    $TableParams = @{
                        Name = "Sites - $($ForestInfo)"
                        List = $false
                        ColumnWidths = 25, 30, 20, 25
                    }
                    if ($Report.ShowTableCaptions) {
                        $TableParams['Caption'] = "- $($TableParams.Name)"
                    }
                    $OutObj | Sort-Object -Property 'Site Name' | Table @TableParams
                    if ($HealthCheck.Site.BestPractice -and (($OutObj | Where-Object { $_.'Subnets' -eq '--'}) -or ($OutObj | Where-Object { $_.'Description' -eq '--'}))) {
                        Paragraph "Health Check:" -Bold -Underline
                        BlankLine
                        if ($OutObj | Where-Object { $_.'Subnets' -eq 'No subnet assigned'}) {
                            Paragraph {
                                Write-PscriboMessage "Discovered Active Directory Sites information of forest $ForestInfo"
                                Text -Bold "Corrective Actions:"
                                Text "Ensure Sites have an associated subnet. If subnets are not associated with AD Sites users in the AD Sites might choose a remote domain controller for authentication which in turn might result in excessive use of a remote domain controller."}
                        }
                        if ($OutObj | Where-Object { $_.'Description' -eq '--'}) {
                            BlankLine
                            Paragraph {
                                Text "Best Practice:" -Bold
                                Text "It is a general rule of good practice to establish well-defined descriptions. This helps to speed up the fault identification process, as well as enabling better documentation of the environment."
                            }
                        }
                    }
                    try {
                        $Replications = Invoke-Command -Session $TempPssSession -ScriptBlock {Get-ADReplicationConnection -Properties * -Filter *}
                        if ($Replications) {
                            Section -ExcludeFromTOC -Style NOTOCHeading4 'Connection Objects' {
                                $OutObj = @()
                                Write-PscriboMessage "Discovered Connection Objects information of forest $ForestInfo"
                                foreach ($Repl in $Replications) {
                                    try {
                                        $inObj = [ordered] @{
                                            'Name' = &{
                                                if ($Repl.AutoGenerated) {
                                                    "<automatically generated>"
                                                } else {
                                                    $Repl.Name
                                                }
                                            }
                                            'From Server' = $Repl.ReplicateFromDirectoryServer.Split(",")[1].SubString($Repl.ReplicateFromDirectoryServer.Split(",")[1].IndexOf("=")+1)
                                            'To Server' = $Repl.ReplicateToDirectoryServer.Split(",")[0].SubString($Repl.ReplicateToDirectoryServer.Split(",")[0].IndexOf("=")+1)
                                            'From Site' = $Repl.fromserver.Split(",")[3].SubString($Repl.fromserver.Split(",")[3].IndexOf("=")+1)
                                        }
                                        $OutObj += [pscustomobject]$inobj

                                        if ($HealthCheck.Site.Replication) {
                                            $OutObj | Where-Object { $_.'Name' -ne '<automatically generated>'} | Set-Style -Style Warning -Property 'Name'
                                        }
                                    }
                                    catch {
                                        Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Site Replication Connection Item)"
                                    }
                                }

                                $TableParams = @{
                                    Name = "Connection Objects - $($ForestInfo)"
                                    List = $false
                                    ColumnWidths = 25, 25, 25, 25
                                }
                                if ($Report.ShowTableCaptions) {
                                    $TableParams['Caption'] = "- $($TableParams.Name)"
                                }
                                $OutObj | Sort-Object -Property 'From Site' | Table @TableParams
                                if ($HealthCheck.Site.BestPractice -and ($OutObj | Where-Object { $_.'Name' -ne '<automatically generated>'})) {
                                    Paragraph "Health Check:" -Bold -Underline
                                    BlankLine
                                    if ($OutObj | Where-Object { $_.'Name' -ne '<automatically generated>'}) {
                                        Paragraph {
                                            Text "Best Practice:" -Bold
                                            Text "By default, the replication topology is managed automatically and optimizes existing connections. However, manual connections created by an administrator are not modified or optimized. Verify that all topology information is entered for Site Links and delete all manual connection objects."
                                        }
                                    }
                                }
                            }
                        } else {
                            Write-PscriboMessage -IsWarning "No Connection Objects information found in $Domain, disabling the section."
                        }
                    }
                    catch {
                        Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Connection Objects)"
                    }
                    try {
                        $Subnet = Invoke-Command -Session $TempPssSession {Get-ADReplicationSubnet -Filter * -Properties *}
                        if ($Subnet) {
                            Section -Style Heading4 'Site Subnets' {
                                $OutObj = @()
                                Write-PscriboMessage "Discovered Active Directory Sites Subnets information of forest $ForestInfo"
                                foreach ($Item in $Subnet) {
                                    try {
                                        Write-PscriboMessage "Collecting $($Item.Name) Site Subnet."
                                        $inObj = [ordered] @{
                                            'Subnet' = $Item.Name
                                            'Description' = ConvertTo-EmptyToFiller $Item.Description
                                            'Sites' = &{
                                                try {
                                                    $Item.Site.Split(",")[0].SubString($Item.Site.Split(",")[0].IndexOf("=")+1)
                                                } catch {"No site assigned"}
                                            }
                                        }
                                        $OutObj += [pscustomobject]$inObj

                                        if ($HealthCheck.Site.BestPractice) {
                                            $OutObj | Where-Object { $_.'Description' -eq '--'} | Set-Style -Style Warning -Property 'Description'
                                            $OutObj | Where-Object { $_.'Sites' -eq 'No site assigned'} | Set-Style -Style Warning -Property 'Sites'
                                        }
                                    }
                                    catch {
                                        Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Site Subnets)"
                                    }
                                }

                                $TableParams = @{
                                    Name = "Site Subnets - $($ForestInfo)"
                                    List = $false
                                    ColumnWidths = 20, 40, 40
                                }
                                if ($Report.ShowTableCaptions) {
                                    $TableParams['Caption'] = "- $($TableParams.Name)"
                                }
                                $OutObj | Sort-Object -Property 'Subnet' | Table @TableParams
                                if ($HealthCheck.Site.BestPractice -and (($OutObj | Where-Object { $_.'Description' -eq '--'}) -or ($OutObj | Where-Object { $_.'Sites' -eq 'No site assigned'}))) {
                                    Paragraph "Health Check:" -Bold -Underline
                                    BlankLine
                                    if ($OutObj | Where-Object { $_.'Description' -eq '--'}) {
                                        Paragraph {
                                            Text "Best Practice:" -Bold
                                            Text "It is a general rule of good practice to establish well-defined descriptions. This helps to speed up the fault identification process, as well as enabling better documentation of the environment."
                                        }
                                        BlankLine
                                    }
                                    if ($OutObj | Where-Object { $_.'Sites' -eq 'No site assigned'}) {
                                        Paragraph {
                                            Text "Corrective Actions:" -Bold
                                            Text "Ensure Subnet have an associated site. If subnets are not associated with AD Sites users in the AD Sites might choose a remote domain controller for authentication which in turn might result in excessive use of a remote domain controller."
                                        }
                                    }
                                }
                                if ($HealthCheck.Site.BestPractice) {
                                    try {
                                        $OutObj = @()
                                        foreach ($Domain in $ADSystem.Domains | Where-Object {$_ -notin $Options.Exclude.Domains}) {
                                            Write-PscriboMessage "Discovered Missing Subnet in AD information from $Domain."
                                            $DomainInfo =  Invoke-Command -Session $TempPssSession {Get-ADDomain $using:Domain -ErrorAction Stop}
                                            foreach ($DC in ($DomainInfo.ReplicaDirectoryServers | Where-Object {$_ -notin $Options.Exclude.DCs})) {
                                                if (Test-Connection -ComputerName $DC -Quiet -Count 2) {
                                                    try {
                                                        $DCPssSession = New-PsSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication -Name 'MissingSubnetinAD'
                                                        $Path = "\\$DC\admin`$\debug\netlogon.log"
                                                        if ((Invoke-Command -Session $DCPssSession {Test-Path -Path $using:path}) -and (Invoke-Command -Session $DCPssSession {(Get-Content -Path $using:path | Measure-Object -Line).lines -gt 0})) {
                                                            Write-PscriboMessage "Collecting Missing Subnet in AD information from $($Domain)."
                                                            $NetLogonContents = Invoke-Command -Session $DCPssSession { (Get-Content -Path $using:Path)[-200..-1] }
                                                            foreach ($Line in $NetLogonContents) {
                                                                if ($Line -match "NO_CLIENT_SITE") {
                                                                    $inObj = [ordered] @{
                                                                        'DC' = $DC
                                                                        'IP' = $Line.Split(":")[4].trim(" ").Split(" ")[1]
                                                                    }

                                                                    $OutObj += [pscustomobject]$inobj
                                                                }

                                                                if ($HealthCheck.Site.BestPractice) {
                                                                    $OutObj | Set-Style -Style Warning -Property 'IP'
                                                                }
                                                            }
                                                        } else {
                                                            Write-PScriboMessage "Unable to read $Path on $DC"
                                                        }
                                                        if ($DCPssSession) {
                                                            Remove-PsSession -Session $DCPssSession
                                                        }
                                                    }
                                                    catch {
                                                        Write-PscriboMessage -IsWarning "Missing Subnet in AD Item Section: $($_.Exception.Message)"
                                                    }
                                                }
                                            }
                                        }
                                        if ($OutObj) {
                                            Section -ExcludeFromTOC -Style NOTOCHeading4 'Missing Subnets in AD' {
                                                Paragraph "The following table list the NO_CLIENT_SITE entries found in the netlogon.log file at each DC in the forest."
                                                Blankline
                                                $TableParams = @{
                                                    Name = "Missing Subnets - $($ForestInfo)"
                                                    List = $false
                                                    ColumnWidths = 40, 60
                                                }

                                                if ($Report.ShowTableCaptions) {
                                                    $TableParams['Caption'] = "- $($TableParams.Name)"
                                                }

                                                $OutObj | Sort-Object -Property 'DC','IP' | Get-Unique -AsString | Table @TableParams
                                                if ($HealthCheck.Site.BestPractice) {
                                                    Paragraph "Health Check:" -Bold -Underline
                                                    BlankLine
                                                    Paragraph {
                                                        Text "Corrective Actions:" -Bold
                                                        Text "Make sure that all the subnets at each Site are properly defined. Missing subnets can cause clients to not use the site's local DCs."
                                                    }
                                                    BlankLine
                                                }
                                            }
                                        } else {
                                            Write-PscriboMessage -IsWarning "No Missing Subnets in AD information found in $Domain, disabling the section."
                                        }
                                    }
                                    catch {
                                        Write-PscriboMessage -IsWarning "Sysvol Replication Table Section: $($_.Exception.Message)"
                                    }
                                }
                            }
                        } else {
                            Write-PscriboMessage -IsWarning "No Site Subnets information found in $Domain, disabling the section."
                        }
                    }
                    catch {
                        Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Site Subnets)"
                    }
                    try {
                        $Link = Invoke-Command -Session $TempPssSession {Get-ADReplicationSiteLink -Filter * -Properties *}
                        if ($Link) {
                            Section -Style Heading4 'Site Links' {
                                $OutObj = @()
                                Write-PscriboMessage "Discovered Active Directory Sites Link information of forest $ForestInfo"
                                foreach ($Item in $Link) {
                                    try {
                                        Write-PscriboMessage "Collecting '$($Item.Name)' Site Link"
                                        $SiteArray = @()
                                        $Sites = $Item.siteList
                                        foreach ($Object in $Sites) {
                                            $SiteName =  Invoke-Command -Session $TempPssSession {Get-ADReplicationSite -Identity $using:Object}
                                            $SiteArray += $SiteName.Name
                                        }
                                        $inObj = [ordered] @{
                                            'Site Link Name' = $Item.Name
                                            'Cost' = $Item.Cost
                                            'Replication Frequency' = "$($Item.ReplicationFrequencyInMinutes) min"
                                            'Transport Protocol' = $Item.InterSiteTransportProtocol
                                            'Options' = Switch ($Item.Options) {
                                                $null {'Change Notification is Disabled'}
                                                '0' {'(0)Change Notification is Disabled'}
                                                '1' {'(1)Change Notification is Enabled with Compression'}
                                                '2' {'(2)Force sync in opposite direction at end of sync'}
                                                '3' {'(3)Change Notification is Enabled with Compression and Force sync in opposite direction at end of sync'}
                                                '4' {'(4)Disable compression of Change Notification messages'}
                                                '5' {'(5)Change Notification is Enabled without Compression'}
                                                '6' {'(6)Force sync in opposite direction at end of sync and Disable compression of Change Notification messages'}
                                                '7' {'(7)Change Notification is Enabled without Compression and Force sync in opposite direction at end of sync'}
                                                Default {"Unknown siteLink option: $($Item.Options)"}
                                            }
                                            'Sites' = $SiteArray -join "; "
                                            'Protected From Accidental Deletion' = ConvertTo-TextYN $Item.ProtectedFromAccidentalDeletion
                                            'Description' = ConvertTo-EmptyToFiller $Item.Description
                                        }
                                        $OutObj = [pscustomobject]$inobj

                                        if ($HealthCheck.Site.BestPractice) {
                                            $OutObj | Where-Object { $_.'Description' -eq '--'} | Set-Style -Style Warning -Property 'Description'
                                            $OutObj | Where-Object { $_.'Options' -eq 'Change Notification is Disabled' -or $Null -eq 'Options' } | Set-Style -Style Warning -Property 'Options'
                                            $OutObj | Where-Object { $_.'Protected From Accidental Deletion' -eq 'No'} | Set-Style -Style Warning -Property 'Protected From Accidental Deletion'
                                        }

                                        $TableParams = @{
                                            Name = "Site Links - $($Item.Name)"
                                            List = $true
                                            ColumnWidths = 40, 60
                                        }
                                        if ($Report.ShowTableCaptions) {
                                            $TableParams['Caption'] = "- $($TableParams.Name)"
                                        }
                                        $OutObj | Sort-Object -Property 'Site Link Name' | Table @TableParams
                                        if ($HealthCheck.Site.BestPractice -and (($OutObj | Where-Object { $_.'Protected From Accidental Deletion' -eq 'No'}) -or (($OutObj | Where-Object { $_.'Description' -eq '--'}) -or ($OutObj | Where-Object { $_.'Options' -eq 'Change Notification is Disabled' -or $Null -eq 'Options' })))) {
                                            Paragraph "Health Check:" -Bold -Underline
                                            BlankLine
                                            if ($OutObj | Where-Object { $_.'Description' -eq '--'}) {
                                                Paragraph {
                                                    Text "Best Practice:" -Bold
                                                    Text "It is a general rule of good practice to establish well-defined descriptions. This helps to speed up the fault identification process, as well as enabling better documentation of the environment."
                                                }
                                                BlankLine
                                            }
                                            if ($OutObj | Where-Object { $_.'Options' -eq 'Change Notification is Disabled' -or $Null -eq 'Options' }) {
                                                Paragraph {
                                                    Text "Best Practice:" -Bold
                                                    Text "Enabling change notification treats an INTER-site replication connection like an INTRA-site connection. Replication between sites with change notification is almost instant. Microsoft recommends using an Option number value of 5 (Change Notification is Enabled without Compression)."
                                                }
                                                BlankLine
                                            }
                                            if ($OutObj | Where-Object { $_.'Protected From Accidental Deletion' -eq 'No'}) {
                                                Paragraph {
                                                    Text "Best Practice:" -Bold
                                                    Text "If the Site Links in your Active Directory are not protected from accidental deletion, your environment can experience disruptions that might be caused by accidental bulk deletion of objects."
                                                }
                                                BlankLine
                                            }
                                        }

                                    }
                                    catch {
                                        Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Site Links)"
                                    }
                                }
                            }
                        } else {
                            Write-PscriboMessage -IsWarning "No Site Links information found in $Domain, disabling the section."
                        }
                    }
                    catch {
                        Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Site Subnets)"
                    }
                    try {
                        $OutObj = @()
                        foreach ($Domain in $ADSystem.Domains | Where-Object {$_ -notin $Options.Exclude.Domains}) {
                            Write-PscriboMessage "Discovered AD Domain Sysvol Replication information from $Domain."
                            $DomainInfo =  Invoke-Command -Session $TempPssSession {Get-ADDomain $using:Domain -ErrorAction Stop}
                            foreach ($DC in ($DomainInfo.ReplicaDirectoryServers | Where-Object {$_ -notin $Options.Exclude.DCs})) {
                                if (Test-Connection -ComputerName $DC -Quiet -Count 2) {
                                    $DCCIMSession = New-CIMSession $DC -Credential $Credential -Authentication $Options.PSDefaultAuthentication
                                    $Replication = Get-CIMInstance -CIMSession $DCCIMSession -Namespace "root/microsoftdfs" -Class "dfsrreplicatedfolderinfo" -Filter "ReplicatedFolderName = 'SYSVOL Share'" -EA 0 -Verbose:$False | Select-Object State
                                    if ($DCCIMSession) {
                                        Remove-CimSession -CimSession $DCCIMSession
                                    }

                                    try {
                                        Write-PscriboMessage "Collecting Sysvol Replication information from $($Domain)."
                                        $inObj = [ordered] @{
                                            'DC Name' = $DC.split(".", 2)[0]
                                            'Replication Status' = Switch ($Replication.State) {
                                                0  {'Uninitialized'}
                                                1  {'Initialized'}
                                                2  {'Initial synchronization'}
                                                3  {'Auto recovery'}
                                                4  {'Normal'}
                                                5  {'In error state'}
                                                6  {'Disabled'}
                                                7  {'Unknown'}
                                            }
                                            'Domain' = $Domain
                                        }
                                        $OutObj += [pscustomobject]$inobj
                                    }
                                    catch {
                                        Write-PscriboMessage -IsWarning "Sysvol Replication Item Section: $($_.Exception.Message)"
                                    }

                                    if ($HealthCheck.Site.BestPractice) {
                                        $ReplicationStatusError = @(
                                            'Uninitialized',
                                            'Auto recovery',
                                            'In error state',
                                            'Disabled',
                                            'Unknown'
                                        )
                                        $ReplicationStatusWarn = @(
                                            'Initialized',
                                            'Initial synchronization'
                                        )
                                        $OutObj | Where-Object { $_.'Replication Status' -eq 'Normal' } | Set-Style -Style OK -Property 'Replication Status'
                                        $OutObj | Where-Object { $_.'Replication Status' -in $ReplicationStatusError } | Set-Style -Style Critical -Property 'Replication Status'
                                        $OutObj | Where-Object { $_.'Replication Status' -in $ReplicationStatusWarn } | Set-Style -Style Warning -Property 'Replication Status'
                                    }
                                }
                            }
                        }
                        if ($OutObj) {
                            Section -Style Heading4 'Sysvol Replication' {
                                $TableParams = @{
                                    Name = "Sysvol Replication - $($Domain.ToString().ToUpper())"
                                    List = $false
                                    ColumnWidths = 33, 33, 34
                                }

                                if ($Report.ShowTableCaptions) {
                                    $TableParams['Caption'] = "- $($TableParams.Name)"
                                }

                                $OutObj | Sort-Object -Property 'Domain' | Table @TableParams
                                if ($HealthCheck.Site.BestPractice -and (($OutObj | Where-Object { $_.'Identical Count' -like 'No' }) -or ($OutObj | Where-Object { $_.'Replication Status' -in $ReplicationStatusError }))) {
                                    Paragraph "Health Check:" -Bold -Underline
                                    BlankLine
                                    Paragraph {
                                        Text "Corrective Actions:" -Bold
                                        Text "SYSVOL is a special directory that resides on each domain controller (DC) within a domain. The directory comprises folders that store Group Policy objects (GPOs) and logon scripts that clients need to access and synchronize between DCs. For these logon scripts and GPOs to function properly, SYSVOL should be replicated accurately and rapidly throughout the domain. Ensure that proper SYSVOL replication is in place to ensure identical GPO/SYSVOL content for the domain controller across all Active Directory domains."
                                    }
                                    BlankLine
                                }
                            }
                        } else {
                            Write-PscriboMessage -IsWarning "No Sysvol Replication information found in $Domain, disabling the section."
                        }
                    }
                    catch {
                        Write-PscriboMessage -IsWarning "Sysvol Replication Table Section: $($_.Exception.Message)"
                    }
                }
            } else {
                Write-PscriboMessage -IsWarning "No Sites information found in $Domain, disabling the section."
            }
        }
        catch {
            Write-PscriboMessage -IsWarning "$($_.Exception.Message) (Domain Site Global)"
        }
    }

    end {}

}