Testimo.psm1

function Get-WinADDC {
    [CmdletBinding()]
    param([string] $Domain = $Env:USERDNSDOMAIN)
    $Output = Get-ADDomainController -Server $Domain -Filter * -ErrorAction Stop
    $Output
}
function Get-WinADDomain {
    [CmdletBinding()]
    param([string] $Domain)
    $Output = Get-ADDomain -Server $Domain -ErrorAction Stop
    $Output
}
function Get-WinADForest {
    [CmdletBinding()]
    param()
    $Output = Get-ADForest -ErrorAction Stop
    $Output
}
function Out-Begin {
    param([string] $Text,
        [int] $Level,
        [string] $Type = 't',
        [string] $Domain,
        [string] $DomainController)
    if ($Domain -and $DomainController) {
        if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow } else { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow }
        $TestText = "[$Type]", "[$Domain]", "[$($DomainController)] ", $Text
    } elseif ($Domain) {
        if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow } else { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow }
        $TestText = "[$Type]", "[$Domain] ", $Text
    } elseif ($DomainController) { Write-Warning "Out-Begin - Shouldn't happen - Fix me." } else {
        if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow } else { [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow }
        $TestText = "[$Type]", "[Forest] ", $Text
    }
    Write-Color -Text $TestText -Color $Color -StartSpaces $Level -NoNewLine
}
function Out-Failure {
    param([string] $Text,
        [int] $Level,
        [string] $ExtendedValue = 'Input data not provided. Failing test.',
        [string] $Domain,
        [string] $DomainController)
    Out-Begin -Text $Text -Level $Level
    Out-Status -Text $Text -Status $false -ExtendedValue $ExtendedValue -Domain $Domain -DomainController $DomainController
}
function Out-Status {
    [CmdletBinding()]
    param([string] $Text,
        [nullable[bool]] $Status,
        [string] $Section,
        [string] $ExtendedValue,
        [string] $Domain,
        [string] $DomainController)
    if ($Status -eq $true) {
        [string] $TextStatus = 'Pass'
        [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::Green, [ConsoleColor]::Cyan, [ConsoleColor]::Cyan, [ConsoleColor]::Green, [ConsoleColor]::Cyan
    } elseif ($Status -eq $false) {
        [string] $TextStatus = 'Fail'
        [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::Red, [ConsoleColor]::Cyan, [ConsoleColor]::Cyan, [ConsoleColor]::Red, [ConsoleColor]::Cyan
    } else {
        [string] $TextStatus = 'Informative'
        [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Cyan, [ConsoleColor]::Cyan, [ConsoleColor]::DarkMagenta, [ConsoleColor]::Cyan
    }
    if ($ExtendedValue) { Write-Color -Text ' [', $TextStatus, ']', " [", $ExtendedValue, "]" -Color $Color } else { Write-Color -Text ' [', $TextStatus, ']' -Color $Color }
    if ($Domain -and $DomainController) {
        $TestType = 'Domain Controller'
        $TestText = "Domain Controller - $DomainController | $Text"
    } elseif ($Domain) {
        $TestType = 'Domain'
        $TestText = "Domain - $Domain | $Text"
    } elseif ($DomainController) { $TestType = 'Should not happen. Find an error.' } else {
        $TestType = 'Forest Wide'
        $TestText = "Forest | $Text"
    }
    if ($null -ne $Status) {
        $Script:TestResults.Add([PSCustomObject]@{Test = $TestText
                TestType = $TestType
                Domain = $Domain
                DomainController = $DomainController
                Status = $Status
                Extended = $ExtendedValue
            })
    }
}
function Out-Summary {
    [CmdletBinding()]
    param([System.Diagnostics.Stopwatch] $Time,
        $Text,
        [int] $Level,
        [string] $Domain,
        [string] $DomainController,
        [PSCustomobject] $TestsSummary)
    $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    $Type = 'i'
    if ($Domain -and $DomainController) {
        if ($Type -eq 't') {
            [ConsoleColor[]] $Color = @([ConsoleColor]::Cyan,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray)
        } else {
            [ConsoleColor[]] $Color = @([ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::DarkGray
                [ConsoleColor]::Yellow,
                [ConsoleColor]::White,
                [ConsoleColor]::Yellow
                [ConsoleColor]::Green
                [ConsoleColor]::Yellow
                [ConsoleColor]::Red
                [ConsoleColor]::Yellow
                [ConsoleColor]::Cyan)
        }
        $TestText = @("[$Type]",
            "[$Domain]",
            "[$($DomainController)] ",
            $Text,
            ' [',
            'Time to execute tests: ',
            $EndTime,
            ']',
            '[',
            'Tests Total: ',
            ($TestsSummary.Total),
            ', Passed: ',
            ($TestsSummary.Passed),
            ', Failed: ',
            ($TestsSummary.Failed),
            ', Skipped: ',
            ($TestsSummary.Skipped),
            ']')
    } elseif ($Domain) {
        if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray } else {
            [ConsoleColor[]] $Color = @([ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::DarkGray
                [ConsoleColor]::Yellow,
                [ConsoleColor]::White,
                [ConsoleColor]::Yellow
                [ConsoleColor]::Green
                [ConsoleColor]::Yellow
                [ConsoleColor]::Red
                [ConsoleColor]::Yellow
                [ConsoleColor]::Cyan)
        }
        $TestText = @("[$Type]",
            "[$Domain] ",
            $Text,
            ' [',
            'Time to execute tests: ',
            $EndTime,
            ']',
            '[',
            'Tests Total: ',
            ($TestsSummary.Total),
            ', Passed: ',
            ($TestsSummary.Passed),
            ', Failed: ',
            ($TestsSummary.Failed),
            ', Skipped: ',
            ($TestsSummary.Skipped),
            ']')
    } elseif ($DomainController) { Write-Warning "Out-Begin - Shouldn't happen - Fix me." } else {
        if ($Type -eq 't') { [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray } else {
            [ConsoleColor[]] $Color = @([ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::Yellow,
                [ConsoleColor]::DarkGray,
                [ConsoleColor]::DarkGray
                [ConsoleColor]::Yellow,
                [ConsoleColor]::White,
                [ConsoleColor]::Yellow
                [ConsoleColor]::Green
                [ConsoleColor]::Yellow
                [ConsoleColor]::Red
                [ConsoleColor]::Yellow
                [ConsoleColor]::Cyan)
        }
        $TestText = @("[$Type]",
            "[Forest] ",
            $Text,
            ' [',
            'Time to execute tests: ',
            $EndTime,
            ']',
            '[',
            'Tests Total: ',
            ($TestsSummary.Total),
            ', Passed: ',
            ($TestsSummary.Passed),
            ', Failed: ',
            ($TestsSummary.Failed),
            ', Skipped: ',
            ($TestsSummary.Skipped),
            ']')
    }
    Write-Color -Text $TestText -Color $Color -StartSpaces $Level
}
$Script:SBDomain = { param([string] $Domain)
    Get-WinADDomain -Domain $Domain }
$Script:SBDomainControllers = { param([string] $Domain)
    Get-WinADDC -Domain $Domain }
$Script:SBDomainControllersDiskSpace = { Get-ComputerDiskLogical -ComputerName $DomainController -OnlyLocalDisk -WarningAction SilentlyContinue }
$Script:SBDomainControllersDiskSpacePercent = { Test-Value -TestName "Disk Free Percent" -Property 'FreePercent' -PropertyExtendedValue 'FreePercent' @args }
$Script:SBDomainControllersDiskSpaceGB = { Test-Value -TestName "Disk Free Size" -Property 'FreeSpace' -PropertyExtendedValue 'FreeSpace' @args }
$Script:SBDomainControllersLDAP = { Test-LDAP -ComputerName $DomainController -WarningAction SilentlyContinue }
$Script:SBDomainControllersLDAP_Port = { Test-Value -TestName "LDAP Port is Available" -Property 'LDAP' @args }
$Script:SBDomainControllersLDAP_PortSSL = { Test-Value -TestName "LDAP SSL Port is Available" -Property 'LDAPS' @args }
$Script:SBDomainControllersLDAP_PortGC = { Test-Value -TestName "LDAP GC Port is Available" -Property 'GlobalCatalogLDAP' @args }
$Script:SBDomainControllersLDAP_PortGC_SSL = { Test-Value -TestName "LDAP GC SSL Port is Available" -Property 'GlobalCatalogLDAPS' @args }
$Script:SBDomainControllersPing = { Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue }
$Script:SBDomainControllersPingTest = { Test-Value -TestName "Responds to PING" -Property 'PingSucceeded' -PropertyExtendedValue 'PingReplyDetails', 'RoundtripTime' @args }
$Script:SBDomainControllersPort53 = { Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue -Port 53 }
$Script:SBDomainControllersPort53Test = { Test-Value -TestName "Port is open" -Property 'TcpTestSucceeded' @args }
$Script:SBDomainControllersRespondsPS = { Get-WinADDomain -Domain $DomainController }
$Script:SBDomainControllersServices = { $Services = @('ADWS', 'DNS', 'DFS', 'DFSR', 'Eventlog', 'EventSystem', 'KDC', 'LanManWorkstation', 'LanManServer', 'NetLogon', 'NTDS', 'RPCSS', 'SAMSS', 'W32Time')
    Get-PSService -Computers $DomainController -Services $Services }
$Script:SBDomainControllersServicesTestStatus = { foreach ($_ in $Object) { Test-Value -TestName "Service $($_.Name) Status" -Property 'Status' -Object $_ -ExpectedValue 'Running' -Level $LevelTest -Domain $Domain -DomainController $DomainController } }
$Script:SBDomainControllersServicesTestStartType = { foreach ($_ in $Object) { Test-Value -TestName "Service $($_.Name) Start Type" -Property 'StartType' -Object $_ -ExpectedValue 'Automatic' -Level $LevelTest -Domain $Domain -DomainController $DomainController } }
$Script:SBDomainPasswordComplexity = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    & $ADModule { param($Domain); Get-WinADDomainDefaultPasswordPolicy -Domain $Domain } $Domain }
$Script:SBDomainPasswordComplexityTest1 = { Test-Value -TestName 'Complexity Enabled' -Property 'Complexity Enabled' @args }
$Script:SBDomainPasswordComplexityTest2 = { Test-Value -TestName 'Lockout Duration' -Property 'Lockout Duration' @args }
$Script:SBDomainPasswordComplexityTest3 = { Test-Value -TestName 'Lockout Observation Window' -Property 'Lockout Observation Window' @args }
$Script:SBDomainPasswordComplexityTest4 = { Test-Value -TestName 'Lockout Threshold' -Property 'Lockout Threshold' @args }
$Script:SBDomainPasswordComplexityTest5 = { Test-Value -TestName 'Max Password Age' -Property 'Max Password Age' @args }
$Script:SBDomainPasswordComplexityTest6 = { Test-Value -TestName 'Min Password Length' -Property 'Min Password Length' @args }
$Script:SBDomainPasswordComplexityTest7 = { Test-Value -TestName 'Min Password Age' -Property 'Min Password Age' @args }
$Script:SBDomainPasswordComplexityTest8 = { Test-Value -TestName 'Password History Count' -Property 'Password History Count' @args }
$Script:SBDomainPasswordComplexityTest9 = { Test-Value -TestName 'Reversible Encryption Enabled' -Property 'Reversible Encryption Enabled' @args }
$Script:SBDomainScavenging = { $PSWinDocumentationDNS = Import-Module PSWinDocumentation.DNS -PassThru
    & $PSWinDocumentationDNS { param($Domain)
        $Object = Get-WinDnsServerScavenging -Domain $Domain
        $Object | Where-Object { $_.ScavengingInterval -ne 0 -and $null -ne $_.ScavengingInterval } } $Domain }
$Script:SBDomainScavengingTest0 = { Test-Value -TestName 'Scavenging DNS Servers Count' @args }
$Script:SBDomainScavengingTest1 = { Test-Value -TestName 'Scavenging Interval' -Property 'ScavengingInterval', 'Days' @args }
$Script:SBDomainScavengingTest2 = { Test-Value -TestName 'Scavenging State' -Property 'ScavengingState' @args }
$Script:SBDomainScavengingTest3 = { Test-Value -TestName 'Last Scavenge Time' -Property 'LastScavengeTime' @args }
$Script:SBDomainTimeSynchronizationInternal = { Get-ComputerTime -TimeTarget $DomainController -WarningAction SilentlyContinue }
$Script:SBDomainTimeSynchronizationExternal = { Get-ComputerTime -TimeTarget $DomainController -TimeSource 'pool.ntp.org' -WarningAction SilentlyContinue }
$Script:SBDomainTimeSynchronizationTest1 = { Test-Value -TestName 'Time Difference' -Property 'TimeDifferenceSeconds' @args }
$Script:SBDomainTrustsData = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    & $ADModule { param($Domain)
        Get-WinADDomainTrusts -Domain $Domain } $Domain @args }
$Script:SBDomainTrustsConnectivity = { foreach ($_ in $Object) { Test-Value -TestName "Trust status verification | Source $Domain, Target $($_.'Trust Target'), Direction $($_.'Trust Direction')" -Property 'Trust Status' -ExpectedValue 'OK' -Object $_ -Level $LevelTest -Domain $Domain -DomainController $DomainController } }
$Script:SBDomainTrustsUnconstrainedDelegation = { foreach ($_ in $Object) { if ($($_.'Trust Direction' -eq 'BiDirectional' -or $_.'Trust Direction' -eq 'InBound')) { Test-Value -TestName "Trust Unconstrained TGTDelegation | Source $Domain, Target $($_.'Trust Target'), Direction $($_.'Trust Direction')" -Property 'TGTDelegation' -ExpectedValue $True -Object $_ -Level $LevelTest -Domain $Domain -DomainController $DomainController } } }
$Script:SBForest = { Get-WinADForest }
$Script:SBForestLastBackup = { Get-WinADLastBackup }
$Script:SBForestLastBackupTest = { foreach ($_ in $Object) { Test-Value -Level $LevelTest -TestName "Last Backup $($_.NamingContext)" -Object $_ -Property 'LastBackupDaysAgo' -PropertyExtendedValue 'LastBackup' -lt -ExpectedValue 2 -Domain $Domain -DomainController $DomainController } }
$Script:SBForestOptionalFeatures = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    & $ADModule { Get-WinADForestOptionalFeatures -WarningAction SilentlyContinue } }
$Script:SBForestOptionalFeaturesTest1 = { Test-Value -TestName 'Recycle Bin Enabled' -Property 'Recycle Bin Enabled' @args }
$Script:SBForestOptionalFeaturesTest2 = { Test-Value -TestName 'LAPS Schema Extended' -Property 'Laps Enabled' @args }
$Script:SBForestOptionalFeaturesTest3 = { Test-Value -TestName 'Privileged Access Management Enabled' -Property 'Privileged Access Management Feature Enabled' @args }
$Script:SBForestReplication = { Get-WinADForestReplication -WarningAction SilentlyContinue }
$Script:SBForestReplicationTest1 = { foreach ($_ in $Object) { Test-Value -TestName "Replication from $($_.Server) to $($_.ServerPartner)" -Property 'Status' -PropertyExtendedValue 'StatusMessage' -ExpectedValue $True -Level $LevelTest -Object $_ -Domain $Domain -DomainController $DomainController } }
function Start-Testing {
    [CmdletBinding()]
    param([ScriptBlock] $Execute,
        [string] $Scope,
        [string] $Domain,
        [string] $DomainController)
    $GlobalTime = Start-TimeLog
    if ($Scope -eq 'Forest') {
        $Level = 3
        $LevelTest = 6
        $LevelSummary = 3
        $LevelTestFailure = 6
    } elseif ($Scope -eq 'Domain') {
        $Level = 6
        $LevelTest = 9
        $LevelSummary = 6
        $LevelTestFailure = 9
    } elseif ($Scope -eq 'DomainControllers') {
        $Level = 9
        $LevelTest = 12
        $LevelSummary = 9
        $LevelTestFailure = 12
    } else { }
    if ($Domain -and $DomainController) { $SummaryText = "Domain $Domain, $DomainController" } elseif ($Domain) {
        Write-Color
        $SummaryText = "Domain $Domain"
    } else { $SummaryText = "Forest" }
    Out-Begin -Type 'i' -Text $SummaryText -Level ($LevelSummary - 3) -Domain $Domain -DomainController $DomainController
    Out-Status -Text $SummaryText -Status $null -ExtendedValue '' -Domain $Domain -DomainController $DomainController
    $TestsSummaryTogether = @(foreach ($Source in $($Script:TestimoConfiguration.$Scope.Sources.Keys)) {
            $CurrentSource = $Script:TestimoConfiguration.$Scope.Sources[$Source]
            if ($CurrentSource['Enable'] -eq $true) {
                [Array] $AllTests = $CurrentSource['Tests'].Keys
                $TestsSummary = [PSCustomobject] @{Passed = 0
                    Failed = 0
                    Skipped = 0
                    Total = 0
                }
                $Time = Start-TimeLog
                $Object = Start-TestProcessing -Test $CurrentSource['SourceName'] -Level $Level -OutputRequired -Domain $Domain -DomainController $DomainController { & $CurrentSource['SourceData'] -DomainController $DomainController -Domain $Domain }
                if ($Object) {
                    $FailAllTests = $false
                    Out-Begin -Text $CurrentSource['SourceName'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Text $CurrentSource['SourceName'] -Status $true -ExtendedValue 'Data is available.' -Domain $Domain -DomainController $DomainController
                    $TestsSummary.Passed = $TestsSummary.Passed + 1
                } else {
                    $FailAllTests = $true
                    Out-Failure -Text $CurrentSource['SourceName'] -Level $LevelTest -ExtendedValue 'No data available.' -Domain $Domain -DomainController $DomainController
                    $TestsSummary.Failed = $TestsSummary.Failed + 1
                }
                foreach ($Test in $AllTests) {
                    $CurrentTest = $CurrentSource['Tests'][$Test]
                    if ($CurrentTest['Enable'] -eq $True) {
                        if (-not $FailAllTests) {
                            if ($CurrentTest['TestParameters']) { $Parameters = $CurrentTest['TestParameters'] } else { $Parameters = $null }
                            $TestsResults = Start-TestingTest -Test $CurrentTest['TestName'] -Level $LevelTest -Domain $Domain -DomainController $DomainController { & $CurrentTest['TestSource'] -Object $Object -Domain $Domain -DomainController $DomainController @Parameters -Level $LevelTest }
                            $TestsSummary.Passed = $TestsSummary.Passed + ($TestsResults | Where-Object { $_ -eq $true }).Count
                            $TestsSummary.Failed = $TestsSummary.Failed + ($TestsResults | Where-Object { $_ -eq $false }).Count
                        } else {
                            $TestsSummary.Failed = $TestsSummary.Failed + 1
                            Out-Failure -Text $CurrentTest['TestName'] -Level $LevelTestFailure -Domain $Domain -DomainController $DomainController
                        }
                    } else { $TestsSummary.Skipped = $TestsSummary.Skipped + 1 }
                }
                $TestsSummary.Total = $TestsSummary.Failed + $TestsSummary.Passed + $TestsSummary.Skipped
                $TestsSummary
                Out-Summary -Text $CurrentSource['SourceName'] -Time $Time -Level $LevelSummary -Domain $Domain -DomainController $DomainController -TestsSummary $TestsSummary
            }
        }
        if ($Execute) { & $Execute })
    $TestsSummaryFinal = [PSCustomObject] @{Passed = ($TestsSummaryTogether.Passed | Measure-Object -Sum).Sum
        Failed = ($TestsSummaryTogether.Failed | Measure-Object -Sum).Sum
        Skipped = ($TestsSummaryTogether.Skipped | Measure-Object -Sum).Sum
        Total = ($TestsSummaryTogether.Total | Measure-Object -Sum).Sum
    }
    $TestsSummaryFinal
    Out-Summary -Text $SummaryText -Time $GlobalTime -Level ($LevelSummary - 3) -Domain $Domain -DomainController $DomainController -TestsSummary $TestsSummaryFinal
}
function Start-TestingTest {
    [CmdletBinding()]
    param([ScriptBlock] $Execute,
        $Test,
        [int] $Level,
        [string] $Domain,
        [string] $DomainController)
    if ($Execute) {
        if ($Script:TestimoConfiguration.Debug.DisableTryCatch) {
            [Array] $Output = & $Execute
            $Output
        } else {
            try {
                [Array] $Output = & $Execute
                $Output
            } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " }
            if (-not $ErrorMessage) { } else { Out-Failure -Text $CurrentTest['TestName'] -Level $Level -ExtendedValue $ErrorMessage -Domain $Domain -DomainController $DomainController }
        }
    }
}
function Start-TestProcessing {
    [CmdletBinding()]
    param([ScriptBlock] $Execute,
        [string] $Test,
        [switch] $OutputRequired,
        [nullable[bool]] $ExpectedStatus,
        [int] $Level = 0,
        [switch] $IsTest,
        [switch] $Simple,
        [string] $Domain,
        [string] $DomainController)
    if ($Execute) {
        if ($IsTest) { Out-Begin -Type 't' -Text $Test -Level $Level -Domain $Domain -DomainController $DomainController } else { Out-Begin -Type 'i' -Text $Test -Level $Level -Domain $Domain -DomainController $DomainController }
        if ($Script:TestimoConfiguration.Debug.DisableTryCatch) {
            [Array] $Output = & $Execute
            $ErrorMessage = $null
        } else { try { [Array] $Output = & $Execute } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " } }
        if (-not $ErrorMessage) {
            foreach ($O in $Output) { if ($OutputRequired.IsPresent) { if ($O['Output']) { foreach ($_ in $O['Output']) { $_ } } else { foreach ($_ in $O) { $_ } } } }
            if ($null -eq $ExpectedStatus) { $TestResult = $null } else { $TestResult = $ExpectedStatus -eq $Output.Status }
            Out-Status -Text $Test -Status $TestResult -ExtendedValue $O.Extended -Domain $Domain -DomainController $DomainController
        } else { Out-Status -Text $Test -Status $false -ExtendedValue $ErrorMessage -Domain $Domain -DomainController $DomainController }
    }
}
function Test-ImoAD {
    [CmdletBinding()]
    param([switch] $ReturnResults)
    $Script:TestResults = [System.Collections.Generic.List[PSCustomObject]]::new()
    $Forest = & $Script:SBForest
    $null = Start-Testing -Scope 'Forest' { foreach ($Domain in $Forest.Domains) {
            $Domain = $Domain.ToLower()
            $null = & $Script:SBDomain -Domain $Domain
            Start-Testing -Scope 'Domain' -Domain $Domain { $DomainControllers = & $Script:SBDomainControllers -Domain $Domain
                foreach ($DomainController in $DomainControllers) {
                    $DomainControllerHostName = $($DomainController.HostName).ToLower()
                    Start-Testing -Scope 'DomainControllers' -Domain $Domain -DomainController $DomainControllerHostName
                } }
        } }
    if ($ReturnResults) { $Script:TestResults }
}
$Script:TestimoConfiguration = [ordered] @{Forest = @{Sources = [ordered] @{OptionalFeatures = @{Enable = $true
                SourceName = 'Optional Features'
                SourceData = $Script:SBForestOptionalFeatures
                Tests = [ordered] @{RecycleBinEnabled = @{Enable = $true
                        TestName = 'RecycleBin Enabled'
                        TestSource = $Script:SBForestOptionalFeaturesTest1
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    LapsAvailable = @{Enable = $true
                        TestName = 'Laps Available'
                        TestSource = $Script:SBForestOptionalFeaturesTest2
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    PrivAccessManagement = @{Enable = $true
                        TestName = 'Privileged Access Management'
                        TestSource = $Script:SBForestOptionalFeaturesTest3
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Replication = @{Enable = $true
                SourceName = 'Forest Replication'
                SourceData = $Script:SBForestReplication
                Tests = [ordered] @{ReplicationTests = @{Enable = $true
                        TestName = 'Replication Test'
                        TestSource = $Script:SBForestReplicationTest1
                        TestParameters = @{OperationType = 'eq' }
                    }
                }
            }
            LastBackup = @{Enable = $true
                SourceName = 'Forest Backup'
                SourceData = $Script:SBForestLastBackup
                Tests = [ordered] @{LastBackupTests = @{Enable = $true
                        TestName = 'Forest Last Backup Time - Context'
                        TestSource = $Script:SBForestLastBackupTest
                        TestParameters = @{ }
                    }
                }
            }
        }
    }
    Domain = @{Sources = [ordered] @{PasswordComplexity = @{Enable = $true
                SourceName = 'Password Complexity Requirements'
                SourceData = $Script:SBDomainPasswordComplexity
                Tests = [ordered] @{ComplexityEnabled = @{Enable = $true
                        TestName = 'Complexity Enabled'
                        TestSource = $Script:SBDomainPasswordComplexityTest1
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    'Lockout Duration' = @{Enable = $true
                        TestName = 'Lockout Duration'
                        TestSource = $Script:SBDomainPasswordComplexityTest2
                        TestParameters = @{ExpectedValue = 30
                            OperationType = 'ge'
                        }
                    }
                    'Lockout Observation Window' = @{Enable = $true
                        TestName = 'Lockout Observation Window'
                        TestSource = $Script:SBDomainPasswordComplexityTest3
                        TestParameters = @{ExpectedValue = 30
                            OperationType = 'ge'
                        }
                    }
                    'Lockout Threshold' = @{Enable = $true
                        TestName = 'Lockout Threshold'
                        TestSource = $Script:SBDomainPasswordComplexityTest4
                        TestParameters = @{ExpectedValue = 5
                            OperationType = 'gt'
                        }
                    }
                    'Max Password Age' = @{Enable = $true
                        TestName = 'Max Password Age'
                        TestSource = $Script:SBDomainPasswordComplexityTest5
                        TestParameters = @{ExpectedValue = 60
                            OperationType = 'le'
                        }
                    }
                    'Min Password Length' = @{Enable = $true
                        TestName = 'Min Password Length'
                        TestSource = $Script:SBDomainPasswordComplexityTest6
                        TestParameters = @{ExpectedValue = 8
                            OperationType = 'gt'
                        }
                    }
                    'Min Password Age' = @{Enable = $true
                        TestName = 'Min Password Age'
                        TestSource = $Script:SBDomainPasswordComplexityTest7
                        TestParameters = @{ExpectedValue = 1
                            OperationType = 'le'
                        }
                    }
                    'Password History Count' = @{Enable = $true
                        TestName = 'Password History Count'
                        TestSource = $Script:SBDomainPasswordComplexityTest8
                        TestParameters = @{ExpectedValue = 10
                            OperationType = 'ge'
                        }
                    }
                    'Reversible Encryption Enabled' = @{Enable = $true
                        TestName = 'Reversible Encryption Enabled'
                        TestSource = $Script:SBDomainPasswordComplexityTest9
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Trusts = @{Enable = $true
                SourceName = "Trust Availability"
                SourceData = $Script:SBDomainTrustsData
                Tests = [ordered] @{TrustsConnectivity = @{Enable = $true
                        TestName = 'Trust status verification'
                        TestSource = $Script:SBDomainTrustsConnectivity
                    }
                    TrustsUnconstrainedDelegation = @{Enable = $true
                        TestName = 'Trust Unconstrained TGTDelegation'
                        TestSource = $Script:SBDomainTrustsUnconstrainedDelegation
                    }
                }
            }
            DNSScavengingForPrimaryDNSServer = @{Enable = $true
                SourceName = "DNS Scavenging - Primary DNS Server"
                SourceData = $Script:SBDomainScavenging
                Tests = [ordered] @{'Scavenging Count' = @{Enable = $true
                        TestName = 'Scavenging Count'
                        TestSource = $Script:SBDomainScavengingTest0
                        TestParameters = @{ExpectedCount = 1
                            OperationType = 'eq'
                        }
                        Explanation = 'Scavenging Count should be 1. There should be 1 DNS server per domain responsible for scavenging. If this returns false, every other test fails.'
                    }
                    'Scavenging Interval' = @{Enable = $true
                        TestName = 'Scavenging Interval'
                        TestSource = $Script:SBDomainScavengingTest1
                        TestParameters = @{ExpectedValue = 7
                            OperationType = 'le'
                        }
                    }
                    'Scavenging State' = @{Enable = $true
                        TestName = 'Scavenging State'
                        TestSource = $Script:SBDomainScavengingTest2
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                        Explanation = 'Scavenging State is responsible for enablement of scavenging for all new zones created.'
                        RecommendedValue = $true
                        ExplanationRecommended = 'It should be enabled so all new zones are subject to scavanging.'
                        DefaultValue = $false
                    }
                    'Last Scavenge Time' = @{Enable = $true
                        TestName = 'Last Scavenge Time'
                        TestSource = $Script:SBDomainScavengingTest3
                        TestParameters = @{ExpectedValue = (Get-Date).AddDays(-7)
                            OperationType = 'lt'
                        }
                    }
                }
            }
        }
    }
    DomainControllers = @{Sources = [ordered] @{RespondsToPowerShellQueries = @{Enable = $true
                SourceName = "Responds to PowerShell Queries"
                SourceData = $Script:SBDomainControllersRespondsPS
            }
            Services = @{Enable = $true
                SourceName = 'Service Status'
                SourceData = $Script:SBDomainControllersServices
                Tests = [ordered] @{ServiceStatus = @{Enable = $true
                        TestName = 'Service is RUNNING'
                        TestSource = $Script:SBDomainControllersServicesTestStatus
                        TestParameters = @{ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    ServiceStartType = @{Enable = $true
                        TestName = 'Service START TYPE is Automatic'
                        TestSource = $Script:SBDomainControllersServicesTestStartType
                        TestParameters = @{ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                }
            }
            LDAP = @{Enable = $true
                SourceName = 'LDAP Connectivity'
                SourceData = $Script:SBDomainControllersLDAP
                Tests = [ordered] @{PortLDAP = @{Enable = $true
                        TestName = 'LDAP Port is Available'
                        TestSource = $Script:SBDomainControllersLDAP_Port
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    PortLDAPS = @{Enable = $true
                        TestName = 'LDAP SSL Port is Available'
                        TestSource = $Script:SBDomainControllersLDAP_PortSSL
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    PortLDAP_GC = @{Enable = $true
                        TestName = 'LDAP GC Port is Available'
                        TestSource = $Script:SBDomainControllersLDAP_PortGC
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    PortLDAPS_GC = @{Enable = $true
                        TestName = 'LDAP SSL GC Port is Available'
                        TestSource = $Script:SBDomainControllersLDAP_PortGC_SSL
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Pingable = @{Enable = $true
                SourceName = 'PING'
                SourceData = $Script:SBDomainControllersPing
                Tests = @{Ping = @{Enable = $true
                        TestName = 'Responding to PING'
                        TestSource = $Script:SBDomainControllersPingTest
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Port53 = @{Enable = $true
                SourceName = 'PORT 53 (DNS)'
                SourceData = $Script:SBDomainControllersPort53
                Tests = @{Ping = @{Enable = $true
                        TestName = 'Port 53 is OPEN'
                        TestSource = $Script:SBDomainControllersPort53Test
                        TestParameters = @{ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            DiskSpace = @{Enable = $true
                SourceName = 'Disk Free'
                SourceData = $Script:SBDomainControllersDiskSpace
                Tests = @{FreeSpace = @{Enable = $true
                        TestName = 'Free Space in GB'
                        TestSource = $Script:SBDomainControllersDiskSpaceGB
                        TestParameters = @{ExpectedValue = 30
                            OperationType = 'gt'
                        }
                    }
                    FreePercent = @{Enable = $true
                        TestName = 'Free Space Percent'
                        TestSource = $Script:SBDomainControllersDiskSpacePercent
                        TestParameters = @{ExpectedValue = 20
                            OperationType = 'gt'
                        }
                    }
                }
            }
            TimeSynchronizationInternal = @{Enable = $true
                SourceName = "Time Synchronization Internal"
                SourceData = $Script:SBDomainTimeSynchronizationInternal
                Tests = [ordered] @{TimeSynchronizationTest = @{Enable = $true
                        TestName = 'Time Difference'
                        TestSource = $Script:SBDomainTimeSynchronizationTest1
                        TestParameters = @{ExpectedValue = 1
                            OperationType = 'le'
                        }
                    }
                }
                MicrosoftMaterials = 'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc773263(v=ws.10)#w2k3tr_times_tools_uhlp'
            }
            TimeSynchronizationExternal = @{Enable = $true
                SourceName = "Time Synchronization External"
                SourceData = $Script:SBDomainTimeSynchronizationExternal
                Tests = [ordered] @{TimeSynchronizationTest = @{Enable = $true
                        TestName = 'Time Difference'
                        TestSource = $Script:SBDomainTimeSynchronizationTest1
                        TestParameters = @{ExpectedValue = 1
                            OperationType = 'le'
                        }
                    }
                }
                MicrosoftMaterials = 'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc773263(v=ws.10)#w2k3tr_times_tools_uhlp'
            }
        }
    }
    AnyServers = @{Sources = [ordered] @{Services = @{Enable = $false
                SourceName = 'Service Status'
                SourceData = $Script:SBDomainControllersServices
                Tests = [ordered] @{ServiceStatus = @{Enable = $true
                        TestName = 'Service is RUNNING'
                        TestSource = $Script:SBDomainControllersServicesTestStatus
                        TestParameters = @{ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    ServiceStartType = @{Enable = $true
                        TestName = 'Service START TYPE is Automatic'
                        TestSource = $Script:SBDomainControllersServicesTestStartType
                        TestParameters = @{ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                }
            }
        }
    }
    Debug = @{DisableTryCatch = $false }
}
function Test-Me {
    param([string] $OperationType,
        [string] $TestName,
        [int] $Level,
        [string] $Domain,
        [string] $DomainController,
        [string[]] $Property,
        [Object] $TestedValue,
        [Object] $Object,
        [Object] $ExpectedValue,
        [string[]] $PropertyExtendedValue,
        [int] $ExpectedCount)
    Out-Begin -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController
    $TestedValue = $Object
    foreach ($V in $Property) { $TestedValue = $TestedValue.$V }
    $ScriptBlock = { $Operators = @{'lt' = 'LessThan'
            'gt' = 'GreaterThan'
            'le' = 'LessOrEqual'
            'ge' = 'GreaterOrEqual'
            'eq' = 'Equal'
        }
        if ($ExpectedCount) {
            if ($OperationType -eq 'lt') { $TestResult = $Object.Count -lt $ExpectedCount } elseif ($OperationType -eq 'gt') { $TestResult = $Object.Count -lt $ExpectedCount } elseif ($OperationType -eq 'ge') { $TestResult = $Object.Count -lt $ExpectedCount } elseif ($OperationType -eq 'le') { $TestResult = $Object.Count -lt $ExpectedCount } else { $TestResult = $Object.Count -lt $ExpectedCount }
            $TextTestedValue = $Object.Count
            $ExpectedValue = $ExpectedCount
        } elseif ($ExpectedValue) {
            if ($null -eq $TestedValue -and $null -ne $ExpectedValue) {
                $TestResult = $false
                $TextTestedValue = 'Null'
            } else {
                if ($OperationType -eq 'lt') { $TestResult = $TestedValue -lt $ExpectedValue } elseif ($OperationType -eq 'gt') { $TestResult = $TestedValue -gt $ExpectedValue } elseif ($OperationType -eq 'ge') { $TestResult = $TestedValue -ge $ExpectedValue } elseif ($OperationType -eq 'le') { $TestResult = $TestedValue -le $ExpectedValue } else { $TestResult = $TestedValue -eq $ExpectedValue }
                $TextTestedValue = $TestedValue
            }
        } else {
            $TestResult = $null
            $ExtendedTextValue = "Test provided but no tests required."
        }
        if ($TestResult -eq $true) { $Extended = "Expected value ($($Operators[$OperationType])): $($ExpectedValue)" } elseif ($TestResult -eq $false) { $Extended = "Expected value ($($Operators[$OperationType])): $ExpectedValue, Found value: $($TextTestedValue)" } else { $Extended = $ExtendedTextValue }
        if ($PropertyExtendedValue.Count -gt 0) {
            $Extended = $Object
            foreach ($V in $PropertyExtendedValue) { $Extended = $Extended.$V }
        }
        Out-Status -Text $TestName -Status $TestResult -ExtendedValue $Extended -Domain $Domain -DomainController $DomainController
        return $TestResult }
    if ($Script:TestimoConfiguration.Debug.DisableTryCatch) { & $ScriptBlock } else {
        try { & $ScriptBlock } catch {
            Out-Status -Text $TestName -Status $false -ExtendedValue $_.Exception.Message -Domain $Domain -DomainController $DomainController
            return $False
        }
    }
}
function Test-Value {
    [CmdletBinding()]
    param([Object] $Object,
        [string] $TestName,
        [string[]] $Property,
        [Object] $ExpectedValue,
        [string[]] $PropertyExtendedValue,
        [switch] $lt,
        [switch] $gt,
        [switch] $le,
        [switch] $eq,
        [switch] $ge,
        [string] $OperationType,
        [int] $Level,
        [string] $Domain,
        [Object] $DomainController,
        [int] $ExpectedCount)
    if (-not $OperationType) { if ($lt) { $OperationType = 'lt' } elseif ($gt) { $OperationType = 'gt' } elseif ($ge) { $OperationType = 'ge' } elseif ($le) { $OperationType = 'le' } else { $OperationType = 'eq' } }
    if ($Object) { if ($ExpectedCount) { Test-Me -Object $Object -ExpectedCount $ExpectedCount -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue } else { foreach ($_ in $Object) { Test-Me -Object $_ -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue } } } else { Write-Warning 'Objected not passed to Test-VALUE.' }
}
Export-ModuleMember -Function @('Test-ImoAD') -Alias @()