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.1
        Author: Jonathan Colon
        Twitter: @jcolonfzenpr
        Github: rebelinux
    .EXAMPLE
 
    .LINK
 
    #>

    [CmdletBinding()]
    param (
    )

    begin {
        Write-PScriboMessage "Collecting Active Directory Sites information of forest $ForestInfo"
    }

    process {
        try {
            $Site = Invoke-Command -Session $TempPssSession { Get-ADReplicationSite -Filter * -Properties * }
            if ($Site) {
                Section -Style Heading3 'Replication' {
                    Paragraph "Replication is the process of transferring and updating Active Directory objects between
                    domain controllers in the Active Directory domain and forest. The folowing setion details Active Directory replication and it´s relationships."

                    BlankLine
                    Section -Style Heading4 'Sites' {
                        $OutObj = @()
                        foreach ($Item in $Site) {
                            try {
                                $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 {
                                    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 = @()
                                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 $ForestInfo, 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 = @()
                                foreach ($Item in $Subnet) {
                                    try {
                                        $inObj = [ordered] @{
                                            'Subnet' = $Item.Name
                                            'Description' = ConvertTo-EmptyToFiller $Item.Description
                                            'Sites' = Switch ([string]::IsNullOrEmpty($Item.Site)) {
                                                $true { "No site assigned" }
                                                $false { $Item.Site.Split(",")[0].SubString($Item.Site.Split(",")[0].IndexOf("=") + 1) }
                                                default { 'Unknown' }
                                            }
                                        }
                                        $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 }) {
                                            $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 })) {
                                                            $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 $ForestInfo, disabling the section."
                                        }
                                    } catch {
                                        Write-PScriboMessage -IsWarning "Sysvol Replication Table Section: $($_.Exception.Message)"
                                    }
                                }
                            }
                        } else {
                            Write-PScriboMessage -IsWarning "No Site Subnets information found in $ForestInfo, disabling the section."
                        }
                    } catch {
                        Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Site Subnets)"
                    }
                    try {
                        try {
                            $Graph = New-ADDiagram -Target $System -Credential $Credential -Format base64 -Direction top-to-bottom -DiagramType Sites
                        } catch {
                            Write-PScriboMessage -IsWarning "Site Topology Diagram Graph: $($_.Exception.Message)"
                        }

                        if ($Graph) {
                            If ((Get-DiaImagePercent -GraphObj $Graph).Width -gt 1500) { $ImagePrty = 10 } else { $ImagePrty = 50 }
                            Section -Style Heading4 "Site Topology Diagram." {
                                Image -Base64 $Graph -Text "Site Topology Diagram" -Percent $ImagePrty -Align Center
                                Paragraph "Image preview: Opens the image in a new tab to view it at full resolution." -Tabs 2
                            }
                            BlankLine -Count 2
                        }
                    } catch {
                        Write-PScriboMessage -IsWarning "Site Topology Diagram Section: $($_.Exception.Message)"
                    }
                    try {
                        $DomainDN = Invoke-Command -Session $TempPssSession { (Get-ADDomain -Identity (Get-ADForest | Select-Object -ExpandProperty RootDomain )).DistinguishedName }
                        $InterSiteTransports = Invoke-Command -Session $TempPssSession { Get-ADObject -Filter { (objectClass -eq "interSiteTransport") } -SearchBase "CN=Inter-Site Transports,CN=Sites,CN=Configuration,$using:DomainDN" -Properties * }
                        if ($InterSiteTransports) {
                            Section -Style Heading4 'Inter-Site Transports' {
                                Paragraph "Site links in Active Directory represent the inter-site connectivity and method used to transfer replication traffic.There are two transport protocols that can be used for replication via site links. The default protocol used in site link is IP, and it performs synchronous replication between available domain controllers. The SMTP method can be used when the link between sites is not reliable."
                                BlankLine
                                try {
                                    $OutObj = @()
                                    foreach ($Item in $InterSiteTransports) {
                                        $SiteArray = @()
                                        Switch ($Item.options) {
                                            $null {
                                                $BridgeAlSiteLinks = "Yes"
                                                $IgnoreSchedules = "No"
                                            }
                                            0 {
                                                $BridgeAlSiteLinks = "Yes"
                                                $IgnoreSchedules = "No"
                                            }
                                            1 {
                                                $BridgeAlSiteLinks = "Yes"
                                                $IgnoreSchedules = "Yes"
                                            }
                                            2 {
                                                $BridgeAlSiteLinks = "No"
                                                $IgnoreSchedules = "No"
                                            }
                                            3 {
                                                $BridgeAlSiteLinks = "No"
                                                $IgnoreSchedules = "Yes"
                                            }
                                            default {
                                                $BridgeAlSiteLinks = "Unknown"
                                                $IgnoreSchedules = "Unknown"
                                            }
                                        }

                                        $inObj = [ordered] @{
                                            'Name' = $Item.Name
                                            'Bridge All Site Links' = $BridgeAlSiteLinks
                                            'Ignore Schedules' = $IgnoreSchedules
                                        }
                                        $OutObj += [pscustomobject]$inobj
                                    }

                                    $TableParams = @{
                                        Name = "Inter-Site Transports - $($ForestInfo)"
                                        List = $false
                                        ColumnWidths = 34, 33, 33
                                    }
                                    if ($Report.ShowTableCaptions) {
                                        $TableParams['Caption'] = "- $($TableParams.Name)"
                                    }
                                    $OutObj | Sort-Object -Property 'Name' | Table @TableParams
                                } catch {
                                    Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Inter-Site Transports section)"
                                }
                                try {
                                    Section -Style Heading4 'IP' {
                                        try {
                                            $IPLink = Invoke-Command -Session $TempPssSession { Get-ADReplicationSiteLink -Filter * -Properties * | Where-Object { $_.InterSiteTransportProtocol -eq "IP" } }
                                            if ($IPLink) {
                                                Section -Style Heading5 'Site Links' {
                                                    $OutObj = @()
                                                    foreach ($Item in $IPLink) {
                                                        try {
                                                            $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) (IP Site Links table)"
                                                        }
                                                    }
                                                }
                                            } else {
                                                Write-PScriboMessage -IsWarning "No IP Site Links information found in $ForestInfo, disabling the section."
                                            }
                                        } catch {
                                            Write-PScriboMessage -IsWarning "$($_.Exception.Message) (IP Site Links Section)"
                                        }
                                        try {
                                            $IPLinkBridges = Invoke-Command -Session $TempPssSession { Get-ADReplicationSiteLinkBridge -Filter * -Properties * | Where-Object { $_.InterSiteTransportProtocol -eq "IP" } }
                                            if ($IPLinkBridges) {
                                                Section -Style Heading5 'Site Link Bridges' {
                                                    $OutObj = @()
                                                    foreach ($Item in $IPLinkBridges) {
                                                        try {
                                                            $SiteArray = @()
                                                            $Sites = $Item.siteLinkList
                                                            foreach ($Object in $Sites) {
                                                                $SiteName = Invoke-Command -Session $TempPssSession { Get-ADReplicationSiteLink -Identity $using:Object }
                                                                $SiteArray += $SiteName.Name
                                                            }
                                                            $inObj = [ordered] @{
                                                                'Site Link Bridges Name' = $Item.Name
                                                                'Transport Protocol' = $Item.InterSiteTransportProtocol
                                                                'Site Links' = $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 { $_.'Protected From Accidental Deletion' -eq 'No' } | Set-Style -Style Warning -Property 'Protected From Accidental Deletion'
                                                            }

                                                            $TableParams = @{
                                                                Name = "Site Links Bridges - $($Item.Name)"
                                                                List = $true
                                                                ColumnWidths = 40, 60
                                                            }
                                                            if ($Report.ShowTableCaptions) {
                                                                $TableParams['Caption'] = "- $($TableParams.Name)"
                                                            }
                                                            $OutObj | Table @TableParams
                                                            if ($HealthCheck.Site.BestPractice -and (($OutObj | Where-Object { $_.'Protected From Accidental Deletion' -eq 'No' }) -or (($OutObj | Where-Object { $_.'Description' -eq '--' })))) {
                                                                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 { $_.'Protected From Accidental Deletion' -eq 'No' }) {
                                                                    Paragraph {
                                                                        Text "Best Practice:" -Bold
                                                                        Text "If the Site Links Bridges 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) (IP Site Links Bridges table)"
                                                        }
                                                    }
                                                }
                                            } else {
                                                Write-PScriboMessage -IsWarning "No IP Site Links Bridges information found in $ForestInfo, disabling the section."
                                            }
                                        } catch {
                                            Write-PScriboMessage -IsWarning "$($_.Exception.Message) (IP Site Links Section)"
                                        }
                                    }
                                } catch {
                                    Write-PScriboMessage -IsWarning "$($_.Exception.Message) (IP)"
                                }
                                try {
                                    $IPLink = Invoke-Command -Session $TempPssSession { Get-ADReplicationSiteLink -Filter * -Properties * | Where-Object { $_.InterSiteTransportProtocol -eq "SMTP" } }
                                    if ($IPLink) {
                                        Section -Style Heading4 'SMTP' {
                                            Paragraph "SMTP replication is used for sites that cannot use the others, but as a general rule, it should never be used. It is reserved when network connections are not always available, therefore, you can schedule replication."
                                            try {
                                                Section -Style Heading5 'Site Links' {
                                                    $OutObj = @()
                                                    foreach ($Item in $IPLink) {
                                                        try {
                                                            $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) (SMTP Site Links table)"
                                                        }
                                                    }
                                                }
                                            } catch {
                                                Write-PScriboMessage -IsWarning "$($_.Exception.Message) (SMTP Site Links Section)"
                                            }
                                            try {
                                                $IPLinkBridges = Invoke-Command -Session $TempPssSession { Get-ADReplicationSiteLinkBridge -Filter * -Properties * | Where-Object { $_.InterSiteTransportProtocol -eq "SMTP" } }
                                                if ($IPLinkBridges) {
                                                    Section -Style Heading5 'Site Link Bridges' {
                                                        $OutObj = @()
                                                        foreach ($Item in $IPLinkBridges) {
                                                            try {
                                                                $SiteArray = @()
                                                                $Sites = $Item.siteLinkList
                                                                foreach ($Object in $Sites) {
                                                                    $SiteName = Invoke-Command -Session $TempPssSession { Get-ADReplicationSiteLink -Identity $using:Object }
                                                                    $SiteArray += $SiteName.Name
                                                                }
                                                                $inObj = [ordered] @{
                                                                    'Site Link Bridges Name' = $Item.Name
                                                                    'Transport Protocol' = $Item.InterSiteTransportProtocol
                                                                    'Site Links' = $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 { $_.'Protected From Accidental Deletion' -eq 'No' } | Set-Style -Style Warning -Property 'Protected From Accidental Deletion'
                                                                }

                                                                $TableParams = @{
                                                                    Name = "Site Links Bridges - $($Item.Name)"
                                                                    List = $true
                                                                    ColumnWidths = 40, 60
                                                                }
                                                                if ($Report.ShowTableCaptions) {
                                                                    $TableParams['Caption'] = "- $($TableParams.Name)"
                                                                }
                                                                $OutObj | Table @TableParams
                                                                if ($HealthCheck.Site.BestPractice -and (($OutObj | Where-Object { $_.'Protected From Accidental Deletion' -eq 'No' }) -or (($OutObj | Where-Object { $_.'Description' -eq '--' })))) {
                                                                    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 { $_.'Protected From Accidental Deletion' -eq 'No' }) {
                                                                        Paragraph {
                                                                            Text "Best Practice:" -Bold
                                                                            Text "If the Site Links Bridges 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) (SMTP Site Links Bridges table)"
                                                            }
                                                        }
                                                    }
                                                } else {
                                                    Write-PScriboMessage -IsWarning "No SMTP Site Links Bridges information found in $ForestInfo, disabling the section."
                                                }
                                            } catch {
                                                Write-PScriboMessage -IsWarning "$($_.Exception.Message) (SMTP Site Links Section)"
                                            }
                                        }
                                    } else {
                                        Write-PScriboMessage -IsWarning "No SMTP Site Links information found in $ForestInfo, disabling the section."
                                    }
                                } catch {
                                    Write-PScriboMessage -IsWarning "$($_.Exception.Message) (SMTP)"
                                }
                            }
                        } else {
                            Write-PScriboMessage -IsWarning "No SMTP Site Links information found in $ForestInfo, disabling the section."
                        }
                    } catch {
                        Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Site Subnets)"
                    }
                    try {
                        $OutObj = @()
                        foreach ($Domain in $ADSystem.Domains | Where-Object { $_ -notin $Options.Exclude.Domains }) {
                            $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 -Name "SysvolReplication"
                                    $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 {
                                        $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 $ForestInfo, disabling the section."
                        }
                    } catch {
                        Write-PScriboMessage -IsWarning "Sysvol Replication Table Section: $($_.Exception.Message)"
                    }
                }
            } else {
                Write-PScriboMessage -IsWarning "No Sites information found in $ForestInfo, disabling the section."
            }
        } catch {
            Write-PScriboMessage -IsWarning "$($_.Exception.Message) (Domain Site Global)"
        }
    }

    end {}

}