Testimo.psm1

$Script:SBComputerOperatingSystem = { Get-ComputerOperatingSystem -ComputerName $DomainController -WarningAction SilentlyContinue }
$Script:SBDomainControllersDFSR = { Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName $DomainController }
$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:SBDomainControllersServices = { $Services = @('ADWS', 'DNS', 'DFS', 'DFSR', 'Eventlog', 'EventSystem', 'KDC', 'LanManWorkstation', 'LanManServer', 'NetLogon', 'NTDS', 'RPCSS', 'SAMSS', 'Spooler', 'W32Time')
    Get-PSService -Computers $DomainController -Services $Services }
$Script:SBDomainControllersSMB = { Get-ComputerSMB -ComputerName $DomainController }
$Script:SBDomainControllersTimeSettings = { Get-TimeSetttings -ComputerName $DomainController -Domain $Domain }
$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:SBDomainEmptyOrganizationalUnits = { param($Domain)
    $OrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties distinguishedname -Server $Domain | Select-Object -ExpandProperty distinguishedname
    $AllUsedOU = Get-ADObject -Filter "ObjectClass -eq 'user' -or ObjectClass -eq 'computer' -or ObjectClass -eq 'group'" -Server $Domain | Where-Object { ($_.DistinguishedName -notlike '*LostAndFound*') -and ($_.DistinguishedName -match 'OU=(.*)') } | ForEach-Object { $matches[0] } | Select-Object -Unique
    $OrganizationalUnits | Where-Object { ($AllUsedOU -notcontains $_) -and -not (Get-ADOrganizationalUnit -Filter * -SearchBase $_ -SearchScope 1 -Server $Domain) } }
$Script:SBDomainOrphanedFSP = { param($Domain)
    $AllFSP = Get-WinADUsersFP -Domain $Domain
    $OrphanedObjects = $AllFSP | Where-Object { $_.TranslatedName -eq $null }
    $OrphanedObjects }
$Script:SBDomainPasswordComplexity = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    & $ADModule { param($Domain); Get-WinADDomainDefaultPasswordPolicy -Domain $Domain } $Domain }
function Get-ForestDFSHealth {
    param([string[]] $Domains,
        [int] $EventDays = 1)
    $Forest = Get-ADForest
    if (-not $Domains) { $Domains = $Forest.Domains }
    [Array] $Table = foreach ($Domain in $Domains) {
        $DomainControllers = Get-ADDomainController -Filter * -Server $Domain
        [Array]$GPOs = @(Get-GPO -All -Domain $Domain)
        foreach ($DC in $DomainControllers) {
            $DCName = $DC.Name
            $Hostname = $DC.Hostname
            $DN = $DC.ComputerObjectDN
            $LocalSettings = "CN=DFSR-LocalSettings,$DN"
            $Subscriber = "CN=Domain System Volume,$LocalSettings"
            $Subscription = "CN=SYSVOL Subscription,$Subscriber"
            $DomainSummary = [ordered] @{"DC" = $DCName
                "IsPDC" = $DC.OperationMasterRoles -contains 'PDCEmulator'
                "Domain" = $Domain
                "GPOCount" = $GPOs.Count
                "SysvolCount" = 0
                "Availability" = $false
                "Member(CN=Topology)" = $null
                "DFSErrors" = 0
                "DFSEvents" = $null
                "DFSLocalSetting" = ''
                "DomainSystemVolume" = ''
                "SYSVOLSubscription" = ''
            }
            try {
                $MemberReference = (Get-ADObject $Subscriber -Properties msDFSR-MemberReference -Server $Domain -ErrorAction Stop).'msDFSR-MemberReference' -like "CN=$DCName,*"
                if ($MemberReference) { $DomainSummary['Member(CN=Topology)'] = $true }
            } catch { $DomainSummary['Member(CN=Topology)'] = $false }
            try {
                $DFSLocalSetting = Get-ADObject $LocalSettings -Server $Domain -ErrorAction Stop
                if ($DFSLocalSetting) { $DomainSummary['DFSLocalSetting'] = $true }
            } catch { $DomainSummary['DFSLocalSetting'] = $false }
            try {
                $DomainSystemVolume = Get-ADObject $Subscriber -Server $Domain -ErrorAction Stop
                if ($DomainSystemVolume) { $DomainSummary['DomainSystemVolume'] = $true }
            } catch { $DomainSummary['DomainSystemVolume'] = $false }
            try {
                $SysVolSubscription = Get-ADObject $Subscription -Server $Domain -ErrorAction Stop
                if ($SysVolSubscription) { $DomainSummary['SYSVOLSubscription'] = $true }
            } catch { $DomainSummary['SYSVOLSubscription'] = $false }
            try {
                $SYSVOL = Get-ChildItem -Path "\\$Hostname\SYSVOL\$Domain\Policies" -ErrorAction Stop
                $DomainSummary['SysvolCount'] = $SYSVOL.Count
            } catch { $DomainSummary['SysvolCount'] = 0 }
            if (Test-Connection $Hostname -ErrorAction SilentlyContinue) { $DomainSummary['Availability'] = $true } else { $DomainSummary['Availability'] = $false }
            try {
                $Yesterday = (Get-Date).AddDays($EventDays)
                [Array] $Events = Get-WinEvent -LogName "DFS Replication" -ComputerName $Hostname | Where-Object { ($_.LevelDisplayName -eq "Error") -and ($_.TimeCreated -ge $Yesterday) }
                $DomainSummary['DFSErrors'] = $Events.Count
                $DomainSummary['DFSEvents'] = $Events
            } catch { $DomainSummary['DFSErrors'] = $null }
        }
        [PSCustomObject] $DomainSummary
    }
    $Table
}
$Script:SBSysvolMode = { $DistinguishedName = (Get-ADDomain -Server $Domain).DistinguishedName
    $Object = "CN=DFSR-GlobalSettings,CN=System,$DistinguishedName"
    $Object = Get-ADObject -Identity $ADObj -Properties * -Server $Domain
    if ($Object.'msDFSR-Flags' -gt 47) { [PSCustomObject] @{'SysvolMode' = 'DFS-R' }
    } else { [PSCustomObject] @{'SysvolMode' = 'Not DFS-R' }
    } }
$Script:SBDomainTimeSynchronizationInternal = { Get-ComputerTime -TimeTarget $DomainController -WarningAction SilentlyContinue }
$Script:SBDomainTimeSynchronizationExternal = { Get-ComputerTime -TimeTarget $DomainController -TimeSource 'pool.ntp.org' -WarningAction SilentlyContinue }
$Script:SBDomainTombstoneLifetime = { $Output = (Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$((Get-ADRootDSE).configurationNamingContext)" -Properties tombstoneLifetime).tombstoneLifetime
    if ($null -eq $Output) { [PSCustomObject] @{TombstoneLifeTime = 60 }
    } else { [PSCustomObject] @{TombstoneLifeTime = $Output }
    } }
$script:SBDomainWellKnownFolders = { param($Domain)
    $DomainInformation = Get-ADDomain -Server $Domain
    $WellKnownFolders = $DomainInformation | Select-Object -Property UsersContainer, ComputersContainer, DomainControllersContainer, DeletedObjectsContainer, SystemsContainer, LostAndFoundContainer, QuotasContainer, ForeignSecurityPrincipalsContainer
    $CurrentWellKnownFolders = [ordered] @{ }
    $DomainDistinguishedName = $DomainInformation.DistinguishedName
    $DefaultWellKnownFolders = [ordered] @{UsersContainer = "CN=Users,$DomainDistinguishedName"
        ComputersContainer = "CN=Computers,$DomainDistinguishedName"
        DomainControllersContainer = "OU=Domain Controllers,$DomainDistinguishedName"
        DeletedObjectsContainer = "CN=Deleted Objects,$DomainDistinguishedName"
        SystemsContainer = "CN=System,$DomainDistinguishedName"
        LostAndFoundContainer = "CN=LostAndFound,$DomainDistinguishedName"
        QuotasContainer = "CN=NTDS Quotas,$DomainDistinguishedName"
        ForeignSecurityPrincipalsContainer = "CN=ForeignSecurityPrincipals,$DomainDistinguishedName"
    }
    foreach ($_ in $WellKnownFolders.PSObject.Properties.Name) {
        $CurrentWellKnownFolders[$_] = $DomainInformation.$_
        $CurrentWellKnownFolders[$_] = $DomainInformation.$_
    }
    Compare-MultipleObjects -Object @($DefaultWellKnownFolders, $CurrentWellKnownFolders) -SkipProperties }
$Script:SBForestOptionalFeatures = { $ADModule = Import-Module PSWinDocumentation.AD -PassThru
    & $ADModule { Get-WinADForestOptionalFeatures -WarningAction SilentlyContinue } }
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:SBTestFSMODomainRoles = { 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:SBResolveDNSExternal = { $Output = Invoke-Command -ComputerName $DomainController -ErrorAction Stop { Resolve-DnsName -Name 'evotec.xyz' -ErrorAction SilentlyContinue }
    $Output }
$Script:SBResolveDNSInternal = { $Output = Invoke-Command -ComputerName $DomainController -ErrorAction Stop { param([string] $DomainController)
        $AllDomainControllers = Get-ADDomainController -Identity $DomainController -Server $DomainController
        $IPs = $AllDomainControllers.IPv4Address | Sort-Object
        $Output = Resolve-DnsName -Name $DomainController -ErrorAction SilentlyContinue
        @{'Result' = 'IP Comparison'
            'Status' = if ($null -eq (Compare-Object -ReferenceObject $IPs -DifferenceObject ($Output.IP4Address | Sort-Object))) { $true } else { $false }
            'IPAddresses' = $Output.IP4Address
        } } -ArgumentList $DomainController
    $Output }
$Script:SBDomainControllersPort53 = { Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue -Port 53 }
$Script:SBTestServerPorts = { $TcpPorts = @(53, 88, 135, 139, 389, 445, 464, 636, 3268, 3269, 9389)
    Test-ComputerPort -ComputerName $DomainController -PortTCP $TcpPorts -WarningAction SilentlyContinue }
$Script:SBTestServerPortsRDP = { $TcpPorts = @(3389)
    Test-ComputerPort -ComputerName $DomainController -PortTCP $TcpPorts -WarningAction SilentlyContinue }
$Script:SBWindowsRemoteManagement = { Test-WinRM -ComputerName $DomainController }
$Script:SBTestWindowsUpdates = { Get-HotFix -ComputerName $DomainController | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1 }
$Script:SBKeberosAccountTimeChange = { Get-ADUser krbtgt -Properties Created, PasswordLastSet, msDS-KeyVersionNumber -Server $Domain }
$Script:SBGroupsAccountOperators = { Get-ADGroupMember -Identity 'S-1-5-32-548' -Recursive -Server $Domain }
$Script:SBUsersAccountAdministrator = { $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
        }
    } }
$Script:SBGroupSchemaAdmins = { $DomainSID = (Get-ADDomain -Server $Domain).DomainSID
    Get-ADGroupMember -Recursive -Server $Domain -Identity "$DomainSID-518" }
function Test-DNSNameServers {
    [cmdletBinding()]
    param([string] $DomainController,
        [string] $Domain)
    if ($DomainController) {
        $AllDomainControllers = (Get-ADDomainController -Server $Domain -Filter { IsReadOnly -eq $false }).HostName
        try {
            $Hosts = Get-DnsServerResourceRecord -ZoneName $Domain -ComputerName $DomainController -RRType NS -ErrorAction Stop
            $NameServers = (($Hosts | Where-Object { $_.HostName -eq '@' }).RecordData.NameServer) -replace ".$"
            $Compare = ((Compare-Object -ReferenceObject $AllDomainControllers -DifferenceObject $NameServers -IncludeEqual).SideIndicator -notin @('=>', '<='))
            [PSCustomObject] @{DomainControllers = $AllDomainControllers
                NameServers = $NameServers
                Status = $Compare
                Comment = "Name servers found $($NameServers -join ', ')"
            }
        } catch {
            [PSCustomObject] @{DomainControllers = $AllDomainControllers
                NameServers = $null
                Status = $false
                Comment = $_.Exception.Message
            }
        }
    }
}
$Script:SBServerDnsNameServers = { Test-DNSNameServers @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: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: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 Get-TestimoDomain {
    [CmdletBinding()]
    param([string] $Domain)
    $Output = Get-ADDomain -Server $Domain -ErrorAction Stop
    $Output
}
function Get-TestimoDomainControllers {
    [CmdletBinding()]
    param([string] $Domain)
    try {
        $DomainControllers = Get-ADDomainController -Server $Domain -Filter * -ErrorAction Stop
        foreach ($_ in $DomainControllers) {
            if ($_.HostName -notin $Script:TestimoConfiguration['Exclusions']['DomainControllers']) {
                [PSCustomObject] @{Name = $($_.HostName).ToLower()
                    IsPDC = $_.OperationMasterRoles -contains 'PDCEmulator'
                }
            }
        }
    } catch { return }
}
function Get-TestimoForest {
    [CmdletBinding()]
    param()
    try {
        $Forest = Get-ADForest -ErrorAction Stop
        $Domains = foreach ($_ in $Forest.Domains) { if ($_ -notin $Script:TestimoConfiguration['Exclusions']['Domains']) { $_.ToLower() } }
        [ordered] @{Name = $Forest.Name
            ForestMode = $Forest.ForestMode
            Domains = $Domains
            PartitionsContainer = $Forest.PartitionsContainer
            DomainNamingMaster = $Forest.DomainNamingMaster
            SchemaMaster = $Forest.SchemaMaster
            GlobalCatalogs = $Forest.GlobalCatalogs
            Sites = $Forest.Sites
            SPNSuffixes = $Forest.SPNSuffixes
            UPNSuffixes = $Forest.UPNSuffixes
            ApplicationPartitions = $Forest.ApplicationPartitions
            CrossForestReferences = $Forest.CrossForestReferences
        }
    } catch { return }
}
function Out-Begin {
    [CmdletBinding()]
    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 Start-Testing {
    [CmdletBinding()]
    param([ScriptBlock] $Execute,
        [string] $Scope,
        [string] $Domain,
        [string] $DomainController,
        [bool] $IsPDC,
        [Object] $ForestInformation,
        [Object] $DomainInformation)
    $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" }
    [bool] $IsDomainRoot = $ForestInformation.Name -eq $Domain
    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)) {
            $CurrentSection = $Script:TestimoConfiguration.$Scope.Sources[$Source]
            if ($CurrentSection['Enable'] -eq $true) {
                $CurrentSource = $CurrentSection['Source']
                [Array] $AllTests = $CurrentSection['Tests'].Keys
                $TestsSummary = [PSCustomobject] @{Passed = 0
                    Failed = 0
                    Skipped = 0
                    Total = 0
                }
                $Time = Start-TimeLog
                if ($CurrentSource['Requirements']) {
                    if ($null -ne $CurrentSource['Requirements']['IsDomainRoot']) { if (-not $CurrentSource['Requirements']['IsDomainRoot'] -eq $IsDomainRoot) { continue } }
                    if ($null -ne $CurrentSource['Requirements']['IsPDC']) { if (-not $CurrentSource['Requirements']['IsPDC'] -eq $IsPDC) { continue } }
                }
                if ($CurrentSource['Parameters']) {
                    $SourceParameters = $CurrentSource['Parameters']
                    $Object = Start-TestProcessing -Test $CurrentSource['Name'] -Level $Level -OutputRequired -Domain $Domain -DomainController $DomainController { & $CurrentSource['Data'] @SourceParameters -DomainController $DomainController -Domain $Domain }
                } else { $Object = Start-TestProcessing -Test $CurrentSource['Name'] -Level $Level -OutputRequired -Domain $Domain -DomainController $DomainController { & $CurrentSource['Data'] -DomainController $DomainController -Domain $Domain } }
                if ($Object -and ($null -eq $CurrentSource['ExpectedOutput'] -or $CurrentSource['ExpectedOutput'] -eq $true)) {
                    $FailAllTests = $false
                    Out-Begin -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Text $CurrentSource['Name'] -Status $true -ExtendedValue 'Data is available.' -Domain $Domain -DomainController $DomainController
                    $TestsSummary.Passed = $TestsSummary.Passed + 1
                } elseif ($Object -and $CurrentSource['ExpectedOutput'] -eq $false) {
                    $FailAllTests = $true
                    Out-Failure -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'Data is available. This is a bad thing.' -Domain $Domain -DomainController $DomainController
                    $TestsSummary.Failed = $TestsSummary.Failed + 1
                } elseif ($null -eq $Object -and $CurrentSource['ExpectedOutput'] -eq $false) {
                    $FailAllTests = $false
                    Out-Begin -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Text $CurrentSource['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['Name'] -Level $LevelTest -ExtendedValue 'No data available.' -Domain $Domain -DomainController $DomainController
                    $TestsSummary.Failed = $TestsSummary.Failed + 1
                }
                foreach ($Test in $AllTests) {
                    $CurrentTest = $CurrentSection['Tests'][$Test]
                    if ($CurrentTest['Enable'] -eq $True) {
                        if ($CurrentTest['Requirements']) {
                            if ($null -ne $CurrentTest['Requirements']['IsDomainRoot']) {
                                if (-not $CurrentTest['Requirements']['IsDomainRoot'] -eq $IsDomainRoot) {
                                    $TestsSummary.Skipped = $TestsSummary.Skipped + 1
                                    continue
                                }
                            }
                            if ($null -ne $CurrentTest['Requirements']['IsPDC']) {
                                if (-not $CurrentTest['Requirements']['IsPDC'] -eq $IsPDC) {
                                    $TestsSummary.Skipped = $TestsSummary.Skipped + 1
                                    continue
                                }
                            }
                        }
                        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['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 }
    }
}
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
        }
    }
}
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,
        [scriptblock] $WhereObject)
    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 {
            if ($WhereObject) {
                $Object = $Object | Where-Object $WhereObject
                Test-Me -Object $Object -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: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 = $true
                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'
                        }
                    }
                }
            }
            TombstoneLifetime = @{Enable = $true
                Source = @{Name = 'Tombstone Lifetime'
                    Data = $Script:SBDomainTombstoneLifetime
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{TombstoneLifetime = @{Enable = $true
                        Name = 'TombstoneLifetime should be set to minimum of 180 days'
                        Parameters = @{ExpectedValue = 180
                            Property = 'TombstoneLifeTime'
                            OperationType = 'ge'
                        }
                    }
                }
                RecommendedLinks = @('https://helpcenter.netwrix.com/Configure_IT_Infrastructure/AD/AD_Tombstone.html')
            }
        }
    }
    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'
                        }
                    }
                }
            }
            WellKnownFolders = @{Enable = $true
                Source = @{Name = 'Well known folders'
                    Data = $Script:SBDomainWellKnownFolders
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{UsersContainer = @{Enable = $true
                        Name = "Users Container shouldn't be at default"
                        Parameters = @{WhereObject = { $_.Name -eq 'UsersContainer' }
                            ExpectedValue = $false
                            Property = 'Status'
                            OperationType = 'eq'
                            PropertyExtendedValue = '1'
                        }
                    }
                    ComputersContainer = @{Enable = $true
                        Name = "Computers Container shouldn't be at default"
                        Parameters = @{WhereObject = { $_.Name -eq 'ComputersContainer' }
                            ExpectedValue = $false
                            Property = 'Status'
                            OperationType = 'eq'
                            PropertyExtendedValue = '1'
                        }
                    }
                    DomainControllersContainer = @{Enable = $true
                        Name = "Domain Controllers Container should be at default location"
                        Parameters = @{WhereObject = { $_.Name -eq 'DomainControllersContainer' }
                            ExpectedValue = $true
                            Property = 'Status'
                            OperationType = 'eq'
                            PropertyExtendedValue = '1'
                        }
                    }
                    DeletedObjectsContainer = @{Enable = $true
                        Name = "Deleted Objects Container should be at default location"
                        Parameters = @{WhereObject = { $_.Name -eq 'DeletedObjectsContainer' }
                            ExpectedValue = $true
                            Property = 'Status'
                            OperationType = 'eq'
                            PropertyExtendedValue = '1'
                        }
                    }
                    SystemsContainer = @{Enable = $true
                        Name = "Systems Container should be at default location"
                        Parameters = @{WhereObject = { $_.Name -eq 'SystemsContainer' }
                            ExpectedValue = $true
                            Property = 'Status'
                            OperationType = 'eq'
                            PropertyExtendedValue = '1'
                        }
                    }
                    LostAndFoundContainer = @{Enable = $true
                        Name = "Lost And Found Container should be at default location"
                        Parameters = @{WhereObject = { $_.Name -eq 'LostAndFoundContainer' }
                            ExpectedValue = $true
                            Property = 'Status'
                            OperationType = 'eq'
                            PropertyExtendedValue = '1'
                        }
                    }
                    QuotasContainer = @{Enable = $true
                        Name = "Quotas Container shouldn be at default location"
                        Parameters = @{WhereObject = { $_.Name -eq 'QuotasContainer' }
                            ExpectedValue = $true
                            Property = 'Status'
                            OperationType = 'eq'
                            PropertyExtendedValue = '1'
                        }
                    }
                    ForeignSecurityPrincipalsContainer = @{Enable = $true
                        Name = "Foreign Security Principals Container should be at default location"
                        Parameters = @{WhereObject = { $_.Name -eq 'ForeignSecurityPrincipalsContainer' }
                            ExpectedValue = $true
                            Property = 'Status'
                            OperationType = 'eq'
                            PropertyExtendedValue = '1'
                        }
                    }
                }
            }
            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 = $false
                            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
                    }
                }
            }
            OrphanedForeignSecurityPrincipals = @{Enable = $true
                Source = @{Name = "Orphaned Foreign Security Principals"
                    Data = $Script:SBDomainOrphanedFSP
                    Area = 'Cleanup'
                    ExpectedOutput = $false
                }
            }
            EmptyOrganizationalUnits = @{Enable = $true
                Source = @{Name = "Orphaned/Empty Organizational Units"
                    Data = $Script:SBDomainEmptyOrganizationalUnits
                    Area = 'Cleanup'
                    ExpectedOutput = $false
                }
            }
            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:SBKeberosAccountTimeChange
                    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:SBGroupsAccountOperators
                    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!"
                }
            }
            SecurityGroupsSchemaAdmins = @{Enable = $true
                Source = @{Name = "Groups: Schema Admins should be empty"
                    Data = $Script:SBGroupSchemaAdmins
                    Area = ''
                    Parameters = @{ }
                    Requirements = @{IsDomainRoot = $true }
                    ExpectedOutput = $false
                    Explanation = "Schema Admins should be empty."
                }
            }
            SecurityUsersAcccountAdministrator = @{Enable = $true
                Source = @{Name = "Users: Administrator"
                    Data = $Script:SBUsersAccountAdministrator
                    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:SBWindowsRemoteManagement
                    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:SBComputerOperatingSystem
                    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'
                        }
                    }
                }
            }
            Services = @{Enable = $true
                Source = @{Name = 'Service Status'
                    Data = $Script:SBDomainControllersServices
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{ADWSServiceStatus = @{Enable = $true
                        Name = 'ADWS Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'ADWS' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    ADWSServiceStartType = @{Enable = $true
                        Name = 'ADWS Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'ADWS' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    DNSServiceStatus = @{Enable = $true
                        Name = 'DNS Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'DNS' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    DNSServiceStartType = @{Enable = $true
                        Name = 'DNS Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'DNS' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    DFSServiceStatus = @{Enable = $true
                        Name = 'DFS Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'DFS' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    DFSServiceStartType = @{Enable = $true
                        Name = 'DFS Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'DFS' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    DFSRServiceStatus = @{Enable = $true
                        Name = 'DFSR Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'DFSR' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    DFSRServiceStartType = @{Enable = $true
                        Name = 'DFSR Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'DFSR' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    EventlogServiceStatus = @{Enable = $true
                        Name = 'Eventlog Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'Eventlog' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    EventlogServiceStartType = @{Enable = $true
                        Name = 'Eventlog Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'Eventlog' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    EventSystemServiceStatus = @{Enable = $true
                        Name = 'EventSystem Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'EventSystem' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    EventSystemServiceStartType = @{Enable = $true
                        Name = 'EventSystem Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'EventSystem' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    KDCServiceStatus = @{Enable = $true
                        Name = 'KDC Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'KDC' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    KDCServiceStartType = @{Enable = $true
                        Name = 'KDC Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'KDC' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    LanManWorkstationServiceStatus = @{Enable = $true
                        Name = 'LanManWorkstation Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'LanManWorkstation' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    LanManWorkstationServiceStartType = @{Enable = $true
                        Name = 'LanManWorkstation Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'LanManWorkstation' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    LanManServerServiceStatus = @{Enable = $true
                        Name = 'LanManServer Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'LanManServer' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    LanManServerServiceStartType = @{Enable = $true
                        Name = 'LanManServer Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'LanManServer' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    NetLogonServiceStatus = @{Enable = $true
                        Name = 'NetLogon Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'NetLogon' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    NetLogonServiceStartType = @{Enable = $true
                        Name = 'NetLogon Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'NetLogon' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    NTDSServiceStatus = @{Enable = $true
                        Name = 'NTDS Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'NTDS' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    NTDSServiceStartType = @{Enable = $true
                        Name = 'NTDS Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'NTDS' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    RPCSSServiceStatus = @{Enable = $true
                        Name = 'RPCSS Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'RPCSS' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    RPCSSServiceStartType = @{Enable = $true
                        Name = 'RPCSS Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'RPCSS' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    SAMSSServiceStatus = @{Enable = $true
                        Name = 'SAMSS Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'SAMSS' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    SAMSSServiceStartType = @{Enable = $true
                        Name = 'SAMSS Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'SAMSS' }
                            Property = 'StartType'
                            ExpectedValue = 'Automatic'
                            OperationType = 'eq'
                        }
                    }
                    SpoolerServiceStatus = @{Enable = $true
                        Name = 'Spooler Service is STOPPED'
                        Parameters = @{WhereObject = { $_.Name -eq 'Spooler' }
                            Property = 'Status'
                            ExpectedValue = 'Stopped'
                            OperationType = 'eq'
                        }
                    }
                    SpoolerServiceStartType = @{Enable = $true
                        Name = 'Spooler Service START TYPE is DISABLED'
                        Parameters = @{WhereObject = { $_.Name -eq 'Spooler' }
                            Property = 'StartType'
                            ExpectedValue = 'Disabled'
                            OperationType = 'eq'
                        }
                    }
                    W32TimeServiceStatus = @{Enable = $true
                        Name = 'W32Time Service is RUNNING'
                        Parameters = @{WhereObject = { $_.Name -eq 'W32Time' }
                            Property = 'Status'
                            ExpectedValue = 'Running'
                            OperationType = 'eq'
                        }
                    }
                    W32TimeServiceStartType = @{Enable = $true
                        Name = 'W32Time Service START TYPE is Automatic'
                        Parameters = @{WhereObject = { $_.Name -eq 'W32Time' }
                            Property = 'StartType'
                            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:SBTestServerPorts
                    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:SBTestServerPortsRDP
                    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'
                        }
                    }
                }
            }
            TimeSettings = [ordered] @{Enable = $true
                Source = @{Name = "Time Settings"
                    Data = $Script:SBDomainControllersTimeSettings
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{NTPServerEnabled = @{Enable = $true
                        Name = 'NtpServer must be enabled.'
                        Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController }
                            Property = 'NtpServerEnabled'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                    VMTimeProvider = @{Enable = $true
                        Name = 'Virtual Machine Time Provider should be disabled.'
                        Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController }
                            Property = 'VMTimeProvider'
                            ExpectedValue = $false
                            OperationType = 'eq'
                        }
                    }
                    NtpTypeNonPDC = [ordered] @{Enable = $true
                        Name = 'NTP Server should be set to Domain Hierarchy'
                        Requirements = @{IsPDC = $false }
                        Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController }
                            Property = 'NtpType'
                            ExpectedValue = 'NT5DS'
                            OperationType = 'eq'
                        }
                    }
                    NtpTypePDC = [ordered] @{Enable = $true
                        Name = 'NTP Server should be set to AllSync'
                        Requirements = @{IsPDC = $true }
                        Parameters = @{WhereObject = { $_.ComputerName -eq $DomainController }
                            Property = 'NtpType'
                            ExpectedValue = 'AllSync'
                            OperationType = 'eq'
                        }
                    }
                }
            }
            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:SBTestWindowsUpdates
                    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:SBResolveDNSInternal
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{ResolveDNSInternal = @{Enable = $true
                        Name = 'Should resolve Internal DNS'
                        Parameters = @{Property = 'Status'
                            ExpectedValue = $true
                            OperationType = 'eq'
                            PropertyExtendedValue = 'IPAddresses'
                        }
                        Explanation = 'DNS should resolve internal domains correctly.'
                    }
                }
            }
            DnsResolveExternal = @{Enable = $true
                Source = @{Name = "Resolves external DNS queries"
                    Data = $Script:SBResolveDNSExternal
                    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.'
                    }
                }
            }
            DnsNameServes = @{Enable = $true
                Source = @{Name = "Name servers for primary domain zone"
                    Data = $Script:SBServerDnsNameServers
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{DnsNameServersIdentical = @{Enable = $true
                        Name = 'DNS Name servers for primary zone are identical'
                        Parameters = @{Property = 'Status'
                            ExpectedValue = $True
                            OperationType = 'eq'
                            PropertyExtendedValue = 'Comment'
                        }
                        Explanation = 'DNS Name servers for primary zone should be equal to Domain Controllers for a Domain.'
                    }
                }
            }
            SMBProtocols = @{Enable = $true
                Source = @{Name = 'SMB Protocols'
                    Data = $Script:SBDomainControllersSMB
                    Area = ''
                    Parameters = @{ }
                }
                Tests = [ordered] @{EnableSMB1Protocol = @{Enable = $true
                        Name = 'SMB v1 Protocol should be disabled'
                        Parameters = @{Property = 'EnableSMB1Protocol'
                            ExpectedValue = $false
                            OperationType = 'eq'
                        }
                    }
                    EnableSMB2Protocol = @{Enable = $true
                        Name = 'SMB v2 Protocol should be enabled'
                        Parameters = @{Property = 'EnableSMB2Protocol'
                            ExpectedValue = $true
                            OperationType = 'eq'
                        }
                    }
                }
            }
            DFSRAutoRecovery = @{Enable = $true
                Source = @{Name = 'DFSR AutoRecovery'
                    Data = $Script:SBDomainControllersDFSR
                    Area = ''
                    Parameters = @{ }
                    RecommendedLinks = @('https://secureinfra.blog/2019/04/30/field-notes-a-quick-tip-on-dfsr-automatic-recovery-while-you-prepare-for-an-ad-domain-upgrade/')
                }
                Tests = [ordered] @{EnableSMB1Protocol = @{Enable = $true
                        Name = 'DFSR AutoRecovery should be enabled'
                        Parameters = @{Property = 'StopReplicationOnAutoRecovery'
                            ExpectedValue = 0
                            OperationType = 'eq'
                        }
                    }
                }
            }
        }
    }
    Debug = @{ShowErrors = $false }
}
function Test-IMO {
    [alias('Test-ImoAD')]
    [CmdletBinding()]
    param([switch] $ReturnResults,
        [string[]] $ExludeDomains,
        [string[]] $ExludeDomainControllers,
        [switch] $ShowErrors)
    $global:ProgressPreference = 'SilentlyContinue'
    $global:ErrorActionPreference = 'Stop'
    $Script:TestResults = [System.Collections.Generic.List[PSCustomObject]]::new()
    $Script:TestimoConfiguration.Debug.ShowErrors = $ShowErrors
    $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 ', ')
    }
    $ForestInformation = Get-TestimoForest
    $null = Start-Testing -Scope 'Forest' -ForestInformation $ForestInformation { foreach ($Domain in $ForestInformation.Domains) {
            $DomainInformation = Get-TestimoDomain -Domain $Domain
            Start-Testing -Scope 'Domain' -Domain $Domain -DomainInformation $DomainInformation -ForestInformation $ForestInformation { $DomainControllers = Get-TestimoDomainControllers -Domain $Domain
                foreach ($DC in $DomainControllers) { Start-Testing -Scope 'DomainControllers' -Domain $Domain -DomainController $DC.Name -IsPDC $DC.IsPDC -DomainInformation $DomainInformation -ForestInformation ForestInformation } }
        } }
    if ($ReturnResults) { $Script:TestResults }
}
Export-ModuleMember -Function @('Test-IMO') -Alias @('Test-ImoAD')