Src/Public/Invoke-AsBuiltReport.Microsoft.AD.ps1

function Invoke-AsBuiltReport.Microsoft.AD {
    <#
    .SYNOPSIS
        PowerShell script to document the configuration of Microsoft AD in Word/HTML/Text formats
    .DESCRIPTION
        Documents the configuration of Microsoft AD in Word/HTML/Text formats using PScribo.
    .NOTES
        Version: 0.9.7
        Author: Jonathan Colon
        Twitter: @jcolonfzenpr
        Github: rebelinux
        Credits: Iain Brighton (@iainbrighton) - PScribo module
 
    .LINK
        https://github.com/AsBuiltReport/AsBuiltReport.Microsoft.AD
    #>


    # Do not remove or add to these parameters
    param (
        [String[]] $Target,
        [PSCredential] $Credential
    )

    #Requires -RunAsAdministrator

    if ($psISE) {
        Write-Error -Message $reportTranslate.InvokeAsBuiltReportMicrosoftAD.PwshISE
        break
    }

    Write-Host $reportTranslate.InvokeAsBuiltReportMicrosoftAD.ProjectWebsite -ForegroundColor White
    Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ReportModuleInfo3 -f 'Microsoft.AD') -ForegroundColor White
    Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ReportModuleInfo1 -f 'Microsoft.AD') -ForegroundColor White
    Write-Host ($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ReportModuleInfo2 -f 'Microsoft.AD') -ForegroundColor White
    Write-Host $reportTranslate.InvokeAsBuiltReportMicrosoftAD.CommunityProject -ForegroundColor White
    Write-Host "$($reportTranslate.InvokeAsBuiltReportMicrosoftAD.ReportModuleInfo4) " -NoNewline
    Write-Host $reportTranslate.InvokeAsBuiltReportMicrosoftAD.ReportModuleInfo5 -ForegroundColor Cyan
    Write-Host $reportTranslate.InvokeAsBuiltReportMicrosoftAD.ReportModuleInfo6

    # Check the version of the dependency modules
    $ModuleArray = @('AsBuiltReport.Microsoft.AD', 'Diagrammer.Microsoft.AD', 'Diagrammer.Core')

    foreach ($Module in $ModuleArray) {
        try {
            $InstalledVersion = Get-Module -ListAvailable -Name $Module -ErrorAction SilentlyContinue | Sort-Object -Property Version -Descending | Select-Object -First 1 -ExpandProperty Version

            if ($InstalledVersion) {
                Write-Host " - $Module module v$($InstalledVersion.ToString()) is currently installed."
                $LatestVersion = Find-Module -Name $Module -Repository PSGallery -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Version
                if ($InstalledVersion -lt $LatestVersion) {
                    Write-Host " - $Module module v$($LatestVersion.ToString()) is available." -ForegroundColor Red
                    Write-Host " - Run 'Update-Module -Name $Module -Force' to install the latest version." -ForegroundColor Red
                }
            }
        } catch {
            Write-PScriboMessage -IsWarning $_.Exception.Message
        }
    }

    #Validate Required Modules and Features
    $OSType = (Get-ComputerInfo).OsProductType
    if ($OSType -eq 'WorkStation') {
        Get-RequiredFeature -Name 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0' -OSType $OSType
        Get-RequiredFeature -Name 'Rsat.CertificateServices.Tools~~~~0.0.1.0' -OSType $OSType
        Get-RequiredFeature -Name 'Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0' -OSType $OSType
        Get-RequiredFeature -Name 'Rsat.Dns.Tools~~~~0.0.1.0' -OSType $OSType
    }
    if ($OSType -eq 'Server' -or $OSType -eq 'DomainController') {
        Get-RequiredFeature -Name RSAT-AD-PowerShell -OSType $OSType
        Get-RequiredFeature -Name RSAT-ADCS -OSType $OSType
        Get-RequiredFeature -Name RSAT-ADCS-mgmt -OSType $OSType
        Get-RequiredFeature -Name RSAT-DNS-Server -OSType $OSType
        Get-RequiredFeature -Name GPMC -OSType $OSType
    }

    Get-RequiredModule -Name PSPKI -Version '4.3.0'

    # Import Report Configuration
    $script:Report = $ReportConfig.Report
    $script:InfoLevel = $ReportConfig.InfoLevel
    $script:Options = $ReportConfig.Options

    # Used to set values to TitleCase where required
    $script:TextInfo = (Get-Culture).TextInfo

    if ($Healthcheck) {
        Section -Style TOC -ExcludeFromTOC 'DISCLAIMER' {
            Paragraph $reportTranslate.InvokeAsBuiltReportMicrosoftAD.DISCLAIMER
        }
        PageBreak
    }

    #---------------------------------------------------------------------------------------------#
    # Connection Section #
    #---------------------------------------------------------------------------------------------#
    foreach ($System in $Target) {

        if (Select-String -InputObject $System -Pattern "^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") {
            throw "Please use the Fully Qualified Domain Name (FQDN) instead of an IP address when connecting to the Domain Controller: $System"
        }

        if ($Options.WinRMSSL) {
            $WinRMType = "WinRM with SSL"
            $CIMType = "CIM with SSL"
        } else {
            $WinRMType = "WinRM"
            $CIMType = "CIM"
        }

        # WinRM Session variables
        $DCStatus = @()
        $DomainStatus = @()
        $CIMTable = @()
        $PSSTable = @()

        try {
            $script:TempPssSession = Get-ValidPSSession -ComputerName $System -SessionName $System -PSSTable ([ref]$PSSTable)
        } catch {
            throw "Failed to establish a PSSession ($WinRMType) with the Domain Controller '$System': $($_.Exception.Message)"
        }

        try {
            # By default, SSL is not used with New-CimSession. WsMan encrypts all content that is transmitted over the network, even when using HTTP.
            $script:TempCIMSession = Get-ValidCIMSession -ComputerName $System -SessionName $System -CIMTable ([ref]$CIMTable)
        } catch {
            Write-PScriboMessage -IsWarning -Message "Unable to establish a CimSession ($CIMType) with the Domain Controller '$System'."
        }

        try {
            Write-PScriboMessage -Message "Connecting to retrieve Forest information from Domain Controller '$System'."
            $script:ADSystem = Invoke-CommandWithTimeout -Session $TempPssSession -ScriptBlock { Get-ADForest -ErrorAction Stop }
        } catch {
            throw "Unable to retrieve Forest information from Domain Controller '$System'."
        }

        $script:ForestInfo = $ADSystem.RootDomain.toUpper()
        [array]$RootDomains = $ADSystem.RootDomain
        if ($Options.Include.Domains) {
            [array]$ChildDomains = $ADSystem.Domains | Where-Object { $_ -ne $RootDomains -and $_ -in $Options.Include.Domains }
        } else {
            [array]$ChildDomains = $ADSystem.Domains | Where-Object { $_ -ne $RootDomains -and $_ -notin $Options.Exclude.Domains }
        }
        [array] $script:OrderedDomains = @($RootDomains, $ChildDomains)

        # Forest Section
        Get-AbrForestSection

        # Domain Section
        Get-AbrDomainSection -DomainStatus ([ref]$DomainStatus)

        # DNS Section
        Get-AbrDnsSection -DomainStatus ([ref]$DomainStatus)

        # PKI Section
        Get-AbrPKISection

        #---------------------------------------------------------------------------------------------#
        # Export Diagram Section #
        #---------------------------------------------------------------------------------------------#

        if ($Options.ExportDiagrams) {
            Write-Host " "
            Write-Host "ExportDiagrams option enabled: Exporting diagrams:"
            $Options.DiagramType.PSobject.Properties | ForEach-Object {
                if ($_.Value -and $_.Name -eq 'Trusts') {
                    foreach ($Domain in $OrderedDomains) {
                        $ValidDC = Get-ValidDCfromDomain -Domain $Domain[0] -DCStatus ([ref]$DCStatus)
                        if ($ValidDC) {
                            $DCPssSession = Get-ValidPSSession -ComputerName $ValidDC -SessionName $($ValidDC) -PSSTable ([ref]$PSSTable)
                            if ($DCPssSession) {
                                Get-AbrDiagrammer -DiagramType $_.Name -PSSessionObject $DCPssSession -Domain $Domain[0] -FileName "AsBuiltReport.$($Global:Report)-($($_.Name))-$($Domain[0])"
                            }
                        }
                    }
                } elseif ($_.Value) {
                    Get-AbrDiagrammer -DiagramType $_.Name -PSSessionObject $TempPssSession
                }
            }
        }

        #---------------------------------------------------------------------------------------------#
        # Clean Connection Sessions Section #
        #---------------------------------------------------------------------------------------------#
        if ($PSSTable) {
            foreach ($PSSession in ($PSSTable | Where-Object { $_.Status -ne 'Offline' })) {
                # Remove used CIMSession
                Write-PScriboMessage -Message "Clearing PSSession with ID $($PSSession.Id)"
                Remove-PSSession -Id $PSSession.id
            }
        }

        if ($CIMTable) {
            foreach ($CIMSession in ($CIMTable | Where-Object { $_.Status -ne 'Offline' })) {
                # Remove used CIMSession
                Write-PScriboMessage -Message "Clearing CIM Session with ID $($CIMSession.Id)"
                Remove-CimSession -Id $CIMSession.id
            }
        }

        #---------------------------------------------------------------------------------------------#
        # Connection Status Section #
        #---------------------------------------------------------------------------------------------#

        $DCOffine = $DCStatus | Where-Object { $Null -ne $_.DCName -and $_.Status -eq 'Offline' } | Select-Object -Property @{N = 'Name'; E = { $_.DCName } }, @{N = 'WinRM Status'; E = { $_.Status } }, @{N = 'Ping Status'; E = { $_.PingStatus } }, @{N = 'Protocol'; E = { $_.Protocol } } | ForEach-Object { [pscustomobject]$_ }
        $DomainOffline = $DomainStatus | Where-Object { $Null -ne $_.Name -and $_.Status -eq 'Offline' }
        if ($DCOffine -or $DomainOffline) {
            Write-Host " "
            Write-Host "The following Systems could not be reached:`n"
            if ($DCOffine) {
                Write-Host "Domain Controllers"
                Write-Host "------------------"
                Write-Host " "
                Write-PSObject $DCOffine -MatchMethod Query, Query, Query, Query -Column 'WinRM Status', 'WinRM Status', 'Ping Status', 'Ping Status' -Value "'WinRM Status' -eq 'Offline'", "'WinRM Status' -eq 'Online'", "'Ping Status' -eq 'Offline'", "'Ping Status' -eq 'Online'" -ValueForeColor Red, Green, Red, Green
                Write-Host " "
            }
            if ($DomainOffline) {
                Write-Host "Domains"
                Write-Host "--------"
                Write-Host " "
                $DomainOffline | ForEach-Object {
                    Write-Host "$($_.Name)" -ForegroundColor Red
                }
                Write-Host " "
            }
        }
    }#endregion foreach loop
}