Testimo.psm1

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 -Domain $Domain -DomainController $DomainController
    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'
        $TestText = "Forest | $Text"
    }
    if ($null -ne $Status) {
        $Script:TestResults.Add([PSCustomObject]@{Name = $TestText
                Type = $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
}
function Get-WinADDomain {
    [CmdletBinding()]
    param([string] $Domain)
    $Output = Get-ADDomain -Server $Domain -ErrorAction Stop
    $Output
}
$Script:SBDomain = { param([string] $Domain) }
function Get-WinADDC {
    [CmdletBinding()]
    param([string] $Domain = $Env:USERDNSDOMAIN)
    $Output = Get-ADDomainController -Server $Domain -Filter * -ErrorAction Stop
    $Output
}
$Script:SBDomainControllers = { param([string] $Domain)
    try {
        $DomainControllers = Get-ADDomainController -Server $Domain -Filter * -ErrorAction Stop
        foreach ($_ in $DomainControllers.HostName) { if ($_ -notin $Script:TestimoConfiguration['Exclusions']['DomainControllers']) { $_.ToLower() } }
    } catch { return } }
$Script:SBDomainControllersDiskSpace = { Get-ComputerDiskLogical -ComputerName $DomainController -OnlyLocalDisk -WarningAction SilentlyContinue }
$Script:SBDomainControllersFirewall = { Get-ComputerNetwork -ComputerName $DomainController }
$Script:SBDomainControllersLDAP = { Test-LDAP -ComputerName $DomainController -WarningAction SilentlyContinue }
$Script:SBDomainControllersPing = { Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue }
$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:SBDomainDNSForwaders = { $PSWinDocumentationDNS = Import-Module PSWinDocumentation.DNS -PassThru
    & $PSWinDocumentationDNS { param($Domain)
        $Forwarders = Get-WinDnsServerForwarder -Domain $Domain -WarningAction SilentlyContinue
        Compare-MultipleObjects -Objects $Forwarders -FormatOutput -CompareSorted:$true -ExcludeProperty GatheredFrom -SkipProperties -Property 'IpAddress' } $Domain }
$Script:SBDomainDNSScavenging = { $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:SBDomainDnsZones = { $PSWinDocumentationDNS = Import-Module PSWinDocumentation.DNS -PassThru
    & $PSWinDocumentationDNS { param($Domain)
        $Zones = Get-WinDnsServerZones -ZoneName $Domain -Domain $Domain
        Compare-MultipleObjects -Objects $Zones -FormatOutput -CompareSorted:$true -ExcludeProperty GatheredFrom -SkipProperties -Property 'AgingEnabled' } $Domain }
$Script:SBDomainPasswordComplexity = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    & $ADModule { param($Domain); Get-WinADDomainDefaultPasswordPolicy -Domain $Domain } $Domain }
$Script:SBDomainTimeSynchronizationInternal = { Get-ComputerTime -TimeTarget $DomainController -WarningAction SilentlyContinue }
$Script:SBDomainTimeSynchronizationExternal = { Get-ComputerTime -TimeTarget $DomainController -TimeSource 'pool.ntp.org' -WarningAction SilentlyContinue }
$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 } } }
function Get-WinADForest {
    [CmdletBinding()]
    param()
}
$Script:SBForest = { try {
        $Forest = Get-ADForest -ErrorAction Stop
        foreach ($_ in $Forest.Domains) { if ($_ -notin $Script:TestimoConfiguration['Exclusions']['Domains']) { $_.ToLower() } }
    } catch { return } }
$Script:SBForestLastBackup = { Get-WinADLastBackup }
$Script:SBForestLastBackupTest = { foreach ($_ in $Object) { Test-Value -Level $LevelTest -TestName "Last Backup $($_.NamingContext)" -Object $_ -Property 'LastBackupDaysAgo' -PropertyExtendedValue 'LastBackup' -OperationType 'lt' -ExpectedValue 2 -Domain $Domain -DomainController $DomainController } }
$Script:SBForestOptionalFeatures = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    & $ADModule { Get-WinADForestOptionalFeatures -WarningAction SilentlyContinue } }
$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 Test-ADRolesAvailability {
    param([string] $Domain)
    $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    $Roles = & $ADModule { param($Domain); Get-WinADForestRoles -Domain $Domain } $Domain
    if ($Domain -ne '') {
        [PSCustomObject] @{PDCEmulator = $Roles['PDCEmulator']
            PDCEmulatorAvailability = if ($Roles['PDCEmulator']) { (Test-NetConnection -ComputerName $Roles['PDCEmulator']).PingSucceeded } else { $false }
            RIDMaster = $Roles['RIDMaster']
            RIDMasterAvailability = if ($Roles['RIDMaster']) { (Test-NetConnection -ComputerName $Roles['RIDMaster']).PingSucceeded } else { $false }
            InfrastructureMaster = $Roles['InfrastructureMaster']
            InfrastructureMasterAvailability = if ($Roles['InfrastructureMaster']) { (Test-NetConnection -ComputerName $Roles['InfrastructureMaster']).PingSucceeded } else { $false }
        }
    } else {
        [PSCustomObject] @{SchemaMaster = $Roles['SchemaMaster']
            SchemaMasterAvailability = if ($Roles['SchemaMaster']) { (Test-NetConnection -ComputerName $Roles['SchemaMaster']).PingSucceeded } else { $false }
            DomainNamingMaster = $Roles['DomainNamingMaster']
            DomainNamingMasterAvailability = if ($Roles['DomainNamingMaster']) { (Test-NetConnection -ComputerName $Roles['DomainNamingMaster']).PingSucceeded } else { $false }
        }
    }
}
function Test-FSMORolesAvailability {
    param([string] $Domain = $Env:USERDNSDOMAIN)
    $DC = Get-ADDomainController -Server $Domain -Filter *
    $Output = foreach ($S in $DC) {
        if ($S.OperationMasterRoles.Count -gt 0) { $Status = Test-Connection -ComputerName $S.HostName -Count 2 -Quiet } else { $Status = $null }
        foreach ($_ in $S.OperationMasterRoles) {
            [PSCustomObject] @{Role = $_
                HostName = $S.HostName
                Status = $Status
            }
        }
    }
    $Output
}
$Script:SBForestRoles = { Test-ADRolesAvailability }
$Script:SBDomainRoles = { Test-ADRolesAvailability -Domain $Domain }
$Script:TestFSMODomainRoles = { Test-FSMORolesAvailability -Domain $Domain }
function Test-ADSites {
    param()
    $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    $Sites = & $ADModule { Get-WinADForestSites }
    [Array] $SitesWithoutDC = $Sites | Where-Object { $_.DomainControllersCount -eq 0 }
    [Array] $SitesWithoutSubnets = $Sites | Where-Object { $_.SubnetsCount -eq 0 }
    [PSCustomObject] @{SitesWithoutDC = $SitesWithoutDC.Count
        SitesWithoutSubnets = $SitesWithoutSubnets.Count
        SitesWithoutDCName = $SitesWithoutDC.Name -join ', '
        SitesWithoutSubnetsName = $SitesWithoutSubnets.Name -join ', '
    }
}
$Script:SBForestSites = { Test-ADSites }
$Script:SBForestSiteLinks = { Get-WinADSiteLinks }
function Test-ADSiteLinks {
    param([string] $Splitter)
    [Array] $SiteLinks = Get-WinADSiteConnections
    $Collection = @($SiteLinks).Where( { $_.Options -notcontains 'IsGenerated' -and $_.EnabledConnection -eq $true }, 'Split')
    $LinksManual = foreach ($Link in $Collection[0]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
    $LinksAutomatic = foreach ($Link in $Collection[1]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
    $CollectionNotifications = @($SiteLinks).Where( { $_.Options -notcontains 'UseNotify' -and $_.EnabledConnection -eq $true }, 'Split')
    $LinksNotUsingNotifications = foreach ($Link in $CollectionNotifications[0]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
    $LinksUsingNotifications = foreach ($Link in $CollectionNotifications[1]) { "$($Link.ServerFrom) to $($Link.ServerTo)" }
    [ordered] @{SiteLinksManual = if ($Splitter -eq '') { $LinksManual } else { $LinksManual -join $Splitter }
        SiteLinksAutomatic = if ($Splitter -eq '') { $LinksAutomatic } else { $LinksAutomatic -join $Splitter }
        SiteLinksUseNotify = if ($Splitter -eq '') { $LinksUsingNotifications } else { $LinksUsingNotifications -join $Splitter }
        SiteLinksNotUsingNotify = if ($Splitter -eq '') { $LinksNotUsingNotifications } else { $LinksNotUsingNotifications -join $Splitter }
        SiteLinksUseNotifyCount = $CollectionNotifications[1].Count
        SiteLinksNotUsingNotifyCount = $CollectionNotifications[0].Count
        SiteLinksManualCount = $Collection[0].Count
        SiteLinksAutomaticCount = $Collection[1].Count
        SiteLinksTotalCount = ($SiteLinks | Where-Object { $_.EnabledConnection -eq $true }).Count
    }
}
$Script:SBForestSiteLinksConnections = { Test-ADSiteLinks -Splitter ', ' }
$Script:KeberosAccountTimeChange = { Get-ADUser krbtgt -Properties Created, PasswordLastSet, msDS-KeyVersionNumber -Server $Domain }
$Script:GroupsAccountOperators = { Get-ADGroupMember -Identity 'S-1-5-32-548' -Recursive -Server $Domain }
$Script:UsersAccountAdministrator = { $DomainSID = (Get-ADDomain -Server $Domain).DomainSID
    $User = Get-ADUser -Identity "$DomainSID-500" -Properties PasswordLastSet, LastLogonDate, servicePrincipalName -Server $Domain
    if ($User.Enabled -eq $false) {
        [PSCustomObject] @{Name = 'Administrator'
            PasswordLastSet = Get-Date
        }
    } else {
        [PSCustomObject] @{Name = 'Administrator'
            PasswordLastSet = $User.PasswordLastSet
        }
    } }
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['Source']['Name'] -Level $Level -OutputRequired -Domain $Domain -DomainController $DomainController { & $CurrentSource['Source']['Data'] -DomainController $DomainController -Domain $Domain }
                if ($Object) {
                    $FailAllTests = $false
                    Out-Begin -Text $CurrentSource['Source']['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Text $CurrentSource['Source']['Name'] -Status $true -ExtendedValue 'Data is available.' -Domain $Domain -DomainController $DomainController
                    $TestsSummary.Passed = $TestsSummary.Passed + 1
                } elseif ($null -eq $Object -and $CurrentSource['Source']['ExpectedOutput'] -eq $false) {
                    $FailAllTests = $false
                    Out-Begin -Text $CurrentSource['Source']['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Text $CurrentSource['Source']['Name'] -Status $true -ExtendedValue 'No data returned, which is a good thing.' -Domain $Domain -DomainController $DomainController
                    $TestsSummary.Passed = $TestsSummary.Passed + 1
                } else {
                    $FailAllTests = $true
                    Out-Failure -Text $CurrentSource['Source']['Name'] -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['Parameters']) { $Parameters = $CurrentTest['Parameters'] } else { $Parameters = $null }
                            $TestsResults = Start-TestingTest -Test $CurrentTest['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController { if ($CurrentTest['Data'] -is [ScriptBlock]) { & $CurrentTest['Data'] -Object $Object -Domain $Domain -DomainController $DomainController @Parameters -Level $LevelTest } else { Test-Value -Object $Object -Domain $Domain -DomainController $DomainController @Parameters -Level $LevelTest -TestName $CurrentTest['Name'] } }
                            $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['Name'] -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['Source']['Name'] -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.ShowErrors) {
            [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.ShowErrors) {
            [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 }
    }
}
$Script:ComputerOperatingSystem = { Get-ComputerOperatingSystem -ComputerName $DomainController -WarningAction SilentlyContinue }
function Test-IMO {
    [alias('Test-ImoAD')]
    [CmdletBinding()]
    param([switch] $ReturnResults,
        [string[]] $ExludeDomains,
        [string[]] $ExludeDomainControllers,
        [switch] $ShowErrors)
    $global:ProgressPreference = 'SilentlyContinue'
    $Script:TestResults = [System.Collections.Generic.List[PSCustomObject]]::new()
    $Script:TestimoConfiguration.Exclusions.Domains = $ExludeDomains
    $Script:TestimoConfiguration.Exclusions.DomainControllers = $ExludeDomainControllers
    if ($Script:TestimoConfiguration.Exclusions.Domains) {
        Out-Begin -Text 'Following Domains will be ignored' -Level 0
        Out-Status -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ($Script:TestimoConfiguration.Exclusions.Domains -join ', ')
    }
    if ($Script:TestimoConfiguration.Exclusions.DomainControllers) {
        Out-Begin -Text 'Following Domain Controllers will be ignored' -Level 0
        Out-Status -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ($Script:TestimoConfiguration.Exclusions.DomainControllers -join ', ')
    }
    $Domains = & $Script:SBForest
    $null = Start-Testing -Scope 'Forest' { foreach ($Domain in $Domains) {
            Start-Testing -Scope 'Domain' -Domain $Domain { $DomainControllers = & $Script:SBDomainControllers -Domain $Domain
                foreach ($DomainController in $DomainControllers) { Start-Testing -Scope 'DomainControllers' -Domain $Domain -DomainController $DomainController } }
        } }
    if ($ReturnResults) { $Script:TestResults }
}
$Script:TestimoConfiguration = [ordered] @{Exclusions = @{ }
    Forest = @{Sources = [ordered] @{OptionalFeatures = @{Enable = $true
                Source = @{Name = 'Optional Features'
                    Data = $Script:SBForestOptionalFeatures
                    Parameters = @{ }
                }
                Tests = [ordered] @{RecycleBinEnabled = @{Enable = $true
                        Name = 'Recycle Bin Enabled'
                        Parameters = @{Property = 'Recycle Bin Enabled'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    LapsAvailable = @{Enable = $true
                        Name = 'LAPS Schema Extended'
                        Parameters = @{Property = 'Laps Enabled'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    PrivAccessManagement = @{Enable = $true
                        Name = 'Privileged Access Management Enabled'
                        Parameters = @{Property = 'Privileged Access Management Feature Enabled'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Replication = @{Enable = $false
                Source = @{Name = 'Forest Replication'
                    Data = $Script:SBForestReplication
                    Parameters = @{ }
                }
                Tests = [ordered] @{ReplicationTests = @{Enable = $true
                        Name = 'Replication Test'
                        Data = $Script:SBForestReplicationTest1
                        Parameters = @{OperationType = 'eq' }
                    }
                }
            }
            LastBackup = @{Enable = $true
                Source = @{Name = 'Forest Backup'
                    Data = $Script:SBForestLastBackup
                    Parameters = @{ }
                }
                Tests = [ordered] @{LastBackupTests = @{Enable = $true
                        Name = 'Forest Last Backup Time - Context'
                        Data = $Script:SBForestLastBackupTest
                        Parameters = @{ }
                    }
                }
            }
            Sites = @{Enable = $true
                Source = @{Name = 'Sites Verification'
                    Area = 'Sites'
                    Data = $Script:SBForestSites
                    Parameters = @{ }
                }
                Tests = [ordered] @{SitesWithoutDC = @{Enable = $true
                        Name = 'Sites without Domain Controllers'
                        Description = 'Verify each `site has at least [one subnet configured]`'
                        Parameters = @{Property = 'SitesWithoutDC'
                            ExpectedValue = 0
                            OperationType = 'eq'
                        }
                    }
                    SitesWithoutSubnets = @{Enable = $true
                        Name = 'Sites without Subnets'
                        Parameters = @{Property = 'SitesWithoutSubnets'
                            ExpectedValue = 0
                            OperationType = 'eq'
                        }
                    }
                }
            }
            SiteLinks = @{Enable = $true
                Source = @{Name = 'Site Links'
                    Data = $Script:SBForestSiteLinks
                    Area = 'Sites'
                    Parameters = @{ }
                }
                Tests = [ordered] @{MinimalReplicationFrequency = @{Enable = $true
                        Name = 'Replication Frequency should be set to maximum 60 minutes'
                        Description = ''
                        Parameters = @{Property = 'ReplicationFrequencyInMinutes'
                            ExpectedValue = 60
                            OperationType = 'lt'
                        }
                    }
                    UseNotificationsForLinks = @{Enable = $true
                        Name = 'Automatic site links should use notifications'
                        Description = ''
                        Parameters = @{Property = 'Options'
                            ExpectedValue = 'UseNotify'
                            OperationType = 'contains'
                            PropertyExtendedValue = 'Options'
                        }
                    }
                }
            }
            SiteLinksConnections = @{Enable = $true
                Source = @{Name = 'Site Links Connections'
                    Data = $Script:SBForestSiteLinksConnections
                    Area = 'Sites'
                    Parameters = @{ }
                }
                Tests = [ordered] @{AutomaticSiteLinks = @{Enable = $true
                        Name = 'All site links are automatic'
                        Description = 'Verify there are no manually configured sitelinks'
                        Parameters = @{Property = 'SiteLinksManualCount'
                            ExpectedValue = 0
                            OperationType = 'eq'
                            PropertyExtendedValue = 'SiteLinksManual'
                        }
                    }
                    SiteLinksNotifications = @{Enable = $true
                        Name = 'All site links use notifications'
                        Parameters = @{Property = 'SiteLinksNotUsingNotifyCount'
                            ExpectedValue = 0
                            OperationType = 'eq'
                        }
                    }
                    SiteLinksDoNotUseNotifications = @{Enable = $false
                        Name = 'All site links are not using notifications'
                        Parameters = @{Property = 'SiteLinksUseNotifyCount'
                            ExpectedValue = 0
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Roles = @{Enable = $true
                Source = @{Name = 'Roles availability'
                    Data = $Script:SBForestRoles
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{SchemaMasterAvailability = @{Enable = $true
                        Name = 'Schema Master Availability'
                        Parameters = @{ExpectedValue = $true
                            Property = 'SchemaMasterAvailability'
                            OperationType = 'eq'
                            PropertyExtendedValue = 'SchemaMaster'
                        }
                    }
                    DomainNamingMasterAvailability = @{Enable = $true
                        Name = 'Domain Master Availability'
                        Parameters = @{ExpectedValue = $true
                            Property = 'DomainNamingMasterAvailability'
                            OperationType = 'eq'
                            PropertyExtendedValue = 'DomainNamingMaster'
                        }
                    }
                }
            }
        }
    }
    Domain = @{Sources = [ordered] @{Roles = @{Enable = $true
                Source = @{Name = 'Roles availability'
                    Data = $Script:SBDomainRoles
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{PDCEmulator = @{Enable = $true
                        Name = 'PDC Emulator Availability'
                        Parameters = @{ExpectedValue = $true
                            Property = 'PDCEmulatorAvailability'
                            OperationType = 'eq'
                            PropertyExtendedValue = 'PDCEmulator'
                        }
                    }
                    RIDMaster = @{Enable = $true
                        Name = 'RID Master Availability'
                        Parameters = @{ExpectedValue = $true
                            Property = 'RIDMasterAvailability'
                            OperationType = 'eq'
                            PropertyExtendedValue = 'RIDMaster'
                        }
                    }
                    InfrastructureMaster = @{Enable = $true
                        Name = 'Infrastructure Master Availability'
                        Parameters = @{ExpectedValue = $true
                            Property = 'InfrastructureMasterAvailability'
                            OperationType = 'eq'
                            PropertyExtendedValue = 'InfrastructureMaster'
                        }
                    }
                }
            }
            PasswordComplexity = @{Enable = $true
                Source = @{Name = 'Password Complexity Requirements'
                    Data = $Script:SBDomainPasswordComplexity
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{ComplexityEnabled = @{Enable = $true
                        Name = 'Complexity Enabled'
                        Parameters = @{Property = 'Complexity Enabled'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    'Lockout Duration' = @{Enable = $true
                        Name = 'Lockout Duration'
                        Parameters = @{Property = 'Lockout Duration'
                            ExpectedValue = 30
                            OperationType = 'ge'
                        }
                    }
                    'Lockout Observation Window' = @{Enable = $true
                        Name = 'Lockout Observation Window'
                        Parameters = @{Property = 'Lockout Observation Window'
                            ExpectedValue = 30
                            OperationType = 'ge'
                        }
                    }
                    'Lockout Threshold' = @{Enable = $true
                        Name = 'Lockout Threshold'
                        Parameters = @{Property = 'Lockout Threshold'
                            ExpectedValue = 5
                            OperationType = 'gt'
                        }
                    }
                    'Max Password Age' = @{Enable = $true
                        Name = 'Max Password Age'
                        Parameters = @{Property = 'Max Password Age'
                            ExpectedValue = 60
                            OperationType = 'le'
                        }
                    }
                    'Min Password Length' = @{Enable = $true
                        Name = 'Min Password Length'
                        Parameters = @{Property = 'Min Password Length'
                            ExpectedValue = 8
                            OperationType = 'gt'
                        }
                    }
                    'Min Password Age' = @{Enable = $true
                        Name = 'Min Password Age'
                        Parameters = @{Property = 'Min Password Age'
                            ExpectedValue = 1
                            OperationType = 'le'
                        }
                    }
                    'Password History Count' = @{Enable = $true
                        Name = 'Password History Count'
                        Parameters = @{Property = 'Password History Count'
                            ExpectedValue = 10
                            OperationType = 'ge'
                        }
                    }
                    'Reversible Encryption Enabled' = @{Enable = $true
                        Name = 'Reversible Encryption Enabled'
                        Parameters = @{Property = 'Reversible Encryption Enabled'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Trusts = @{Enable = $true
                Source = @{Name = "Trust Availability"
                    Data = $Script:SBDomainTrustsData
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{TrustsConnectivity = @{Enable = $true
                        Name = 'Trust status verification'
                        Data = $Script:SBDomainTrustsConnectivity
                    }
                    TrustsUnconstrainedDelegation = @{Enable = $true
                        Name = 'Trust Unconstrained TGTDelegation'
                        Data = $Script:SBDomainTrustsUnconstrainedDelegation
                    }
                }
            }
            DNSScavengingForPrimaryDNSServer = @{Enable = $true
                Source = @{Name = "DNS Scavenging - Primary DNS Server"
                    Data = $Script:SBDomainDnsScavenging
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{ScavengingCount = @{Enable = $true
                        Name = 'Scavenging DNS Servers Count'
                        Parameters = @{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.'
                    }
                    ScavengingInterval = @{Enable = $true
                        Name = 'Scavenging Interval'
                        Parameters = @{Property = 'ScavengingInterval', 'Days'
                            ExpectedValue = 7
                            OperationType = 'le'
                        }
                    }
                    'Scavenging State' = @{Enable = $true
                        Name = 'Scavenging State'
                        Parameters = @{Property = 'ScavengingState'
                            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
                        Name = 'Last Scavenge Time'
                        Parameters = @{Property = 'LastScavengeTime'
                            ExpectedValue = (Get-Date).AddDays(-7)
                            OperationType = 'lt'
                        }
                    }
                }
            }
            DNSForwaders = @{Enable = $true
                Source = @{Name = "DNS Forwarders"
                    Data = $Script:SBDomainDNSForwaders
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{SameForwarders = @{Enable = $true
                        Name = 'Same DNS Forwarders'
                        Parameters = @{Property = 'Status'
                            ExpectedValue = $true
                            OperationType = 'eq'
                            PropertyExtendedValue = 'Source'
                        }
                        Explanation = 'DNS forwarders within one domain should have identical setup'
                    }
                }
            }
            DnsZonesAging = @{Enable = $true
                Source = @{Name = "Aging primary DNS Zone"
                    Data = $Script:SBDomainDnsZones
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{EnabledAgingEnabled = @{Enable = $true
                        Name = 'Zone DNS aging should be enabled'
                        Parameters = @{Property = 'Source'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                        Explanation = 'Primary DNS zone should have aging enabled.'
                    }
                    EnabledAgingIdentical = @{Enable = $true
                        Name = 'Zone DNS aging should be identical on all DCs'
                        Parameters = @{Property = 'Status'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                        Explanation = 'Primary DNS zone should have aging enabled, on all DNS servers.'
                    }
                }
            }
            KerberosAccountAge = @{Enable = $true
                Source = @{Name = "Kerberos Account Age"
                    Data = $Script:KeberosAccountTimeChange
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{EnabledAgingEnabled = @{Enable = $true
                        Name = 'Kerberos Last Password Change Should be less than 180 days'
                        Parameters = @{Property = 'PasswordLastSet'
                            ExpectedValue = (Get-Date).AddDays(-180)
                            OperationType = 'gt'
                        }
                        Explanation = ''
                    }
                }
            }
            SecurityGroupsAccountOperators = @{Enable = $true
                Source = @{Name = "Groups: Account operators should be empty"
                    Data = $Script:GroupsAccountOperators
                    Area = ''
                    Parameters = @{ }
                    ExpectedOutput = $false
                    Explanation = "The Account Operators group should not be used. Custom delegate instead. This group is a great 'backdoor' priv group for attackers. Microsoft even says don't use this group!"
                }
            }
            SecurityUsersAcccountAdministrator = @{Enable = $true
                Source = @{Name = "Users: Administrator"
                    Data = $Script:UsersAccountAdministrator
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{PasswordLastSet = @{Enable = $true
                        Name = 'Administrator Last Password Change Should be less than 360 days ago'
                        Parameters = @{Property = 'PasswordLastSet'
                            ExpectedValue = (Get-Date).AddDays(-360)
                            OperationType = 'gt'
                        }
                        Explanation = 'Administrator account should be disabled or LastPasswordChange should be less than 1 year ago.'
                    }
                }
            }
        }
    }
    DomainControllers = @{Sources = [ordered] @{WindowsRemoteManagement = @{Enable = $true
                Source = @{Name = 'Windows Remote Management'
                    Data = $Script:WindowsRemoteManagement
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{OperatingSystem = @{Enable = $true
                        Name = 'Test submits an identification request that determines whether the WinRM service is running.'
                        Parameters = @{Property = 'Status'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            OperatingSystem = @{Enable = $true
                Source = @{Name = 'Operating System'
                    Data = $Script:ComputerOperatingSystem
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{OperatingSystem = @{Enable = $true
                        Name = 'Operating system Windows Server 2012 and up'
                        Parameters = @{Property = 'OperatingSystem'
                            ExpectedValue = 'Microsoft Windows Server 2019*', 'Microsoft Windows Server 2016*', 'Microsoft Windows Server 2012*'
                            OperationType = 'like'
                            OperationResult = 'OR'
                            PropertyExtendedValue = 'OperatingSystem'
                        }
                    }
                }
            }
            RespondsToPowerShellQueries = @{Enable = $true
                Source = @{Name = "Responds to PowerShell Queries"
                    Data = $Script:SBDomainControllersRespondsPS
                    Area = ''
                    Parameters = @{ }
                }
            }
            Services = @{Enable = $true
                Source = @{Name = 'Service Status'
                    Data = $Script:SBDomainControllersServices
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{ServiceStatus = @{Enable = $true
                        Name = 'Service is RUNNING'
                        Data = $Script:SBDomainControllersServicesTestStatus
                        Parameters = @{ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    ServiceStartType = @{Enable = $true
                        Name = 'Service START TYPE is Automatic'
                        Data = $Script:SBDomainControllersServicesTestStartType
                        Parameters = @{ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                }
            }
            LDAP = @{Enable = $true
                Source = @{Name = 'LDAP Connectivity'
                    Data = $Script:SBDomainControllersLDAP
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{PortLDAP = @{Enable = $true
                        Name = 'LDAP Port is Available'
                        Parameters = @{Property = 'LDAP'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    PortLDAPS = @{Enable = $true
                        Name = 'LDAP SSL Port is Available'
                        Parameters = @{Property = 'LDAPS'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    PortLDAP_GC = @{Enable = $true
                        Name = 'LDAP GC Port is Available'
                        Parameters = @{Property = 'GlobalCatalogLDAP'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    PortLDAPS_GC = @{Enable = $true
                        Name = 'LDAP SSL GC Port is Available'
                        Parameters = @{Property = 'GlobalCatalogLDAPS'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Pingable = @{Enable = $true
                Source = @{Name = 'Ping Connectivity'
                    Data = $Script:SBDomainControllersPing
                    Area = ''
                    Parameters = @{ }
                }
                Tests = @{Ping = @{Enable = $true
                        Name = 'Responding to PING'
                        Parameters = @{Property = 'PingSucceeded'
                            PropertyExtendedValue = 'PingReplyDetails', 'RoundtripTime'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            Ports = @{Enable = $true
                Source = @{Name = 'AD TCP Ports are open'
                    Data = $Script:TestServerPorts
                    Area = ''
                    Parameters = @{ }
                }
                Tests = @{Ping = @{Enable = $true
                        Name = 'Port is OPEN'
                        Parameters = @{Property = 'Status'
                            ExpectedValue = $true
                            OperationType = 'eq'
                            PropertyExtendedValue = 'Summary'
                        }
                    }
                }
            }
            PortsRDP = @{Enable = $true
                Source = @{Name = 'RDP Ports is open'
                    Data = $Script:TestServerPortsRDP
                    Area = ''
                    Parameters = @{ }
                }
                Tests = @{Ping = @{Enable = $true
                        Name = 'Port is OPEN'
                        Parameters = @{Property = 'Status'
                            ExpectedValue = $true
                            OperationType = 'eq'
                            PropertyExtendedValue = 'Summary'
                        }
                    }
                }
            }
            DiskSpace = @{Enable = $true
                Source = @{Name = 'Disk Free'
                    Data = $Script:SBDomainControllersDiskSpace
                    Area = ''
                    Parameters = @{ }
                }
                Tests = @{FreeSpace = @{Enable = $true
                        Name = 'Free Space in GB'
                        Parameters = @{Property = 'FreeSpace'
                            PropertyExtendedValue = 'FreeSpace'
                            ExpectedValue = 10
                            OperationType = 'gt'
                        }
                    }
                    FreePercent = @{Enable = $true
                        Name = 'Free Space Percent'
                        Parameters = @{Property = 'FreePercent'
                            PropertyExtendedValue = 'FreePercent'
                            ExpectedValue = 10
                            OperationType = 'gt'
                        }
                    }
                }
            }
            TimeSynchronizationInternal = @{Enable = $true
                Source = @{Name = "Time Synchronization Internal"
                    Data = $Script:SBDomainTimeSynchronizationInternal
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{TimeSynchronizationTest = @{Enable = $true
                        Name = 'Time Difference'
                        Parameters = @{Property = 'TimeDifferenceSeconds'
                            ExpectedValue = 1
                            OperationType = 'le'
                            PropertyExtendedValue = 'TimeDifferenceSeconds'
                        }
                    }
                }
                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
                Source = @{Name = "Time Synchronization External"
                    Data = $Script:SBDomainTimeSynchronizationExternal
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{TimeSynchronizationTest = @{Enable = $true
                        Name = 'Time Difference'
                        Parameters = @{Property = 'TimeDifferenceSeconds'
                            ExpectedValue = 1
                            OperationType = 'le'
                            PropertyExtendedValue = 'TimeDifferenceSeconds'
                        }
                    }
                }
                MicrosoftMaterials = 'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc773263(v=ws.10)#w2k3tr_times_tools_uhlp'
            }
            WindowsFirewall = @{Enable = $true
                Source = @{Name = "Windows Firewall"
                    Data = $Script:SBDomainControllersFirewall
                    Area = 'Connectivity'
                    Description = 'Verify windows firewall should be enabled for all network cards'
                    Parameters = @{ }
                }
                Tests = [ordered] @{TimeSynchronizationTest = @{Enable = $true
                        Name = 'Windows Firewall should be enabled on network card'
                        Parameters = @{Property = 'FirewallStatus'
                            ExpectedValue = $true
                            OperationType = 'eq'
                            PropertyExtendedValue = 'FirewallProfile'
                        }
                    }
                }
            }
            WindowsUpdates = @{Enable = $true
                Source = @{Name = "Windows Updates"
                    Data = $Script:TestWindowsUpdates
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{PasswordLastSet = @{Enable = $true
                        Name = 'Last Windows Updates should be less than 60 days ago'
                        Parameters = @{Property = 'InstalledOn'
                            ExpectedValue = (Get-Date).AddDays(-60)
                            OperationType = 'gt'
                        }
                        Explanation = 'Last installed update should be less than 60 days ago.'
                    }
                }
            }
            DnsResolveInternal = @{Enable = $true
                Source = @{Name = "Resolves internal DNS queries"
                    Data = $Script:ResolveDNSInternal
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{ResolveDNSInternal = @{Enable = $true
                        Name = 'Should resolve Internal DNS'
                        Parameters = @{Property = 'Status'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                        Explanation = 'DNS should resolve internal domains correctly.'
                    }
                }
            }
            DnsResolveExternal = @{Enable = $true
                Source = @{Name = "Resolves external DNS queries"
                    Data = $Script:ResolveDNSExternal
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{ResolveDNSExternal = @{Enable = $true
                        Name = 'Should resolve External DNS'
                        Parameters = @{Property = 'IPAddress'
                            ExpectedValue = '37.59.176.139'
                            OperationType = 'eq'
                        }
                        Explanation = 'DNS should resolve external queries properly.'
                    }
                }
            }
        }
    }
    AnyServers = @{Sources = [ordered] @{Services = @{Enable = $false
                Source = @{Name = 'Service Status'
                    Data = $Script:SBDomainControllersServices
                    Parameters = @{ }
                }
                Tests = [ordered] @{ServiceStatus = @{Enable = $true
                        Name = 'Service is RUNNING'
                        Data = $Script:SBDomainControllersServicesTestStatus
                        Parameters = @{ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    ServiceStartType = @{Enable = $true
                        Name = 'Service START TYPE is Automatic'
                        Data = $Script:SBDomainControllersServicesTestStartType
                        Parameters = @{ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                }
            }
        }
    }
    Debug = @{ShowErrors = $false }
}
function Test-Me {
    param([string] $OperationType,
        [string] $TestName,
        [int] $Level,
        [string] $Domain,
        [string] $DomainController,
        [string[]] $Property,
        [Object] $TestedValue,
        [Object] $Object,
        [Array] $ExpectedValue,
        [string[]] $PropertyExtendedValue,
        [string] $OperationResult,
        [int] $ExpectedCount = -1)
    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'
            'contains' = 'Contains'
            'like' = 'Like'
        }
        if ($ExpectedCount -ne -1) {
            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 } elseif ($OperationType -eq 'like') { $TestResult = $Object.Count -like $ExpectedCount } elseif ($OperationType -eq 'contains') { $TestResult = $Object.Count -like $ExpectedCount } elseif ($OperationType -eq 'in') { $TestResult = $ExpectedCount -in $Object.Count } elseif ($OperationType -eq 'notin') { $TestResult = $ExpectedCount -notin $Object.Count } else { $TestResult = $Object.Count -lt $ExpectedCount }
            $TextTestedValue = $Object.Count
            $ExpectedValue = $ExpectedCount
        } elseif ($null -ne $ExpectedValue) {
            if ($null -eq $TestedValue -and $null -ne $ExpectedValue) {
                $TestResult = $false
                $TextTestedValue = 'Null'
            } else {
                [Array] $TestResult = for ($i = 0; $i -lt $ExpectedValue.Count; $i++) { if ($OperationType -eq 'lt') { $TestedValue -lt $ExpectedValue[$i] } elseif ($OperationType -eq 'gt') { $TestedValue -gt $ExpectedValue[$i] } elseif ($OperationType -eq 'ge') { $TestedValue -ge $ExpectedValue[$i] } elseif ($OperationType -eq 'le') { $TestedValue -le $ExpectedValue[$i] } elseif ($OperationType -eq 'like') { $TestedValue -like $ExpectedValue[$i] } elseif ($OperationType -eq 'contains') { $TestedValue -contains $ExpectedValue[$i] } elseif ($OperationType -eq 'in') { $ExpectedValue -in $ExpectedValue[$i] } elseif ($OperationType -eq 'notin') { $ExpectedValue -notin $ExpectedValue[$i] } else { $TestedValue -eq $ExpectedValue[$i] } }
                $TextTestedValue = $TestedValue
            }
        } else {
            $TestResult = $null
            $ExtendedTextValue = "Test provided but no tests required."
        }
        if ($null -eq $TestResult) {
            $ReportResult = $null
            $ReportExtended = $ExtendedTextValue
        } else {
            if ($OperationResult -eq 'OR') {
                if ($TestResult -contains $true) {
                    $ReportResult = $true
                    $ReportExtended = "Expected value ($($Operators[$OperationType])): $($ExpectedValue)"
                } else {
                    $ReportResult = $false
                    $ReportExtended = "Expected value ($($Operators[$OperationType])): $ExpectedValue, Found value: $($TextTestedValue)"
                }
            } else {
                if ($TestResult -notcontains $false) {
                    $ReportResult = $true
                    $ReportExtended = "Expected value ($($Operators[$OperationType])): $($ExpectedValue)"
                } else {
                    $ReportResult = $false
                    $ReportExtended = "Expected value ($($Operators[$OperationType])): $ExpectedValue, Found value: $($TextTestedValue)"
                }
            }
        }
        if ($PropertyExtendedValue.Count -gt 0) {
            $ReportExtended = $Object
            foreach ($V in $PropertyExtendedValue) { $ReportExtended = $ReportExtended.$V }
        }
        Out-Status -Text $TestName -Status $ReportResult -ExtendedValue $ReportExtended -Domain $Domain -DomainController $DomainController
        return $ReportResult }
    if ($Script:TestimoConfiguration.Debug.ShowErrors) { & $ScriptBlock } else {
        try { & $ScriptBlock } catch {
            Out-Status -Text $TestName -Status $false -ExtendedValue $_.Exception.Message -Domain $Domain -DomainController $DomainController
            return $False
        }
    }
}
$Script:ResolveDNSExternal = { $Output = Invoke-Command -ComputerName $DomainController { Resolve-DnsName -Name 'evotec.xyz' }
    $Output }
$Script:ResolveDNSInternal = { $AllDomainControllers = Get-ADDomainController -Filter * -Server $Domain
    $IPs = $AllDomainControllers.IPv4Address | Sort-Object
    $Output = Resolve-DnsName -Name $Domain
    @{'Result' = 'IP Comparison'
        'Status' = if ($null -eq (Compare-Object -ReferenceObject $IPs -DifferenceObject ($Output.IP4Address | Sort-Object))) { $true } else { $false }
    } }
$Script:SBDomainControllersPort53 = { Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue -Port 53 }
$Script:TestServerPorts = { $TcpPorts = @(53, 88, 135, 139, 389, 445, 464, 636, 3268, 3269, 9389)
    Test-ComputerPort -ComputerName $DomainController -PortTCP $TcpPorts -WarningAction SilentlyContinue }
$Script:TestServerPortsRDP = { $TcpPorts = @(3389)
    Test-ComputerPort -ComputerName $DomainController -PortTCP $TcpPorts -WarningAction SilentlyContinue }
function Test-Value {
    [CmdletBinding()]
    param([Object] $Object,
        [string] $TestName,
        [string[]] $Property,
        [Object] $ExpectedValue,
        [string[]] $PropertyExtendedValue,
        [string] $OperationType,
        [int] $Level,
        [string] $Domain,
        [Object] $DomainController,
        [int] $ExpectedCount,
        [string] $OperationResult)
    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 -OperationResult $OperationResult } else { foreach ($_ in $Object) { Test-Me -Object $_ -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult } } } else { Write-Warning 'Objected not passed to Test-VALUE.' }
}
$Script:WindowsRemoteManagement = { Test-WinRM -ComputerName $DomainController }
$Script:TestWindowsUpdates = { Get-HotFix -ComputerName $DomainController | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1 }
Export-ModuleMember -Function @('Test-IMO') -Alias @('Test-ImoAD')