Src/Private/Get-AbrADForest.ps1

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

    [CmdletBinding()]
    param (
    )

    begin {
        Write-PscriboMessage "Discovering Active Directory forest information."
    }

    process {
        try {
            $Data = Invoke-Command -Session $TempPssSession {Get-ADForest}
            $ForestInfo =  $Data.RootDomain.toUpper()
            Write-PscriboMessage "Discovered Active Directory information of forest $ForestInfo."
            $DomainDN = Invoke-Command -Session $TempPssSession {(Get-ADDomain -Identity (Get-ADForest | Select-Object -ExpandProperty RootDomain )).DistinguishedName}
            $TombstoneLifetime = Invoke-Command -Session $TempPssSession {Get-ADObject "CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$using:DomainDN" -Properties tombstoneLifetime | Select-Object -ExpandProperty tombstoneLifetime}
            $ADVersion = Invoke-Command -Session $TempPssSession {Get-ADObject (Get-ADRootDSE).schemaNamingContext -property objectVersion | Select-Object -ExpandProperty objectVersion}
            $ValuedsHeuristics = Invoke-Command -Session $TempPssSession {Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$(($using:DomainDN))" -Properties dsHeuristics -ErrorAction SilentlyContinue}

            If ($ADVersion -eq '88') {$server = 'Windows Server 2019'}
            ElseIf ($ADVersion -eq '87') {$server = 'Windows Server 2016'}
            ElseIf ($ADVersion -eq '69') {$server = 'Windows Server 2012 R2'}
            ElseIf ($ADVersion -eq '56') {$server = 'Windows Server 2012'}
            ElseIf ($ADVersion -eq '47') {$server = 'Windows Server 2008 R2'}
            ElseIf ($ADVersion -eq '44') {$server = 'Windows Server 2008'}
            ElseIf ($ADVersion -eq '31') {$server = 'Windows Server 2003 R2'}
            ElseIf ($ADVersion -eq '30') {$server = 'Windows Server 2003'}
            $OutObj = @()
            if ($Data) {
                Write-PscriboMessage "Collecting Active Directory information of forest $ForestInfo."
                foreach ($Item in $Data) {
                    try {
                        $inObj = [ordered] @{
                            'Forest Name' = $Item.RootDomain
                            'Forest Functional Level' = $Item.ForestMode
                            'Schema Version' = "ObjectVersion $ADVersion, Correspond to $server"
                            'Tombstone Lifetime (days)' = $TombstoneLifetime
                            'Domains' = $Item.Domains -join '; '
                            'Global Catalogs' = $Item.GlobalCatalogs -join '; '
                            'Domains Count' = $Item.Domains.Count
                            'Global Catalogs Count' = $Item.GlobalCatalogs.Count
                            'Sites Count' = $Item.Sites.Count
                            'Application Partitions' = $Item.ApplicationPartitions
                            'PartitionsContainer' = [string]$Item.PartitionsContainer
                            'SPN Suffixes' = ConvertTo-EmptyToFiller $Item.SPNSuffixes
                            'UPN Suffixes' = ConvertTo-EmptyToFiller ($Item.UPNSuffixes -join ', ')
                            'Anonymous Access (dsHeuristics)' = &{
                                if (($ValuedsHeuristics.dsHeuristics -eq "") -or ($ValuedsHeuristics.dsHeuristics.Length -lt 7)) {
                                    "Disabled"
                                } elseif (($ValuedsHeuristics.dsHeuristics.Length -ge 7) -and ($ValuedsHeuristics.dsHeuristics[6] -eq "2")) {
                                    "Enabled"
                                }
                            }
                        }
                        $OutObj += [pscustomobject]$inobj
                    }
                    catch {
                        Write-PscriboMessage -IsWarning $_.Exception.Message
                    }
                }

                if ($HealthCheck.Domain.Security) {
                    $OutObj | Where-Object { $_.'Anonymous Access (dsHeuristics)' -eq 'Enabled'} | Set-Style -Style Warning -Property 'Anonymous Access (dsHeuristics)'
                    $OutObj | Where-Object { $_.'Tombstone Lifetime (days)' -lt 180 } | Set-Style -Style Warning -Property 'Tombstone Lifetime (days)'
                }

                $TableParams = @{
                    Name = "Forest Summary - $($ForestInfo)"
                    List = $true
                    ColumnWidths = 40, 60
                }
                if ($Report.ShowTableCaptions) {
                    $TableParams['Caption'] = "- $($TableParams.Name)"
                }
                $OutObj | Table @TableParams
                if ($HealthCheck.Domain.Security -and ($OutObj | Where-Object { $_.'Anonymous Access (dsHeuristics)' -eq 'Enabled'}) ) {
                    Paragraph "Health Check:" -Bold -Underline
                    BlankLine
                    if ($OutObj | Where-Object { $_.'Anonymous Access (dsHeuristics)' -eq 'Enabled'}) {
                        Paragraph {
                            Text "Best Practice:" -Bold
                            Text "Anonymous Access to Active Directory forest data above the rootDSE level must be disabled."
                        }
                        Paragraph "Reference:" -Bold
                        Blankline
                        Paragraph "https://www.stigviewer.com/stig/active_directory_forest/2016-02-19/finding/V-8555" -Color blue
                        BlankLine
                    }
                    if ($OutObj | Where-Object { $_.'Tombstone Lifetime (days)' -lt 180 }) {
                        Paragraph {
                            Text "Best Practice:" -Bold
                            Text "Change the Tombstone Lifetime to 180 days, at a minimum."
                        }
                    }
                }
            }
        }
        catch {
            Write-PscriboMessage -IsWarning $_.Exception.Message
        }
        try {
            Section -Style Heading3 'Certificate Authority' {
                if ($Options.ShowDefinitionInfo) {
                    Paragraph 'In cryptography, a certificate authority or certification authority (CA) is an entity that issues digital certificates. A digital certificate certifies the ownership of a public key by the named subject of the certificate. This allows others (relying parties) to rely upon signatures or on assertions made about the private key that corresponds to the certified public key. A CA acts as a trusted third party trusted both by the subject (owner) of the certificate and by the party relying upon the certificate. The format of these certificates is specified by the X.509 or EMV standard.'
                    BlankLine
                }
                if (!$Options.ShowDefinitionInfo) {
                    Paragraph "The following section provides a summary of the Active Directory PKI Infrastructure Information."
                    BlankLine
                }
                Write-PscriboMessage "Discovering certificate authority information on forest $ForestInfo."
                $ConfigNCDN = $Data.PartitionsContainer.Split(',') | Select-Object -Skip 1
                $rootCA = Get-ADObjectSearch -DN "CN=Certification Authorities,CN=Public Key Services,CN=Services,$($ConfigNCDN -join ',')" -Filter { objectClass -eq "certificationAuthority" } -Properties "Name" -SelectPrty 'DistinguishedName','Name' -Session $TempPssSession
                if ($rootCA) {
                    Section -ExcludeFromTOC -Style NOTOCHeading4 'Certification Authority Root(s)' {
                        $OutObj = @()
                        Write-PscriboMessage "Discovered Certificate Authority Information on forest $ForestInfo."
                        foreach ($Item in $rootCA) {
                            try {
                                Write-PscriboMessage "Collecting Certificate Authority Information '$($Item.Name)'"
                                $inObj = [ordered] @{
                                    'Name' = $Item.Name
                                    'Distinguished Name' = $Item.DistinguishedName
                                }
                                $OutObj += [pscustomobject]$inobj
                            }
                            catch {
                                Write-PscriboMessage -IsWarning $_.Exception.Message
                            }
                        }

                        if ($HealthCheck.Forest.BestPractice) {
                            ($OutObj | Measure-Object).Count -gt 1 | Set-Style -Style Warning
                        }

                        $TableParams = @{
                            Name = "Certificate Authority Root(s) - $($ForestInfo)"
                            List = $false
                            ColumnWidths = 40, 60
                        }
                        if ($Report.ShowTableCaptions) {
                            $TableParams['Caption'] = "- $($TableParams.Name)"
                        }
                        $OutObj | Sort-Object -Property 'Name' | Table @TableParams
                        if ($HealthCheck.Forest.BestPractice -and (($OutObj | Measure-Object).Count -gt 1 ) ) {
                            Paragraph "Health Check:" -Bold -Underline
                            BlankLine
                            Paragraph {
                                Text "Best Practice:" -Bold
                                Text "In most PKI implementations, it is not typical to have multiple Root CAs. Its recommended a detailed review of the current PKI infrastructure and Root CA requirements."
                            }
                        }
                    }
                }
                Write-PscriboMessage "Discovering certificate authority issuers on forest $ForestInfo."
                $ConfigNCDN = $Data.PartitionsContainer.Split(',') | Select-Object -Skip 1
                $subordinateCA = Get-ADObjectSearch -DN "CN=Enrollment Services,CN=Public Key Services,CN=Services,$($ConfigNCDN -join ',')" -Filter { objectClass -eq "pKIEnrollmentService" } -Properties "*" -SelectPrty 'dNSHostName','Name' -Session $TempPssSession
                if ($subordinateCA) {
                    Section -ExcludeFromTOC -Style NOTOCHeading4 'Certification Authority Issuer(s)' {
                        $OutObj = @()
                        Write-PscriboMessage "Discovered Certificate Authority issuers on forest $ForestInfo."
                        foreach ($Item in $subordinateCA) {
                            try {
                                Write-PscriboMessage "Collecting Certificate Authority issuers '$($Item.Name)'"
                                $inObj = [ordered] @{
                                    'Name' = $Item.Name
                                    'DNS Name' = $Item.dNSHostName
                                }
                                $OutObj += [pscustomobject]$inobj
                            }
                            catch {
                                Write-PscriboMessage -IsWarning $_.Exception.Message
                            }
                        }

                        $TableParams = @{
                            Name = "Certificate Authority Issuer(s) - $($ForestInfo)"
                            List = $false
                            ColumnWidths = 40, 60
                        }
                        if ($Report.ShowTableCaptions) {
                            $TableParams['Caption'] = "- $($TableParams.Name)"
                        }
                        $OutObj | Sort-Object -Property 'Name' | Table @TableParams
                    }
                }
            }
        }
        catch {
            Write-PscriboMessage -IsWarning $_.Exception.Message
        }
        try {
            Section -Style Heading3 'Optional Features' {
                Write-PscriboMessage "Discovering Optional Features enabled on forest $ForestInfo."
                $Data = Invoke-Command -Session $TempPssSession {Get-ADOptionalFeature -Filter *}
                $OutObj = @()
                if ($Data) {
                    Write-PscriboMessage "Discovered Optional Features enabled on forest $ForestInfo."
                    foreach ($Item in $Data) {
                        try {
                            Write-PscriboMessage "Collecting Optional Features '$($Item.Name)'"
                            $inObj = [ordered] @{
                                'Name' = $Item.Name
                                'Required Forest Mode' = $Item.RequiredForestMode
                                'Enabled' = Switch (($Item.EnabledScopes).count) {
                                    0 {'No'}
                                    default {'Yes'}
                                }
                            }
                            $OutObj += [pscustomobject]$inobj
                        }
                        catch {
                            Write-PscriboMessage -IsWarning $_.Exception.Message
                        }
                    }

                    if ($HealthCheck.Forest.BestPractice) {
                        $OutObj | Where-Object { $_.'Name' -eq 'Recycle Bin Feature' -and $_.'Enabled' -eq 'No'} | Set-Style -Style Warning -Property 'Enabled'
                    }

                    $TableParams = @{
                        Name = "Optional Features - $($ForestInfo)"
                        List = $false
                        ColumnWidths = 40, 30, 30
                    }
                    if ($Report.ShowTableCaptions) {
                        $TableParams['Caption'] = "- $($TableParams.Name)"
                    }
                    $OutObj | Sort-Object -Property 'Name' | Table @TableParams
                    if ($HealthCheck.Forest.BestPractice -and ($OutObj | Where-Object { $_.'Name' -eq 'Recycle Bin Feature' -and $_.'Enabled' -eq 'No'}) ) {
                        Paragraph "Health Check:" -Bold -Underline
                        BlankLine
                        Paragraph {
                            Text "Best Practice:" -Bold
                            Text "Accidental deletion of Active Directory objects is common for Active Directory Domain Services (AD DS) users. With the Recycle Bin Feature, one could recover accidentally deleted objects in Active Directory. Enable the Recycle Bin feature for the forest."
                        }
                        BlankLine
                        Paragraph {
                            Text "Reference:" -Bold
                            BlankLine
                            Text "https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-ad-recycle-bin-understanding-implementing-best-practices-and/ba-p/396944"

                        }
                    }
                }
            }
        }
        catch {
            Write-PscriboMessage -IsWarning $_.Exception.Message
        }
    }

    end {}

}