Testimo.psm1

function Write-Color { 
    <#
    .SYNOPSIS
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    .DESCRIPTION
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    It provides:
    - Easy manipulation of colors,
    - Logging output to file (log)
    - Nice formatting options out of the box.
    - Ability to use aliases for parameters
 
    .PARAMETER Text
    Text to display on screen and write to log file if specified.
    Accepts an array of strings.
 
    .PARAMETER Color
    Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER BackGroundColor
    Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER StartTab
    Number of tabs to add before text. Default is 0.
 
    .PARAMETER LinesBefore
    Number of empty lines before text. Default is 0.
 
    .PARAMETER LinesAfter
    Number of empty lines after text. Default is 0.
 
    .PARAMETER StartSpaces
    Number of spaces to add before text. Default is 0.
 
    .PARAMETER LogFile
    Path to log file. If not specified no log file will be created.
 
    .PARAMETER DateTimeFormat
    Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss
 
    .PARAMETER LogTime
    If set to $true it will add time to log file. Default is $true.
 
    .PARAMETER LogRetry
    Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2.
 
    .PARAMETER Encoding
    Encoding of the log file. Default is Unicode.
 
    .PARAMETER ShowTime
    Switch to add time to console output. Default is not set.
 
    .PARAMETER NoNewLine
    Switch to not add new line at the end of the output. Default is not set.
 
    .PARAMETER NoConsoleOutput
    Switch to not output to console. Default all output goes to console.
 
    .EXAMPLE
    Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                      "followed by red ",
                      "and then we have Magenta... ",
                      "isn't it fun? ",
                      "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                      "followed by red ",
                      "and then we have Magenta... ",
                      "isn't it fun? ",
                      "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1
 
    .EXAMPLE
    Write-Color "1. ", "Option 1" -Color Yellow, Green
    Write-Color "2. ", "Option 2" -Color Yellow, Green
    Write-Color "3. ", "Option 3" -Color Yellow, Green
    Write-Color "4. ", "Option 4" -Color Yellow, Green
    Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1
 
    .EXAMPLE
    Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss"
    Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt"
 
    .EXAMPLE
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    Write-Color -t "my text" -c yellow -b green
    Write-Color -text "my text" -c red
 
    .EXAMPLE
    Write-Color -Text "Testuję czy się ładnie zapisze, czy będą problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput
 
    .NOTES
    Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
    Project support: https://github.com/EvotecIT/PSWriteColor
    Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    #>

    [alias('Write-Colour')]
    [CmdletBinding()]
    param (
        [alias ('T')] [String[]]$Text,
        [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White,
        [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null,
        [alias ('Indent')][int] $StartTab = 0,
        [int] $LinesBefore = 0,
        [int] $LinesAfter = 0,
        [int] $StartSpaces = 0,
        [alias ('L')] [string] $LogFile = '',
        [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss',
        [alias ('LogTimeStamp')][bool] $LogTime = $true,
        [int] $LogRetry = 2,
        [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode',
        [switch] $ShowTime,
        [switch] $NoNewLine,
        [alias('HideConsole')][switch] $NoConsoleOutput
    )
    if (-not $NoConsoleOutput) {
        $DefaultColor = $Color[0]
        if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) {
            Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated."
            return
        }
        if ($LinesBefore -ne 0) {
            for ($i = 0; $i -lt $LinesBefore; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        } # Add empty line before
        if ($StartTab -ne 0) {
            for ($i = 0; $i -lt $StartTab; $i++) {
                Write-Host -Object "`t" -NoNewline 
            } 
        }  # Add TABS before text
        if ($StartSpaces -ne 0) {
            for ($i = 0; $i -lt $StartSpaces; $i++) {
                Write-Host -Object ' ' -NoNewline 
            } 
        }  # Add SPACES before text
        if ($ShowTime) {
            Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline 
        } # Add Time before output
        if ($Text.Count -ne 0) {
            if ($Color.Count -ge $Text.Count) {
                # the real deal coloring
                if ($null -eq $BackGroundColor) {
                    for ($i = 0; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline 
                    }
                }
                else {
                    for ($i = 0; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline 
                    }
                }
            }
            else {
                if ($null -eq $BackGroundColor) {
                    for ($i = 0; $i -lt $Color.Length ; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline 
                    }
                    for ($i = $Color.Length; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline 
                    }
                }
                else {
                    for ($i = 0; $i -lt $Color.Length ; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline 
                    }
                    for ($i = $Color.Length; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline 
                    }
                }
            }
        }
        if ($NoNewLine -eq $true) {
            Write-Host -NoNewline 
        }
        else {
            Write-Host 
        } # Support for no new line
        if ($LinesAfter -ne 0) {
            for ($i = 0; $i -lt $LinesAfter; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        }  # Add empty line after
    }
    if ($Text.Count -and $LogFile) {
        # Save to file
        $TextToFile = ""
        for ($i = 0; $i -lt $Text.Length; $i++) {
            $TextToFile += $Text[$i]
        }
        $Saved = $false
        $Retry = 0
        Do {
            $Retry++
            try {
                if ($LogTime) {
                    "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false
                }
                else {
                    "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false
                }
                $Saved = $true
            }
            catch {
                if ($Saved -eq $false -and $Retry -eq $LogRetry) {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))"
                }
                else {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)"
                }
            }
        } Until ($Saved -eq $true -or $Retry -ge $LogRetry)
    }
 }
$ComputersUnsupported = @{
    Name   = 'DomainComputersUnsupported'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Computers Unsupported"
        Data           = {
            $Computers = Get-ADComputer -Filter { ( operatingsystem -like "*xp*") -or (operatingsystem -like "*vista*") -or ( operatingsystem -like "*Windows NT*") -or ( operatingsystem -like "*2000*") -or ( operatingsystem -like "*2003*") } -Property Name, OperatingSystem, OperatingSystemServicePack, lastlogontimestamp -Server $Domain
            $Computers | Select-Object Name, OperatingSystem, OperatingSystemServicePack, @{name = "lastlogontimestamp"; expression = { [datetime]::fromfiletime($_.lastlogontimestamp) } }
        }
        Details        = [ordered] @{
            Area        = 'Objects'
            Category    = 'Cleanup'
            Importance  = 3
            Description = 'Computers running an unsupported operating system.'
            Resolution  = 'Upgrade or remove computers from Domain.'
            Resources   = @()
        }
        ExpectedOutput = $false
    }
}
$ComputersUnsupportedMainstream = @{
    Name   = 'DomainComputersUnsupportedMainstream'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Computers Unsupported Mainstream Only"
        Data           = {
            $Computers = Get-ADComputer -Filter { ( operatingsystem -like "*2008*") } -Property Name, OperatingSystem, OperatingSystemServicePack, lastlogontimestamp -Server $Domain
            $Computers | Select-Object Name, OperatingSystem, OperatingSystemServicePack, @{name = "lastlogontimestamp"; expression = { [datetime]::fromfiletime($_.lastlogontimestamp) } }
        }
        Details        = [ordered] @{
            Area        = 'Objects'
            Category    = 'Cleanup'
            Importance  = 3
            Description = 'Computers running an unsupported operating system, but with possibly Microsoft support.'
            Resolution  = 'Consider upgrading computers running Windows Server 2008 or Windows Server 2008 R2 to a version that still offers mainstream support from Microsoft.'
            Resources   = @()
        }
        ExpectedOutput = $false
    }
}
$DHCPAuthorized = @{
    Name   = 'DomainDHCPAuthorized'
    Enable = $false
    Scope  = 'Domain'
    Source = @{
        Name           = "DHCP authorized in domain"
        Data           = {
            #$DomainInformation = Get-ADDomain -Identity 'ad.evotec.pl'
            $SearchBase = 'cn=configuration,{0}' -f $DomainInformation.DistinguishedName
            Get-ADObject -SearchBase $searchBase -Filter "objectclass -eq 'dhcpclass' -AND Name -ne 'dhcproot'" #| select name
        }
        Requirements   = @{
           IsDomainRoot = $true
        }
        Details        = [ordered] @{
            Area        = 'DHCP'
            Category    = 'Configuration'
            Severity    = ''
            Importance  = 0
            Description = ""
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DHCPAuthorized = @{
            Enable     = $true
            Name       = 'At least 1 DHCP Server Authorized'
            Parameters = @{
                ExpectedCount = '1'
                OperationType = 'ge'
            }
        }
    }
}

$DNSForwaders = @{
    Name   = 'DomainDNSForwaders'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "DNS Forwarders"
        Data           = {
            [Array] $Forwarders = Get-WinADDnsServerForwarder -Forest $ForestName -Domain $Domain -WarningAction SilentlyContinue
            if ($Forwarders.Count -gt 1) {
                $Comparision = Compare-MultipleObjects -Objects $Forwarders -FormatOutput -CompareSorted:$true -ExcludeProperty GatheredFrom -SkipProperties -Property 'IpAddress' -WarningAction SilentlyContinue
                [PSCustomObject] @{
                    Source = $Comparision.Source -join ', '
                    Status = $Comparision.Status
                }
            } elseif ($Forwarders.Count -eq 0) {
                [PSCustomObject] @{
                    # This code takes care of no forwarders
                    Source = 'No forwarders set'
                    Status = $false
                }
            } else {
                # This code takes care of only 1 server within a domain. If there is 1 server available (as others may be dead/unavailable at the time it assumes Pass)
                [PSCustomObject] @{
                    Source = $Forwarders[0].IPAddress -join ', '
                    Status = $true
                }
            }
        }
        Details        = [ordered] @{
            Area        = 'DNS'
            Category    = 'Configuration'
            Importance  = 3
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        SameForwarders = @{
            Enable      = $true
            Name        = 'Same DNS Forwarders'
            Parameters  = @{
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Source'
            }
            Description = 'DNS forwarders within one domain should have identical setup'
        }
    }
}
$DNSScavengingForPrimaryDNSServer = @{
    Name   = 'DomainDNSScavengingForPrimaryDNSServer'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "DNS Scavenging - Primary DNS Server"
        Data           = {
            Get-WinADDnsServerScavenging -Forest $ForestName -IncludeDomains $Domain
        }
        Details        = [ordered] @{
            Area        = 'DNS'
            Category    = 'Configuration'
            Importance  = 3
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        ScavengingCount      = @{
            Enable      = $true
            Name        = 'Scavenging DNS Servers Count'
            Parameters  = @{
                WhereObject   = { $null -ne $_.ScavengingInterval -and $_.ScavengingInterval -ne 0 }
                ExpectedCount = 1
                OperationType = 'eq'
            }
            Description = '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 = @{
                WhereObject   = { $null -ne $_.ScavengingInterval -and $_.ScavengingInterval -ne 0 }
                Property      = 'ScavengingInterval', 'Days'
                ExpectedValue = 7
                OperationType = 'le'
            }
        }
        'Scavenging State'   = @{
            Enable                 = $true
            Name                   = 'Scavenging State'
            Parameters             = @{
                WhereObject   = { $null -ne $_.ScavengingInterval -and $_.ScavengingInterval -ne 0 }
                Property      = 'ScavengingState'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Description            = 'Scavenging State is responsible for enablement of scavenging for all new zones created.'
            RecommendedValue       = $true
            DescriptionRecommended = 'It should be enabled so all new zones are subject to scavanging.'
            DefaultValue           = $false
        }
        'Last Scavenge Time' = @{
            Enable     = $true
            Name       = 'Last Scavenge Time'
            Parameters = @{
                WhereObject   = { $null -ne $_.ScavengingInterval -and $_.ScavengingInterval -ne 0 }
                # this date should be the same as in Scavending Interval
                Property      = 'LastScavengeTime'
                # we need to use string which will be converted to ScriptBlock later on due to configuration export to JSON
                ExpectedValue = '(Get-Date).AddDays(-7)'
                OperationType = 'gt'
            }
        }
    }
}
$DnsZonesAging = @{
    Name   = 'DomainDnsZonesAging'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Aging primary DNS Zone"
        Data           = {
            Get-WinDnsServerZones -Forest $ForestName -ZoneName $Domain -IncludeDomains $Domain
        }
        Details        = [ordered] @{
            Area        = ''
            Category    = ''
            Severity    = ''
            Importance  = 0
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        EnabledAgingEnabledAndIdentical = @{
            Enable      = $true
            Name        = 'Zone DNS aging should be identical on all DCs'
            Parameters  = @{
                WhereObject   = { $_.AgingEnabled -eq $false }
                ExpectedCount = 0
            }
            Description = 'Primary DNS zone should have aging enabled, on all DNS servers.'
        }
    }
}
$DNSZonesDomain0ADEL = @{
    Name   = 'DomainDNSZonesDomain0ADEL'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "DomainDNSZones should have proper FSMO Owner (0ADEL)"
        Data           = {
            #$DomainController = 'ad.evotec.pl'
            #$DomainInformation = Get-ADDomain -Server $DomainController
            $IdentityDomain = "CN=Infrastructure,DC=DomainDnsZones,$(($DomainInformation).DistinguishedName)"
            $FSMORoleOwner = (Get-ADObject -Identity $IdentityDomain -Properties fSMORoleOwner -Server $Domain)
            $FSMORoleOwner
        }
        Details        = [ordered] @{
            Area        = 'DNS'
            Category    = 'Configuration'
            Severity    = ''
            Importance  = 0
            Description = ""
            Resolution  = ''
            Resources   = @(
                'https://blogs.technet.microsoft.com/the_9z_by_chris_davis/2011/12/20/forestdnszones-or-domaindnszones-fsmo-says-the-role-owner-attribute-could-not-be-read/'
                'https://support.microsoft.com/en-us/help/949257/error-message-when-you-run-the-adprep-rodcprep-command-in-windows-serv'
                'https://social.technet.microsoft.com/Forums/en-US/8b4a7794-13b2-4ef0-90c8-16799e9fd529/orphaned-fsmoroleowner-entry-for-domaindnszones?forum=winserverDS'
            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DNSZonesDomain0ADEL = @{
            Enable     = $true
            Name       = 'DomainDNSZones should have proper FSMO Owner (0ADEL)'
            Parameters = @{
                ExpectedValue = '0ADEL:'
                Property      = 'fSMORoleOwner'
                OperationType = 'notmatch'
            }
        }
    }
}
$DNSZonesForest0ADEL = @{
    Name   = 'DomainDNSZonesForest0ADEL'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "ForestDNSZones should have proper FSMO Owner (0ADEL)"
        Data           = {
            #$DomainController = 'ad.evotec.xyz'
            #$DomainInformation = Get-ADDomain -Server $DomainController
            $IdentityForest = "CN=Infrastructure,DC=ForestDnsZones,$(($DomainInformation).DistinguishedName)"
            $FSMORoleOwner = (Get-ADObject -Identity $IdentityForest -Properties fSMORoleOwner -Server $Domain)
            $FSMORoleOwner
        }
        Requirements   = @{
            IsDomainRoot = $true
        }
        Details        = [ordered] @{
            Area        = 'DNS'
            Category    = 'Configuration'
            Severity    = ''
            Importance  = 0
            Description = ""
            Resolution  = ''
            Resources   = @(
                'https://blogs.technet.microsoft.com/the_9z_by_chris_davis/2011/12/20/forestdnszones-or-domaindnszones-fsmo-says-the-role-owner-attribute-could-not-be-read/'
                'https://support.microsoft.com/en-us/help/949257/error-message-when-you-run-the-adprep-rodcprep-command-in-windows-serv'
                'https://social.technet.microsoft.com/Forums/en-US/8b4a7794-13b2-4ef0-90c8-16799e9fd529/orphaned-fsmoroleowner-entry-for-domaindnszones?forum=winserverDS'
            )

        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DNSZonesForest0ADEL = @{
            Enable     = $true
            Name       = 'ForestDNSZones should have proper FSMO Owner (0ADEL)'
            Parameters = @{
                ExpectedValue = '0ADEL:'
                Property      = 'fSMORoleOwner'
                OperationType = 'notmatch'
            }
        }
    }
}


$DomainDomainControllers = @{
    Name            = 'DomainDomainControllers'
    Enable          = $true
    Scope           = 'Domain'
    Source          = @{
        Name           = "Domain Controller Objects"
        Data           = {
            Get-WinADForestControllerInformation -Forest $ForestName -Domain $Domain
        }
        Requirements   = @{}
        Details        = [ordered] @{
            Category    = 'Cleanup', 'Security'
            Importance  = 0
            ActionType  = 0
            Description = "Following test verifies Domain Controller status in Active Directory. It verifies critical aspects of Domain Controler such as Domain Controller Owner and Domain Controller Manager. It also checks if Domain Controller is enabled, ip address matches dns ip address, verifies whether LastLogonDate and LastPasswordDate are within thresholds. Those additional checks are there to find dead or offline DCs that could potentially impact Active Directory functionality. "
            Resources   = @(
                '[Domain member: Maximum machine account password age](https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/domain-member-maximum-machine-account-password-age)'
                '[Machine Account Password Process](https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/machine-account-password-process/ba-p/396026)'
                '[How to Configure DNS on a Domain Controller with Two IP Addresses](https://petri.com/configure-dns-on-domain-controller-two-ip-addresses)'
                '[USN rollback](https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/detect-and-recover-from-usn-rollback)'
                '[Active Directory Replication Overview & USN Rollback: What It Is & How It Happens](https://adsecurity.org/?p=515)'
            )
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        Enabled              = @{
            Enable     = $true
            Name       = 'DC object should be enabled'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.Enabled -ne $true }
            }
            Details    = [ordered] @{
                Category    = 'Cleanup'
                Importance  = 0
                ActionType  = 0
                StatusTrue  = 1
                StatusFalse = 3
            }
        }

        OwnerType            = @{
            Enable     = $true
            Name       = 'DC OwnerType should be Administrative'
            Parameters = @{
                #ExpectedValue = 'Administrative'
                #Property = 'OwnerType'
                #OperationType = 'eq'
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.OwnerType -ne 'Administrative' }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
        ManagedBy            = @{
            Enable     = $true
            Name       = 'DC field ManagedBy should be empty'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.ManagerNotSet -ne $true }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 3
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 2
            }
        }
        DNSStatus            = @{
            Enable     = $true
            Name       = 'DNS should return IP Address for DC'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.DNSStatus -ne $true }
            }
            Details    = [ordered] @{
                Category    = 'Cleanup'
                Importance  = 0
                ActionType  = 0
                StatusTrue  = 1
                StatusFalse = 2
            }
        }
        IPAddressStatusV4    = @{
            Enable     = $true
            Name       = 'DNS returned IPAddressV4 should match AD'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.IPAddressStatusV4 -ne $true }
            }
            Details    = [ordered] @{
                Category    = 'Cleanup'
                Importance  = 0
                ActionType  = 0
                StatusTrue  = 1
                StatusFalse = 2
            }
        }
        IPAddressStatusV6    = @{
            Enable     = $true
            Name       = 'DNS returned IPAddressV6 should match AD'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.IPAddressStatusV6 -ne $true }
            }
            Details    = [ordered] @{
                Category    = 'Cleanup'
                Importance  = 0
                ActionType  = 0
                StatusTrue  = 1
                StatusFalse = 2
            }
        }
        IPAddressSingleV4    = @{
            Enable     = $true
            Name       = 'There should be single IPv4 address set'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.IPAddressHasOneIpV4 -ne $true }
            }
            Details    = [ordered] @{
                Category    = 'Cleanup'
                Importance  = 0
                ActionType  = 1
                StatusTrue  = 1
                StatusFalse = 2
            }
        }
        IPAddressSingleV6    = @{
            Enable     = $true
            Name       = 'There should be single IPv6 address set'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.IPAddressHasOneipV6 -ne $true }
            }
            Details    = [ordered] @{
                Category    = 'Cleanup'
                Importance  = 0
                ActionType  = 1
                StatusTrue  = 1
                StatusFalse = 2
            }
        }
        PasswordNotRequired  = @{
            Enable     = $true
            Name       = "PasswordNotRequired shouldn't be set"
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.PasswordNotRequired -ne $false }
            }
            Details    = [ordered] @{
                Category    = 'Security', 'Cleanup'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        PasswordNeverExpires = @{
            Enable     = $true
            Name       = "PasswordNeverExpires shouldn't be set"
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.PasswordNeverExpires -ne $false }
            }
            Details    = [ordered] @{
                Category    = 'Security', 'Cleanup'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        PasswordLastChange   = @{
            Enable     = $true
            Name       = 'DC Password Change Less Than X days'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.PasswordLastChangedDays -ge 60 }
            }
            Details    = [ordered] @{
                Category    = 'Cleanup'
                Importance  = 1
                ActionType  = 1
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        LastLogonDays        = @{
            Enable     = $true
            Name       = 'DC Last Logon Less Than X days'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.LastLogonDays -ge 15 }
            }
            Details    = [ordered] @{
                Category    = 'Cleanup'
                Importance  = 1
                ActionType  = 1
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
    }
    DataInformation = {
        New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
        New-HTMLList {
            New-HTMLListItem -FontWeight bold, normal -Text "Enabled", " - means Domain Controller is enabled. If it's disabled it should be removed using proper cleanup method and according to company operation procedures. "
            New-HTMLListItem -FontWeight bold, normal -Text "DNSStatus", " - means Domain Controller IP address is available in DNS. If it's not registrered this means DC may not be functioning properly. "
            New-HTMLListItem -FontWeight bold, normal -Text "IPAddressStatusV4", " - means Domain Controller IP matches the one returned by DNS for IPV4. "
            New-HTMLListItem -FontWeight bold, normal -Text "IPAddressStatusV6", " - means Domain Controller IP matches the one returned by DNS for IPV6. "
            New-HTMLListItem -FontWeight bold, normal -Text "IPAddressHasOneIpV4", " - means Domain Controller has only one 1 IPV4 ipaddress (or not set at all). If it has more than 1 it's bad. "
            New-HTMLListItem -FontWeight bold, normal -Text "IPAddressHasOneipV6", " - means Domain Controller has only one 1 IPV6 ipaddress (or not set at all). If it has more than 1 it's bad. "
            New-HTMLListItem -FontWeight bold, normal -Text "ManagerNotSet", " - means ManagedBy property is not set (as required). If it's set it's bad. "
            New-HTMLListItem -FontWeight bold, normal -Text "OwnerType", " - means Domain Controller Owner is of certain type. Required type is Administrative. If it's different that means there's security risk involved. "
            New-HTMLListItem -FontWeight bold, normal -Text "PasswordNotRequired", " - should not be set. If it's set it can affect replication and security of Domain Controller. "
            New-HTMLListItem -FontWeight bold, normal -Text "PasswordLastChangedDays", " - displays last password change by Domain Controller. If it's more than 60 days it usually means DC is down or otherwise affected. "
            New-HTMLListItem -FontWeight bold, normal -Text "LastLogonDays", " - display last logon days of DC. If it's more than 15 days it usually means DC is down or otherwise affected. "
        } -FontSize 10pt
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -BackgroundColor Salmon -Value $false
        New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        New-HTMLTableCondition -Name 'DNSStatus' -ComparisonType string -BackgroundColor Salmon -Value $false
        New-HTMLTableCondition -Name 'DNSStatus' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        New-HTMLTableCondition -Name 'ManagerNotSet' -ComparisonType string -BackgroundColor Salmon -Value $false
        New-HTMLTableCondition -Name 'ManagerNotSet' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        New-HTMLTableCondition -Name 'IPAddressStatusV4' -ComparisonType string -BackgroundColor Salmon -Value $false
        New-HTMLTableCondition -Name 'IPAddressStatusV4' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        New-HTMLTableCondition -Name 'IPAddressStatusV6' -ComparisonType string -BackgroundColor Salmon -Value $false
        New-HTMLTableCondition -Name 'IPAddressStatusV6' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        New-HTMLTableCondition -Name 'IPAddressHasOneIpV4' -ComparisonType string -BackgroundColor Salmon -Value $false
        New-HTMLTableCondition -Name 'IPAddressHasOneIpV4' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        New-HTMLTableCondition -Name 'IPAddressHasOneipV6' -ComparisonType string -BackgroundColor Salmon -Value $false
        New-HTMLTableCondition -Name 'IPAddressHasOneipV6' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        New-HTMLTableCondition -Name 'OwnerType' -ComparisonType string -BackgroundColor Salmon -Value 'Administrative' -Operator ne
        New-HTMLTableCondition -Name 'OwnerType' -ComparisonType string -BackgroundColor PaleGreen -Value 'Administrative' -Operator eq
        New-HTMLTableCondition -Name 'ManagedBy' -ComparisonType string -Color Salmon -Value '' -Operator ne
        New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -BackgroundColor PaleGreen -Value $false -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'PasswordNeverExpires' -ComparisonType string -BackgroundColor PaleGreen -Value $false -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -BackgroundColor PaleGreen -Value 40 -Operator le
        New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -BackgroundColor OrangePeel -Value 41 -Operator ge
        New-HTMLTableCondition -Name 'PasswordLastChangedDays' -ComparisonType number -BackgroundColor Crimson -Value 60 -Operator ge
        New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -BackgroundColor PaleGreen -Value 15 -Operator lt
        New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -BackgroundColor OrangePeel -Value 15 -Operator ge
        New-HTMLTableCondition -Name 'LastLogonDays' -ComparisonType number -BackgroundColor Crimson -Value 30 -Operator ge
    }
    Solution        = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                #New-HTMLText -Text 'Following steps will guide you how to fix permissions consistency'
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Prepare environment' {
                        New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery."
                        New-HTMLCodeBlock -Code {
                            Install-Module ADEssentials -Force
                            Import-Module ADEssentials -Force
                        } -Style powershell
                        New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step."
                    }
                    New-HTMLWizardStep -Name 'Prepare report' {
                        New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing permissions inconsistencies. To generate new report please use:"
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoBefore.DomainDomainControllers.html -Type DomainDomainControllers
                        }
                        New-HTMLText -Text @(
                            "When executed it will take a while to generate all data and provide you with new report depending on size of environment."
                            "Once confirmed that data is still showing issues and requires fixing please proceed with next step."
                        )
                        New-HTMLText -Text "Alternatively if you prefer working with console you can run: "
                        New-HTMLCodeBlock -Code {
                            $Output = Get-WinADForestControllerInformation -IncludeDomains 'TargetDomain'
                            $Output | Format-Table # do your actions as desired
                        }
                        New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you."
                    }
                    New-HTMLWizardStep -Name 'Fix Domain Controller Owner' {
                        New-HTMLText -Text @(
                            "Domain Controller Owner should always be set to Domain Admins. "
                            "When non Domain Admin adds computer to domain that later on gets promoted to Domain Controller that person becomes the owner of the AD object. "
                            "This is very dangerous and requires fixing. "
                            "Following command when executed fixes domain controller owner. "
                            "It makes sure each DC is owned by Domain Admins. "
                            "If it's owned by Domain Admins already it will be skipped. "
                            "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental overwrite."
                        ) -FontWeight normal, normal, normal, normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Black, Black, Black, Red, Black
                        New-HTMLText -Text "Make sure to fill in TargetDomain to match your Domain Admin permission account"

                        New-HTMLCodeBlock -Code {
                            Repair-WinADForestControllerInformation -Verbose -LimitProcessing 3 -Type Owner -IncludeDomains "TargetDomain" -WhatIf
                        }
                        New-HTMLText -TextBlock {
                            "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be fixed matches expected data. Once happy with results please follow with command: "
                        }
                        New-HTMLCodeBlock -Code {
                            Repair-WinADForestControllerInformation -Verbose -LimitProcessing 3 -Type Owner -IncludeDomains "TargetDomain"
                        }
                        New-HTMLText -TextBlock {
                            "This command when executed repairs only first X domain controller owners. Use LimitProcessing parameter to prevent mass fixing and increase the counter when no errors occur. "
                            "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. "
                        }
                    }
                    New-HTMLWizardStep -Name 'Fix Domain Controller Manager' {
                        New-HTMLText -Text @(
                            "Domain Controller Manager should not be set. "
                            "There's no reason for anyone outside of Domain Admins group to be manager of Domain Controller object. "
                            "Since Domain Admins are by design Owners of Domain Controller object ManagedBy field should not be set. "
                            "Following command fixes this by clearing ManagedBy field. "
                        )
                        New-HTMLCodeBlock -Code {
                            Repair-WinADForestControllerInformation -Verbose -LimitProcessing 3 -Type Manager -IncludeDomains "TargetDomain" -WhatIf
                        }
                        New-HTMLText -TextBlock {
                            "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be fixed matches expected data. Once happy with results please follow with command: "
                        }
                        New-HTMLCodeBlock -Code {
                            Repair-WinADForestControllerInformation -Verbose -LimitProcessing 3 -Type Manager -IncludeDomains "TargetDomain"
                        }
                        New-HTMLText -TextBlock {
                            "This command when executed repairs only first X domain controller managers. Use LimitProcessing parameter to prevent mass fixing and increase the counter when no errors occur. "
                            "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. "
                        }
                    }
                    New-HTMLWizardStep -Name 'Remaining Problems' {
                        New-HTMLText -Text @(
                            "If there are any Domain Controllers that are disabled, or last logon date or last password set are above thresholds those should be investigated if those are still up and running. "
                            "In Active Directory based domains, each device has an account and password. "
                            "By default, the domain members submit a password change every 30 days. "
                            "If last password change is above threshold that means DC may already be offline. "
                            "If last logon date is above threshold that also means DC may already be offline. "
                            "Bringing back DC to life after longer downtime period can cause serious issues when done improperly. "
                            "Please investigate and decide with other Domain Admins how to deal with dead/offline DC. "
                        )
                        New-HTMLText -LineBreak
                        New-HTMLText -Text @(
                            "Additionally DNS should return IP Address of DC when asked, and it should be the same IP Address as the one stored in Active Directory. "
                            "If those do not match or IP Address is not set/returned it needs investigation why is it so. "
                            "It's possible the DC is down/dead and should be safely removed from Active Directory to prevent potential issues. "
                            "Alternatively it's possible there are some network issues with it. "
                        )
                    }
                    New-HTMLWizardStep -Name 'Verification report' {
                        New-HTMLText -TextBlock {
                            "Once cleanup task was executed properly, we need to verify that report now shows no problems."
                        }
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoAfter.DomainDomainControllers.html -Type DomainDomainControllers
                        }
                        New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$DomainFSMORoles = @{
    Name   = 'DomainRoles'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = 'Domain Roles Availability'
        Data           = {
            Test-ADRolesAvailability -Domain $Domain
        }
        Details        = [ordered] @{
            Area        = ''
            Category    = ''
            Severity    = ''
            Importance  = 0
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    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'
            }
        }
    }
}
$DomainLDAP = @{
    Name            = 'DomainLDAP'
    Enable          = $true
    Scope           = 'Domain'
    Source          = @{
        Name           = 'LDAP Connectivity'
        Data           = {
            Test-LDAP -Forest $ForestName -IncludeDomains $Domain -SkipRODC:$SkipRODC -WarningAction SilentlyContinue -VerifyCertificate
        }
        Details        = [ordered] @{
            Category    = 'Health'
            Description = 'Domain Controllers require certain ports to be open, and serving proper certificate for SSL connectivity. '
            Importance  = 0
            ActionType  = 0
            Resources   = @(
                "[Testing LDAP and LDAPS connectivity with PowerShell](https://evotec.xyz/testing-ldap-and-ldaps-connectivity-with-powershell/)"
                "[2020 LDAP channel binding and LDAP signing requirements for Windows](https://support.microsoft.com/en-us/topic/2020-ldap-channel-binding-and-ldap-signing-requirements-for-windows-ef185fb8-00f7-167d-744c-f299a66fc00a)"
            )
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        PortLDAP                 = @{
            Enable     = $true
            Name       = 'LDAP Port is Available'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.LDAP -eq $false }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
        PortLDAPS                = @{
            Enable     = $true
            Name       = 'LDAP SSL Port is Available'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.LDAPS -eq $false }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        PortLDAP_GC              = @{
            Enable     = $true
            Name       = 'LDAP GC Port is Available'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.GlobalCatalogLDAP -eq $false }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        PortLDAPS_GC             = @{
            Enable     = $true
            Name       = 'LDAP SSL GC Port is Available'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.GlobalCatalogLDAPS -eq $false }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        BindLDAPS                = @{
            Enable     = $true
            Name       = 'LDAP SSL Bind available'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.LDAPSBind -eq $false }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        BindLDAPS_GC             = @{
            Enable     = $true
            Name       = 'LDAP SSL GC Bind is Available'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.GlobalCatalogLDAPSBind -eq $false }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        X509NotBeforeDays        = @{
            Enable     = $true
            Name       = 'Not Before Days should be less/equal 0'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.X509NotBeforeDays -gt 0 }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        X509NotAfterDaysWarning  = @{
            Enable     = $true
            Name       = 'Not After Days should be more than 10 days'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.X509NotAfterDays -lt 10 }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 1
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        X509NotAfterDaysCritical = @{
            Enable     = $true
            Name       = 'Not After Days should be more than 0 days'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.X509NotAfterDays -lt 0 }
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                'Domain Controllers require certain ports for LDAP connectivity to be open, and serving proper certificate for SSL connectivity. '
                'Following ports are required to be available: '
            )
            New-HTMLList {
                New-HTMLListItem -Text 'LDAP port 389'
                New-HTMLListItem -Text 'LDAP SSL port 636'
                New-HTMLListItem -Text 'LDAP Global Catalog port 3268'
                New-HTMLListItem -Text 'LDAP Global Catalog SLL port 3269'
            }
            New-HTMLText -Text @(
                "If any/all of those ports are unavailable for any of the Domain Controllers "
                "it means that either DC is not available from location it's getting tested from ("
                "$Env:COMPUTERNAME"
                ") or those ports are down, or DC doesn't have a proper certificate installed. "
                "Please make sure to verify Domain Controllers that are reporting errors and talk to network team if required to make sure "
                "proper ports are open thru firewall. "
            ) -Color None, None, BilobaFlower, None, None, None
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'GlobalCatalogLDAP' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq
        New-HTMLTableCondition -Name 'GlobalCatalogLDAP' -ComparisonType string -BackgroundColor Salmon -Value $false -Operator eq
        New-HTMLTableCondition -Name 'GlobalCatalogLDAPS' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq
        New-HTMLTableCondition -Name 'GlobalCatalogLDAPS' -ComparisonType string -BackgroundColor Salmon -Value $false -Operator eq
        New-HTMLTableCondition -Name 'GlobalCatalogLDAPSBind' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq
        New-HTMLTableCondition -Name 'GlobalCatalogLDAPSBind' -ComparisonType string -BackgroundColor Salmon -Value $false -Operator eq
        New-HTMLTableCondition -Name 'LDAP' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq
        New-HTMLTableCondition -Name 'LDAP' -ComparisonType string -BackgroundColor Salmon -Value $false -Operator eq
        New-HTMLTableCondition -Name 'LDAPS' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq
        New-HTMLTableCondition -Name 'LDAPS' -ComparisonType string -BackgroundColor Salmon -Value $false -Operator eq
        New-HTMLTableCondition -Name 'LDAPSBind' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq
        New-HTMLTableCondition -Name 'LDAPSBind' -ComparisonType string -BackgroundColor Salmon -Value $false -Operator eq
        New-HTMLTableCondition -Name 'X509NotBeforeDays' -ComparisonType number -BackgroundColor PaleGreen -Value 0 -Operator le
        New-HTMLTableCondition -Name 'X509NotBeforeDays' -ComparisonType number -BackgroundColor Salmon -Value 0 -Operator gt
        New-HTMLTableCondition -Name 'X509NotAfterDays' -ComparisonType number -BackgroundColor PaleGreen -Value 0 -Operator gt
        New-HTMLTableCondition -Name 'X509NotAfterDays' -ComparisonType number -BackgroundColor Salmon -Value 0 -Operator lt
    }
}
$DuplicateObjects = @{
    Name           = 'DomainDuplicateObjects'
    Enable         = $true
    Scope          = 'Domain'
    Source         = @{
        Name           = "Duplicate Objects: 0ACNF (Duplicate RDN)"
        <# Alternative: dsquery * forestroot -gc -attr distinguishedName -scope subtree -filter "(|(cn=*\0ACNF:*)(ou=*OACNF:*))" #>
        Data           = {
            Get-WinADDuplicateObject -IncludeDomains $Domain
        }
        Details        = [ordered] @{
            Category    = 'Cleanup'
            Description = "When two objects are created with the same Relative Distinguished Name (RDN) in the same parent Organizational Unit or container, the conflict is recognized by the system when one of the new objects replicates to another domain controller. When this happens, one of the objects is renamed. Some sources say the RDN is mangled to make it unique. The new RDN will be <Old RDN>\0ACNF:<objectGUID>"
            Importance  = 5
            ActionType  = 2
            Resources   = @(
                'https://social.technet.microsoft.com/wiki/contents/articles/15435.active-directory-duplicate-object-name-resolution.aspx'
                'https://ourwinblog.blogspot.com/2011/05/resolving-computer-object-replication.html'
                'https://kickthatcomputer.wordpress.com/2014/11/22/seek-and-destroy-duplicate-ad-objects-with-cnf-in-the-name/'
                'https://jorgequestforknowledge.wordpress.com/2014/09/17/finding-conflicting-objects-in-your-ad/'
                'https://social.technet.microsoft.com/Forums/en-US/e9327be6-922c-4b9f-8357-417c3ab6a1af/cnf-remove-from-ad?forum=winserverDS'
                'https://kickthatcomputer.wordpress.com/2014/11/22/seek-and-destroy-duplicate-ad-objects-with-cnf-in-the-name/'
                'https://community.spiceworks.com/topic/2113346-active-directory-replication-cnf-guid-entries'
            )
            StatusTrue  = 1
            StatusFalse = 2
        }
        ExpectedOutput = $false
    }
    DataHighlights = {

    }
    Solution       = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Prepare environment' {
                        New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery."
                        New-HTMLCodeBlock -Code {
                            Install-Module ADEssentials -Force
                            Import-Module ADEssentials -Force
                        } -Style powershell
                        New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step."
                    }
                    New-HTMLWizardStep -Name 'Prepare report' {
                        New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing permissions inconsistencies. To generate new report please use:"
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoBefore.DomainDuplicateObjects.html -Type DomainDuplicateObjects
                        }
                        New-HTMLText -Text @(
                            "When executed it will take a while to generate all data and provide you with new report depending on size of environment."
                            "Once confirmed that data is still showing issues and requires fixing please proceed with next step."
                        )
                        New-HTMLText -Text "Alternatively if you prefer working with console you can run: "
                        New-HTMLCodeBlock -Code {
                            $Output = Get-WinADDuplicateObject -IncludeDomains 'TargetDomain'
                            $Output | Format-Table # do your actions as desired
                        }
                        New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you."
                    }
                    New-HTMLWizardStep -Name 'Remove Domain Duplicate Objects' {
                        New-HTMLText -Text @(
                            "CNF objects, Conflict objects or Duplicate Objects are created in Active Directory when there is simultaneous creation of an AD object under the same container "
                            "on two separate Domain Controllers near about the same time or before the replication occurs. "
                            "This results in a conflict and the same is exhibited by a CNF (Duplicate) object. "
                            "While it doesn't nessecary has a huge impact on Active Directory it's important to keep Active Directory in proper, healthy state. "
                        ) -FontWeight normal, normal, normal, normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Black, Black, Black, Red, Black
                        New-HTMLText -Text "Make sure to fill in TargetDomain to match your Domain Admin permission account"

                        New-HTMLCodeBlock -Code {
                            Remove-WinADDuplicateObject -Verbose -LimitProcessing 1 -IncludeDomains "TargetDomain" -WhatIf
                        }
                        New-HTMLText -TextBlock {
                            "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be fixed matches expected data. Once happy with results please follow with command: "
                        }
                        New-HTMLCodeBlock -Code {
                            Remove-WinADDuplicateObject -Verbose -LimitProcessing 1 -IncludeDomains "TargetDomain"
                        }
                        New-HTMLText -TextBlock {
                            "This command when executed removes only first X duplicate/CNF objects. Use LimitProcessing parameter to prevent mass remove and increase the counter when no errors occur. "
                            "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. "
                        }
                    }
                    New-HTMLWizardStep -Name 'Verification report' {
                        New-HTMLText -TextBlock {
                            "Once cleanup task was executed properly, we need to verify that report now shows no problems."
                        }
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoAfter.DomainDuplicateObjects.html -Type DomainDuplicateObjects
                        }
                        New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$ExchangeUsers = @{
    Name   = 'DomainExchangeUsers'
    Enable = $false
    Scope  = 'Domain'
    Source = @{
        Name           = "Exchange Users: Missing MailNickName"
        Data           = {
            Get-ADUser -Filter { Mail -like '*' -and MailNickName -notlike '*' } -Properties mailNickName, mail -Server $Domain
        }
        Details        = [ordered] @{
            Area        = ''
            Category    = ''
            Severity    = ''
            Importance  = 0
            Description = ''
            Resolution  = ''
            Resources   = @(
                'https://evotec.xyz/office-365-msexchhidefromaddresslists-does-not-synchronize-with-office-365/'
            )

        }
        ExpectedOutput = $false
    }
}
$GroupPolicyAssessment = @{
    Name           = 'DomainGroupPolicyAssessment'
    Enable         = $true
    Scope          = 'Domain'
    Source         = @{
        Name           = "Group Policy Assessment"
        Data           = {
            Get-GPOZaurr -Forest $ForestName -IncludeDomains $Domain
        }
        Implementation = {

        }
        Details        = [ordered] @{
            Area        = 'GroupPolicy'
            Category    = 'Cleanup'
            Severity    = ''
            Importance  = 0
            Description = ""
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests          = [ordered] @{
        Empty           = @{
            Enable     = $true
            Name       = 'Group Policy Empty'
            Parameters = @{
                #Bundle = $true
                WhereObject   = { $_.Empty -eq $true }
                ExpectedCount = 0
            }
        }
        Linked          = @{
            Enable     = $true
            Name       = 'Group Policy Unlinked'
            Parameters = @{
                #Bundle = $true
                WhereObject   = { $_.Linked -eq $false }
                ExpectedCount = 0
            }
        }
        Enabled         = @{
            Enable     = $true
            Name       = 'Group Policy Disabled'
            Parameters = @{
                #Bundle = $true
                WhereObject   = { $_.Enabled -eq $false }
                ExpectedCount = 0
            }
        }
        Problem         = @{
            Enable     = $true
            Name       = 'Group Policy with Problem'
            Parameters = @{
                #Bundle = $true
                WhereObject   = { $_.Problem -eq $true }
                ExpectedCount = 0
            }
        }
        Optimized       = @{
            Enable     = $true
            Name       = 'Group Policy Not Optimized'
            Parameters = @{
                #Bundle = $true
                WhereObject   = { $_.Optimized -eq $false }
                ExpectedCount = 0
            }
        }
        ApplyPermission = @{
            Enable     = $true
            Name       = 'Group Policy No Apply Permission'
            Parameters = @{
                # Bundle = $true
                WhereObject   = { $_.ApplyPermissioon -eq $false }
                ExpectedCount = 0
            }
        }
    }
    DataHighlights = {
        New-HTMLTableCondition -Name 'Empty' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'Linked' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'Optimized' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'Problem' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'ApplyPermission' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
    }
}
$GroupPolicyADM = @{
    Name   = 'DomainGroupPolicyADM'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = 'Group Policy Legacy ADM Files'
        Data           = {
            Get-GPOZaurrLegacyFiles -Forest $ForestName -IncludeDomains $Domain
        }
        Implementation = {
            Remove-GPOZaurrLegacyFiles -Verbose -WhatIf
        }
        Details        = [ordered] @{
            Area        = 'GroupPolicy'
            Category    = 'Cleanup'
            Severity    = ''
            Importance  = 0
            Description = ''
            Resolution  = ''
            Resources   = @(
                'https://support.microsoft.com/en-us/help/816662/recommendations-for-managing-group-policy-administrative-template-adm'
                'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-vista/cc709647(v=ws.10)?redirectedfrom=MSDN'
                'https://sdmsoftware.com/group-policy-blog/tips-tricks/understanding-the-role-of-admx-and-adm-files-in-group-policy/'
                'https://social.technet.microsoft.com/Forums/en-US/bbbe04f5-218b-4526-ae67-cf82a20d49fc/deleting-adm-templates?forum=winserverGP'
                'https://gallery.technet.microsoft.com/scriptcenter/Removing-ADM-files-from-b532e3b6#content'
            )
        }
        ExpectedOutput = $false
    }
}
$GroupPolicyOwner = @{
    Name   = 'DomainGroupPolicyOwner'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "GPO: Owner"
        Data           = {
            Get-GPOZaurrOwner -Forest $ForestName -IncludeSysvol -IncludeDomains $Domain
        }
        Details        = [ordered] @{
            Area        = 'GroupPolicy'
            Category    = 'Security'
            Severity    = ''
            Importance  = 0
            Description = ""
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        OwnerConsistent     = @{
            Enable     = $true
            Name       = 'GPO: Owner Consistent'
            Parameters = @{
                WhereObject    = { $_.IsOwnerConsistent -ne $true }
                ExpectedResult = $false # this tests things in bundle rather then per object of array
            }

        }
        OwnerAdministrative = @{
            Enable     = $true
            Name       = 'GPO: Owner Administrative'
            Parameters = @{
                WhereObject    = { $_.OwnerType -ne 'Administrative' -or $_.SysvolType -ne 'Administrative' }
                ExpectedResult = $false # this tests things in bundle rather then per object of array
            }
        }
    }
}
<#
ExpectedCount = 0,1,2,3 and so on
ExpectedValue = [object]
ExpectedResult = $true # just checks if there is result or there is not
#>

$GroupPolicyPermissions = @{
    Name   = 'DomainGroupPolicyPermissions'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Group Policy Required Permissions"
        Data           = {
            Get-GPOZaurrPermissionAnalysis -Forest $ForestName -Domain $Domain
        }
        Details        = [ordered] @{
            Area        = 'GroupPolicy'
            Category    = 'Security'
            Severity    = ''
            Importance  = 0
            Description = "Group Policy permissions should always have Authenticated Users and Domain Computers gropup"
            Resolution  = 'Do not remove Authenticated Users, Domain Computers from Group Policies.'
            Resources   = @(
                'https://secureinfra.blog/2018/12/31/most-common-mistakes-in-active-directory-and-domain-services-part-1/'
                'https://support.microsoft.com/en-us/help/3163622/ms16-072-security-update-for-group-policy-june-14-2016'
            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        Administrative     = @{
            Enable     = $true
            Name       = 'GPO: Administrative Permissions'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.Administrative -eq $false }
            }

        }
        AuthenticatedUsers = @{
            Enable     = $true
            Name       = 'GPO: Authenticated Permissions'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.AuthenticatedUsers -eq $false }
            }
        }
        System             = @{
            Enable     = $true
            Name       = 'GPO: System Permissions'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.System -eq $false }
            }
        }
        Unknown            = @{
            Enable     = $true
            Name       = 'GPO: Unknown Permissions'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.Unknown -eq $true }
            }
        }
    }
    <# Another way to do the same thing as above
    Tests = [ordered] @{
        Administrative = @{
            Enable = $true
            Name = 'GPO: Administrative Permissions'
            Parameters = @{
                Bundle = $true
                Property = 'Administrative'
                ExpectedValue = $false
                OperationType = 'notcontains'
                DisplayResult = $false
            }
 
        }
        AuthenticatedUsers = @{
            Enable = $true
            Name = 'GPO: Authenticated Permissions'
            Parameters = @{
                Bundle = $true
                Property = 'AuthenticatedUsers'
                ExpectedValue = $false
                OperationType = 'notcontains'
                DisplayResult = $false
            }
        }
        System = @{
            Enable = $true
            Name = 'GPO: System Permissions'
            Parameters = @{
                Bundle = $true
                Property = 'System'
                ExpectedValue = $false
                OperationType = 'notcontains'
                DisplayResult = $false
            }
        }
        Unknown = @{
            Enable = $true
            Name = 'GPO: Unknown Permissions'
            Parameters = @{
                Bundle = $true
                Property = 'Unknown'
                ExpectedValue = $false
                OperationType = 'notcontains'
                DisplayResult = $false
            }
        }
    }
    #>

}

$GroupPolicyPermissionConsistency = @{
    Name   = 'DomainGroupPolicyPermissionConsistency'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "GPO: Permission Consistency"
        Data           = {
            Get-GPOZaurrPermissionConsistency -Forest $ForestName -VerifyInheritance -Type Inconsistent -IncludeDomains $Domain
        }
        Details        = [ordered] @{
            Area        = 'GroupPolicy'
            Category    = 'Security'
            Severity    = ''
            Importance  = 0
            Description = "GPO Permissions are stored in Active Directory and SYSVOL at the same time. Setting up permissions for GPO should replicate itself to SYSVOL and those permissions should be consistent. However, sometimes this doesn't happen or is done on purpose."
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $false
    }
}
$GroupPolicySysvol = @{
    Name   = 'DomainGroupPolicySysvol'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "GPO: Sysvol folder existance"
        Data           = {
            Get-GPOZaurrSysvol -Forest $ForestName -IncludeDomains $Domain
        }
        Details        = [ordered] @{
            Area        = 'GroupPolicy'
            Category    = 'Security'
            Severity    = ''
            Importance  = 0
            Description = "GPO Permissions are stored in Active Directory and SYSVOL at the same time. Sometimes when deleting GPO or due to replication issues GPO becomes orphaned (no SYSVOL files) or SYSVOL files exists but no GPO."
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        SysvolExists = @{
            Enable     = $true
            Name       = 'GPO: Files on SYSVOL are not Orphaned'
            Parameters = @{
                WhereObject    = { $_.SysvolStatus -ne 'Exists' -or $_.Status -ne 'Exists' }
                ExpectedResult = $false # this tests things in bundle rather then per object of array
            }

        }
    }
}
$MachineQuota = @{
    Name            = 'DomainMachineQuota'
    Enable          = $true
    Scope           = 'Domain'
    Source          = @{
        Name           = "Machine Quota: Gathering ms-DS-MachineAccountQuota"
        Data           = {
            Get-ADObject -Identity ((Get-ADDomain -Identity $Domain).distinguishedname) -Properties 'ms-DS-MachineAccountQuota' -Server $Domain | Select-Object DistinguishedName, Name, ObjectClass, ObjectGUID, ms-DS-MachineAccountQuota
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Importance  = 0
            Description = "By default, In the Microsoft Active Directory, members of the authenticated user group can join up to 10 computer accounts in the domain. This value is defined in the attribute ms-DS-MachineAccountQuota on the domain-DNS object for a domain."
            Resources   = @(
                '[ms-DS-MachineAccountQuota](https://docs.microsoft.com/en-us/windows/win32/adschema/a-ms-ds-machineaccountquota)'
                "[MachineAccountQuota is USEFUL Sometimes: Exploiting One of Active Directory's Oddest Settings](https://www.netspi.com/blog/technical/network-penetration-testing/machineaccountquota-is-useful-sometimes/)"
                "[How to change the attribute ms-DS-MachineAccountQuota](https://www.jorgebernhardt.com/how-to-change-attribute-ms-ds-machineaccountquota/)"
                "[Default limit to number of workstations a user can join to the domain](https://docs.microsoft.com/pl-PL/troubleshoot/windows-server/identity/default-workstation-numbers-join-domain)"
            )
            Tags        = 'Security', 'Configuration'
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        MachineQuotaIsZero = @{
            Enable     = $true
            Name       = 'Machine Quota: Should be set to 0'
            Parameters = @{
                ExpectedValue = 0
                Property      = 'ms-DS-MachineAccountQuota'
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                "By default, In the Microsoft Active Directory, members of the authenticated user group can join up to 10 computer accounts in the domain. "
                "This value is defined in the attribute "
                "ms-DS-MachineAccountQuota"
                " on the domain-DNS object for a domain. "
                "This value should always be ",
                "0"
                " and permissions to add computers to domain should be managed on Active Directory Delegation level."
            ) -FontWeight normal, normal, bold, normal
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'ms-DS-MachineAccountQuota' -ComparisonType number -BackgroundColor PaleGreen -Value 0 -Operator eq
        New-HTMLTableCondition -Name 'ms-DS-MachineAccountQuota' -ComparisonType number -BackgroundColor Salmon -Value 0 -Operator gt
    }
    Solution        = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizard {
                        New-HTMLWizardStep -Name 'Gather information about ms-DS-MachineAccountQuota' {
                            New-HTMLText -Text @(
                                "ms-DS-MachineAccountQuota "
                                "should always be set to 0 to prevent any users adding computers to domain. This is security risk and should be fixed for all domains in a forest!"
                                "To make sure you can easily revert this setting if something goes wrong you should first get this information before doing any changes."
                            ) -FontWeight bold, normal
                            New-HTMLCodeBlock {
                                Get-ADObject -Identity ((Get-ADDomain -Identity $Domain).distinguishedname) -Properties 'ms-DS-MachineAccountQuota' -Server $Domain
                            }
                        }
                    } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
                    New-HTMLWizardStep -Name 'Fix ms-DS-MachineAccountQuota' {
                        New-HTMLText -Text @(
                            "ms-DS-MachineAccountQuota "
                            "should always be set to 0 to prevent any users adding computers to domain. This is security risk and should be fixed for all domains in a forest!"
                            "This can be done using following cmdlet. Please make sure to use WhatIf to verify what will change."
                        ) -FontWeight bold, normal
                        New-HTMLCodeBlock {
                            Set-ADDomain -Identity $Domain -Replace @{"ms-DS-MachineAccountQuota" = "0" } -WhatIf
                        }
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$NetLogonOwner = @{
    Name   = 'DomainNetLogonOwner'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "NetLogon Owner"
        Data           = {
            Get-GPOZaurrNetLogon -Forest $ForestName -OwnerOnly -IncludeDomains $Domain
        }
        Implementation = {

        }
        Details        = [ordered] @{
            Area        = 'FileSystem'
            Category    = 'Cleanup'
            Severity    = ''
            Importance  = 6
            Description = ""
            Resolution  = ''
            Resources   = @(

            )
            Tags        = 'netlogon', 'grouppolicy', 'gpo', 'sysvol'
        }
        ExpectedOutput = $null
    }
    Tests  = [ordered] @{
        Empty = @{
            Enable     = $true
            Name       = 'Owner should be BUILTIN\Administrators'
            Parameters = @{
                #Bundle = $true
                WhereObject    = { $_.OwnerSid -ne 'S-1-5-32-544' }
                ExpectedCount  = 0
                ExpectedOutput = $true
            }
        }
    }
}
$OrganizationalUnitsEmpty = @{
    Name   = 'DomainOrganizationalUnitsEmpty'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Organizational Units: Orphaned/Empty"
        Data           = {
            <# We should replace it with better alternative
            ([adsisearcher]'(objectcategory=organizationalunit)').FindAll() | Where-Object {
            -not (-join $_.GetDirectoryEntry().psbase.children) }
            #>

            $OrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties distinguishedname -Server $Domain | Select-Object -ExpandProperty distinguishedname
            $WellKnownContainers = Get-ADDomain | Select-Object *Container

            $AllUsedOU = Get-ADObject -Filter "ObjectClass -eq 'user' -or ObjectClass -eq 'computer' -or ObjectClass -eq 'group' -or ObjectClass -eq 'contact'" -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) -and (($_ -notlike $WellKnownContainers.UsersContainer) -or ($_ -notlike $WellKnownContainers.ComputersContainer)) }
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Importance  = 3
            ActionType  = 1
            Description = ''
            Resolution  = ''
            Resources   = @(
                "[Active Directory Friday: Find empty Organizational Unit](https://www.jaapbrasser.com/active-directory-friday-find-empty-organizational-unit/)"
            )
            StatusTrue  = 1
            StatusFalse = 2
        }
        ExpectedOutput = $false
    }
}
$OrganizationalUnitsProtected = @{
    Name   = 'DomainOrganizationalUnitsProtected'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Organizational Units: Protected"
        Data           = {
            $OUs = Get-ADOrganizationalUnit -Properties ProtectedFromAccidentalDeletion, CanonicalName -Filter * -Server $Domain
            $FilteredOus = foreach ($OU in $OUs) {
                if ($OU.ProtectedFromAccidentalDeletion -eq $false) {
                    $OU
                }
            }
            $FilteredOus | Select-Object -Property Name, CanonicalName, DistinguishedName, ProtectedFromAccidentalDeletion
        }
        Details        = [ordered] @{
            Area        = 'Objects'
            Category    = 'Cleanup'
            Severity    = ''
            Importance  = 0
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $false
    }
}
$OrphanedForeignSecurityPrincipals = @{
    Name     = 'DomainOrphanedForeignSecurityPrincipals'
    Enable   = $true
    Scope    = 'Domain'
    Source   = @{
        Name           = "Orphaned Foreign Security Principals"
        Data           = {
            $AllFSP = Get-WinADUsersForeignSecurityPrincipalList -Domain $Domain
            $OrphanedObjects = $AllFSP | Where-Object { $_.TranslatedName -eq $null }
            $OrphanedObjects
        }
        Details        = [ordered] @{
            Category    = 'Cleanup'
            Area = 'Objects'
            Importance  = 0
            ActionType  = 0
            Description = 'An FSP is an Active Directory (AD) security principal that points to a security principal (a user, computer, or group) from a domain of another forest. AD automatically and transparently creates them in a domain the first time after adding a security principal from another forest to a group from that domain. AD creates FSPs in a domain the first time after adding a security principal of a domain from another forest to a group. And when someone removes the security principal the FSP is pointing to, the FSP becomes an orphan because it points to a non-existent security principal.'
            Resolution  = ''
            Resources   = @(
                '[Clean up orphaned Foreign Security Principals](https://4sysops.com/archives/clean-up-orphaned-foreign-security-principals/)'
                '[Foreign Security Principals and Well-Known SIDS, a.k.a. the curly red arrow problem](https://docs.microsoft.com/en-us/archive/blogs/389thoughts/foreign-security-principals-and-well-known-sids-a-k-a-the-curly-red-arrow-problem)'
                '[Active Directory: Foreign Security Principals and Special Identities](https://social.technet.microsoft.com/wiki/contents/articles/51367.active-directory-foreign-security-principals-and-special-identities.aspx)'
                '[Find orphaned foreign security principals and remove them from groups](https://serverfault.com/questions/320840/find-orphaned-foreign-security-principals-and-remove-them-from-groups)'
            )
            StatusTrue  = 1
            StatusFalse = 3
        }
        ExpectedOutput = $false
    }
    Solution = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                #New-HTMLText -Text 'Following steps will guide you how to fix permissions consistency'
                New-HTMLWizard {
                    <#
                    New-HTMLWizardStep -Name 'Prepare environment' {
                        New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery."
                        New-HTMLCodeBlock -Code {
                            Install-Module ADEssentials -Force
                            Import-Module ADEssentials -Force
                        } -Style powershell
                        New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step."
                    }
                    #>

                    New-HTMLWizardStep -Name 'Prepare report' {
                        New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing permissions inconsistencies. To generate new report please use:"
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoBefore.DomainOrphanedForeignSecurityPrincipals.html -Type DomainOrphanedForeignSecurityPrincipals
                        }
                        New-HTMLText -Text @(
                            "When executed it will take a while to generate all data and provide you with new report depending on size of environment."
                            "Once confirmed that data is still showing issues and requires fixing please proceed with next step."
                        )
                        New-HTMLText -Text "Alternatively if you prefer working with console you can run: "
                        New-HTMLCodeBlock -Code {
                            $Output = Get-WinADUsersForeignSecurityPrincipalList -IncludeDomains 'TargetDomain'
                            $Output | Where-Object { $_.TranslatedName -eq $null } | Format-Table
                        }
                        New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you."
                    }
                    New-HTMLWizardStep -Name 'Verify Trusts' {
                        New-HTMLText -Text @(
                            "It's important before deleting any FSP that all trusts are working correctly. "
                            "If trusts are down, translation FSP objects doesn't happen and therefore it would look like that FSP or orphaned. "
                            "Please run following command "
                        )
                        New-HTMLCodeBlock -Code {
                            Show-WinADTrust -Online -Recursive -Verbose
                        }
                        New-HTMLText -Text @(
                            "Zero level trusts are required to be functional and responding. "
                            "First level and above are optional, but should be verified if that's expected before removing FSP objects. "
                        )
                    }
                    New-HTMLWizardStep -Name 'Remove Orphaned FSP Objects (manual)' {
                        New-HTMLText -Text @(
                            "You can find all FSPs in the Active Directory Users and Computers (ADUC) console in a container named ForeignSecurityPrincipals. "
                            "However, you must first enable Advanced Features in the console. Otherwise the container won't show anything."
                            "You can recognize orphan FSPs by empty readable names in the ADUC console. "
                            ""
                            "However, there is a potential issue you need to be aware of. If, at the same time you are looking for orphaned FSPs, "
                            "there is a network connectivity issue between domain controllers and domain controllers from other trusted forests, "
                            "you won't be able to see the readable names. Thus the script and you will incorrectly deduce that they are orphans."
                            "When cleaning up, please consult other Domain Admins and confirm the trusts with other domains are working as required before proceeding."
                        ) -FontWeight normal, normal, normal, normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Black, Black, Black, Red, Black
                    }
                    New-HTMLWizardStep -Name 'Restore FSP object' {
                        New-HTMLText -Text @(
                            "If you've deleted FSP object by accident it's possible to restore such object from Active Directory Recycle Bin."
                        )
                    }
                    New-HTMLWizardStep -Name 'Verification report' {
                        New-HTMLText -TextBlock {
                            "Once cleanup task was executed properly, we need to verify that report now shows no problems."
                        }
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoAfter.DomainOrphanedForeignSecurityPrincipals.html -Type DomainOrphanedForeignSecurityPrincipals
                        }
                        New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$PasswordComplexity = @{
    Name   = 'DomainPasswordComplexity'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = 'Password Complexity Requirements'
        Data           = {
            Get-ADDefaultDomainPasswordPolicy -Server $Domain
        }
        Details        = [ordered] @{
            Area        = 'Objects'
            Category    = 'Security'
            Severity    = ''
            Importance  = 0
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        ComplexityEnabled             = @{
            Enable     = $true
            Name       = 'Complexity Enabled'
            Details    = [ordered] @{
                Area        = ''
                Category    = ''
                Severity    = ''
                Importance  = 0
                Description = ''
                Resolution  = ''
                Resources   = @(

                )
            }
            Parameters = @{
                Property      = 'ComplexityEnabled'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        'LockoutDuration'             = @{
            Enable     = $true
            Name       = 'Lockout Duration'
            Parameters = @{
                Property      = 'LockoutDuration'
                ExpectedValue = 30
                OperationType = 'ge'
            }
        }
        'LockoutObservationWindow'    = @{
            Enable     = $true
            Name       = 'Lockout Observation Window'
            Parameters = @{
                #PropertyExtendedValue = 'LockoutObservationWindow'
                Property      = 'LockoutObservationWindow', 'TotalMinutes'
                ExpectedValue = 30
                OperationType = 'ge'
            }
        }
        'LockoutThreshold'            = @{
            Enable     = $true
            Name       = 'Lockout Threshold'
            Parameters = @{
                Property      = 'LockoutThreshold'
                ExpectedValue = 4
                OperationType = 'gt'
            }
        }
        'MaxPasswordAge'              = @{
            Enable     = $true
            Name       = 'Maximum Password Age'
            Parameters = @{
                Property      = 'MaxPasswordAge', 'TotalDays'
                ExpectedValue = 60
                OperationType = 'le'
            }
        }
        'MinPasswordLength'           = @{
            Enable     = $true
            Name       = 'Minimum Password Length'
            Parameters = @{
                Property      = 'MinPasswordLength'
                ExpectedValue = 8
                OperationType = 'gt'
            }
        }
        'MinPasswordAge'              = @{
            Enable     = $true
            Name       = 'Minimum Password Age'
            Parameters = @{
                #PropertyExtendedValue = 'MinPasswordAge', 'TotalDays'
                Property      = 'MinPasswordAge', 'TotalDays'
                ExpectedValue = 1
                OperationType = 'le'
            }
        }
        'PasswordHistoryCount'        = @{
            Enable     = $true
            Name       = 'Password History Count'
            Parameters = @{
                Property      = 'PasswordHistoryCount'
                ExpectedValue = 10
                OperationType = 'ge'
            }
        }
        'ReversibleEncryptionEnabled' = @{
            Enable     = $true
            Name       = 'Reversible Encryption Enabled'
            Parameters = @{
                Property      = 'ReversibleEncryptionEnabled'
                ExpectedValue = $false
                OperationType = 'eq'
            }
        }
    }
}
$DomainSecurityComputers = @{
    Name            = 'DomainSecurityComputers'
    Enable          = $true
    Scope           = 'Domain'
    Source          = @{
        Name           = "Computers: Standard"
        Data           = {
            $Properties = @(
                'SamAccountName'
                'UserPrincipalName'
                'Enabled'
                'PasswordNotRequired'
                'AllowReversiblePasswordEncryption'
                'UseDESKeyOnly'
                'PasswordLastSet'
                'LastLogonDate'
                'PasswordNeverExpires'
                'PrimaryGroup'
                'PrimaryGroupID'
                'DistinguishedName'
                'Name'
                'SID'
            )
            Get-ADComputer -Filter { (PasswordNeverExpires -eq $true -or AllowReversiblePasswordEncryption -eq $true -or UseDESKeyOnly -eq $true -or (PrimaryGroupID -ne '515' -and PrimaryGroupID -ne '516' -and PrimaryGroupID -ne '521') -or PasswordNotRequired -eq $true) } -Properties $Properties -Server $Domain | Where-Object { $_.SamAccountName -ne 'AZUREADSSOACC$' } | Select-Object -Property $Properties
        }
        Details        = [ordered] @{
            Category    = 'Security', 'Cleanup'
            Importance  = 0
            ActionType  = 0
            Description = 'Account by default have certain settings that make sure the account is fairly safe and can be used within Active Directory.'
            Resources   = @(
                '[Understanding and Remediating "PASSWD_NOTREQD](https://docs.microsoft.com/en-us/archive/blogs/russellt/passwd_notreqd)'
                '[Miscellaneous facts about computer passwords in Active Directory](https://blog.joeware.net/2012/09/12/2590/)'
                '[Domain member: Maximum machine account password age](https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/jj852252(v=ws.11)?redirectedfrom=MSDN)'
                '[Machine Account Password Process](https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/machine-account-password-process/ba-p/396026)'
            )
            StatusTrue  = 1
            StatusFalse = 0
        }
        ExpectedOutput = $false
    }
    Tests           = [ordered] @{
        KeberosDES                        = @{
            Enable      = $true
            Name        = 'Kerberos DES detection'
            Parameters  = @{
                WhereObject    = { $_.UseDESKeyOnly -eq $true }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = "Computer accounts shouldn't use DES encryption. Having UseDESKeyOnly forces the Kerberos encryption to be DES instead of RC4 which is the Microsoft default. DES is 56 bit encryption and is regarded as weak these days so this setting is not recommended."
        }
        AllowReversiblePasswordEncryption = @{
            Enable      = $true
            Name        = 'Reversible Password detection'
            Parameters  = @{
                WhereObject    = { $_.AllowReversiblePasswordEncryption -eq $true }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = "Computer accounts shouldn't use Reversible Password Encryption. Having AllowReversiblePasswordEncryption allows for easy password decryption."
        }
        PasswordNeverExpires              = @{
            Enable      = $true
            Name        = 'PasswordNeverExpires detection'
            Parameters  = @{
                WhereObject    = { $_.PasswordNeverExpires -eq $true }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = "Computer accounts shouldn't use PasswordNeverExpires. Having PasswordNeverExpires is dangerous and shoudn't be used."
        }
        PasswordNotRequired               = @{
            Enable      = $true
            Name        = 'PasswordNotRequired detection'
            Parameters  = @{
                WhereObject    = { $_.PasswordNotRequired -eq $true }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = "Computer accounts shouldn't use PasswordNotRequired. Having PasswordNotRequired is dangerous and shoudn't be used."
        }
        PrimaryGroup                      = @{
            Enable      = $true
            Name        = "Domain Computers or Domain Controllers or Read-Only Domain Controllers."
            Parameters  = @{
                #WhereObject = { $_.PrimaryGroupID -ne 513 -and $_.SID -ne "$((Get-ADDomain).DomainSID.Value)-501" }
                WhereObject    = { $_.PrimaryGroupID -notin 515, 516, 521 }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
            Description = "Computer accounts shouldn't have different group then Domain Computers or Domain Controllers or Read-Only Domain Controllers as their primary group."
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                "Account by default have certain settings that make sure the account is fairly safe and can be used within Active Directory. "
                "Those settings are: "
            )
            New-HTMLList {
                New-HTMLListItem -Text "Password is always required"
                New-HTMLListItem -Text "Password is expiring"
                New-HTMLListItem -Text "Password is not reverisble"
                New-HTMLListItem -Text "Keberos Encryption is set to RC4"
                New-HTMLListItem -Text "Primary Group is always Domain Computers/Domain Cotrollers or Domain Read-Only Controllers"
            }
            New-HTMLText -Text @(
                "It's important that all those settings are set as expected."
            )
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'PasswordNeverExpires' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'AllowReversiblePasswordEncryption' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'UseDESKeyOnly' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'PrimaryGroupID' -ComparisonType number -BackgroundColor PaleGreen -Value 515, 516, 521 -Operator in -FailBackgroundColor Salmon -HighlightHeaders 'PrimaryGroupID', 'PrimaryGroup'
        New-HTMLTableCondition -Name 'PasswordLastSet' -ComparisonType date -BackgroundColor PaleGreen -Value (Get-Date).AddDays(-180) -Operator gt -FailBackgroundColor Salmon -DateTimeFormat 'DD.MM.YYYY HH:mm:ss'
        New-HTMLTableCondition -Name 'LastLogonDate' -ComparisonType date -BackgroundColor PaleGreen -Value (Get-Date).AddDays(-180) -Operator gt -FailBackgroundColor Salmon -DateTimeFormat 'DD.MM.YYYY HH:mm:ss'
    }
    DataInformation = {
        New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
        New-HTMLList {
            New-HTMLListItem -FontWeight bold, normal -Text "PasswordNotRequired", " - means password is not required by the account. This should be investigated right away. "
            New-HTMLListItem -FontWeight bold, normal -Text "PasswordNeverExpires", " - means password is not required by the account. This should be investigated right away. "
            New-HTMLListItem -FontWeight bold, normal -Text "AllowReversiblePasswordEncryption", " - means the password is stored insecurely in Active Directory. Removing this flag is required. "
            New-HTMLListItem -FontWeight bold, normal -Text "UseDESKeyOnly", " - means the kerberos encryption is set to DES which is very weak. Removing flag is required. "
            New-HTMLListItem -FontWeight bold, normal -Text "PrimaryGroupID", " - if primary group ID is something else then 513 it means someone made a primary group change to something else than Domain Users. This should be fixed. "
        } -FontSize 10pt
    }
}
$DomainSecurityDelegatedObjects = @{
    Name            = 'DomainSecurityDelegatedObjects'
    Enable          = $true
    Scope           = 'Domain'
    Source          = @{
        Name           = "Security: Delegated Objects"
        Data           = {
            Get-WinADDelegatedAccounts -Forest $ForestName -IncludeDomains $Domain
        }
        Details        = [ordered] @{
            Category    = 'Security', 'Cleanup'
            Importance  = 0
            ActionType  = 0
            Description = ''
            Resources   = @(
                '[What is KERBEROS DELEGATION? An overview of kerberos delegation](https://stealthbits.com/blog/what-is-kerberos-delegation-an-overview-of-kerberos-delegation/)'
            )
            StatusTrue  = 1
            StatusFalse = 0
        }
        ExpectedOutput = $null
    }
    Tests           = [ordered] @{
        FullDelegation = @{
            Enable      = $true
            Name        = 'There should be no full delegation'
            Parameters  = @{
                WhereObject    = { $_.FullDelegation -eq $true -and $_.IsDC -eq $false }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 9
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = ""
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                "There are a few flavors of Kerberos delegation since it has evolved over the years. The original implementation is unconstrained delegation, this was what existed in Windows Server 2000. Since then, more strict versions of delegation have come along. Constrained delegation, which was available in Windows Server 2003, and Resource-Based Constrained delegation which was made available in 2012, both have improved the security and implementation of Kerberos delegation. "
                "Those settings are: "
            )
            New-HTMLList {
                New-HTMLListItem -Text "Unconstrained (Full) delegation ", " is most When a privileged account authenticates to a host with unconstrained delegation configured, you now can access any configured service within the domain as that privileged user. " -FontWeight bold, normal
                New-HTMLListItem -Text "Constrained delegation ", " takes it a step further by allowing you to configure which services an account can be delegated to. This, in theory, would limit the potential exposure if a compromise occurred." -FontWeight bold, normal
                New-HTMLListItem -Text "Resource-Based Constrained Delegation ", " changes how you can configure constrained delegation, and it will work across a trust. Instead of specifying which object can delegate to which service, the resource hosting the service specifies which objects can delegate to it. From an administrative standpoint, this allows the resource owner to control who can access it. " -FontWeight bold, normal
            }
            New-HTMLText -Text @(
                "It's important that there are no objects with unconstrained delegation anywhere else than on Domain Controller objects."
            )
        }
    }
    DataHighlights  = {
        New-HTMLTableConditionGroup {
            New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Value $true -Operator eq
            New-HTMLTableCondition -Name 'FullDelegation' -ComparisonType string -Value $true -Operator eq
        } -BackgroundColor PaleGreen -HighlightHeaders IsDC, FullDelegation
        New-HTMLTableConditionGroup {
            New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Value $false -Operator eq
            New-HTMLTableCondition -Name 'FullDelegation' -ComparisonType string -Value $true -Operator eq
        } -BackgroundColor Salmon -HighlightHeaders IsDC, FullDelegation

        New-HTMLTableConditionGroup {
            New-HTMLTableCondition -Name 'IsDC' -ComparisonType string -Value $false -Operator eq
            New-HTMLTableCondition -Name 'FullDelegation' -ComparisonType string -Value $false -Operator eq
        } -BackgroundColor PaleGreen -HighlightHeaders IsDC, FullDelegation

        New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'PasswordLastSet' -ComparisonType date -BackgroundColor PaleGreen -Value (Get-Date).AddDays(-180) -Operator gt -FailBackgroundColor Salmon -DateTimeFormat 'DD.MM.YYYY HH:mm:ss'
        New-HTMLTableCondition -Name 'LastLogonDate' -ComparisonType date -BackgroundColor PaleGreen -Value (Get-Date).AddDays(-180) -Operator gt -FailBackgroundColor Salmon -DateTimeFormat 'DD.MM.YYYY HH:mm:ss'
    }
    DataInformation = {

    }
}
$SecurityGroupsAccountOperators = @{
    Name   = 'DomainSecurityGroupsAccountOperators'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Groups: Account operators should be empty"
        Data           = {
            Get-ADGroupMember -Identity 'S-1-5-32-548' -Recursive -Server $Domain
        }
        Details        = [ordered] @{
            Area        = 'Objects'
            Category    = 'Cleanup', 'Security'
            Severity    = ''
            Importance  = 0
            Description = "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!"
            Resolution  = ''
            Resources   = @()
        }
        ExpectedOutput = $false
    }
}
$SecurityGroupsSchemaAdmins = @{
    Name   = 'DomainSecurityGroupsSchemaAdmins'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Groups: Schema Admins should be empty"
        Data           = {
            $DomainSID = (Get-ADDomain -Server $Domain).DomainSID
            Get-ADGroupMember -Recursive -Server $Domain -Identity "$DomainSID-518"
        }
        Requirements   = @{
            IsDomainRoot = $true
        }
        Details        = [ordered] @{
            Area        = 'Objects'
            Category    = 'Cleanup', 'Security'
            Severity    = ''
            Importance  = 0
            Description = "Schema Admins group should be empty. If you need to manage schema you can always add user for the time of modification."
            Resolution  = 'Keep Schema group empty.'
            Resources   = @(
                'https://www.stigviewer.com/stig/active_directory_forest/2016-12-19/finding/V-72835'
            )

        }
        ExpectedOutput = $false
    }
}
$SecurityKRBGT = @{
    Name            = 'DomainSecurityKrbtgt'
    Enable          = $true
    Scope           = 'Domain'
    Source          = @{
        Name           = "Security: Krbtgt password"
        Data           = {
            #Get-ADUser -Filter { name -like "krbtgt*" } -Property Name, Created, logonCount, Modified, PasswordLastSet, PasswordExpired, msDS-KeyVersionNumber, CanonicalName, msDS-KrbTgtLinkBl -Server $Domain
            Get-ADUser -Filter { name -like "krbtgt*" } -Property Name, Created, Modified, PasswordLastSet, PasswordExpired, msDS-KeyVersionNumber, CanonicalName, msDS-KrbTgtLinkBl, Description -Server $Domain | Select-Object Name, Enabled, Description, PasswordLastSet, PasswordExpired, msDS-KrbTgtLinkBl, msDS-KeyVersionNumber, CanonicalName, Created, Modified
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Importance  = 10
            ActionType  = 1
            Description = 'A stolen krbtgt account password can wreak havoc on an organization because it can be used to impersonate authentication throughout the organization thereby giving an attacker access to sensitive data.'
            Resources   = @(
                '[AD Forest Recovery - Resetting the krbtgt password](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/ad-forest-recovery-resetting-the-krbtgt-password)'
                '[KRBTGT Account Password Reset Scripts now available for customers](https://www.microsoft.com/security/blog/2015/02/11/krbtgt-account-password-reset-scripts-now-available-for-customers/)'
                "[Kerberos & KRBTGT: Active Directory's Domain Kerberos Service Account](https://adsecurity.org/?p=483)"
                "[Attacking Read-Only Domain Controllers to Own Active Directory](https://adsecurity.org/?p=3592)"
                '[DETECTING AND PREVENTING A GOLDEN TICKET ATTACK](https://frsecure.com/blog/golden-ticket-attack/)'
                '[Adversary techniques for credential theft and data compromise - Golden Ticket](https://attack.stealthbits.com/how-golden-ticket-attack-works)'
                '[Do You Need to Update KRBTGT Account Password?](https://www.kjctech.net/do-you-need-to-update-krbtgt-account-password/)'
            )
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        PasswordLastSet        = @{
            Enable      = $false
            Name        = 'Krbtgt Last Password Change should changed frequently'
            Parameters  = @{
                Property      = 'PasswordLastSet'
                ExpectedValue = '(Get-Date).AddDays(-180)'
                OperationType = 'gt'
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 8
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = 'LastPasswordChange should be less than 180 days ago.'
        }
        PasswordLastSetPrimary = @{
            Enable      = $true
            Name        = 'Krbtgt DC password should be changed frequently'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'krbtgt' -and $_.PasswordLastSet -lt (Get-Date).AddDays(-180) }
                ExpectedCount = 0
                OperationType = 'eq'
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 8
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = 'LastPasswordChange should be less than 180 days ago.'
        }
        PasswordLastSetAzure = @{
            Enable      = $true
            Name        = 'Krbtgt Azure AD password should be changed frequently'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'krbtgt_AzureAD' -and $_.PasswordLastSet -lt (Get-Date).AddDays(-180) }
                ExpectedCount = 0
                OperationType = 'eq'
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 8
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = 'LastPasswordChange should be less than 180 days ago.'
        }
        PasswordLastSetRODC    = @{
            Enable      = $true
            Name        = 'Krbtgt RODC password should be changed frequently'
            Parameters  = @{
                WhereObject   = { $_.Name -ne 'krbtgt' -and $_.Name -ne 'krbtgt_AzureAD' -and $_.PasswordLastSet -lt (Get-Date).AddDays(-180) }
                ExpectedCount = 0
                OperationType = 'eq'
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 8
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = 'LastPasswordChange should be less than 180 days ago.'
        }
        DeadKerberosAccount    = @{
            Enable      = $true
            Name        = 'Krbtgt RODC account without RODC'
            Parameters  = @{
                WhereObject   = { $_.Name -ne 'krbtgt' -and $_.Name -ne 'krbtgt_AzureAD' -and $_.'msDS-KrbTgtLinkBl'.Count -eq 0 }
                ExpectedCount = 0
                OperationType = 'eq'
            }
            Details     = [ordered] @{
                Category    = 'Security', 'Cleanup'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 2
            }
            Description = 'Kerberos accounts for dead RODCs should be removed'
        }
    }
    DataInformation = {
        New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
        New-HTMLList {
            New-HTMLListItem -FontWeight bold, normal -Text "PasswordLastSet", " - shows the last date password for Kerberos was changed."
            New-HTMLListItem -FontWeight bold, normal -Text "msDS-KrbTgtLinkBl", " - shows linked RODC. If name contains numbers and msDS-KrbTgtLinkBl is empty the kerberos account is not required."
        } -FontSize 10pt

        New-HTMLText -Text "Please keep in mind that if there are more than one keberos account it means there are RODC having own krbtgt account. " -FontSize 10pt
    }
    DataHighlights  = {
        New-HTMLTableConditionGroup {
            New-HTMLTableCondition -Name 'Name' -Value 'krbtgt' -Operator ne -ComparisonType string
            New-HTMLTableCondition -Name 'msDS-KrbTgtLinkBl' -Value '' -Operator eq -ComparisonType string
        } -Row -BackgroundColor Salmon
        New-HTMLTableCondition -Name 'PasswordLastSet' -ComparisonType date -BackgroundColor PaleGreen -Value (Get-Date).AddDays(-180) -Operator gt -FailBackgroundColor Salmon -DateTimeFormat 'DD.MM.YYYY HH:mm:ss'
        New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
    }
}
$SecurityUsers = @{
    Name            = 'DomainSecurityUsers'
    Enable          = $true
    Scope           = 'Domain'
    Source          = @{
        Name           = "Users: Standard"
        Data           = {
            $Properties = @(
                'SamAccountName'
                'UserPrincipalName'
                'Enabled'
                'PasswordNotRequired'
                'AllowReversiblePasswordEncryption'
                'UseDESKeyOnly'
                'PasswordLastSet'
                'LastLogonDate'
                'PrimaryGroup'
                'PrimaryGroupID'
                'DistinguishedName'
                'Name'
                #'ObjectClass'
                #'ObjectGUID'
                'SID'
                'SamAccountType'
                #'GivenName'
                #'Surname'
            )
            $GuestSID = "$($DomainInformation.DomainSID)-501"
            # Skipping trusts with SamAccountType and Guests
            # Skipping Exchange_Online-ApplicationAccount because it doesn't require password by default (also disabled)
            Get-ADUser -Filter { (AllowReversiblePasswordEncryption -eq $true -or UseDESKeyOnly -eq $true -or PrimaryGroupID -ne '513' -or PasswordNotRequired -eq $true) -and (SID -ne $GuestSID -and SamAccountType -ne 805306370) } -Properties $Properties -Server $Domain | Where-Object { $_.UserPrincipalName -notlike 'Exchange_Online-ApplicationAccount*' } | Select-Object -Property $Properties
        }
        Details        = [ordered] @{
            Category    = 'Security', 'Cleanup'
            Importance  = 0
            ActionType  = 0
            Description = 'Account by default have certain settings that make sure the account is fairly safe and can be used within Active Directory.'
            Resources   = @(

            )
            StatusTrue  = 1
            StatusFalse = 0
        }
        ExpectedOutput = $false
    }
    Tests           = [ordered] @{
        KeberosDES                        = @{
            Enable      = $true
            Name        = 'Kerberos DES detection'
            Parameters  = @{
                WhereObject    = { $_.UseDESKeyOnly -eq $true }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = "User accounts shouldn't use DES encryption. Having UseDESKeyOnly forces the Kerberos encryption to be DES instead of RC4 which is the Microsoft default. DES is 56 bit encryption and is regarded as weak these days so this setting is not recommended."
        }
        AllowReversiblePasswordEncryption = @{
            Enable      = $true
            Name        = 'Reversible Password detection'
            Parameters  = @{
                WhereObject    = { $_.AllowReversiblePasswordEncryption -eq $true }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = "User accounts shouldn't use Reversible Password Encryption. Having AllowReversiblePasswordEncryption allows for easy password decryption."
        }
        PasswordNotRequired               = @{
            Enable      = $true
            Name        = 'PasswordNotRequired detection'
            Parameters  = @{
                WhereObject    = { $_.PasswordNotRequired -eq $true }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = "User accounts shouldn't use PasswordNotRequired. Having PasswordNotRequired is dangerous and shoudn't be used."
        }
        PrimaryGroup                      = @{
            Enable      = $true
            Name        = "Primary Group shouldn't be changed from default Domain Users."
            Parameters  = @{
                #WhereObject = { $_.PrimaryGroupID -ne 513 -and $_.SID -ne "$((Get-ADDomain).DomainSID.Value)-501" }
                WhereObject    = { $_.PrimaryGroupID -ne 513 -and $_.SID -ne "$($DomainInformation.DomainSID)-501" }
                ExpectedCount  = 0
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
            Description = "User accounts shouldn't have different group then Domain Users as their primary group."
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                "Account by default have certain settings that make sure the account is fairly safe and can be used within Active Directory. "
                "Those settings are: "
            )
            New-HTMLList {
                New-HTMLListItem -Text "Password is always required"
                New-HTMLListItem -Text "Password is not reverisble"
                New-HTMLListItem -Text "Keberos Encryption is set to RC4"
                New-HTMLListItem -Text "Primary Group is always Domain Users with exception of Domain Guests"
            }
            New-HTMLText -Text @(
                "It's important that all those settings are set as expected."
            )
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'Enabled' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'PasswordNotRequired' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'AllowReversiblePasswordEncryption' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'UseDESKeyOnly' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'PrimaryGroupID' -ComparisonType string -BackgroundColor PaleGreen -Value '513' -Operator eq -FailBackgroundColor Salmon -HighlightHeaders 'PrimaryGroupID', 'PrimaryGroup'
        New-HTMLTableCondition -Name 'PasswordLastSet' -ComparisonType date -BackgroundColor PaleGreen -Value (Get-Date).AddDays(-180) -Operator gt -FailBackgroundColor Salmon -DateTimeFormat 'DD.MM.YYYY HH:mm:ss'
        New-HTMLTableCondition -Name 'LastLogonDate' -ComparisonType date -BackgroundColor PaleGreen -Value (Get-Date).AddDays(-180) -Operator gt -FailBackgroundColor Salmon -DateTimeFormat 'DD.MM.YYYY HH:mm:ss'
    }
    DataInformation = {
        New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
        New-HTMLList {
            New-HTMLListItem -FontWeight bold, normal -Text "PasswordNotRequired", " - means password is not required by the account. This should be investigated right away. "
            New-HTMLListItem -FontWeight bold, normal -Text "AllowReversiblePasswordEncryption", " - means the password is stored insecurely in Active Directory. Removing this flag is required. "
            New-HTMLListItem -FontWeight bold, normal -Text "UseDESKeyOnly", " - means the kerberos encryption is set to DES which is very weak. Removing flag is required. "
            New-HTMLListItem -FontWeight bold, normal -Text "PrimaryGroupID", " - if primary group ID is something else then 513 it means someone made a primary group change to something else than Domain Users. This should be fixed. "
        } -FontSize 10pt
    }
}
$SecurityUsersAcccountAdministrator = @{
    Name   = 'DomainSecurityUsersAcccountAdministrator'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "Users: Administrator (SID-500)"
        Data           = {
            # this test is kind of special
            # basically when account is disabled it doesn't make sense to check for PasswordLastSet
            # therefore i'm adding setting PasswordLastSet to current date to be able to test just that field
            # At least until support for multiple checks is added

            $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                 = $User.SamAccountName
                    Enabled              = $User.Enabled
                    PasswordLastSet      = Get-Date
                    ServicePrincipalName = $User.ServicePrincipalName
                    LastLogonDate        = $User.LastLogonDate
                    DistinguishedName    = $User.DistinguishedName
                    SID                  = $User.SID
                }
            } else {
                [PSCustomObject] @{
                    Name                 = $User.SamAccountName
                    Enabled              = $User.Enabled
                    PasswordLastSet      = $User.PasswordLastSet
                    ServicePrincipalName = $User.ServicePrincipalName
                    LastLogonDate        = $User.LastLogonDate
                    DistinguishedName    = $User.DistinguishedName
                    SID                  = $User.SID
                }
            }
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Importance  = 0
            ActionType  = 0
            Description = "Administrator (SID-500) account is critical account in Active Directory. Due to it's role it shouldn't be used as a daily driver, and only as emeregency account."
            Resources   = @(

            )
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        LastLogonDate        = @{
            Enable      = $true
            Name        = 'Last Logon Date should not be recent'
            Parameters  = @{
                Property      = 'LastLogonDate'
                ExpectedValue = (Get-Date).AddDays(-60)
                OperationType = 'lt'
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 9
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = ""
        }
        ServicePrincipalName = @{
            Enable      = $true
            Name        = 'Service Principal Name should be empty'
            Parameters  = @{
                Property      = 'servicePrincipalName'
                ExpectedValue = $null
                OperationType = 'eq'
            }
            Details     = [ordered] @{
                Category    = 'Security'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
            Description = ""
        }
        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'
            }
            Description = 'Administrator account should be disabled or LastPasswordChange should be less than 1 year ago.'
        }
    }
}
$SysVolDFSR = @{
    Name   = 'DomainSysVolDFSR'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = "DFSR Flags"
        Data           = {
            $DistinguishedName = (Get-ADDomain -Server $Domain).DistinguishedName
            $ADObject = "CN=DFSR-GlobalSettings,CN=System,$DistinguishedName"
            $Object = Get-ADObject -Identity $ADObject -Properties * -Server $Domain
            if ($Object.'msDFSR-Flags' -gt 47) {
                [PSCustomObject] @{
                    'SysvolMode' = 'DFS-R'
                    'Flags'      = $Object.'msDFSR-Flags'
                }
            } else {
                [PSCustomObject] @{
                    'SysvolMode' = 'Not DFS-R'
                    'Flags'      = $Object.'msDFSR-Flags'
                }
            }
        }
        Details        = [ordered] @{
            Category        = 'Health'
            Area    = 'SYSVOL'
            Severity    = ''
            Importance  = 0
            Description = 'Checks if DFS-R is available.'
            Resolution  = ''
            Resources   = @(
                'https://blogs.technet.microsoft.com/askds/2009/01/05/dfsr-sysvol-migration-faq-useful-trivia-that-may-save-your-follicles/'
                'https://dirteam.com/sander/2019/04/10/knowledgebase-in-place-upgrading-domain-controllers-to-windows-server-2019-while-still-using-ntfrs-breaks-sysvol-replication-and-dslocator/'
            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DFSRSysvolState = @{
            Enable     = $true
            Name       = 'DFSR Sysvol State'
            Parameters = @{
                Property              = 'SysvolMode'
                ExpectedValue         = 'DFS-R'
                OperationType         = 'eq'
                PropertyExtendedValue = 'Flags'
            }
        }
    }
}
$WellKnownFolders = @{
    Name   = 'DomainWellKnownFolders'
    Enable = $true
    Scope  = 'Domain'
    Source = @{
        Name           = 'Well known folders'
        Data           = {
            $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.$_
            }
            Compare-MultipleObjects -Objects @($DefaultWellKnownFolders, $CurrentWellKnownFolders) -SkipProperties
        }
        Details        = [ordered] @{
            Area        = 'Objects'
            Category    = 'Configuration'
            Severity    = 'Low'
            Importance  = 5
            Description = 'Verifies whether well-known folders are at their defaults or not.'
            Resolution  = 'Follow given resources to redirect users and computers containers to managable Organizational Units. If other Well Known folers are wrong - investigate.'
            Resources   = @(
                'https://support.microsoft.com/en-us/help/324949/redirecting-the-users-and-computers-containers-in-active-directory-dom'
            )
        }
        ExpectedOutput = $true
    }
    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 should 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'
            }
        }
    }
}
$DCDNSForwaders = @{
    Name   = 'DCDNSForwaders'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "DC DNS Forwarders"
        Data           = {
            $Forwarders = Get-WinADDnsServerForwarder -Forest $ForestName -Domain $Domain -IncludeDomainControllers $DomainController -WarningAction SilentlyContinue -Formatted
            $Forwarders
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Area        = 'DNS'
            Importance  = 5
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        SameForwarders = @{
            Enable      = $true
            Name        = 'Multiple DNS Forwarders'
            Parameters  = @{
                Property              = 'ForwardersCount'
                ExpectedValue         = 1
                OperationType         = 'gt'
                PropertyExtendedValue = 'IPAddress'
            }
            Description = 'DNS: More than one forwarding server should be configured'
        }
    }
}
$DFS = @{
    Name   = 'DCDFS'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "SYSVOL/DFS Verification"
        Data           = {
            Get-WinADDFSHealth -Forest $ForestName -Domains $Domain -DomainControllers $DomainController -EventDays $EventDays
        }
        Parameters     = @{
            EventDays = 3
        }
        Details        = [ordered] @{
            Area        = 'DFS'
            Category    = 'Health'
            Importance  = 10
            Description = "Provides health verification of SYSVOL/DFS on Domain Controller."
            Resolution  = ''
            Resources   = @(
                'https://support.microsoft.com/en-us/help/2218556/how-to-force-an-authoritative-and-non-authoritative-synchronization-fo'
                'https://www.itprotoday.com/windows-78/fixing-broken-sysvol-replication'
                'https://www.brisk-it.net/when-dfs-replication-goes-wrong-and-how-to-fix-it/'
                'https://gallery.technet.microsoft.com/scriptcenter/AD-DFS-Replication-Auto-812a88bc'
                'https://www.reddit.com/r/sysadmin/comments/7gey4k/resuming_dfs_replication_after_4_years_of_no/'
                'https://kimconnect.com/fix-dfs-replication-problems/'
                'https://community.spiceworks.com/topic/2205945-repairing-broken-dfs-replication'

                'https://support.microsoft.com/en-us/help/2958414/dfs-replication-how-to-troubleshoot-missing-sysvol-and-netlogon-shares'
                'https://noobient.com/2013/11/11/fixing-sysvol-replication-on-windows-server-2012/'

                # personal favourite to fix DFSR issues
                'https://jackstromberg.com/2014/07/sysvol-and-group-policy-out-of-sync-on-server-2012-r2-dcs-using-dfsr/'
            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        Status              = @{
            Enable     = $true
            Name       = 'DFS should be Healthy'
            Parameters = @{
                ExpectedValue = $true
                Property      = 'Status'
                OperationType = 'eq'
            }
        }
        ReplicationState    = @{
            Enable     = $true
            Name       = 'Replication State should be NORMAL'
            Parameters = @{
                ExpectedValue = 'Normal'
                Property      = 'ReplicationState'
                OperationType = 'eq'
            }
        }
        CentralRepository   = @{
            Enable     = $true
            Name       = 'Central Repository for GPO for Domain should be available'
            Parameters = @{
                ExpectedValue = $true
                Property      = 'CentralRepository'
                OperationType = 'eq'
            }
        }
        CentralRepositoryDC = @{
            Enable     = $true
            Name       = 'Central Repository for GPO for DC should be available'
            Parameters = @{
                ExpectedValue = $true
                Property      = 'CentralRepositoryDC'
                OperationType = 'eq'
            }
        }
        IdenticalCount      = @{
            Enable     = $true
            Name       = 'GPO Count should match folder count'
            Parameters = @{
                ExpectedValue = $true
                Property      = 'IdenticalCount'
                OperationType = 'eq'
            }
        }
        MemberReference     = @{
            Enable     = $true
            Name       = 'MemberReference should return TRUE'
            Parameters = @{
                ExpectedValue = $true
                Property      = 'MemberReference'
                OperationType = 'eq'
            }
        }
        DFSErrors           = @{
            Enable     = $true
            Name       = 'DFSErrors should be 0'
            Parameters = @{
                ExpectedValue = 0
                Property      = 'DFSErrors'
                OperationType = 'eq'
            }
        }
        DFSLocalSetting     = @{
            Enable     = $true
            Name       = 'DFSLocalSetting should be TRUE'
            Parameters = @{
                ExpectedValue = $true
                Property      = 'DFSLocalSetting'
                OperationType = 'eq'
            }
        }
        DomainSystemVolume  = @{
            Enable     = $true
            Name       = 'DomainSystemVolume should be TRUE'
            Parameters = @{
                ExpectedValue = $true
                Property      = 'DomainSystemVolume'
                OperationType = 'eq'
            }
        }
        SYSVOLSubscription  = @{
            Enable     = $true
            Name       = 'SYSVOLSubscription should be TRUE'
            Parameters = @{
                ExpectedValue = $true
                Property      = 'SYSVOLSubscription'
                OperationType = 'eq'
            }
        }
        DFSRAutoRecovery    = @{
            Enable     = $true
            Name       = 'DFSR AutoRecovery should be enabled (not stopped)'
            Parameters = @{
                Property      = 'StopReplicationOnAutoRecovery'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://secureinfra.blog/2019/04/30/field-notes-a-quick-tip-on-dfsr-automatic-recovery-while-you-prepare-for-an-ad-domain-upgrade/'
                    'https://richardjgreen.net/active-directory-dfs-r-auto-recovery/'
                )
            }
        }
    }
}

<#
DomainController Domain Status IsPDC GroupPolicyCount SysvolCount CentralRepository CentralRepositoryDC IdenticalCount Availability MemberReference DFSErrors DFSEvents DFSLocalSetting DomainSystemVolume SYSVOLSubscription
---------------- ------ ------ ----- ---------------- ----------- ----------------- ------------------- -------------- ------------ --------------- --------- --------- --------------- ------------------ ------------------
AD2 ad.evotec.xyz False False 14 12 False False False True True 0 True True True
AD1 ad.evotec.xyz True True 14 14 False False True True True 0 True True True
AD3 ad.evotec.xyz False False 14 0 False False False True True 0 True True True
#>

$Diagnostics = @{
    Name   = 'DCDiagnostics'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'Diagnostics (DCDIAG)'
        Data           = {
            Test-ADDomainController -Forest $ForestName -ComputerName $DomainController -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Area        = 'Overall'
            Category    = 'Health'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(
                'https://social.technet.microsoft.com/Forums/en-US/b48ee073-eb71-4852-8f56-ecf6f76b3fff/how-could-i-change-result-of-dcdiag-language-to-english-?forum=winserver8gen'
            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        Connectivity                     = @{
            Enable     = $true
            Name       = 'DCDiag Connectivity'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'Network' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        Advertising                      = @{
            Enable     = $true
            Name       = 'DCDiag Advertising'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'Advertising' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        CheckSecurityError               = @{
            Enable     = $true
            Name       = 'DCDiag CheckSecurityError'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CheckSecurityError' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        CutoffServers                    = @{
            Enable     = $true
            Name       = 'DCDiag CutoffServers'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CutoffServers' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        FrsEvent                         = @{
            Enable     = $true
            Name       = 'DCDiag FrsEvent'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'FrsEvent' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        DFSREvent                        = @{
            Enable     = $true
            Name       = 'DCDiag DFSREvent'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'DFSREvent' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        SysVolCheck                      = @{
            Enable     = $true
            Name       = 'DCDiag SysVolCheck'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'SysVolCheck' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        FrsSysVol                        = @{
            Enable     = $true
            Name       = 'DCDiag FrsSysVol'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'FrsSysVol' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        KccEvent                         = @{
            Enable     = $true
            Name       = 'DCDiag KccEvent'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'KccEvent' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        KnowsOfRoleHolders               = @{
            Enable     = $true
            Name       = 'DCDiag KnowsOfRoleHolders'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'KnowsOfRoleHolders' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        MachineAccount                   = @{
            Enable     = $true
            Name       = 'DCDiag MachineAccount'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'MachineAccount' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        NCSecDesc                        = @{
            Enable     = $true
            Name       = 'DCDiag NCSecDesc'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'NCSecDesc' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        NetLogons                        = @{
            Enable     = $true
            Name       = 'DCDiag NetLogons'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'NetLogons' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        ObjectsReplicated                = @{
            Enable     = $true
            Name       = 'DCDiag ObjectsReplicated'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'ObjectsReplicated' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        Replications                     = @{
            Enable     = $true
            Name       = 'DCDiag Replications'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'Replications' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        RidManager                       = @{
            Enable     = $true
            Name       = 'DCDiag RidManager'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'RidManager' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        Services                         = @{
            Enable     = $true
            Name       = 'DCDiag Services'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'Services' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        SystemLog                        = @{
            Enable     = $true
            Name       = 'DCDiag SystemLog'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'SystemLog' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        Topology                         = @{
            Enable     = $true
            Name       = 'DCDiag Topology'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'Topology' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        VerifyEnterpriseReferences       = @{
            Enable     = $true
            Name       = 'DCDiag VerifyEnterpriseReferences'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'VerifyEnterpriseReferences' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        VerifyReferences                 = @{
            Enable     = $true
            Name       = 'DCDiag VerifyReferences'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'VerifyReferences' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        VerifyReplicas                   = @{
            Enable     = $true
            Name       = 'DCDiag VerifyReplicas'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'VerifyReplicas' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        DNS                              = @{
            Enable     = $true
            Name       = 'DCDiag DNS'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'DNS' -and $_.Target -ne $Domain }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        ForestDnsZonesCheckSDRefDom      = @{
            Enable     = $true
            Name       = 'DCDiag ForestDnsZones CheckSDRefDom'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CheckSDRefDom' -and $_.Target -eq 'ForestDnsZones' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        ForestDnsZonesCrossRefValidation = @{
            Enable     = $true
            Name       = 'DCDiag ForestDnsZones CrossRefValidation'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CrossRefValidation' -and $_.Target -eq 'ForestDnsZones' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        DomainDnsZonesCheckSDRefDom      = @{
            Enable     = $true
            Name       = 'DCDiag DomainDnsZones CheckSDRefDom'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CheckSDRefDom' -and $_.Target -eq 'DomainDnsZones' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        DomainDnsZonesCrossRefValidation = @{
            Enable     = $true
            Name       = 'DCDiag DomainDnsZones CrossRefValidation'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CrossRefValidation' -and $_.Target -eq 'DomainDnsZones' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        SchemaCheckSDRefDom              = @{
            Enable     = $true
            Name       = 'DCDiag Schema CheckSDRefDom'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CheckSDRefDom' -and $_.Target -eq 'Schema' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        SchemaCrossRefValidation         = @{
            Enable     = $true
            Name       = 'DCDiag Schema CrossRefValidation'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CrossRefValidation' -and $_.Target -eq 'Schema' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        ConfigurationCheckSDRefDom       = @{
            Enable     = $true
            Name       = 'DCDiag Configuration CheckSDRefDom'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CheckSDRefDom' -and $_.Target -eq 'Configuration' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        ConfigurationCrossRefValidation  = @{
            Enable     = $true
            Name       = 'DCDiag Configuration CrossRefValidation'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CrossRefValidation' -and $_.Target -eq 'Configuration' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        NetbiosCheckSDRefDom             = @{
            Enable     = $true
            Name       = 'DCDiag NETBIOS CheckSDRefDom'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CheckSDRefDom' -and ($_.Target -ne 'Configuration' -and $_.Target -ne 'ForestDnsZones' -and $_.Target -ne 'DomainDnsZones' -and $_.Target -ne 'Schema') }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        NetbiosCrossRefValidation        = @{
            Enable     = $true
            Name       = 'DCDiag NETBIOS CrossRefValidation'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'CrossRefValidation' -and ($_.Target -ne 'Configuration' -and $_.Target -ne 'ForestDnsZones' -and $_.Target -ne 'DomainDnsZones' -and $_.Target -ne 'Schema') }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        DNSDomain                        = @{
            Enable     = $true
            Name       = 'DCDiag DNS'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'DNS' -and $_.Target -eq $Domain }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        LocatorCheck                     = @{
            Enable     = $true
            Name       = 'DCDiag LocatorCheck'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'LocatorCheck' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        FsmoCheck                        = @{
            Enable     = $true
            Name       = 'DCDiag FsmoCheck'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'FsmoCheck' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        Intersite                        = @{
            Enable     = $true
            Name       = 'DCDiag Intersite'
            Parameters = @{
                WhereObject   = { $_.Test -eq 'Intersite' }
                Property      = 'Result'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
    }
}

<#
ComputerName Target Test Result Data
------------ ------ ---- ------ ----
ad1.ad.evotec.xyz AD1 Connectivity True Starting test: Connectivity...
ad1.ad.evotec.xyz AD1 Advertising True Starting test: Advertising...
ad1.ad.evotec.xyz AD1 CheckSecurityError True Starting test: CheckSecurityError...
ad1.ad.evotec.xyz AD1 CutoffServers True Starting test: CutoffServers...
ad1.ad.evotec.xyz AD1 FrsEvent True Starting test: FrsEvent...
ad1.ad.evotec.xyz AD1 DFSREvent True Starting test: DFSREvent...
ad1.ad.evotec.xyz AD1 SysVolCheck True Starting test: SysVolCheck...
ad1.ad.evotec.xyz AD1 FrsSysVol True Starting test: FrsSysVol...
ad1.ad.evotec.xyz AD1 KccEvent True Starting test: KccEvent...
ad1.ad.evotec.xyz AD1 KnowsOfRoleHolders True Starting test: KnowsOfRoleHolders...
ad1.ad.evotec.xyz AD1 MachineAccount True Starting test: MachineAccount...
ad1.ad.evotec.xyz AD1 NCSecDesc True Starting test: NCSecDesc...
ad1.ad.evotec.xyz AD1 NetLogons True Starting test: NetLogons...
ad1.ad.evotec.xyz AD1 ObjectsReplicated True Starting test: ObjectsReplicated...
ad1.ad.evotec.xyz AD1 Replications True Starting test: Replications...
ad1.ad.evotec.xyz AD1 RidManager True Starting test: RidManager...
ad1.ad.evotec.xyz AD1 Services True Starting test: Services...
ad1.ad.evotec.xyz AD1 SystemLog False Starting test: SystemLog...
ad1.ad.evotec.xyz AD1 Topology True Starting test: Topology...
ad1.ad.evotec.xyz AD1 VerifyEnterpriseReferences True Starting test: VerifyEnterpriseReferences...
ad1.ad.evotec.xyz AD1 VerifyReferences True Starting test: VerifyReferences...
ad1.ad.evotec.xyz AD1 VerifyReplicas True Starting test: VerifyReplicas...
ad1.ad.evotec.xyz AD1 DNS True Starting test: DNS...
ad1.ad.evotec.xyz ForestDnsZones CheckSDRefDom True Starting test: CheckSDRefDom...
ad1.ad.evotec.xyz ForestDnsZones CrossRefValidation True Starting test: CrossRefValidation...
ad1.ad.evotec.xyz DomainDnsZones CheckSDRefDom True Starting test: CheckSDRefDom...
ad1.ad.evotec.xyz DomainDnsZones CrossRefValidation True Starting test: CrossRefValidation...
ad1.ad.evotec.xyz Schema CheckSDRefDom True Starting test: CheckSDRefDom...
ad1.ad.evotec.xyz Schema CrossRefValidation True Starting test: CrossRefValidation...
ad1.ad.evotec.xyz Configuration CheckSDRefDom True Starting test: CheckSDRefDom...
ad1.ad.evotec.xyz Configuration CrossRefValidation True Starting test: CrossRefValidation...
ad1.ad.evotec.xyz ad CheckSDRefDom True Starting test: CheckSDRefDom...
ad1.ad.evotec.xyz ad CrossRefValidation True Starting test: CrossRefValidation...
ad1.ad.evotec.xyz ad.evotec.xyz DNS True Starting test: DNS...
ad1.ad.evotec.xyz ad.evotec.xyz LocatorCheck True Starting test: LocatorCheck...
ad1.ad.evotec.xyz ad.evotec.xyz FsmoCheck True Starting test: FsmoCheck...
ad1.ad.evotec.xyz ad.evotec.xyz Intersite True Starting test: Intersite...
#>


$DiskSpace = @{
    Name   = 'DCDiskSpace'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'Disk Free'
        Data           = {
            Get-ComputerDiskLogical -ComputerName $DomainController -OnlyLocalDisk -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Area        = 'WindowsConfiguration'
            Category    = 'Health'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        FreeSpace   = @{
            Enable     = $true
            Name       = "Free Space in GB"
            Parameters = @{
                Property              = 'FreeSpace'
                PropertyExtendedValue = 'FreeSpace'
                ExpectedValue         = 10
                OperationType         = 'gt'
                OverwriteName         = { "Free Space in GB / $($_.DeviceID)" }
            }
        }
        FreePercent = @{
            Enable     = $true
            Name       = 'Free Space Percent'
            Parameters = @{
                Property              = 'FreePercent'
                PropertyExtendedValue = 'FreePercent'
                ExpectedValue         = 10
                OperationType         = 'gt'
                OverwriteName         = { "Free Space in Percent / $($_.DeviceID)" }
            }
        }
    }
}
$DNSNameServers = @{
    Name   = 'DCDnsNameServes'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Name servers for primary domain zone"
        Data           = {
            Test-DNSNameServers -Domain $Domain -DomainController $DomainController
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Area        = 'DNS'
            Severity    = 'Medium'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DnsNameServersIdentical = @{
            Enable      = $true
            Name        = 'DNS Name servers for primary zone are identical'
            Parameters  = @{
                Property              = 'Status'
                ExpectedValue         = $True
                OperationType         = 'eq'
                PropertyExtendedValue = 'Comment'
            }
            Description = 'DNS Name servers for primary zone should be equal to Domain Controllers for a Domain.'
        }
    }
}
$DNSResolveExternal = @{
    Name   = 'DCDnsResolveExternal'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Resolves external DNS queries"
        Data           = {
            $Output = Invoke-Command -ComputerName $DomainController -ErrorAction Stop {
                Resolve-DnsName -Name 'testimo-check.evotec.xyz' -ErrorAction SilentlyContinue | Where-Object { $_.Section -eq 'Answer' -and $_.Type -eq 'A' }
            }
            $Output
        }
        Details        = [ordered] @{
            Area        = 'DNS'
            Category    = 'Health'
            Severity    = 'High'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        ResolveDNSExternal = @{
            Enable      = $true
            Name        = 'Should resolve External DNS'
            Parameters  = @{
                Property      = 'IPAddress'
                ExpectedValue = '1.1.1.1'
                OperationType = 'eq'
            }
            Description = 'DNS should resolve external queries properly.'
        }
    }
}

$DNSResolveInternal = @{
    Name   = 'DCDnsResolveInternal'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Resolves internal DNS queries"
        Data           = {
            $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
        }
        Details        = [ordered] @{
            Category    = 'Health'
            Area        = 'DNS'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        ResolveDNSInternal = @{
            Enable      = $true
            Name        = 'Should resolve Internal DNS'
            Parameters  = @{
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'IPAddresses'
            }
            Description = 'DNS should resolve internal domains correctly.'
        }
    }
}
$EventLogs = @{
    Name   = 'DCEventLogs'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Event Logs"
        Data           = {
            Get-EventsInformation -LogName 'Application', 'System', 'Security', 'Microsoft-Windows-PowerShell/Operational' -Machine $DomainController -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Area        = 'EventLogs'
            Category    = 'Health'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        ApplicationLogMode                               = @{
            Enable     = $true
            Name       = 'Application Log mode is set to AutoBackup'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Application' }
                Property      = 'LogMode'
                ExpectedValue = 'AutoBackup'
                OperationType = 'eq'
            }
        }
        ApplicationLogFull                               = @{
            Enable     = $true
            Name       = 'Application log is not full'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Application' }
                Property      = 'IsLogFull'
                ExpectedValue = $false
                OperationType = 'eq'
            }
        }
        PowershellLogMode                                = @{
            Enable     = $true
            Name       = 'PowerShell Log mode is set to AutoBackup'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Microsoft-Windows-PowerShell/Operational' }
                Property      = 'LogMode'
                ExpectedValue = 'AutoBackup'
                OperationType = 'eq'
            }
        }
        PowerShellLogFull                                = @{
            Enable     = $true
            Name       = 'PowerShell log is not full'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Microsoft-Windows-PowerShell/Operational' }
                Property      = 'IsLogFull'
                ExpectedValue = $false
                OperationType = 'eq'
            }
        }
        SystemLogMode                                    = @{
            Enable     = $true
            Name       = 'System Log mode is set to AutoBackup'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'System' }
                Property      = 'LogMode'
                ExpectedValue = 'AutoBackup'
                OperationType = 'eq'
            }
        }
        SystemLogFull                                    = @{
            Enable     = $true
            Name       = 'System log is not full'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'System' }
                Property      = 'IsLogFull'
                ExpectedValue = $false
                OperationType = 'eq'
            }
        }

        SecurityLogMode                                  = @{
            Enable     = $true
            Name       = 'Security Log mode is set to AutoBackup'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Security' }
                Property      = 'LogMode'
                ExpectedValue = 'AutoBackup'
                OperationType = 'eq'
            }
        }
        SecurityLogFull                                  = @{
            Enable     = $true
            Name       = 'Security log is not full'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Security' }
                Property      = 'IsLogFull'
                ExpectedValue = $false
                OperationType = 'eq'
            }
        }
        SecurityMaximumLogSize                           = @{
            Enable     = $true
            Name       = 'Security Log Maximum Size smaller then 4GB'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Security' }
                Property      = 'FileSizeMaximumMB'
                ExpectedValue = 4000
                OperationType = 'le'
            }
        }
        SecurityCurrentLogSize                           = @{
            Enable     = $true
            Name       = 'Security Log Current Size smaller then 4GB'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Security' }
                Property      = 'FileSizeCurrentMB'
                ExpectedValue = 4000
                OperationType = 'le'
            }
        }
        SecurityPermissionsDefaultNetworkService         = @{
            Enable     = $true
            Name       = 'Security Log has NT AUTHORITY\NETWORK SERVICE with AccessAllowed'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Security' -and $_.SecurityDescriptorDiscretionaryAcl -contains 'NT AUTHORITY\NETWORK SERVICE: AccessAllowed (ListDirectory)' }
                ExpectedCount = 1
                OperationType = 'eq'
            }
        }
        SecurityPermissionsDefaultSYSTEM                 = @{
            Enable     = $true
            Name       = 'Security Log has NT AUTHORITY\SYSTEM with AccessAllowed'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Security' -and $_.SecurityDescriptorDiscretionaryAcl -contains 'NT AUTHORITY\SYSTEM: AccessAllowed (ChangePermissions, CreateDirectories, Delete, GenericExecute, ListDirectory, ReadPermissions, TakeOwnership)' }
                ExpectedCount = 1
                OperationType = 'eq'
            }
        }
        SecurityPermissionsNDefaultBuiltinAdministrators = @{
            Enable     = $true
            Name       = 'Security Log has BUILTIN\Administrators with AccessAllowed'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Security' -and $_.SecurityDescriptorDiscretionaryAcl -contains 'BUILTIN\Administrators: AccessAllowed (CreateDirectories, ListDirectory)' }
                ExpectedCount = 1
                OperationType = 'eq'
            }
        }
        SecurityPermissionsDefaultBuiltinEventLogReaders = @{
            Enable     = $true
            Name       = 'Security Log has BUILTIN\Event Log Readers with AccessAllowed'
            Parameters = @{
                WhereObject   = { $_.LogName -eq 'Security' -and $_.SecurityDescriptorDiscretionaryAcl -contains 'BUILTIN\Event Log Readers: AccessAllowed (ListDirectory)' }
                ExpectedCount = 1
                OperationType = 'eq'
            }
        }
    }
}

#$Test = Get-EventsInformation -LogName 'Security' -Machine AD1
#$Test = Get-EventsInformation -LogName 'Microsoft-Windows-PowerShell/Operational' -Machine AD1
#$Test
#$Test.SecurityDescriptorDiscretionaryAcl

# PowerShellCore/Operational
# Microsoft-Windows-PowerShell/Operational

#$D = ConvertFrom-SddlString 'O:BAG:SYD:(A;;0x2;;;S-1-15-2-1)(A;;0x2;;;S-1-15-3-1024-3153509613-960666767-3724611135-2725662640-12138253-543910227-1950414635-4190290187)(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x7;;;SO)(A;;0x3;;;IU)(A;;0x3;;;SU)(A;;0x3;;;S-1-5-3)(A;;0x3;;;S-1-5-33)(A;;0x1;;;S-1-5-32-573)'
#$D.DiscretionaryAcl

#Get-PSRegistry -RegistryPath 'HKLM\Software\Policies\Microsoft\Windows\PowerShell' | ft -a
#Get-PSRegistry -RegistryPath 'HKLM\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' -ComputerName AD1 | ft -a
#Get-PSRegistry -RegistryPath 'HKLM\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\Transcription' -ComputerName AD1 | ft -a
#Get-PSRegistry -RegistryPath 'HKLM\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ModuleLogging' -ComputerName AD1 | ft -a
#Get-PSRegistry -registrypath 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\PowerShell' -ComputerName AD1
#Get-PSRegistry -registrypath 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies'
#Get-PSRegistry -RegistryPath 'HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell' -Verbose
$FileSystem = @{
    Name   = 'DCFileSystem'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "FileSystem"
        Data           = {
            Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Control\FileSystem' -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Area        = 'FileSystem'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(
                ''
            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-WinADLMSettings'
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        NtfsDisable8dot3NameCreation = @{
            Enable     = $true
            Name       = 'NtfsDisable8dot3NameCreation'
            Parameters = @{
                Property      = 'NtfsDisable8dot3NameCreation'
                ExpectedValue = 0
                OperationType = 'gt'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://guyrleech.wordpress.com/2014/04/15/ntfs-8-3-short-names-solving-the-issues/'
                    'https://blogs.technet.microsoft.com/josebda/2012/11/13/windows-server-2012-file-server-tip-disable-8-3-naming-and-strip-those-short-names-too/'
                    'https://support.microsoft.com/en-us/help/121007/how-to-disable-8-3-file-name-creation-on-ntfs-partitions'
                )
            }
        }
    }
}

$GroupPolicySYSVOLDC = @{
    Name   = 'DCGroupPolicySYSVOLDC'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Group Policy SYSVOL Verification"
        Data           = {
            Get-GPOZaurrSysvol -IncludeDomains $Domain -IncludeDomainControllers $DomainController -VerifyDomainControllers | Where-Object { $_.Status -ne 'Exists' }
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $false
    }
}
$Information = @{
    Name   = 'DCInformation'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Domain Controller Information"
        Data           = {
            Get-ADDomainController -Server $DomainController
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        IsEnabled       = @{
            Enable     = $true
            Name       = 'Is Enabled'
            Parameters = @{
                Property      = 'Enabled'
                ExpectedValue = $True
                OperationType = 'eq'
            }
        }
        IsGlobalCatalog = @{
            Enable     = $true
            Name       = 'Is Global Catalog'
            Parameters = @{
                Property      = 'IsGlobalCatalog'
                ExpectedValue = $True
                OperationType = 'eq'
            }
        }
    }
}
$LanManagerSettings = @{
    Name   = 'DCLanManagerSettings'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Lan Manager Settings"
        Data           = {
            Get-WinADLMSettings -DomainController $DomainController
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(
                'https://adsecurity.org/?p=3377'
            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-WinADLMSettings'
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        Level                     = @{
            Enable     = $true
            Name       = 'LM Level'
            Parameters = @{
                Property      = 'Level'
                ExpectedValue = 5
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        AuditBaseObjects          = @{
            Enable     = $true
            Name       = 'Audit Base Objects'
            Parameters = @{
                Property      = 'AuditBaseObjects'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpac/262a2bed-93d4-4c04-abec-cf06e9ec72fd'
                )
            }
        }
        CrashOnAuditFail          = @{
            Enable     = $true
            Name       = 'Crash On Audit Fail'
            Parameters = @{
                Property      = 'CrashOnAuditFail'
                ExpectedValue = 0
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'http://systemmanager.ru/win2k_regestry.en/46686.htm'
                )
            }
        }
        EveryoneIncludesAnonymous = @{
            Enable     = $true
            Name       = 'Everyone Includes Anonymous'
            Parameters = @{
                Property      = 'EveryoneIncludesAnonymous'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Title       = 'Disable and Enforce the Setting "Network access: Let Everyone permissions apply to anonymous users"'
                Area        = ''
                Description = 'This setting helps to prevent an unauthorized user could from anonymously listing account names and shared resources and use using the information to attempt to guess passwords, perform social engineering attacks, or launch DoS attacks.'
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://www.stigviewer.com/stig/windows_7/2014-04-02/finding/V-3377'
                )
            }
        }
        SecureBoot                = @{
            Enable     = $true
            Name       = 'Secure Boot'
            Parameters = @{
                Property      = 'SecureBoot'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        LSAProtectionCredentials  = @{
            Enable     = $true
            Name       = 'LSAProtectionCredentials'
            Parameters = @{
                Property      = 'LSAProtectionCredentials'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        LimitBlankPasswordUse     = @{
            Enable     = $true
            Name       = 'LimitBlankPasswordUse'
            Parameters = @{
                Property      = 'LimitBlankPasswordUse'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        NoLmHash                  = @{
            Enable     = $true
            Name       = 'NoLmHash'
            Parameters = @{
                Property      = 'NoLmHash'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        DisableDomainCreds        = @{
            Enable     = $true
            Name       = 'DisableDomainCreds'
            Parameters = @{
                Property      = 'DisableDomainCreds'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://www.stigviewer.com/stig/windows_8/2014-01-07/finding/V-3376'
                )
            }
        }
        ForceGuest                = @{
            Enable     = $true
            Name       = 'ForceGuest'
            Parameters = @{
                Property      = 'ForceGuest'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
    }
}
$LanManServer = @{
    Name   = 'DCLanManServer'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Lan Man Server"
        Data           = {
            #Get-WinADLMSettings -DomainController $DomainController
            Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanManServer\Parameters' -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Category        = 'Security'
            Description = 'Lan Man Server'
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-PSRegistry'
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DisableCompression       = @{
            Enable     = $false
            Name       = 'Disable Compression SMBv3'
            Parameters = @{
                Property      = 'DisableCompression'
                ExpectedValue = 1
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category        = 'Security'
                Description = 'Microsoft is aware of a remote code execution vulnerability in the way that the Microsoft Server Message Block 3.1.1 (SMBv3) protocol handles certain requests. An attacker who successfully exploited the vulnerability could gain the ability to execute code on the target SMB Server or SMB Client. To exploit the vulnerability against an SMB Server, an unauthenticated attacker could send a specially crafted packet to a targeted SMBv3 Server. To exploit the vulnerability against an SMB Client, an unauthenticated attacker would need to configure a malicious SMBv3 Server and convince a user to connect to it.'
                Resolution  = 'Disable SMBv3 compression or apply patch. Since patch is available disabling is not nessecary.'
                Importance  = 10
                Resources   = @(
                    'https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/adv200005'
                )
            }
        }
        EnableForcedLogoff       = @{
            Enable     = $true
            Name       = 'Enable Forced Logoff'
            Parameters = @{
                Property      = 'EnableForcedLogoff'
                ExpectedValue = 1
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'Users are not forcibly disconnected when logon hours expire.'
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://www.stigviewer.com/stig/windows_7/2012-07-02/finding/V-1136'
                )
            }
        }
        EnableSecuritySignature  = @{
            Enable     = $true
            Name       = 'Enable Security Signature'
            Parameters = @{
                Property      = 'EnableSecuritySignature'
                ExpectedValue = 1
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'Microsoft network server: Digitally sign communications (if client agrees)'
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://support.microsoft.com/en-us/help/887429/overview-of-server-message-block-signing' # XP
                    'https://community.spiceworks.com/topic/2131862-how-to-set-microsoft-network-server-digitally-sign-communications-always'
                    'https://www.stigviewer.com/stig/windows_server_2016/2017-11-20/finding/V-73663'
                )
            }
        }
        RequireSecuritySignature = @{
            Enable     = $true
            Name       = 'Require Security Signature'
            Parameters = @{
                Property      = 'RequireSecuritySignature'
                ExpectedValue = 1
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category            = 'Security'
                Area            = ''
                Description     = 'Microsoft network server: Digitally sign communications (always)'
                Vulnerability   = 'Session hijacking uses tools that allow attackers who have access to the same network as the client
                computer or server to interrupt, end, or steal a session in progress. Attackers can potentially intercept and modify
                unsigned Server Message Block (SMB) packets and then modify the traffic and forward it so that the server might
                perform objectionable actions. Alternatively, the attacker could pose as the server or client after legitimate
                authentication and gain unauthorized access to data.
                SMB is the resource-sharing protocol that is supported by many Windows operating systems. It is the basis of NetBIOS
                and many other protocols. SMB signatures authenticate both users and the servers that host the data. If either side
                fails the authentication process, data transmission does not take place.'

                PotentialImpact = 'The Windows implementation of the SMB file and print-sharing protocol support mutual authentication,
                which prevents session hijacking attacks and supports message authentication to prevent man-in-the-middle attacks.
                SMB signing provides this authentication by placing a digital signature into each SMB, which is then verified by both the client computer and the server.
                Implementing SMB signing may negatively affect performance because each packet must be signed and verified. If these policy settings are enabled on a server that is performing multiple roles, such as a small business server that is serving as a domain controller, file server, print server, and application server, performance may be substantially slowed. Additionally, if you configure computers to ignore all unsigned SMB communications, older applications and operating systems cannot connect. However, if you completely disable all SMB signing, computers are vulnerable to session-hijacking attacks.'

                Resolution      = ''
                Importance      = 10
                Resources       = @(
                    'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/hh125918(v=ws.10)?redirectedfrom=MSDN#vulnerability'
                    'https://support.microsoft.com/en-us/help/887429/overview-of-server-message-block-signing' # XP
                    'https://community.spiceworks.com/topic/2131862-how-to-set-microsoft-network-server-digitally-sign-communications-always'
                )
            }
        }
    }
}


#Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters' -ComputerName AD1

#Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -ComputerName AD1

$LDAP = @{
    Name   = 'DCLDAP'
    Enable = $false
    Scope  = 'DC'
    Source = @{
        Name           = 'LDAP Connectivity'
        Data           = {
            Test-LDAP -ComputerName $DomainController -WarningAction SilentlyContinue -VerifyCertificate
        }
        Details        = [ordered] @{
            Category    = 'Health'
            Description = ''
            Resolution  = ''
            Importance  = 0
            ActionType  = 0
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        PortLDAP                 = @{
            Enable     = $true
            Name       = 'LDAP Port is Available'

            Parameters = @{
                Property      = 'LDAP'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 2
            }
        }
        PortLDAPS                = @{
            Enable     = $true
            Name       = 'LDAP SSL Port is Available'
            Parameters = @{
                Property      = 'LDAPS'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 2
            }
        }
        PortLDAP_GC              = @{
            Enable     = $true
            Name       = 'LDAP GC Port is Available'
            Parameters = @{
                Property      = 'GlobalCatalogLDAP'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 2
            }
        }
        PortLDAPS_GC             = @{
            Enable     = $true
            Name       = 'LDAP SSL GC Port is Available'
            Parameters = @{
                Property      = 'GlobalCatalogLDAPS'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 2
            }
        }
        BindLDAPS                = @{
            Enable     = $true
            Name       = 'LDAP SSL Bind available'
            Parameters = @{
                Property      = 'LDAPSBind'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 2
            }
        }
        BindLDAPS_GC             = @{
            Enable     = $true
            Name       = 'LDAP SSL GC Bind is Available'
            Parameters = @{
                Property      = 'GlobalCatalogLDAPSBind'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 2
            }
        }
        X509NotBeforeDays        = @{
            Enable     = $true
            Name       = 'Not Before Days should be less/equal 0'
            Parameters = @{
                Property      = 'X509NotBeforeDays'
                ExpectedValue = 0
                OperationType = 'le'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 2
            }
        }
        X509NotAfterDaysWarning  = @{
            Enable     = $true
            Name       = 'Not After Days should be more than 10 days'
            Parameters = @{
                Property      = 'X509NotAfterDays'
                ExpectedValue = 10
                OperationType = 'gt'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 1
            }
        }
        X509NotAfterDaysCritical = @{
            Enable     = $true
            Name       = 'Not After Days should be more than 0 days'
            Parameters = @{
                Property      = 'X509NotAfterDays'
                ExpectedValue = 0
                OperationType = 'gt'
            }
            Details    = [ordered] @{
                Category   = 'Health'
                Importance = 10
                ActionType = 2
            }
        }
    }
}
$LDAPInsecureBindings = @{
    Name   = 'DCLDAPInsecureBindings'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'LDAP Insecure Bindings'
        Data           = {
            Get-WinADLDAPBindingsSummary -IncludeDomainControllers $DomainController -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Area        = 'LDAP'
            Category    = 'Security'
            Description = 'LDAP channel binding and LDAP signing provide ways to increase the security of network communications between an Active Directory Domain Services (AD DS) or an Active Directory Lightweight Directory Services (AD LDS) and its clients. There is a vulerability in the default configuration for Lightweight Directory Access Protocol (LDAP) channel binding and LDAP signing and may expose Active directory domain controllers to elevation of privilege vulnerabilities. Microsoft Security Advisory ADV190023 address the issue by recommending the administrators enable LDAP channel binding and LDAP signing on Active Directory Domain Controllers. This hardening must be done manually until the release of the security update that will enable these settings by default.'
            Resolution  = 'Make sure to remove any Clients performing simple or unsigned bindings.'
            Importance  = 10
            Resources   = @(
                'https://evotec.xyz/four-commands-to-help-you-track-down-insecure-ldap-bindings-before-march-2020/'
                'https://support.microsoft.com/en-us/topic/2020-ldap-channel-binding-and-ldap-signing-requirements-for-windows-kb4520412-ef185fb8-00f7-167d-744c-f299a66fc00a'
                'https://support.microsoft.com/en-us/help/4520412/2020-ldap-channel-binding-and-ldap-signing-requirement-for-windows'
            )
        }
        ExpectedOutput = $false
    }
    Tests  = [ordered] @{
        SimpleBinds   = @{
            Enable     = $true
            Name       = 'Simple binds performed without SSL/TLS is 0'
            Parameters = @{
                Property      = 'Number of simple binds performed without SSL/TLS'
                ExpectedValue = 0
                OperationType = 'eq'
            }
        }
        UnsignedBinds = @{
            Enable     = $true
            Name       = 'Negotiate/Kerberos/NTLM/Digest binds performed without signing is 0'
            Parameters = @{
                Property      = 'Number of Negotiate/Kerberos/NTLM/Digest binds performed without signing'
                ExpectedValue = 0
                OperationType = 'eq'
            }
        }

    }
}
$MSSLegacy = @{
    Name   = 'DCMSSLegacy'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "MSS (Legacy)"
        Data           = {
            Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Area        = 'Network'
            Category    = 'Security'
            Description = 'Provides verification of MSS Network Settings on Domain Controllers'
            Resolution  = ''
            Importance  = 10
            Resources   = @(
                'https://blogs.technet.microsoft.com/secguide/2016/10/02/the-mss-settings/'
            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-PSRegistry'
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DisableIPSourceRouting = @{
            Enable     = $true
            Name       = 'DisableIPSourceRouting'
            Parameters = @{
                Property      = 'DisableIPSourceRouting'
                ExpectedValue = 2
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Description = 'Highest protection, source routing is completely disabled'
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://blogs.technet.microsoft.com/secguide/2016/10/02/the-mss-settings/'
                )
            }
        }
        EnableICMPRedirect     = @{
            Enable     = $true
            Name       = 'EnableICMPRedirect'
            Parameters = @{
                Property      = 'EnableICMPRedirect'
                ExpectedValue = 0
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://blogs.technet.microsoft.com/secguide/2016/10/02/the-mss-settings/'
                )
            }
        }
    }
}


#Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters' -ComputerName AD1
#Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -ComputerName AD1
$NetSessionEnumeration = @{
    Name   = 'DCNetSessionEnumeration'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Net Session Enumeration"
        Data           = {
            $Registry = Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\DefaultSecurity" -ComputerName $DomainController
            $CSD = [System.Security.AccessControl.CommonSecurityDescriptor]::new($true, $false, $Registry.SrvsvcSessionInfo, 0)
            $CSD.DiscretionaryAcl.SecurityIdentifier | Where-Object { $_ -eq 'S-1-5-11' }
            # ConvertFrom-SID -sid $CSD.DiscretionaryAcl.SecurityIdentifier | Where-Object { $_.Name -eq 'Authenticated Users' }
        }
        Details        = [ordered] @{
            Category        = 'Security'
            Description = 'Net Session Enumeration is a method used to retrieve information about established sessions on a server. Any domain user can query a server for its established sessions.'
            Resolution  = 'Hardening Net Session Enumeration'
            Importance  = 10
            Resources   = @(
                'https://gallery.technet.microsoft.com/Net-Cease-Blocking-Net-1e8dcb5b'
            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-PSRegistry'
        }
        ExpectedOutput = $false
    }
}
$NetworkCardSettings = @{
    Name   = 'DCNetworkCardSettings'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Get all network interfaces and firewall status"
        Data           = {
            Get-ComputerNetwork -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Area        = 'Network'
            Category    = 'Configuration'
            Importance  = 0
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        NETBIOSOverTCPIP        = @{
            Enable     = $true
            Name       = 'NetBIOS over TCPIP should be disabled.'
            Parameters = @{
                Property      = 'NetBIOSOverTCPIP'
                ExpectedValue = 'Disabled'
                OperationType = 'eq'
            }
            Details    = @{
                Area        = 'Network'
                Category    = 'Configuration'
                Importance  = 9 # 10 is top
                Description = @'
                NetBIOS over TCP/IP is a networking protocol that allows legacy computer applications relying on the NetBIOS to be used on modern TCP/IP networks.
                Enabling NetBios might help an attackers access shared directories, files and also gain sensitive information such as computer name, domain, or workgroup.
'@

                Resolution  = 'Disable NetBIOS over TCPIP'
                Resources   = @(
                    'http://woshub.com/how-to-disable-netbios-over-tcpip-and-llmnr-using-gpo/'
                )
            }
        }
        Loopbackpresent        = @{
            Enable     = $true
            Name       = 'Loopback IP address should be list in DNS servers on network card'
            Parameters = @{
                Property      = 'DNSServerSearchOrder'
                ExpectedValue = '127.0.0.1'
                OperationType = 'Contains'
            }
        }
        WindowsFirewall        = @{
            Enable     = $true
            Name       = 'Windows Firewall should be enabled on network card'
            Parameters = @{
                Property      = 'FirewallStatus'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        WindowsFirewallProfile = @{
            Enable     = $true
            Name       = 'Windows Firewall should be set on domain network profile'
            Parameters = @{
                Property      = 'FirewallProfile'
                ExpectedValue = 'DomainAuthenticated'
                OperationType = 'eq'
            }
        }
        DHCPDisabled           = @{
            Enable     = $false
            Name       = 'DHCP should be disabled on network card'
            Parameters = @{
                Property      = 'DHCPEnabled'
                ExpectedValue = $false
                OperationType = 'eq'
            }
        }
    }
}

$NTDSParameters = @{
    Name   = 'DCNTDSParameters'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "NTDS Parameters"
        Data           = {
            Get-PSRegistry -RegistryPath "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DsaNotWritable = @{
            Enable     = $true
            Name       = 'Domain Controller should be writeable'
            Parameters = @{
                Property       = 'Dsa Not Writable'
                ExpectedOutput = $false
            }
        }
    }
}
$OperatingSystem = @{
    Name   = 'DCOperatingSystem'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'Operating System'
        Data           = {
            Get-ComputerOperatingSystem -ComputerName $DomainController -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    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*', 'Microsoft Windows Server 2022*')
                OperationType         = 'like'
                # this means Expected Value will require at least one $true comparison
                # anything else will require all values to match $true
                OperationResult       = 'OR'
                # This overwrites value, normally it shows results of comparison
                PropertyExtendedValue = 'OperatingSystem'
            }

        }
    }
}
$Pingable = @{
    Name   = 'DCPingable'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'Ping Connectivity'
        Data           = {
            Test-NetConnection -ComputerName $DomainController -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        Ping = @{
            Enable     = $true
            Name       = 'Responding to PING'
            Parameters = @{
                Property              = 'PingSucceeded'
                PropertyExtendedValue = 'PingReplyDetails', 'RoundtripTime'
                ExpectedValue         = $true
                OperationType         = 'eq'
            }
        }
    }
}
$Ports = [ordered] @{
    Name = 'DCPorts'
    Enable = $true
    Scope  = 'DC'
    Source = [ordered] @{
        Name           = 'TCP Ports are open/closed as required' # UDP Testing is unreliable for now
        Data           = {
            # Port 389, 636, 3268, 3269 are tested as LDAP Ports with proper LDAP
            $TcpPorts = @(53, 88, 135, 139, 389, 445, 464, 636, 3268, 3269, 9389)
            # $TcpPorts = @(25, 53, 88, 464, 5722, 9389)
            Test-ComputerPort -ComputerName $DomainController -PortTCP $TcpPorts -WarningAction SilentlyContinue
            <#
                ComputerName Port Protocol Status Summary Response
                ------------ ---- -------- ------ ------- --------
                AD1 53 TCP True TCP 53 Successful
                AD1 3389 TCP True TCP 3389 Successful
                AD7 53 TCP False TCP 53 Failed
                AD7 3389 TCP False TCP 3389 Failed
            #>


            # UDP Testing is unreliable
            <# Potential ports to test
                'WinRm' = @{ 'TCP' = 5985 }
                'Smb' = @{ 'TCP' = 445; 'UDP' = 445 }
                'Dns' = @{ 'TCP' = 53; 'UDP' = 53 }
                'ActiveDirectoryGeneral' = @{ 'TCP' = 25, 88, 389, 464, 636, 5722, 9389; 'UDP' = 88, 123, 389, 464 }
                'ActiveDirectoryGlobalCatalog' = @{ 'TCP' = 3268, 3269 }
                'NetBios' = @{ 'TCP' = 135, 137, 138, 139; 'UDP' = 137, 138, 139 }
 
                Test-ComputerPort -ComputerName $DomainController -PortTCP 25, 88, 389, 464, 636, 5722, 9389 -PortUDP 88, 123, 389, 464
            #>

        }
        Requirements   = @{
            CommandAvailable = 'Test-NetConnection'
        }
        Details        = [ordered] @{
            Area        = ''
            Category    = ''
            Severity    = ''
            Importance   = 0
            Description = ''
            Resolution  = ''
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        Port53   = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '53' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
        Port88   = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '88' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
        Port135  = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '135' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
        Port139  = [ordered] @{
            Enable     = $true
            Name       = 'Port is CLOSED'
            Parameters = @{
                WhereObject           = { $_.Port -eq '139' }
                Property              = 'Status'
                ExpectedValue         = $false
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
            Details    = [ordered] @{
                Area        = ''
                Category    = ''
                Severity    = ''
                Importance   = 0
                Description = @'
                NetBIOS over TCP/IP is a networking protocol that allows legacy computer applications relying on the NetBIOS to be used on modern TCP/IP networks.
                Enabling NetBios might help an attackers access shared directories, files and also gain sensitive information such as computer name, domain, or workgroup.
'@

                Resolution  = 'Disable NETBIOS over TCPIP'
                Resources   = @(
                    'http://woshub.com/how-to-disable-netbios-over-tcpip-and-llmnr-using-gpo/'
                )
            }
        }
        Port445  = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '445' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
        Port464  = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '464' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
        Port636  = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '636' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }

        Port3268 = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '3268' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
        Port3269 = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '3269' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
        Port9389 = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                WhereObject           = { $_.Port -eq '9389' }
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
    }
}
# This is already done in RDPSecurity as well, stays disabled by default.

$RDPPorts = [ordered] @{
    Name   = 'DCRDPPorts'
    Enable = $false
    Scope  = 'DC'
    Source = [ordered] @{
        Name           = 'RDP Port is open'
        Data           = {
            Test-ComputerPort -ComputerName $DomainController -PortTCP 3389 -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        PortOpen = [ordered] @{
            Enable     = $false
            Name       = 'Port is OPEN'
            Parameters = @{
                Property              = 'Status'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'Summary'
            }
        }
    }
}
$RDPSecurity = [ordered] @{
    Name   = 'DCRDPSecurity'
    Enable = $true
    Scope  = 'DC'
    Source = [ordered] @{
        Name           = 'RDP Security'
        Data           = {
            Get-ComputerRDP -ComputerName $DomainController -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Area        = 'Network'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        PortOpen                 = [ordered] @{
            Enable     = $true
            Name       = 'Port is OPEN'
            Parameters = @{
                Property              = 'Network'
                ExpectedValue         = $true
                OperationType         = 'eq'
                PropertyExtendedValue = 'ConnectivitySummary'
            }
            Details    = [ordered] @{
                Area        = 'Network'
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://lazywinadmin.com/2014/04/powershell-getset-network-level.html'
                    'https://devblogs.microsoft.com/scripting/weekend-scripter-report-on-network-level-authentication/'
                )
            }
        }
        NLAAuthenticationEnabled = [ordered] @{
            Enable     = $true
            Name       = 'NLA Authentication is Enabled'
            Parameters = @{
                Property      = 'UserAuthenticationRequired'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = 'Network'
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://lazywinadmin.com/2014/04/powershell-getset-network-level.html'
                    'https://devblogs.microsoft.com/scripting/weekend-scripter-report-on-network-level-authentication/'
                )
            }
        }
        MinimalEncryptionLevel   = [ordered] @{
            Enable     = $true
            Name       = 'Minimal Encryption Level is set to at least High'
            Parameters = @{
                Property              = 'MinimalEncryptionLevelValue'
                ExpectedValue         = 3
                OperationType         = 'ge'
                PropertyExtendedValue = 'MinimalEncryptionLevel'
            }
            Details    = [ordered] @{
                Area        = 'Network'
                Description = 'Remote connections must be encrypted to prevent interception of data or sensitive information. Selecting "High Level" will ensure encryption of Remote Desktop Services sessions in both directions.'
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://www.stigviewer.com/stig/windows_server_2012_member_server/2014-01-07/finding/V-3454'
                )
            }
        }
    }
}

$Services = [ordered] @{
    Name   = 'DCServices'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'Service Status'
        Data           = {
            $Services = @('ADWS', 'DNS', 'DFS', 'DFSR', 'Eventlog', 'EventSystem', 'KDC', 'LanManWorkstation', 'LanManServer', 'NetLogon', 'NTDS', 'RPCSS', 'SAMSS', 'Spooler', 'W32Time', 'XblGameSave', 'XblAuthManager')
            Get-PSService -Computers $DomainController -Services $Services
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = 'Active Directory is dependent on several Windows services. If one or more of these services is not configured for automatic startup, AD functions may be partially or completely unavailable until the services are manually started. This could result in a failure to replicate data or to support client authentication and authorization requests.'
            Importance  = 0
            Resources   = @(
                'https://www.stigviewer.com/stig/microsoft_windows_server_2012_domain_controller/2013-07-25/finding/WN12-AD-000010-DC'
            )
            Tags        = 'Services', 'Configuration'
            StatusTrue  = 1
            StatusFalse = 5
        }
        ExpectedOutput = $true
    }
    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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                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 = 'Auto'
                OperationType = 'eq'
            }
        }
        SpoolerServiceStatus              = @{
            Enable     = $true
            Name       = 'Spooler Service is STOPPED'

            Parameters = @{
                WhereObject    = { $_.Name -eq 'Spooler' }
                Property       = 'Status'
                ExpectedValue  = 'Stopped'
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details    = [ordered] @{
                Area        = 'Services'
                Category    = 'Security','Configuration'
                Severity    = ''
                Importance  = 0
                Description = 'Due to security concerns SPOOLER should be disabled and stopped. However in some cases it may be required to have SPOOLER service up and running to cleanup stale printer objects from AD.'
                Resolution  = ''
                Resources   = @(
                    'https://adsecurity.org/?p=4056'
                    'https://docs.microsoft.com/en-us/windows-server/security/windows-services/security-guidelines-for-disabling-system-services-in-windows-server#print-spooler'
                )
            }

        }
        SpoolerServiceStartType           = @{
            Enable     = $true
            Name       = 'Spooler Service START TYPE is DISABLED'

            Parameters = @{
                WhereObject    = { $_.Name -eq 'Spooler' }
                Property       = 'StartType'
                ExpectedValue  = 'Disabled'
                OperationType  = 'eq'
                ExpectedOutput = $false
            }
            Details    = [ordered] @{
                Area        = 'Services'
                Category    = 'Security','Configuration'
                Severity    = ''
                Importance  = 0
                Description = 'Due to security concerns SPOOLER should be disabled and stopped. However in some cases it may be required to have SPOOLER service up and running to cleanup stale printer objects from AD.'
                Resolution  = ''
                Resources   = @(
                    'https://adsecurity.org/?p=4056'
                    'https://docs.microsoft.com/en-us/windows-server/security/windows-services/security-guidelines-for-disabling-system-services-in-windows-server#print-spooler'
                )
            }
        }

        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 = 'Auto'
                OperationType = 'eq'
            }
        }
        XblAuthManagerServiceStatus       = @{
            Enable     = $true
            Name       = 'XblAuthManager Service is STOPPED'

            Parameters = @{
                WhereObject    = { $_.Name -eq 'XblAuthManager' }
                Property       = 'Status'
                ExpectedValue  = 'Stopped', 'N/A'
                OperationType  = 'in'
                ExpectedOutput = $false
            }
        }
        XblAuthManagerStartupType         = @{
            Enable     = $true
            Name       = 'XblAuthManager Service START TYPE is Disabled'

            Parameters = @{
                WhereObject    = { $_.Name -eq 'XblAuthManager' }
                Property       = 'StartType'
                ExpectedValue  = 'Disabled', 'N/A'
                OperationType  = 'in'
                ExpectedOutput = $false
            }
        }
        XblGameSaveServiceStatus          = @{
            Enable     = $true
            Name       = 'XblGameSave Service is STOPPED'
            Parameters = @{
                WhereObject    = { $_.Name -eq 'XblGameSave' }
                Property       = 'Status'
                ExpectedValue  = 'Stopped', 'N/A'
                OperationType  = 'in'
                ExpectedOutput = $false
            }
        }
        XblGameSaveStartupType            = @{
            Enable     = $true
            Name       = 'XblGameSave Service START TYPE is Disabled'

            Parameters = @{
                WhereObject    = { $_.Name -eq 'XblGameSave' }
                Property       = 'StartType'
                ExpectedValue  = 'Disabled', 'N/A'
                OperationType  = 'in'
                ExpectedOutput = $false
            }

        }
    }
}
$ServiceWINRM = @{
    Name   = 'DCServiceWINRM'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Service WINRM"
        Data           = {
            Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service' -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Category        = 'Security'
            Area        = ''
            Description = 'Storage of administrative credentials could allow unauthorized access. Disallowing the storage of RunAs credentials for Windows Remote Management will prevent them from being used with plug-ins. The Windows Remote Management (WinRM) service must not store RunAs credentials.'
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-PSRegistry'
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        DisableRunAs = @{
            Enable     = $true
            Name       = 'DisableRunAs'
            Parameters = @{
                Property      = 'DisableRunAs'
                ExpectedValue = 1
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'Storage of administrative credentials could allow unauthorized access. Disallowing the storage of RunAs credentials for Windows Remote Management will prevent them from being used with plug-ins. The Windows Remote Management (WinRM) service must not store RunAs credentials.'
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://www.stigviewer.com/stig/windows_server_2016/2018-03-07/finding/V-73603'
                )
            }
        }
    }
}


$SMBProtocols = @{
    Name   = 'DCSMBProtocols'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'SMB Protocols'
        Data           = {
            Get-ComputerSMB -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(
                'https://community.spiceworks.com/topic/2153374-bpa-on-windows-server-2016-warns-about-smb-not-in-a-default-configuration'
            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-ComputerSMB'
        }
        ExpectedOutput = $true
    }
    # BPA Recommendations
    Tests  = [ordered] @{
        AsynchronousCredits             = @{
            Enable     = $true
            Name       = 'AsynchronousCredits'
            Parameters = @{
                Property      = 'AsynchronousCredits'
                ExpectedValue = 64
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'AsynchronousCredits should have the recommended value'
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        AutoDisconnectTimeout           = @{
            Enable     = $true
            Name       = 'AutoDisconnectTimeout'
            Parameters = @{
                Property      = 'AutoDisconnectTimeout'
                ExpectedValue = 0
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'AutoDisconnectTimeout should have the recommended value'
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        CachedOpenLimit                 = @{
            Enable     = $true
            Name       = 'CachedOpenLimit'
            Parameters = @{
                Property      = 'CachedOpenLimit'
                ExpectedValue = 5
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'CachedOpenLimit should have the recommended value'
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        DurableHandleV2TimeoutInSeconds = @{
            Enable     = $true
            Name       = 'DurableHandleV2TimeoutInSeconds'
            Parameters = @{
                Property      = 'DurableHandleV2TimeoutInSeconds'
                ExpectedValue = 30
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'DurableHandleV2TimeoutInSeconds should have the recommended value'
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        EnableSMB1Protocol              = @{
            Enable     = $true
            Name       = 'SMB v1 Protocol should be disabled'
            Parameters = @{
                Property      = 'EnableSMB1Protocol'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        EnableSMB2Protocol              = @{
            Enable     = $true
            Name       = 'SMB v2 Protocol should be enabled'
            Parameters = @{
                Property      = 'EnableSMB2Protocol'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        MaxThreadsPerQueue              = @{
            Enable     = $true
            Name       = 'MaxThreadsPerQueue'
            Parameters = @{
                Property      = 'MaxThreadsPerQueue'
                ExpectedValue = 20
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'MaxThreadsPerQueue should have the recommended value'
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        Smb2CreditsMin                  = @{
            Enable     = $true
            Name       = 'Smb2CreditsMin'
            Parameters = @{
                Property      = 'Smb2CreditsMin'
                ExpectedValue = 128
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'Smb2CreditsMin should have the recommended value'
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
        Smb2CreditsMax                  = @{
            Enable     = $true
            Name       = 'Smb2CreditsMax'
            Parameters = @{
                Property      = 'Smb2CreditsMax'
                ExpectedValue = 2048
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = 'Smb2CreditsMax should have the recommended value'
                Resolution  = ''
                Importance  = 10
                Resources   = @(
                    'https://github.com/EvotecIT/Testimo/issues/50'
                )
            }
        }
        RequireSecuritySignature        = @{
            Enable     = $true
            Name       = 'SMB v2 Require Security Signature'
            Parameters = @{
                Property      = 'RequireSecuritySignature'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Area        = ''
                Description = ''
                Resolution  = ''
                Importance  = 10
                Resources   = @(

                )
            }
        }
    }
}

<#
AnnounceComment :
AnnounceServer : False
AsynchronousCredits : 64
AuditSmb1Access : False
AutoDisconnectTimeout : 15
AutoShareServer : True
AutoShareWorkstation : True
CachedOpenLimit : 10
DurableHandleV2TimeoutInSeconds : 180
EnableAuthenticateUserSharing : False
EnableDownlevelTimewarp : False
EnableForcedLogoff : True
EnableLeasing : True
EnableMultiChannel : True
EnableOplocks : True
EnableSecuritySignature : False
EnableSMB1Protocol : False
EnableSMB2Protocol : True
EnableStrictNameChecking : True
EncryptData : False
IrpStackSize : 15
KeepAliveTime : 2
MaxChannelPerSession : 32
MaxMpxCount : 50
MaxSessionPerConnection : 16384
MaxThreadsPerQueue : 20
MaxWorkItems : 1
NullSessionPipes :
NullSessionShares :
OplockBreakWait : 35
PendingClientTimeoutInSeconds : 120
RejectUnencryptedAccess : True
RequireSecuritySignature : False
ServerHidden : True
Smb2CreditsMax : 2048
Smb2CreditsMin : 128
SmbServerNameHardeningLevel : 0
TreatHostAsStableStorage : False
ValidateAliasNotCircular : True
ValidateShareScope : True
ValidateShareScopeNotAliased : True
ValidateTargetName : True
 
#>

$SMBShares = @{
    Name   = 'DCSMBShares'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'Default SMB Shares'
        Data           = {
            Get-ComputerSMBShare -ComputerName $DomainController -Translated
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-ComputerSMBShare'
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        AdminShare   = @{
            Enable     = $true
            Name       = 'Remote Admin Share is available'
            Parameters = @{
                WhereObject           = { $_.Name -eq 'ADMIN$' }
                ExpectedCount         = 1
                PropertyExtendedValue = 'Path'
            }
        }
        DefaultShare = @{
            Enable     = $true
            Name       = 'Default Share is available'
            Parameters = @{
                WhereObject           = { $_.Name -eq 'C$' }
                ExpectedCount         = 1
                PropertyExtendedValue = 'Path'
            }
        }
        RemoteIPC    = @{
            Enable     = $true
            Name       = 'Remote IPC Share is available'
            Parameters = @{
                WhereObject           = { $_.Name -eq 'IPC$' }
                ExpectedCount         = 1
                PropertyExtendedValue = 'Path'
            }
        }
        NETLOGON     = @{
            Enable     = $true
            Name       = 'NETLOGON Share is available'
            Parameters = @{
                WhereObject           = { $_.Name -eq 'NETLOGON' }
                ExpectedCount         = 1
                PropertyExtendedValue = 'Path'
            }
        }
        SYSVOL       = @{
            Enable     = $true
            Name       = 'SYSVOL Share is available'
            Parameters = @{
                WhereObject           = { $_.Name -eq 'SYSVOL' }
                ExpectedCount         = 1
                PropertyExtendedValue = 'Path'
            }
        }
    }
}
$SMBSharesPermissions = @{
    Name   = 'DCSMBSharesPermissions'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'Default SMB Shares Permissions'
        Data           = {
            Get-ComputerSMBSharePermissions -ComputerName $DomainController -ShareName 'Netlogon', 'Sysvol' -Translated
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Description = "SMB Shares for Sysvol and Netlogon should be at their defaults. That means 2 permissions for Netlogon and 3 for SysVol."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 3
            Resources   = @(

            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-ComputerSMBSharePermissions'
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        OverallCount                  = @{
            Enable     = $true
            Name       = 'Should only have default number of permissions'
            Parameters = @{
                ExpectedCount = 5
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Description = "SMB Shares for Sysvol and Netlogon should be at their defaults. That means 2 permissions for Netlogon and 3 for SysVol."
                Resolution  = 'Add/Remove unnecessary permissions.'
                Importance  = 5
                Resources   = @(

                )
            }
        }
        NetlogonEveryone              = @{
            Enable      = $true
            Name        = 'Netlogon Share Permissions - Everyone'
            Parameters  = @{
                # NETLOGON share should have Everyone with Read access rights
                WhereObject   = { $_.Name -eq 'NETLOGON' -and $_.AccountSID -eq 'S-1-1-0' }
                ExpectedCount = 1
            }
            Category    = 'Security'
            Description = "SMB Shares for NETLOGON should contain Everyone with Read access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
        NetlogonAdministrators        = @{
            Enable      = $true
            Name        = 'Netlogon Share Permissions - BUILTIN\Administrators'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'NETLOGON' -and $_.AccountSID -eq 'S-1-5-32-544' }
                ExpectedCount = 1
            }
            Category    = 'Security'
            Description = "SMB Shares for NETLOGON should contain BUILTIN\Administrators with Full access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
        SysvolEveryone                = @{
            Enable      = $true
            Name        = 'SysVol Share Permissions - Everyone'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'SYSVOL' -and $_.AccountSID -eq 'S-1-1-0' }
                ExpectedCount = 1
            }
            Category    = 'Security'
            Description = "SMB Shares for SYSVOL should contain Everyone with Read access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
        SysvolAdministrators          = @{
            Enable      = $true
            Name        = 'SysVol Share Permissions - BUILTIN\Administrators'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'SYSVOL' -and $_.AccountSID -eq 'S-1-5-32-544' }
                ExpectedCount = 1
            }
            Category    = 'Security'
            Description = "SMB Shares for SYSVOL should contain BUILTIN\Administrators with Full access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
        SysvolAuthenticatedUsers      = @{
            Enable      = $true
            Name        = 'SysVol Share Permissions - NT AUTHORITY\Authenticated Users'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'SYSVOL' -and $_.AccountSID -eq 'S-1-5-11' }
                ExpectedCount = 1
            }
            Category    = 'Security'
            Description = "SMB Shares for SYSVOL should contain NT AUTHORITY\Authenticated Users with Full access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }

        NetlogonEveryoneValue         = @{
            Enable      = $true
            Name        = 'Netlogon Share Permissions Value - Everyone'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'NETLOGON' -and $_.AccountSID -eq 'S-1-1-0' }
                Property      = 'AccessRight'
                ExpectedValue = 'Read'
                OperationType = 'eq'
            }
            Category    = 'Security'
            Description = "SMB Shares for NETLOGON should contain Everyone with Read access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
        NetlogonAdministratorsValue   = @{
            Enable      = $true
            Name        = 'Netlogon Share Permissions Value - BUILTIN\Administrators'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'NETLOGON' -and $_.AccountSID -eq 'S-1-5-32-544' }
                Property      = 'AccessRight'
                ExpectedValue = 'Full'
                OperationType = 'eq'
            }
            Category    = 'Security'
            Description = "SMB Shares for NETLOGON should contain BUILTIN\Administrators with Full access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
        SysvolEveryoneValue           = @{
            Enable      = $true
            Name        = 'SysVol Share Permissions Value - Everyone'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'SYSVOL' -and $_.AccountSID -eq 'S-1-1-0' }
                Property      = 'AccessRight'
                ExpectedValue = 'Read'
                OperationType = 'eq'
            }
            Category    = 'Security'
            Description = "SMB Shares for SYSVOL should contain Everyone with Read access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
        SysvolAdministratorsValue     = @{
            Enable      = $true
            Name        = 'SysVol Share Permissions Value - BUILTIN\Administrators'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'SYSVOL' -and $_.AccountSID -eq 'S-1-5-32-544' }
                Property      = 'AccessRight'
                ExpectedValue = 'Full'
                OperationType = 'eq'
            }
            Category    = 'Security'
            Description = "SMB Shares for SYSVOL should contain BUILTIN\Administrators with Full access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
        SysvolAuthenticatedUsersValue = @{
            Enable      = $true
            Name        = 'SysVol Share Permissions Value - NT AUTHORITY\Authenticated Users'
            Parameters  = @{
                WhereObject   = { $_.Name -eq 'SYSVOL' -and $_.AccountSID -eq 'S-1-5-11' }
                Property      = 'AccessRight'
                ExpectedValue = 'Full'
                OperationType = 'eq'
            }
            Category    = 'Security'
            Description = "SMB Shares for SYSVOL should contain NT AUTHORITY\Authenticated Users with Full access rights."
            Resolution  = 'Add/Remove unnecessary permissions.'
            Importance  = 5
            Resources   = @(

            )
        }
    }
}

$TimeSettings = [ordered] @{
    Name   = 'DCTimeSettings'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Time Settings"
        Data           = {
            Get-TimeSettings -ComputerName $DomainController -Domain $Domain
        }
        Details        = [ordered] @{
            Category        = 'Configuration'
            Description = ''
            Resolution  = ''
            Importance  = 2
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        NTPServerEnabled           = @{
            Enable     = $true
            Name       = 'NtpServer must be enabled.'
            Parameters = @{
                WhereObject   = { $_.ComputerName -eq $DomainController }
                Property      = 'NtpServerEnabled'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        NTPServerIntervalMissing   = @{
            Enable     = $true
            Name       = 'Ntp Server Interval should be set'
            Parameters = @{
                WhereObject   = { $_.ComputerName -eq $DomainController }
                Property      = 'NtpServerIntervals'
                ExpectedValue = 'Missing'
                OperationType = 'notcontains'
            }
        }
        NTPServerIntervalIncorrect = @{
            Enable     = $true
            Name       = 'Ntp Server Interval should be within known settings'
            Parameters = @{
                WhereObject   = { $_.ComputerName -eq $DomainController }
                Property      = 'NtpServerIntervals'
                ExpectedValue = 'Incorrect'
                OperationType = 'notcontains'
            }
        }
        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 NTP'
            Requirements = @{
                IsPDC = $true
            }
            Parameters   = @{
                WhereObject   = { $_.ComputerName -eq $DomainController }
                Property      = 'NtpType'
                ExpectedValue = 'NTP'
                OperationType = 'eq'

            }
        }
        WindowsSecureTimeSeeding   = [ordered] @{
            Enable     = $true
            Name       = 'Windows Secure Time Seeding should be disabled.'
            Parameters = @{
                WhereObject   = { $_.ComputerName -eq $DomainController }
                Property      = 'WindowsSecureTimeSeeding'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = @{
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
                Resources   = @(
                    '[Windows Secure Time Seeding, should you disable it?](https://www.askwoody.com/forums/topic/windows-secure-time-seeding-should-you-disable-it/)'
                    '[Wrong system time and insecure Secure Time Seeding](https://www.kaspersky.com/blog/windows-system-time-sudden-changes/48956/)'
                    '[How to enable or disable Secure Time Seeding in Windows computers](https://www.thewindowsclub.com/secure-time-seeding-windows-10)'
                    '[Windows feature that resets system clocks based on random data is wreaking havoc](https://arstechnica.com/security/2023/08/windows-feature-that-resets-system-clocks-based-on-random-data-is-wreaking-havoc/3/)'
                )
            }
        }
    }
}
$TimeSynchronizationExternal = @{
    Name               = 'DCTimeSynchronizationExternal'
    Enable             = $true
    Scope              = 'DC'
    Source             = @{
        Name           = "Time Synchronization External"
        Data           = {
            Get-ComputerTime -TimeTarget $DomainController -WarningAction SilentlyContinue -TimeSource $TimeSource
        }
        Parameters     = @{
            TimeSource = 'pool.ntp.org'
        }
        Details        = [ordered] @{
            Area        = ''
            Category    = 'Configuration'
            Description = ''
            Resolution  = ''
            Importance  = 2
            Resources   = @(
                '[How to: Fix Time Sync in your Domain](https://community.spiceworks.com/how_to/166215-fix-time-sync-in-your-domain-use-w32time)'
                '[Windows Time Settings in a Domain](https://www.concurrency.com/blog/october-2018/windows-time-settings-in-a-domain)'
            )
        }
        ExpectedOutput = $true
    }
    Tests              = [ordered] @{
        TimeSynchronizationTest = @{
            Enable     = $true
            Name       = 'Time Difference'
            Details    = [ordered] @{
                Area        = ''
                Category    = 'Configuration'
                Description = ''
                Importance  = 2
                Resources   = @(

                )
            }
            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'
}
$TimeSynchronizationInternal = @{
    Name   = 'DCTimeSynchronizationInternal'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Time Synchronization Internal"
        Data           = {
            Get-ComputerTime -TimeTarget $DomainController -WarningAction SilentlyContinue
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 2
            Resources   = @(
                'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc773263(v=ws.10)#w2k3tr_times_tools_uhlp'
            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        LastBootUpTime          = @{
            Enable     = $true
            Name       = 'Last Boot Up time should be less than X days'
            Parameters = @{
                Property      = 'LastBootUpTime'
                ExpectedValue = '(Get-Date).AddDays(-60)'
                OperationType = 'gt'
            }
        }
        TimeSynchronizationTest = @{
            Enable     = $true
            Name       = 'Time Difference'
            Parameters = @{
                Property              = 'TimeDifferenceSeconds'
                ExpectedValue         = 1
                OperationType         = 'le'
                PropertyExtendedValue = 'TimeDifferenceSeconds'
            }
        }
    }
}

<#
Name LocalDateTime RemoteDateTime InstallTime LastBootUpTime TimeDifferenceMinutes TimeDifferenceSeconds TimeDifferenceMilliseconds TimeSourceName Status
---- ------------- -------------- ----------- -------------- --------------------- --------------------- -------------------------- -------------- ------
AD2 17.09.2019 07:38:57 17.09.2019 07:38:57 30.05.2018 18:30:48 13.09.2019 07:54:10 0,0417166666666667 2,503 2503 AD1.ad.evotec.xyz
AD3 17.09.2019 07:38:56 17.09.2019 02:38:57 26.05.2019 17:30:17 13.09.2019 07:54:09 0,02175 1,305 1305 AD1.ad.evotec.xyz
EVOWin 17.09.2019 07:38:57 17.09.2019 07:38:57 24.05.2019 22:46:45 13.09.2019 07:53:44 0,0415 2,49 2490 AD1.ad.evotec.xyz
#>

$UNCHardenedPaths = @{
    Name   = 'DCUNCHardenedPaths'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Hardened UNC Paths"
        Data           = {
            Get-PSRegistry -RegistryPath "HKLM\SOFTWARE\Policies\Microsoft\Windows\NetworkProvider\HardenedPaths" -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Category        = 'Security'
            Area        = ''
            Description = 'Hardened UNC Paths must be defined to require mutual authentication and integrity for at least the \\*\SYSVOL and \\*\NETLOGON shares.'
            Resolution  = 'Harden UNC Paths for SYSVOL and NETLOGON'
            Importance  = 10
            Resources   = @(
                'https://docs.microsoft.com/en-us/archive/blogs/leesteve/demystifying-the-unc-hardening-dilemma'
                'https://www.stigviewer.com/stig/windows_10/2016-06-24/finding/V-63577'
                'https://support.microsoft.com/en-us/help/3000483/ms15-011-vulnerability-in-group-policy-could-allow-remote-code-executi'
            )
        }
        Requirements   = @{
            CommandAvailable = 'Get-PSRegistry'
        }
        Implementation = {

        }
        Rollback       = {
            Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\NetworkProvider\HardenedPaths" -Name "*"
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        NetLogonUNCPath = @{
            Enable      = $true
            Name        = 'Netlogon UNC Hardening'
            Parameters  = @{
                Property      = '\\*\NETLOGON'
                ExpectedValue = 'RequireMutualAuthentication=1, RequireIntegrity=1', 'RequireMutualAuthentication=1,RequireIntegrity=1'
                OperationType = 'in'
            }
            Description = "Hardened UNC Paths must be defined to require mutual authentication and integrity for at least the \\*\SYSVOL and \\*\NETLOGON shares."
        }
        SysVolUNCPath   = @{
            Enable      = $true
            Name        = 'SysVol UNC Hardening'
            Parameters  = @{
                Property      = '\\*\SYSVOL'
                ExpectedValue = 'RequireMutualAuthentication=1, RequireIntegrity=1', 'RequireMutualAuthentication=1,RequireIntegrity=1'
                OperationType = 'in'
            }
            Description = "Hardened UNC Paths must be defined to require mutual authentication and integrity for at least the \\*\SYSVOL and \\*\NETLOGON shares."
        }
    }
}
$WindowsFeaturesOptional = @{
    Name     = 'DCWindowsFeaturesOptional'
    Enable   = $true
    Scope    = 'DC'
    Source   = @{
        Name           = "Windows Features Optional"
        Data           = {
            $Output = Invoke-Command -ComputerName $DomainController -ErrorAction Stop {
                Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2
            }
            $Output | Select-Object -Property DisplayName, Description, RestartRequired, FeatureName, State
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = 'Windows optional features'
            Importance  = 0
            ActionType  = 0
            Resources   = @(
                "[The Windows PowerShell 2.0 feature must be disabled on the system](https://www.stigviewer.com/stig/windows_10/2017-04-28/finding/V-70637)"
            )
            Tags        = 'Features', 'Configuration'
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests    = [ordered] @{
        # WindowsPowerShellRoot = @{
        # Enable = $true
        # Name = 'Windows PowerShell Root should be disabled'
        # Parameters = @{
        # WhereObject = { $_.FeatureName -eq 'MicrosoftWindowsPowerShellRoot' }
        # Property = 'State'
        # ExpectedValue = 'Disabled'
        # OperationType = 'eq'
        # }
        # Details = @{
        # Description = "Windows PowerShell 2.0 Engine includes the core components from Windows PowerShell 2.0 for backward compatibility with existing Windows PowerShell host applications. Windows PowerShell 5.0 added advanced logging features which can provide additional detail when malware has been run on a system. Disabling the Windows PowerShell 2.0 mitigates against a downgrade attack that evades the Windows PowerShell 5.0 script block logging feature."
        # Tags = 'Backup', 'Configuration'
        # StatusTrue = 1
        # StatusFalse = 4
        # }
        # }
        WindowsPowerShell2 = @{
            Enable     = $true
            Name       = 'Windows PowerShell 2.0 should be disabled'
            Parameters = @{
                WhereObject   = { $_.FeatureName -eq 'MicrosoftWindowsPowerShellV2' }
                Property      = 'State'
                ExpectedValue = 'Disabled'
                OperationType = 'eq'
            }
            Details    = @{
                Category    = 'Configuration'
                Importance  = 8
                ActionType  = 2
                Description = "Windows PowerShell 2.0 Engine includes the core components from Windows PowerShell 2.0 for backward compatibility with existing Windows PowerShell host applications. Windows PowerShell 5.0 added advanced logging features which can provide additional detail when malware has been run on a system. Disabling the Windows PowerShell 2.0 mitigates against a downgrade attack that evades the Windows PowerShell 5.0 script block logging feature."
                Tags        = 'Backup', 'Configuration'
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
    }
    Solution = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Disabling Windows PowerShell 2.0' {
                        New-HTMLText -Text @(
                            "Windows PowerShell 5.0 added advanced logging features which can provide additional detail when malware has been run on a system. Disabling the Windows PowerShell 2.0 mitigates against a downgrade attack that evades the Windows PowerShell 5.0 script block logging feature."
                        )
                        New-HTMLText -Text @(
                            "Run 'Windows PowerShell' with elevated privileges (run as administrator)."
                        )
                        New-HTMLCodeBlock -Style powershell {
                            Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2
                        }

                        New-HTMLText -Text @(
                            "In some cases it may be required to disable MicrosoftWindowsPowerShellRoot. "
                            "Run 'Windows PowerShell' with elevated privileges (run as administrator)."
                        )
                        New-HTMLCodeBlock -Style powershell {
                            Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellRoot
                        }
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}

$WindowsRemoteManagement = @{
    Name   = 'DCWindowsRemoteManagement'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = 'Windows Remote Management'
        Data           = {
            Test-WinRM -ComputerName $DomainController
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        WindowsRemoteManagement = @{
            Enable    = $true
            Name       = 'Test submits an identification request that determines whether the WinRM service is running.'
            Parameters = @{
                Property      = 'Status'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
    }
}
$WindowsRolesAndFeatures = @{
    Name   = 'DCWindowsRolesAndFeatures'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Windows Roles and Features"
        Data           = {
            Get-WindowsFeature -ComputerName $DomainController #| Where-Object { $_.'InstallState' -eq 'Installed' }
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        ActiveDirectoryDomainServices = @{
            Enable     = $true
            Name       = 'Active Directory Domain Services is installed'
            Parameters = @{
                WhereObject   = { $_.Name -eq 'AD-Domain-Services' }
                Property      = 'Installed'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        DNSServer                     = @{
            Enable     = $true
            Name       = 'DNS Server is installed'
            Parameters = @{
                WhereObject   = { $_.Name -eq 'DNS' }
                Property      = 'Installed'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        FileandStorageServices        = @{
            Enable     = $true
            Name       = 'File and Storage Services is installed'
            Parameters = @{
                WhereObject   = { $_.Name -eq 'FileAndStorage-Services' }
                Property      = 'Installed'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        FileandiSCSIServices          = @{
            Enable     = $true
            Name       = 'File and iSCSI Services is installed'
            Parameters = @{
                WhereObject   = { $_.Name -eq 'File-Services' }
                Property      = 'Installed'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        FileServer                    = @{
            Enable     = $true
            Name       = 'File Server is installed'
            Parameters = @{
                WhereObject   = { $_.Name -eq 'FS-FileServer' }
                Property      = 'Installed'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        StorageServices               = @{
            Enable     = $true
            Name       = 'Storage Services is installed'
            Parameters = @{
                WhereObject   = { $_.Name -eq 'Storage-Services' }
                Property      = 'Installed'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
        WindowsPowerShell51           = @{
            Enable     = $true
            Name       = 'Windows PowerShell 5.1 is installed'
            Parameters = @{
                WhereObject   = { $_.Name -eq 'PowerShell' }
                Property      = 'Installed'
                ExpectedValue = $true
                OperationType = 'eq'
            }
        }
    }
}
$WindowsUpdates = @{
    Name   = 'DCWindowsUpdates'
    Enable = $true
    Scope  = 'DC'
    Source = @{
        Name           = "Windows Updates"
        Data           = {
            Get-HotFix -ComputerName $DomainController | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1 -Property HotFixID, InstalledOn, Description, InstalledBy
        }
        Details        = [ordered] @{
            Area        = ''
            Description = ''
            Resolution  = ''
            Importance  = 10
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        WindowsUpdates = @{
            Enable     = $true
            Name       = 'Last Windows Updates should be less than X days ago'
            Parameters = @{
                Property      = 'InstalledOn'
                ExpectedValue = '(Get-Date).AddDays(-60)'
                OperationType = 'gt'
            }
        }
    }
}
$Backup = @{
    Name            = "ForestBackup"
    Enable          = $true
    Scope           = 'Forest'
    Source          = @{
        Name           = 'Forest Backup'
        Data           = {
            Get-WinADLastBackup -Forest $ForestName
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = 'Active Directory is critical system for any company. Having a proper, up to date backup in place is crucial.'
            Importance  = 0
            ActionType  = 0
            Resources   = @(
                '[Backing Up and Restoring an Active Directory Server](https://docs.microsoft.com/en-us/windows/win32/ad/backing-up-and-restoring-an-active-directory-server)'
                '[Backup Active Directory (Full and Incremental Backup)](https://activedirectorypro.com/backup-active-directory/)'
            )
            Tags        = 'Backup', 'Configuration'
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        LastBackupTests = @{
            Enable     = $true
            Name       = 'Forest Last Backup Time'
            Parameters = @{
                ExpectedValue         = 2
                OperationType         = 'lt'
                Property              = 'LastBackupDaysAgo'
                PropertyExtendedValue = 'LastBackup'
                OverwriteName         = { "Last Backup $($_.NamingContext)" }
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                'Active Directory is critical system for any company. Having a proper, up to date backup is crucial. '
                'Last backup should be maximum few days old, if not less than 24 hours old. '
                "Please keep in mind that this test doesn't verifies the backup, nor provides information if the backup was saved to proper place and will be available for restore operations. "
                "This tests merely checks what was reported by Active Directory - that backup did happen. "
                "You should make sure that your backup, and more importantly restore process actually works! "
            )
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'LastBackupDaysAgo' -ComparisonType number -BackgroundColor PaleGreen -Value 2 -Operator lt
        New-HTMLTableCondition -Name 'LastBackupDaysAgo' -ComparisonType number -BackgroundColor Salmon -Value 2 -Operator ge
        New-HTMLTableCondition -Name 'LastBackupDaysAgo' -ComparisonType number -BackgroundColor Tomato -Value 10 -Operator ge
    }
}
$DuplicateSPN = @{
    Name            = 'ForestDuplicateSPN'
    Enable          = $true
    Scope           = 'Forest'
    Source          = @{
        Name           = 'Duplicate SPN'
        Data           = {
            Get-WinADDuplicateSPN -Forest $ForestName
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Description = "SPNs must be unique, so if an SPN already exists for a service on a server then you must delete the SPN that is is already registered to one account and recreate the SPN registered to the correct account."
            Importance  = 5
            ActionType  = 1
            Resources   = @(
                "[Duplicate SPN found - Troubleshooting Duplicate SPNs](https://support.squaredup.com/hc/en-us/articles/4406616176657-Duplicate-SPN-found-Troubleshooting-Duplicate-SPNs)"
                "[SPN and UPN uniqueness](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/spn-and-upn-uniqueness)"
                "[Name Formats for Unique SPNs](https://docs.microsoft.com/en-us/windows/win32/ad/name-formats-for-unique-spns)"
                "[Kerberos - duplicate SPNs](https://itworldjd.wordpress.com/2017/02/15/kerberos-duplicate-spns/)"
            )
            StatusTrue  = 0
            StatusFalse = 5
        }
        ExpectedOutput = $false
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                'Services use service publication in the Active Directory directory service to provide information about themselves in the directory for easy discovery by client applications and other services. '
                'Service publication occurs when the installation program for a service publishes information about the service, including binding and keyword data, to the directory. '
                'Service publication happens by creating service objects (also called connection point objects) in Active Directory. '
            )
            New-HTMLText -Text @(
                'In addition, Active Directory supports service principal names (SPNs) as a means by which client applications can identify '
                'and authenticate the services that they use. Service authentication happens through Kerberos authentication of SPNs. '
                'Kerberos uses SPNs extensively. When a Kerberos client uses its TGT to request a service ticket for a specific service, the service uses SPN to identify it. '
                'The KDC will grant the client a service ticket that is encrypted in part with a shared secret '
                'that the service account identified by the AD account matches the SPN has (basically the account password). '
            )
            New-HTMLText -Text @(
                'In the case of a duplicate SPN, what can happen is that the KDC will generate a service ticket that may base its shared secret on the wrong account. '
                'Then, when the client provides that ticket to the service during authentication, the service itself cannot decrypt it, and the authentication fails. '
                'The server will typically log an "AP Modified" error, and the client will see a "wrong principal" error code.'
            )
        }
    }
    DataInformation = {
        New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
        New-HTMLList {
            New-HTMLListItem -FontWeight bold, normal -Text "IsOrphaned", " - means object contains AdminCount set to 1, while not being a critical object or direct or indirect member of any critical system groups. "
            New-HTMLListItem -FontWeight bold, normal -Text "IsMember", " - means object is memberof (direct or indirect) of critical system groups. "
            New-HTMLListItem -FontWeight bold, normal -Text "IsCriticalSystemObject", " - means object is critical system object. "
        } -FontSize 10pt
    }
    DataHighlights  = {
        # New-HTMLTableCondition -Name 'IsOrphaned' -ComparisonType string -BackgroundColor Salmon -Value $true
        # New-HTMLTableCondition -Name 'IsOrphaned' -ComparisonType string -BackgroundColor PaleGreen -Value $false
        # New-HTMLTableCondition -Name 'IsCriticalSystemObject' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        # New-HTMLTableCondition -Name 'IsCriticalSystemObject' -ComparisonType string -BackgroundColor TangerineYellow -Value $false
    }
}
$ForestConfigurationPartitionOwners = @{
    Name           = 'ForestConfigurationPartitionOwners'
    Enable         = $true
    Scope          = 'Forest'
    Source         = @{
        Name           = "Configuration Partitions: Owners"
        Data           = {
            Get-WinADACLConfiguration -Forest $ForestName -Owner -ObjectType site, subnet, siteLink
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Severity    = ''
            Importance  = 5
            Description = "The configuration partition contains replication topology and other configuration data that must be replicated throughout the forest. Every domain controller in the forest has a replica of the same configuration partition. Just like schema partition, there is just one master configuration partition per forest and a second one on all DCs in a forest. It contains the forest-wide active directory topology including DCs, sites, services, subnets and sitelinks. It is replicated to all DCs in a forest. Owners of Active Directory Configuration Partition, and more specifically Sites, Subnets and Sitelinks should always be set to Administrative (Domain Admins / Enterprise Admins). Being an owner of a site, subnet or sitelink is potentially dangerous and can lead to domain compromise. In comparison to ForestConfigurationPartitionOwnersContainers this test focuses only on chosen object types and nothing else. If there are issues reported in this test you may consider running Testimo with ForestConfigurationPartitionOwnersContainers check to verify if everything is as required. "
            Resources   = @(
                '[Escalating privileges with ACLs in Active Directory](https://blog.fox-it.com/2018/04/26/escalating-privileges-with-acls-in-active-directory/)'
                '[Site Topology Owner Role](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/site-topology-owner-role)'
            )
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests          = [ordered] @{
        SiteOwners     = @{
            Enable     = $true
            Name       = 'Site Owners should be Administrative'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.ObjectType -eq 'Site' -and $_.OwnerType -ne 'Administrative' }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
        SubnetOwners   = @{
            Enable     = $true
            Name       = 'Subnet Owners should be Administrative'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.ObjectType -eq 'Subnet' -and $_.OwnerType -ne 'Administrative' }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
        SiteLinkOwners = @{
            Enable     = $true
            Name       = 'SiteLink Owners should be Administrative'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.ObjectType -eq 'SiteLink' -and $_.OwnerType -ne 'Administrative' }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
    }
    DataHighlights = {
        New-HTMLTableCondition -Name 'OwnerType' -ComparisonType string -BackgroundColor Salmon -Value 'Administrative' -Operator ne -Row
        New-HTMLTableCondition -Name 'OwnerType' -ComparisonType string -BackgroundColor PaleGreen -Value 'Administrative' -Operator eq -Row
    }
    Solution       = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Prepare environment' {
                        New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery."
                        New-HTMLCodeBlock -Code {
                            Install-Module ADEssentials -Force
                            Import-Module ADEssentials -Force
                        } -Style powershell
                        New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step."
                    }
                    New-HTMLWizardStep -Name 'Prepare report' {
                        New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing owners. To generate new report please use:"
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoBefore.ForestConfigurationPartitionOwners.html -Type ForestConfigurationPartitionOwners
                        }
                        New-HTMLText -Text @(
                            "When executed it will take a while to generate all data and provide you with new report depending on size of environment."
                            "Once confirmed that data is still showing issues and requires fixing please proceed with next step."
                        )
                        New-HTMLText -Text "Alternatively if you prefer working with console you can run: "
                        New-HTMLCodeBlock -Code {
                            $Output = Get-WinADACLConfiguration -ObjectType site, subnet, siteLink -Owner -Verbose
                            $Output | Format-Table # do your actions as desired
                        }
                        New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you."
                    }
                    New-HTMLWizardStep -Name 'Fix AD Partition Configuration Owners' {
                        New-HTMLText -Text @(
                            "Configuration partition contains important AD Objects. Those are among other objects Subnets, Sites and SiteLinks. "
                            "Those objects should have proper owners which usually means being owned by Domain Admins/Enterprise Admins or at some cases by NT AUTHORITY\SYSTEM account. "
                            "Following command when executed fixes owners of those types. "
                            "If the object has proper owner, the owner change is skipped. "
                            "It makes sure each critical AD Object is owned Administrative or WellKnownAdministrative account. "
                            "Make sure when running it for the first time to run it with ",
                            "WhatIf",
                            " parameter as shown below to prevent accidental overwrite."
                        ) -FontWeight normal, normal, normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Black, Black, Red, Black
                        New-HTMLCodeBlock -Code {
                            Repair-WinADACLConfigurationOwner -ObjectType site, siteLink, subnet -Verbose -WhatIf -LimitProcessing 2
                        }
                        New-HTMLText -TextBlock {
                            "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be fixed matches expected data. Once happy with results please follow with command: "
                        }
                        New-HTMLCodeBlock -Code {
                            Repair-WinADACLConfigurationOwner -ObjectType site, siteLink, subnet -Verbose -WhatIf
                        }
                        New-HTMLText -TextBlock {
                            "This command when executed repairs only first X object owners. Use LimitProcessing parameter to prevent mass fixing and increase the counter when no errors occur. "
                            "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. "
                        }
                    }
                    New-HTMLWizardStep -Name 'Verification report' {
                        New-HTMLText -TextBlock {
                            "Once cleanup task was executed properly, we need to verify that report now shows no problems."
                        }
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoAfter.ForestConfigurationPartitionOwners.html -Type ForestConfigurationPartitionOwners
                        }
                        New-HTMLText -TextBlock {
                            "If there were issues reported by this test you may consider running additional test "
                            "ForestConfigurationPartitionOwnersContainers "
                            "which focuses on whole containers rather than just specific objects. "
                            "This is to make sure most of configuration partition is as expected when it comes to object owners."
                        } -FontWeight normal, bold, normal, normal -Color Black, Amaranth, Black, Black
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoAfter.ForestConfigurationPartitionOwnersContainers.html -Type ForestConfigurationPartitionOwnersContainers
                        }
                        New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$ForestConfigurationPartitionOwnersContainers = @{
    # This test is disabled by default because
    Name           = 'ForestConfigurationPartitionOwnersContainers'
    Enable         = $false
    Scope          = 'Forest'
    Source         = @{
        Name           = "Configuration Partitions: Container Owners"
        Data           = {
            Get-WinADACLConfiguration -Forest $ForestName -Owner -ContainerType site, subnet, siteLink
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Importance  = 5
            Description = "The configuration partition contains replication topology and other configuration data that must be replicated throughout the forest. Every domain controller in the forest has a replica of the same configuration partition. Just like schema partition, there is just one master configuration partition per forest and a second one on all DCs in a forest. It contains the forest-wide active directory topology including DCs, sites, services, subnets and sitelinks. It is replicated to all DCs in a forest. Owners of Active Directory Configuration Partition, and more specifically Sites, Subnets and Sitelinks should always be set to Administrative (Domain Admins / Enterprise Admins). Being an owner of a site, subnet or sitelink is potentially dangerous and can lead to domain compromise. While ForestConfigurationPartitionOwners test checks only specific objects for ownership this test checks all objects within specific containers. This means every single object is required to have proper membership. "
            Resources   = @(
                '[Escalating privileges with ACLs in Active Directory](https://blog.fox-it.com/2018/04/26/escalating-privileges-with-acls-in-active-directory/)'
            )
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests          = [ordered] @{
        SiteOwners     = @{
            Enable     = $true
            Name       = 'Site Container Owners should be Administrative or WellKnownAdministrative'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.ObjectType -eq 'Site' -and $_.OwnerType -notin 'Administrative', 'WellKnownAdministrative' }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
        SubnetOwners   = @{
            Enable     = $true
            Name       = 'Subnet Container Owners should be Administrative or WellKnownAdministrative'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.ObjectType -eq 'Subnet' -and $_.OwnerType -notin 'Administrative', 'WellKnownAdministrative' }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
        SiteLinkOwners = @{
            Enable     = $true
            Name       = 'SiteLink Container Owners should be Administrative or WellKnownAdministrative'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.ObjectType -eq 'SiteLink' -and $_.OwnerType -notin 'Administrative', 'WellKnownAdministrative' }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 5
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
    }
    DataHighlights = {
        New-HTMLTableCondition -Name 'OwnerType' -ComparisonType string -BackgroundColor Salmon -Value 'Administrative' -Operator ne -Row
        New-HTMLTableCondition -Name 'OwnerType' -ComparisonType string -BackgroundColor PaleGreen -Value 'Administrative' -Operator eq -Row
        New-HTMLTableCondition -Name 'OwnerType' -ComparisonType string -BackgroundColor PaleGreen -Value 'WellKnownAdministrative' -Operator eq -Row
    }
    Solution       = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Prepare environment' {
                        New-HTMLText -Text "To be able to execute actions in automated way please install required modules. Those modules will be installed straight from Microsoft PowerShell Gallery."
                        New-HTMLCodeBlock -Code {
                            Install-Module ADEssentials -Force
                            Import-Module ADEssentials -Force
                        } -Style powershell
                        New-HTMLText -Text "Using force makes sure newest version is downloaded from PowerShellGallery regardless of what is currently installed. Once installed you're ready for next step."
                    }
                    New-HTMLWizardStep -Name 'Prepare report' {
                        New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding fixing owners. To generate new report please use:"
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoBefore.ForestConfigurationPartitionOwnersContainers.html -Type ForestConfigurationPartitionOwnersContainers
                        }
                        New-HTMLText -Text @(
                            "When executed it will take a while to generate all data and provide you with new report depending on size of environment."
                            "Once confirmed that data is still showing issues and requires fixing please proceed with next step."
                        )
                        New-HTMLText -Text "Alternatively if you prefer working with console you can run: "
                        New-HTMLCodeBlock -Code {
                            $Output = Get-WinADACLConfiguration -ContainerType site, subnet, siteLink -Owner -Verbose
                            $Output | Format-Table # do your actions as desired
                        }
                        New-HTMLText -Text "It provides same data as you see in table above just doesn't prettify it for you."
                    }
                    New-HTMLWizardStep -Name 'Fix AD Partition Configuration (Containers) Owners' {
                        New-HTMLText -Text @(
                            "Configuration partition contains important AD Objects. Those are among other objects Subnets, Sites and SiteLinks. "
                            "Those objects should have proper owners which usually means being owned by Domain Admins/Enterprise Admins or at some cases by NT AUTHORITY\SYSTEM account. "
                            "Following command when executed fixes owners of those types. "
                            "If the object has proper owner, the owner change is skipped. "
                            "It makes sure each critical AD Object is owned Administrative or WellKnownAdministrative account. "
                            "Make sure when running it for the first time to run it with ",
                            "WhatIf",
                            " parameter as shown below to prevent accidental overwrite."
                        ) -FontWeight normal, normal, normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Black, Black, Red, Black
                        New-HTMLText -Text "Make sure to fill in TargetDomain to match your Domain Admin permission account"

                        New-HTMLCodeBlock -Code {
                            Repair-WinADACLConfigurationOwner -ContainerType site, siteLink, subnet -Verbose -WhatIf -LimitProcessing 2
                        }
                        New-HTMLText -TextBlock {
                            "After execution please make sure there are no errors, make sure to review provided output, and confirm that what is about to be fixed matches expected data. Once happy with results please follow with command: "
                        }
                        New-HTMLCodeBlock -Code {
                            Repair-WinADACLConfigurationOwner -ContainerType site, siteLink, subnet -Verbose -WhatIf
                        }
                        New-HTMLText -TextBlock {
                            "This command when executed repairs only first X object owners. Use LimitProcessing parameter to prevent mass fixing and increase the counter when no errors occur. "
                            "Repeat step above as much as needed increasing LimitProcessing count till there's nothing left. In case of any issues please review and action accordingly. "
                        }
                    }
                    New-HTMLWizardStep -Name 'Verification report' {
                        New-HTMLText -TextBlock {
                            "Once cleanup task was executed properly, we need to verify that report now shows no problems."
                        }
                        New-HTMLCodeBlock -Code {
                            Invoke-Testimo -FilePath $Env:UserProfile\Desktop\TestimoAfter.ForestConfigurationPartitionOwnersContainers.html -Type ForestConfigurationPartitionOwnersContainers
                        }
                        New-HTMLText -Text "If everything is healthy in the report you're done! Enjoy rest of the day!" -Color BlueDiamond
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$ForestDHCP = @{
    Name            = "ForestDHCP"
    Enable          = $true
    Scope           = 'Forest'
    Source          = @{
        Name           = 'Forest DHCP'
        Data           = {
            Get-WinADDHCP
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = 'DHCP is important part of any network. Having DHCP registered in AD makes sure that all computers properly register themselves in DNS automatically. However, while tempting to put DHCP on the very same server with AD and DNS it should be hosted separately. The DHCP Server service performs TCP/IP configuration for DHCP clients, including dynamic assignments of IP addresses, specification of DNS servers, and connection-specific DNS names. Domain controllers do not require the DHCP Server service to operate and for higher security and server hardening it is recommended not to install the DHCP Server role on domain controllers.'
            Importance  = 0
            ActionType  = 0
            Resources   = @(
                "[Disable or remove the DHCP Server service installed on any domain controllers](https://learn.microsoft.com/en-us/services-hub/microsoft-engage-center/health/remediation-steps-ad/disable-or-remove-the-dhcp-server-service-installed-on-any-domain-controllers)"
            )
            Tags        = 'DHCP', 'Configuration'
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $null
    }
    Tests           = [ordered] @{
        DHCPonDC          = @{
            Enable     = $true
            Name       = 'DHCP on Domain Controller'
            Parameters = @{
                WhereObject   = { $_.IsDC -eq $true }
                ExpectedCount = 0
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
        DHCPResolvesInDNS = @{
            Enable     = $true
            Name       = 'DHCP Resolves in DNS'
            Parameters = @{
                WhereObject   = { $_.IsInDNS -eq $false }
                ExpectedCount = 0
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                'DHCP is important part of any network. Having DHCP registered in AD makes sure that all computers properly register themselves in DNS automatically. '
                'However, while tempting to put DHCP on the very same server with AD and DNS it should be hosted separately. '
                "The DHCP Server service performs TCP/IP configuration for DHCP clients, including dynamic assignments of IP addresses, "
                "specification of DNS servers, and connection-specific DNS names. "
                "Domain controllers do not require the DHCP Server service to operate and for higher security and server hardening it is recommended not to install the DHCP Server role on domain controllers."
            )
            New-HTMLText -LineBreak
            New-HTMLText -Text @(
                'This test verifies that DHCP registed servers are registred in DNS (aka NOT DEAD) and that DHCP is not hosted on a Domain Controller.'
            )
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'IsInDNS' -ComparisonType bool -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'IsDC' -ComparisonType bool -BackgroundColor PaleGreen -Value $false -Operator eq -FailBackgroundColor Salmon
    }
    Solution        = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Prepare DHCP service removal' {
                        New-HTMLText -Text @(
                            "The DHCP Server service performs TCP/IP configuration for DHCP clients, including dynamic assignments of IP addresses, "
                            "specification of DNS servers, and connection-specific DNS names. "
                            "Domain controllers do not require the DHCP Server service to operate and for higher security and server hardening it is recommended not to install the DHCP Server role on domain controllers."
                        )
                        New-HTMLText -LineBreak
                        New-HTMLText -Text "Prepare & send communication about DHCP removal."
                    }
                    New-HTMLWizardStep -Name 'Move DHCP service' {
                        New-HTMLText -Text @(
                            "Please make sure before removing DHCP service that you first take care of moving DHCP service to a different server/device. "
                            "Please use proper SOP that's approved for your environment!"
                        )
                    }
                    New-HTMLWizardStep -Name 'Remove DHCP service from Domain Controller' {
                        New-HTMLText -Text "Following steps give a brief overview on steps required to disable and remove DHCP service. Please make sure you follow proper SOP as depending on Windows version and environment the steps may be different."

                        New-HTMLList {
                            New-HTMLListItem -Text 'Stop the DHCP Server service and disable it'
                            New-HTMLListItem -Text 'Click Start, type Run, type services.msc, and then click OK.'
                            New-HTMLListItem -Text 'In the list of services, look for a service titled DHCP Server.'
                            New-HTMLListItem -Text 'If it exists, double-click DHCP Server.'
                            New-HTMLListItem -Text 'On the General tab, under Startup type, select Disabled.'
                            New-HTMLListItem -Text "If the Service status says ‘Running’, click Stop."
                            New-HTMLListItem -Text 'Click OK.'
                        }

                        New-HTMLText -Text 'If there are no issues after disabling DHCP - remove DHCP Service in the Server Manager.'

                        New-HTMLList {
                            New-HTMLListItem -Text 'In the Server Manager, click Manage, and then click Remove Roles and Features.'
                            New-HTMLListItem -Text 'Click Next.'
                            New-HTMLListItem -Text 'Select the local server, and click Next.'
                            New-HTMLListItem -Text 'On the Remove server roles page, uncheck the checkbox for DHCP Server.'
                            New-HTMLListItem -Text 'Click Remove Features, then click Next.'
                            New-HTMLListItem -Text 'On the Remove features page, click Next.'
                            New-HTMLListItem -Text 'Click Remove.'
                            New-HTMLListItem -Text 'When the removal is complete, click Close.'
                        }

                        New-HTMLText -Text 'Repeat these steps for all affected domain controllers.'
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}

$ForestFSMORoles = @{
    Name   = 'ForestRoles'
    Enable = $true
    Scope  = 'Forest'
    Source = @{
        Name           = 'Roles availability'
        Data           = {
            Test-ADRolesAvailability -Forest $ForestName
        }
        Details        = [ordered] @{
            Category    = 'Health'
            Description = ''
            Resolution  = ''
            Importance  = 0
            ActionType  = 0
            Severity    = 'High'
            Resources   = @(

            )
            StatusTrue  = 0
            StatusFalse = 2
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        SchemaMasterAvailability       = @{
            Enable     = $true
            Name       = 'Schema Master Availability'
            Parameters = @{
                ExpectedValue         = $true
                Property              = 'SchemaMasterAvailability'
                OperationType         = 'eq'
                PropertyExtendedValue = 'SchemaMaster'
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 10
            }
        }
        DomainNamingMasterAvailability = @{
            Enable     = $true
            Name       = 'Domain Master Availability'
            Parameters = @{
                ExpectedValue         = $true
                Property              = 'DomainNamingMasterAvailability'
                OperationType         = 'eq'
                PropertyExtendedValue = 'DomainNamingMaster'
            }
            Details    = [ordered] @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 10
            }
        }
    }
}
$ForestSubnets = @{
    Name            = 'ForestSubnets'
    Enable          = $true
    Scope           = 'Forest'
    Source          = [ordered] @{
        Name           = 'Subnets verification'
        Data           = {
            Get-WinADForestSubnet -VerifyOverlap -Forest $ForestName
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = "AD subnets are used so that a machine can work out which AD site they should be in. If you have a subnet that hasn’t been defined to Active Directory, any machines will have difficulty identifying which AD site they should be in. This can easily lead to them authenticating against a domain controller that’s inappropriate from a network standpoint, which will cause a poor logon experience for those users."
            Importance  = 3
            ActionType  = 1
            Resources   = @(
                "[Configuring Active Directory Sites and Subnets](https://theitbros.com/active-directory-sites-and-subnets/)"
                "[How to Create an Active Directory Subnet/Site with /32 or /128 and Why](https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/how-to-create-an-active-directory-subnet-site-with-32-or-128-and/ba-p/256105)"
                "[Active Directory subnets, sites, and site links](https://www.windows-active-directory.com/active-directory-subnets-sites-and-site-links.html)"
                "[Chapter 16. Managing sites and subnets](https://livebook.manning.com/book/learn-active-directory-management-in-a-month-of-lunches/chapter-16/44)"
            )
            StatusTrue  = 1
            StatusFalse = 2
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        SubnetsWithoutSites = @{
            Enable      = $true
            Name        = 'Subnets without Sites'
            Description = 'Verify each subnet is attached to a site'
            Parameters  = @{
                WhereObject   = { $_.SiteStatus -eq $false }
                ExpectedCount = 0
            }
            Details     = [ordered] @{
                Category    = 'Configuration'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
        SubnetsOverlapping  = @{
            Enable     = $true
            Name       = 'Subnets overlapping'
            Parameters = @{
                WhereObject   = { $_.Overlap -eq $true }
                ExpectedCount = 0
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                "AD subnets are used so that a machine can work out which AD site they should be in. "
                "If you have a subnet that hasn’t been defined to Active Directory, any machines will have difficulty identifying which AD site they should be in. "
                "This can easily lead to them authenticating against a domain controller that’s inappropriate from a network standpoint, which will cause a poor logon experience for those users. "
                "There are 3 stages to this test: "
            )
            New-HTMLList {
                New-HTMLListItem -Text "Gather data about subnets. It should return at least one subnet to pass a test. "
                New-HTMLListItem -Text "Find subnets that are not attached to any sites. "
                New-HTMLListItem -Text "Find subnets that are overlapping with other subnets. "
            }
            New-HTMLText -Text @(
                "All three tests are required to pass for properly configured Active Directory. "
            )
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'SiteStatus' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
        #New-HTMLTableCondition -Name 'SiteStatus' -ComparisonType string -BackgroundColor Salmon -Value $false -Operator eq
        New-HTMLTableCondition -Name 'Overlap' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq
        New-HTMLTableCondition -Name 'Overlap' -ComparisonType string -BackgroundColor Salmon -Value $true -Operator eq
    }
    DataInformation = {
        New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
        New-HTMLList {
            New-HTMLListItem -FontWeight bold, normal -Text "SiteStatus", " - means subnet is assigned to a site. If it's false that means subnet is orphaned and it should be reassigned to proper site or deleted. "
            New-HTMLListItem -FontWeight bold, normal -Text "Overlap", " - means subnet is overlapping with other subnets which are shown in OverLapList column. This needs to be resolved by working with Network Team. "
        } -FontSize 10pt

        New-HTMLText -Text "Please keep in mind that overlapping is only assesed for IPv4. IPv6 is not assed. Site Status however works as expected for IPv6 as well." -FontSize 10pt
    }
    Solution        = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Investigate Subnets without Sites' {
                        New-HTMLText -Text @(
                            "Subnets without sites are pretty uncommon. "
                            "This usually happens if site is deleted while the subnets are still attached to it. "
                            "Subnets without sites have no use. "
                            ""
                            "Please move subnet to proper site, or if it's no longer needed, remove it totally. "
                        )
                    }
                    New-HTMLWizardStep -Name 'Investigate Subnets overlapping' {
                        New-HTMLText -Text @(
                            "Subnets are supposed to be unique across forest. "
                            "You can assign only one subnet to only one site. "
                            "However it's possible to define subnets that overlap already defined subnets such as 10.0.0.0/8 will overlap with 10.0.20.32/32. "
                            "This shouldn't happen as it will influence authentication process and cause poor logon experience. "
                            ""
                            "Investigate why subnets are added with overlap and fix it. "
                            "Please make sure to consult it with appriopriate people or/and network team. "
                        ) -FontWeight normal, bold, normal, normal, normal, normal, bold, bold
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$OptionalFeatures = [ordered] @{
    Name            = "ForestOptionalFeatures"
    Enable          = $true
    Scope           = 'Forest'
    Source          = [ordered] @{
        Name           = 'Optional Features'
        Data           = {
            Get-WinADForestOptionalFeatures
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = "Verifies availability of Recycle Bin, LAPS and PAM in the Active Directory Forest."
            Importance  = 0
            ActionType  = 0
            Resources   = @(

            )
            StatusTrue  = 0
            StatusFalse = 5
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        RecycleBinEnabled    = @{
            Enable     = $true
            Name       = 'Recycle Bin Enabled'
            Parameters = @{
                Property      = 'Recycle Bin Enabled'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Description = "The AD Recycle bin allows you to quickly restore deleted objects without the need of a system state or 3rd party backup. The recycle bin feature preserves all link valued and non link valued attributes. This means that a restored object will retain all it's settings when restored."
                Importance  = 5
                ActionType  = 2
                Resources   = @(
                    '[How to Enable Active Directory Recycle Bin (Server 2016)](https://activedirectorypro.com/enable-active-directory-recycle-bin-server-2016/)'
                )
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        LapsAvailable        = @{
            Enable     = $true
            Name       = 'LAPS Schema Extended'
            Parameters = @{
                Property      = 'LAPS Enabled'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Description = "Microsoft Local Administrator Password Solution (LAPS) is a password manager that utilizes Active Directory to manage and rotate passwords for local Administrator accounts across all of your Windows endpoints. LAPS is a great mitigation tool against lateral movement and privilege escalation, by forcing all local Administrator accounts to have unique, complex passwords, so an attacker compromising one local Administrator account can’t move laterally to other endpoints and accounts that may share that same password."
                Importance  = 10
                ActionType  = 2
                Resources   = @(
                    '[Running LAPS in the race to security](https://blog.stealthbits.com/running-laps-in-the-race-to-security/)'
                    '[Lithnet LAPS Web App](https://github.com/lithnet/laps-web)'
                    '[Lithnet Access Manager](https://github.com/lithnet/access-manager)'
                    '[Getting Bitlocker and LAPS summary report with PowerShell](https://evotec.xyz/getting-bitlocker-and-laps-summary-report-with-powershell/)'
                    '[Backing up Bitlocker Keys and LAPS passwords from Active Directory](https://evotec.xyz/backing-up-bitlocker-keys-and-laps-passwords-from-active-directory/)'
                )
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        WindowsLapsAvailable = @{
            Enable     = $true
            Name       = 'Windows LAPS Schema Extended'
            Parameters = @{
                Property      = 'Windows LAPS Enabled'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Description = "Microsoft Local Administrator Password Solution (LAPS) is a password manager that utilizes Active Directory to manage and rotate passwords for local Administrator accounts across all of your Windows endpoints. LAPS is a great mitigation tool against lateral movement and privilege escalation, by forcing all local Administrator accounts to have unique, complex passwords, so an attacker compromising one local Administrator account can’t move laterally to other endpoints and accounts that may share that same password."
                Importance  = 10
                ActionType  = 2
                Resources   = @(
                    '[LAPS is now integrated into Windows](https://borncity.com/win/2023/04/19/windows-server-update-your-active-directory-schema-for-the-current-windows-laps-version/)'
                )
                StatusTrue  = 1
                StatusFalse = 4
            }
        }
        PrivAccessManagement = @{
            Enable     = $true
            Name       = 'Privileged Access Management Enabled'
            Parameters = @{
                Property      = 'Privileged Access Management Feature Enabled'
                ExpectedValue = $true
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Description = "Privileged Access Management (PAM) is a solution that helps organizations restrict privileged access within an existing Active Directory environment. Consider introducing PAM to your environment."
                Importance  = 5
                ActionType  = 0
                Resources   = @(
                    '[Privileged Access Management for Active Directory Domain Services](https://docs.microsoft.com/en-us/microsoft-identity-manager/pam/privileged-identity-management-for-active-directory-domain-services)'
                )
                StatusTrue  = 1
                StatusFalse = 0
            }
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                "Following test verifies availability of Recycle Bin, LAPS and PAM in the Active Directory Forest. "
                "While LAPS and RecycleBin are quite critical for properly functioning Active Directory, PAM is just a recommendation and is not so easy to implement. "
                "Therefore only 2 out of 3 tests are considered critical. PAM test is optional. "
            )
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'Value' -ComparisonType string -BackgroundColor PaleGreen -Value $true -Operator eq
        New-HTMLTableCondition -Name 'Value' -ComparisonType string -BackgroundColor Salmon -Value $false -Operator eq
    }
}
$OrphanedAdmins = @{
    Name            = "ForestOrphanedAdmins"
    Enable          = $true
    Scope           = 'Forest'
    Source          = @{
        Name           = 'Orphaned Administrative Objects (AdminCount)'
        Data           = {
            Get-WinADPrivilegedObjects -Forest $ForestName
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Description = "Active Directory user, group, and computer objects possess an AdminCount attribute. The AdminCount attribute’s value defaults to NOT SET. Its utility comes from the fact when a user, group, or computer is added, either directly or transitively, to any of a specific set of protected groups its value is updated to 1. This can provide a relatively simple method by which objects with inherited administrative privileges may be identified. Consider this: a user is stamped with an AdminCount of 1, as a result of being added to Domain Admins; the user is removed from Domain Admins; the AdminCount value persists. In this instance the user is considered as orphaned. The ramifications? The AdminSDHolder ACL will be stamped upon this user every hour to protect against tampering. In turn, this can cause unexpected issues with delegation and application permissions."
            Importance  = 4
            ActionType  = 1
            Resources   = @(
                '[Security Focus: Orphaned AdminCount -eq 1 AD Users](https://blogs.technet.microsoft.com/poshchap/2016/07/29/security-focus-orphaned-admincount-eq-1-ad-users/)'
                "[Fun with Active Directory's AdminCount Attiribute](https://stealthbits.com/blog/fun-with-active-directorys-admincount-attribute/)"
                '[AdminSDHolder, Protected Groups and SDPROP](https://technet.microsoft.com/en-us/magazine/2009.09.sdadminholder.aspx)'
                '[Scanning for Active Directory Privileges & Privileged Accounts](https://adsecurity.org/?p=3658)'
                'https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn535495(v=ws.11)'
            )
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        Enabled = @{
            Enable     = $true
            Name       = 'No orphaned AdminCount'
            Parameters = @{
                ExpectedCount = 0
                OperationType = 'eq'
                WhereObject   = { $_.IsOrphaned -ne $false }
            }
            Details    = [ordered] @{
                Category    = 'Security'
                Importance  = 4
                StatusTrue  = 1
                StatusFalse = 2
            }
        }
    }
    DataInformation = {
        New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
        New-HTMLList {
            New-HTMLListItem -FontWeight bold, normal -Text "IsOrphaned", " - means object contains AdminCount set to 1, while not being a critical object or direct or indirect member of any critical system groups. "
            New-HTMLListItem -FontWeight bold, normal -Text "IsMember", " - means object is memberof (direct or indirect) of critical system groups. "
            New-HTMLListItem -FontWeight bold, normal -Text "IsCriticalSystemObject", " - means object is critical system object. "
        } -FontSize 10pt
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'IsOrphaned' -ComparisonType string -BackgroundColor Salmon -Value $true
        New-HTMLTableCondition -Name 'IsOrphaned' -ComparisonType string -BackgroundColor PaleGreen -Value $false
        New-HTMLTableCondition -Name 'IsCriticalSystemObject' -ComparisonType string -BackgroundColor PaleGreen -Value $true
        New-HTMLTableCondition -Name 'IsCriticalSystemObject' -ComparisonType string -BackgroundColor TangerineYellow -Value $false
    }
}
$Replication = @{
    Name   = "ForestReplication"
    Enable = $true
    Scope  = 'Forest'
    Source = @{
        Name           = 'Forest Replication'
        Data           = {
            Get-WinADForestReplication -WarningAction SilentlyContinue -Forest $ForestName
        }
        Details        = [ordered] @{
            Category    = 'Health'
            Description = ''
            Importance  = 10
            ActionType  = 2
            Severity    = 'High'
            Resources   = @(
                "[Active Directory Replication](https://blog.netwrix.com/2017/02/20/active-directory-replication/)"
                "[Active Directory Replication Concepts](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/get-started/replication/active-directory-replication-concepts)"
                "[Repadmin: How to Check Active Directory Replication](https://activedirectorypro.com/repadmin-how-to-check-active-directory-replication/)"
            )
            StatusTrue  = 1
            StatusFalse = 5
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        ReplicationTests = @{
            Enable     = $true
            Name       = 'Replication Test'
            Parameters = @{
                ExpectedValue         = $true
                Property              = 'Status'
                OperationType         = 'eq'
                PropertyExtendedValue = 'StatusMessage'
                OverwriteName         = { "Replication from $($_.Server) to $($_.ServerPartner)" }
            }
            Details    = @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
    }
}

$ReplicationStatus = @{
    Name   = "ForestReplicationStatus"
    Enable = $true
    Scope  = 'Forest'
    Source = @{
        Name           = 'Forest Replication using RepAdmin'
        Data           = {
            $Header = '"showrepl_COLUMNS","Destination DSA Site","Destination DSA","Naming Context","Source DSA Site","Source DSA","Transport Type","Number of Failures","Last Failure Time","Last Success Time","Last Failure Status"'
            $data = repadmin /showrepl * /csv
            $data[0] = $Header
            $data | ConvertFrom-Csv
        }
        Details        = [ordered] @{
            Category    = 'Health'
            Description = ''
            Importance  = 10
            ActionType  = 2
            Severity    = 'High'
            Resources   = @(
                "[Active Directory Replication](https://blog.netwrix.com/2017/02/20/active-directory-replication/)"
                "[Active Directory Replication Concepts](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/get-started/replication/active-directory-replication-concepts)"
                "[Repadmin: How to Check Active Directory Replication](https://activedirectorypro.com/repadmin-how-to-check-active-directory-replication/)"
            )
            StatusTrue  = 1
            StatusFalse = 5
        }
        Requirements   = @{
            CommandAvailable = 'repadmin'
            IsInternalForest = $true
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        ReplicationTests = @{
            Enable     = $true
            Name       = 'Replication Test'
            Parameters = @{
                ExpectedValue         = 0
                Property              = 'Number of Failures'
                OperationType         = 'eq'
                PropertyExtendedValue = 'Last Success Time'
                OverwriteName         = { "Replication from $($_.'Source DSA') to $($_.'Destination DSA'), Naming Context: $($_.'Naming Context')" }
            }
            Details    = @{
                Category    = 'Health'
                Importance  = 10
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
    }
}
$RootKDS = @{
    Name            = "ForestRootKDS"
    Enable          = $true
    Scope           = 'Forest'
    Source          = @{
        Name           = 'Forest Root KDS Key'
        Data           = {
            Get-KdsRootKey
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = 'Active Directory KDS Root Key is required to create GMSA accounts'
            Importance  = 6
            ActionType  = 1
            Resources   = @(
                '[ConfigMgr – SQL and Active Directory gMSA](https://configmgr.com/tag/root-key/)'
                '[Create the Key Distribution Services KDS Root Key](https://docs.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/create-the-key-distribution-services-kds-root-key)'
            )
            Tags        = 'Configuration', 'GMSA'
            StatusTrue  = 1
            StatusFalse = 3
        }
        ExpectedOutput = $true
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                "Domain Controllers (DC) require a root key to begin generating gMSA passwords. "
                "The domain controllers will wait up to 10 hours from time of creation to allow all domain controllers to converge their AD replication before allowing the creation of a gMSA. "
                "The 10 hours is a safety measure to prevent password generation from occurring before all DCs in the environment are capable of answering gMSA requests. "
                "If you try to use a gMSA too soon the key might not have been replicated to all domain controllers and therefore password retrieval might fail when the gMSA host attempts to retrieve the password. "
                "gMSA password retrieval failures can also occur when using DCs with limited replication schedules or if there is a replication issue."
            )
        }
    }
    DataHighlights  = {

    }
    Solution        = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Setting up KDS Root Key' {
                        New-HTMLText -Text @(
                            "On the Windows Server 2012 or later domain controller, run the Windows PowerShell from the Taskbar. "
                            "To create the KDS root key using the Add-KdsRootKey cmdlet, run the following command: "
                        )
                        New-HTMLCodeBlock {
                            Add-KdsRootKey -EffectiveImmediately
                        }
                        New-HTMLText -Text "The 10 hours is a safety measure to prevent password generation from occurring before all DCs in the environment are capable of answering gMSA requests."
                        New-HTMLText -LineBreak
                        New-HTMLText -Text @(
                            "The Effective time parameter can be used to give time for keys to be propagated to all DCs before use. "
                            "Using Add-KdsRootKey -EffectiveImmediately will add a root key to the target DC which will be used by the KDS service immediately. "
                            "However, other domain controllers will not be able to use the root key until replication is successful."
                            "For test environments with only one DC, you can create a KDS root key and set the start time in the past to avoid the interval wait for key generation by using the following procedure. "
                            "Validate that a 4004 event has been logged in the kds event log."
                        )
                        New-HTMLText -Text "To create the ", "KDS root key ", "using the ", "Add-KdsRootKey", " cmdlet" -Color None, Tangerine, None, Tangerine, None
                        New-HTMLCodeBlock {
                            Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))
                        }
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$SiteLinks = @{
    Name   = "ForestSiteLinks"
    Enable = $true
    Scope  = 'Forest'
    Source = @{
        Name           = 'Site Links'
        Data           = {
            Get-WinADSiteLinks -Forest $ForestName
        }
        Details        = [ordered] @{
            Area        = 'Sites'
            Category    = 'Configuration'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Severity    = 'Informational'
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    Tests  = [ordered] @{
        MinimalReplicationFrequency = @{
            Enable     = $true
            Name       = 'Replication Frequency should be set to maximum 60 minutes'
            Parameters = @{
                Property      = 'ReplicationFrequencyInMinutes'
                ExpectedValue = 60
                OperationType = 'le'
            }
        }
        UseNotificationsForLinks    = @{
            Enable     = $true
            Name       = 'Automatic site links should use notifications'
            Parameters = @{
                Property              = 'Options'
                ExpectedValue         = 'UseNotify'
                OperationType         = 'contains'
                PropertyExtendedValue = 'Options'
            }
        }
    }
}

$SiteLinksConnections = @{
    Name   = "ForestSiteLinksConnections"
    Enable = $true
    Scope  = 'Forest'
    Source = @{
        Name           = 'Site Links Connections'
        Data           = {
            Test-ADSiteLinks -Splitter ', ' -Forest $ForestName
        }
        Details        = [ordered] @{
            Area        = 'Sites'
            Category    = 'Configuration'
            Description = ''
            Resolution  = ''
            Importance  = 10
            Severity    = 'Informational'
            Resources   = @(

            )
        }
        ExpectedOutput = $true
    }
    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'
            }
        }
        SiteLinksCrossSiteNotifications = @{
            Enable     = $true
            Name       = 'All cross-site links use notifications'
            Parameters = @{
                Property      = 'SiteLinksCrossSiteNotUseNotifyCount'
                ExpectedValue = 0
                OperationType = 'eq'
            }
        }
        SiteLinksSameSiteNotifications  = @{
            Enable     = $true
            Name       = 'All same-site links have no notifications'
            Parameters = @{
                Property      = 'SiteLinksSameSiteUseNotifyCount'
                ExpectedValue = 0
                OperationType = 'eq'
            }
        }
        NoDisabledLinks                 = @{
            Enable     = $true
            Name       = 'All links are enabled'
            Parameters = @{
                Property      = 'SiteLinksDisabledCount'
                ExpectedValue = 0
                OperationType = 'eq'
            }
        }
    }
}
$Sites = @{
    Name            = "ForestSites"
    Enable          = $true
    Scope           = 'Forest'
    Source          = [ordered] @{
        Name           = 'Forest Sites'
        Data           = {
            Get-WinADForestSites -Forest $ForestName
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = 'Sites are Active Directory objects that represent one or more TCP/IP subnets with highly reliable and fast network connections. Site information allows administrators to configure Active Directory access and replication to optimize usage of the physical network. Site objects are associated with a set of subnets, and each domain controller in a forest is associated with an Active Directory site according to its IP address. Sites can host domain controllers from more than one domain, and a domain can be represented in more than one site.'
            Importance  = 0
            ActionType  = 0
            Resources   = @(
                "[Site Functions](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/site-functions)"
                "[Active Directory Sites](https://www.windows-active-directory.com/active-directory-sites.html)"
            )
            StatusTrue  = 0
            StatusFalse = 0
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        SitesWithoutDC      = @{
            Enable      = $true
            Name        = 'Sites without Domain Controllers'
            Description = 'Verify each `site has at least [one subnet configured]`'
            Parameters  = @{
                WhereObject   = { $_.DomainControllersCount -eq 0 }
                ExpectedCount = 0
            }
            Details     = [ordered] @{
                Category    = 'Configuration'
                Importance  = 0
                ActionType  = 0
                StatusTrue  = 1
                StatusFalse = 0
            }
        }
        SitesWithoutSubnets = @{
            Enable     = $true
            Name       = 'Sites without Subnets'
            Parameters = @{
                WhereObject   = { $_.SubnetsCount -eq 0 }
                ExpectedCount = 0
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 2
            }
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                "Sites are Active Directory objects that represent one or more TCP/IP subnets with highly reliable and fast network connections. "
                "Site information allows administrators to configure Active Directory access and replication to optimize usage of the physical network. "
                "Site objects are associated with a set of subnets, and each domain controller in a forest is associated with an Active Directory site according to its IP address. "
                "Sites can host domain controllers from more than one domain, and a domain can be represented in more than one site."
            ) #-LineBreak
            New-HTMLText -Text @(
                "Sites without subnets have no role and just stay there unused. "
                "Sites without Domain Controllers still have their role in the Active Directory Topology. "
                "Following tests finds "
                "sites without subnets "
                "and Domain Admins role is to asses whether such stie is still needed and is just missing a subnet, or should be deleted because it's no longer required. "
                "Following tests also finds "
                "sites without Domain Controllers"
                ", but this test is just informational - although if Domain Admin is aware of a site that is no longer required it should be deleted. "
            ) -FontWeight normal, normal, normal, bold, normal, normal, bold, normal
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'SubnetsCount' -ComparisonType number -BackgroundColor PaleGreen -Value 0 -Operator gt
        New-HTMLTableCondition -Name 'SubnetsCount' -ComparisonType number -BackgroundColor Salmon -Value 0 -Operator eq
        New-HTMLTableCondition -Name 'DomainControllersCount' -ComparisonType number -BackgroundColor PaleGreen -Value 0 -Operator gt
    }
    Solution        = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Investigate Sites without Subnets' {
                        New-HTMLText -Text @(
                            "Sites without subnets have no use. "
                            "It can mean the site is no longer in use and can be safely deleted. "
                            ""
                            "Please investigate and find out if that's really the case. "
                            "Otherwise you should create proper subnet for given site. "
                        )
                    }
                    New-HTMLWizardStep -Name 'Investigate Sites without Domain Controllers (optional)' {
                        New-HTMLText -Text @(
                            "Sites without Domain Controllers do happen and are quite common. "
                            "But this isn't always true. "
                            "Consider investigating whether sites without Domain Controller are as expected. "
                        ) -FontWeight normal, bold, normal, normal, normal, normal, bold, bold
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}
$TombstoneLifetime = @{
    Name           = "ForestTombstoneLifetime"
    Enable         = $true
    Scope          = 'Forest'
    Source         = [ordered]@{
        Name           = 'Tombstone Lifetime'
        Data           = {
            # Check tombstone lifetime (if blank value is 60)
            # Recommended value 720
            # Minimum value 180
            $Output = (Get-ADObject -Identity "CN=Directory Service,CN=Windows NT,CN=Services,$((Get-ADRootDSE -Server $ForestName).configurationNamingContext)" -Server $ForestName -Properties tombstoneLifetime, 'msDS-deletedObjectLifetime') | Select-Object -Property DistinguishedName, Name, objectClass, ObjectGuid, tombstoneLifetime, 'msDS-deletedObjectLifetime'
            if ($null -eq $Output) {
                [PSCustomObject] @{
                    'TombstoneLifeTime'          = 60
                    'msDS-deletedObjectLifetime' = 60
                }
            } else {
                if ($Output.tombstoneLifetime -and $Output.'msDS-deletedObjectLifetime') {
                    [PSCustomObject] @{
                        DistinguishedName            = $Output.DistinguishedName
                        'TombstoneLifeTime'          = $Output.tombstoneLifetime
                        'msDS-deletedObjectLifetime' = $Output.'msDS-deletedObjectLifetime'
                    }
                } elseif ($Output.tombstoneLifetime) {
                    [PSCustomObject] @{
                        DistinguishedName            = $Output.DistinguishedName
                        'TombstoneLifeTime'          = $Output.tombstoneLifetime
                        'msDS-deletedObjectLifetime' = 60
                    }
                } elseif ($Output.'msDS-deletedObjectLifetime') {
                    [PSCustomObject] @{
                        DistinguishedName            = $Output.DistinguishedName
                        'TombstoneLifeTime'          = 60
                        'msDS-deletedObjectLifetime' = $Output.'msDS-deletedObjectLifetime'
                    }
                } else {
                    [PSCustomObject] @{
                        DistinguishedName            = $Output.DistinguishedName
                        'TombstoneLifeTime'          = 60
                        'msDS-deletedObjectLifetime' = 60
                    }
                }
            }
        }
        Details        = [ordered] @{
            Category    = 'Configuration'
            Description = "A tombstone is a container object consisting of the deleted objects from AD. These objects have not been physically removed from the database. When an AD object, such as a user is deleted, the object technically remains in the directory for a given period of time; known as the Tombstone Lifetime. At that point, Active Directory sets the ‘isDeleted' attribute of the deleted object to TRUE and moves it to a special container called Tombstone (previously known as CN=Deleted Objects.) Once the object is older than the tombstone lifetime, it will be removed (physically deleted) by the garbage collection process."
            Importance  = 0
            ActionType  = 0
            Resources   = @(
                '[Understanding Tombstones, Active Directory, and How To Protect It](https://support.storagecraft.com/s/article/Understanding-Tombstones-Active-Directory-and-How-To-Protect-It?language=en_US)'
                '[Adjust Active Directory Tombstone Lifetime](https://helpcenter.netwrix.com/NA/Configure_IT_Infrastructure/AD/AD_Tombstone.html)'
            )
            StatusTrue  = 0
            StatusFalse = 2
        }
        ExpectedOutput = $true
    }
    Tests          = [ordered] @{
        TombstoneLifetime  = [ordered] @{
            Enable     = $true
            Name       = 'TombstoneLifetime should be set to minimum of 180 days'
            Parameters = @{
                ExpectedValue = 180
                Property      = 'TombstoneLifeTime'
                OperationType = 'ge'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 7
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
        RecycleBinLifetime = [ordered] @{
            Enable     = $true
            Name       = 'RecycleBinLifetime should be set to minimum of 180 days'
            Parameters = @{
                ExpectedValue = 180
                Property      = 'msDS-deletedObjectLifetime'
                OperationType = 'ge'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 7
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 3
            }
        }
    }
    DataHighlights = {
        New-HTMLTableCondition -Name 'tombstoneLifetime' -ComparisonType number -BackgroundColor PaleGreen -Value 180 -Operator ge
        New-HTMLTableCondition -Name 'tombstoneLifetime' -ComparisonType number -BackgroundColor Orange -Value 180 -Operator lt
        New-HTMLTableCondition -Name 'tombstoneLifetime' -ComparisonType number -BackgroundColor Salmon -Value 60 -Operator le
        New-HTMLTableCondition -Name 'msDS-deletedObjectLifetime' -ComparisonType number -BackgroundColor PaleGreen -Value 180 -Operator ge
        New-HTMLTableCondition -Name 'msDS-deletedObjectLifetime' -ComparisonType number -BackgroundColor Orange -Value 180 -Operator lt
        New-HTMLTableCondition -Name 'msDS-deletedObjectLifetime' -ComparisonType number -BackgroundColor Salmon -Value 60 -Operator le
    }
}
$Trusts = @{
    Name           = "ForestTrusts"
    Enable         = $true
    Scope          = 'Forest'
    Source         = @{
        Name           = "Trust Availability"
        Data           = {
            Get-WinADTrust -Forest $ForestName
        }
        Details        = [ordered] @{
            Category    = 'Health', 'Configuration'
            Importance  = 4
            ActionType  = 0
            Description = 'Verifies if trusts are available and tests for trust unconstrained TGTDelegation'
            Resolution  = ''
            Resources   = @(
                '[Changes to Ticket-Granting Ticket (TGT) Delegation Across Trusts in Windows Server (CIS edition)](https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/changes-to-ticket-granting-ticket-tgt-delegation-across-trusts/ba-p/440261)'
                "[Visually display Active Directory Trusts using PowerShell](https://evotec.xyz/visually-display-active-directory-trusts-using-powershell/)"
            )
            StatusTrue  = 0
            StatusFalse = 3
        }
        ExpectedOutput = $null
    }
    Tests          = [ordered] @{
        TrustsConnectivity            = @{
            Enable     = $true
            Name       = 'Trust status'
            Parameters = @{
                OverwriteName = { "Trust status | Source $($_.'TrustSource'), Target $($_.'TrustTarget'), Direction $($_.'TrustDirection')" }
                Property      = 'TrustStatus'
                ExpectedValue = 'OK'
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 0
            }
        }
        TrustsQueryStatus             = @{
            Enable     = $true
            Name       = 'Trust Query Status'
            Parameters = @{
                OverwriteName = { "Trust query | Source $($_.'TrustSource'), Target $($_.'TrustTarget'), Direction $($_.'TrustDirection')" }
                Property      = 'QueryStatus'
                ExpectedValue = 'OK'
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
        TrustsUnconstrainedDelegation = @{
            Enable     = $true
            Name       = 'Trust unconstrained TGTDelegation'
            Parameters = @{
                # TGTDelegation should be set to $True (contrary to name)
                OverwriteName = { "Trust unconstrained TGTDelegation | Source $($_.'TrustSource'), Target $($_.'TrustTarget'), Direction $($_.'TrustDirection')" }
                WhereObject   = { ($_.'TrustAttributes' -ne 'Within Forest') -and ($_.'TrustDirection' -eq 'BiDirectional' -or $_.'TrustDirection' -eq 'InBound') }
                Property      = 'IsTGTDelegationEnabled'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = [ordered] @{
                Category    = 'Configuration'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
    }
    DataHighlights = {
        New-HTMLTableCondition -Name 'QueryStatus' -ComparisonType string -BackgroundColor PaleGreen -Value 'OK' -Operator eq -FailBackgroundColor Salmon
        New-HTMLTableCondition -Name 'TrustStatus' -ComparisonType string -BackgroundColor PaleGreen -Value 'OK' -Operator eq -FailBackgroundColor Orange
        New-TableConditionGroup {
            New-HTMLTableCondition -Name 'TrustType' -ComparisonType string -Value 'Forest', 'External' -Operator in
            New-HTMLTableCondition -Name 'SIDFilteringQuarantined' -ComparisonType string -Value $true -Operator eq
        } -Logic AND -HighlightHeaders 'SIDFilteringQuarantined', 'TrustType' -BackgroundColor PaleGreen

        New-TableConditionGroup {
            New-HTMLTableCondition -Name 'TrustType' -ComparisonType string -Value 'Forest', 'External' -Operator in
            New-HTMLTableCondition -Name 'SIDFilteringQuarantined' -ComparisonType string -Value $false -Operator eq
        } -Logic AND -HighlightHeaders 'SIDFilteringQuarantined', 'TrustType' -BackgroundColor Salmon

        New-TableConditionGroup {
            New-HTMLTableCondition -Name 'TrustType' -ComparisonType string -Value 'TreeRoot' -Operator eq
        } -Logic AND -HighlightHeaders 'SIDFilteringQuarantined', 'TrustType' -BackgroundColor PaleGreen
    }
}
$VulnerableSchemaClass = @{
    Name            = "ForestVulnerableSchemaClass"
    Enable          = $true
    Scope           = 'Forest'
    Source          = @{
        Name           = 'Vurnerable Schema Class'
        Data           = {
            Test-WinADVulnerableSchemaClass
        }
        Details        = [ordered] @{
            Category    = 'Security'
            Description = 'Environments running supported versions of Exchange Server should address CVE-2021-34470 by applying the CU and/or SU for the respective versions of Exchange, as described in Released: July 2021 Exchange Server Security Updates. Environments where the latest version of Exchange Server is any version before Exchange 2013, or environments where all Exchange servers have been removed, one can use a script to address the vulnerability.'
            Resolution  = ''
            Importance  = 5
            ActionType  = 1
            StatusTrue  = 1
            StatusFalse = 5
            Resources   = @(
                "[Test-CVE-2021-34470](https://microsoft.github.io/CSS-Exchange/Security/Test-CVE-2021-34470/)"
                "[July 2021 Exchange Server Security Updates](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-july-2021-exchange-server-security-updates/ba-p/2523421)"
            )
        }
        ExpectedOutput = $true
    }
    Tests           = [ordered] @{
        VurnerableSchemaClass = @{
            Enable     = $true
            Name       = 'Schema Class should not be vulnerable'
            Parameters = @{
                Property      = 'Vulnerable'
                ExpectedValue = $false
                OperationType = 'eq'
            }
            Details    = @{
                Category    = 'Security'
                Importance  = 5
                ActionType  = 2
                StatusTrue  = 1
                StatusFalse = 5
            }
        }
    }
    DataDescription = {
        New-HTMLSpanStyle -FontSize 10pt {
            New-HTMLText -Text @(
                'Environments running supported versions of Exchange Server should address CVE-2021-34470 by applying the CU and/or SU for the respective versions of Exchange, as described in Released: July 2021 Exchange Server Security Updates. '
            )
            New-HTMLText -Text @(
                'Environments where the latest version of Exchange Server is any version before Exchange 2013, or environments where all Exchange servers have been removed, can use this script to address the vulnerability.'
            )
        }
    }
    DataHighlights  = {
        New-HTMLTableCondition -Name 'Vulnerable' -ComparisonType string -BackgroundColor PaleGreen -Value $false -Operator eq
        New-HTMLTableCondition -Name 'Vulnerable' -ComparisonType string -BackgroundColor Salmon -Value $true -Operator eq
    }
    Solution        = {
        New-HTMLContainer {
            New-HTMLSpanStyle -FontSize 10pt {
                New-HTMLWizard {
                    New-HTMLWizardStep -Name 'Vulnerable Schema Class' {
                        New-HTMLText -Text @(
                            "Depending whether there is still an Exchange Server present or not there are two ways to address the vulnerability."
                        ) -FontWeight normal, normal, normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Black, Black, Red, Black
                    }
                    New-HTMLWizardStep -Name 'Exchange Server is still in use' {
                        New-HTMLText -Text @(
                            "If the Exchange Server is still present, you can apply the CU "
                            "for the respective version of Exchange along with preparing the schema which will fix the vulnerability."
                            "More details can be found on [July 2021 Exchange Server Security Updates](https://techcommunity.microsoft.com/t5/exchange-team-blog/released-july-2021-exchange-server-security-updates/ba-p/2523421)"
                        )
                    }
                    New-HTMLWizardStep -Name 'Exchange Server is not in use anymore or older version' {
                        New-HTMLText -Text "Without explicit action by a schema admin in your organization, you might be vulnerable to CVE-2021-34470 if:"
                        New-HTMLList {
                            New-HTMLListItem -Text "You ran Exchange Server in the past, but you have since uninstalled all Exchange servers."
                            New-HTMLListItem -Text "You still run Exchange Server, but only versions older than Exchange 2013 (namely,Exchange 2003, Exchange 2007 and/or Exchange 2010)."
                        }
                        New-HTMLText -Text "If your organization is in one of these scenarios, we recommend the following to update your Active Directory schema to address the vulnerability in CVE-2021-34470:"
                        New-HTMLText -Text "Download the script [Test-CVE-2021-34470](https://microsoft.github.io/CSS-Exchange/Security/Test-CVE-2021-34470/) from GitHub and use it to apply the needed schema update; please note the script requirements on the GitHub page."
                    }
                } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
            }
        }
    }
}

function Add-TestimoBaselines {
    <#
    .SYNOPSIS
    Adds baseline objects to the Testimo configuration.
 
    .DESCRIPTION
    This function adds baseline objects to the Testimo configuration. It compares baseline sources and targets, excluding specified properties, and stores the comparison results in the Testimo configuration.
 
    .PARAMETER BaseLineObjects
    Specifies an array of baseline objects to be added to the Testimo configuration.
 
    .EXAMPLE
    Add-TestimoBaselines -BaseLineObjects @($BaselineObject1, $BaselineObject2)
    Adds $BaselineObject1 and $BaselineObject2 to the Testimo configuration and performs a comparison between their baseline sources and targets.
 
    .NOTES
    File Name : Add-TestimoBaselines.ps1
    Prerequisite : This function requires Compare-MultipleObjects function.
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary[]] $BaseLineObjects
    )
    $ListNewSources = [System.Collections.Generic.List[string]]::new()
    $ListOverwritten = [System.Collections.Generic.List[string]]::new()
    foreach ($Source in $BaseLineObjects) {
        if (-not $Script:TestimoConfiguration[$Source.Scope]) {
            $Script:TestimoConfiguration[$Source.Scope] = [ordered] @{}
        }

        #$Execute = Compare-MultipleObjects -FlattenObject -Objects $Source.BaseLineSource, $Source.BaseLineTarget

        $ExcludeProperties = @(
            'id'
            "*@odata*"
            "#microsoft.graph*"
            "ResourceID"
            "Credential"
            "ResourceName"
            if ($Source.ExcludeProperty) {
                $Source.ExcludeProperty
            }
        )
        If ($Source.BaseLineSource -and $Source.BaseLineTarget) {
            try {
                $DataOutput = Compare-MultipleObjects -FlattenObject -Objects $Source.BaseLineSource, $Source.BaseLineTarget -ObjectsName "Source", "Target" -ExcludeProperty $ExcludeProperties -SkipProperties -AllProperties
            } catch {
                $DataOutput = $null
                Write-Warning -Message "Error comparing $($Source.BaseLineSource) and $($Source.BaseLineTarget) with error: $($_.Exception.Message)"
            }
        } else {
            $DataOutput = $null
        }

        $Script:TestimoConfiguration[$Source.Scope][$Source.Name] = @{
            Name           = $Source.Name
            Enable         = $true
            Scope          = $Source.Scope
            Source         = @{
                Name           = $Source.DisplayName
                DataCode       = 'Compare-MultipleObjects -FlattenObject -Objects $Source.BaseLineSource, $Source.BaseLineTarget -CompareNames "Source", "Target" -ExcludeProperty "*@odata*" -SkipProperties'
                DataOutput     = $DataOutput
                Details        = [ordered] @{
                    Area        = ''
                    Category    = $Source.Category
                    Severity    = ''
                    Importance  = 0
                    Description = ''
                    Resolution  = ''
                    Resources   = @(

                    )
                }
                ExpectedOutput = $true
            }
            Tests          = [ordered] @{
                Baseline = @{
                    Enable     = $true
                    Name       = 'Baseline comparison'
                    Parameters = @{
                        #OverwriteName = { "Trust status | Source $($_.'TrustSource'), Target $($_.'TrustTarget'), Direction $($_.'TrustDirection')" }
                        Property      = 'Status'
                        ExpectedValue = $true
                        OperationType = 'eq'
                        OverwriteName = { "Property $($_.Name)" }
                    }
                    Details    = [ordered] @{
                        Category    = $Source.Category
                        Importance  = 5
                        ActionType  = 2
                        StatusTrue  = 1
                        StatusFalse = 5
                    }
                }
            }
            DataHighlights = {
                New-HTMLTableCondition -Name 'Status' -ComparisonType bool -BackgroundColor PaleGreen -Value $true -Operator eq -FailBackgroundColor Salmon
            }
        }
        $ListNewSources.Add($Source.Name)
    }
    if ($ListNewSources.Count -gt 0) {
        Out-Informative -Text 'Following baseline sources were added' -Level 0 -Status $true -ExtendedValue ($ListNewSources -join ', ') -OverrideTextStatus "External Baselines"
    }
    if ($ListOverwritten.Count -gt 0) {
        Out-Informative -Text 'Following baseline sources overwritten' -Level 0 -Status $true -ExtendedValue ($ListOverwritten -join ', ') -OverrideTextStatus "Overwritten Baselines"
    }
}
function Add-TestimoSources {
    <#
    .SYNOPSIS
    Adds external sources to the Testimo configuration.
 
    .DESCRIPTION
    This function adds external sources to the Testimo configuration based on the provided folder paths. It processes PowerShell script files within the specified folders to extract configuration data and updates the Testimo configuration accordingly.
 
    .PARAMETER FolderPath
    Specifies the folder paths containing the PowerShell script files to process.
 
    .EXAMPLE
    Add-TestimoSources -FolderPath "C:\Scripts"
 
    Description:
    Adds external sources from the "C:\Scripts" folder to the Testimo configuration.
 
    .EXAMPLE
    Add-TestimoSources -FolderPath "C:\Scripts", "D:\Scripts"
 
    Description:
    Adds external sources from both "C:\Scripts" and "D:\Scripts" folders to the Testimo configuration.
    #>

    [CmdletBinding()]
    param(
        [string[]] $FolderPath
    )
    $ListNewSources = [System.Collections.Generic.List[string]]::new()
    $ListOverwritten = [System.Collections.Generic.List[string]]::new()
    foreach ($Folder in $FolderPath) {
        $FilesWithCode = @( Get-ChildItem -Path "$Folder\*.ps1" -ErrorAction SilentlyContinue -Recurse )

        Foreach ($import in $FilesWithCode) {
            $Content = Get-Content -LiteralPath $import.fullname -Raw
            $Script = [scriptblock]::Create($Content)
            $Data = $Script.Invoke()
            foreach ($Source in $Data) {
                if (-not $Script:TestimoConfiguration[$Source.Scope]) {
                    $Script:TestimoConfiguration[$Source.Scope] = [ordered] @{}
                }
                if ($Source.Scope -in 'Forest', 'Domain', 'DC') {
                    if ($Script:TestimoConfiguration['ActiveDirectory'][$Source.Name]) {
                        $ListOverwritten.Add($Source.Name)
                    } else {
                        $ListNewSources.Add($Source.Name)
                    }
                    $Script:TestimoConfiguration['ActiveDirectory'][$Source.Name] = $Source
                } else {
                    $Script:TestimoConfiguration[$Source.Scope][$Source.Name] = $Source
                    $ListNewSources.Add($Source.Name)
                }
            }
        }
    }
    if ($ListNewSources.Count -gt 0) {
        Out-Informative -Text 'Following external sources were added' -Level 0 -Status $true -ExtendedValue ($ListNewSources -join ', ') -OverrideTextStatus "External Sources"
    }
    if ($ListOverwritten.Count -gt 0) {
        Out-Informative -Text 'Following external sources overwritten' -Level 0 -Status $true -ExtendedValue ($ListOverwritten -join ', ') -OverrideTextStatus "Overwritten Sources"
    }
}
function Get-RequestedSources {
    <#
    .SYNOPSIS
    Retrieves requested sources based on specified criteria.
 
    .DESCRIPTION
    This function retrieves requested sources based on the provided sources, exclude sources, include tags, and exclude tags. It filters out sources that do not match the criteria and categorizes them into working and non-working lists.
 
    .PARAMETER Sources
    Specifies an array of sources to be considered.
 
    .PARAMETER ExcludeSources
    Specifies an array of sources to be excluded from consideration.
 
    .PARAMETER IncludeTags
    Specifies an array of tags that sources must include to be considered.
 
    .PARAMETER ExcludeTags
    Specifies an array of tags that sources must exclude to be considered.
 
    .EXAMPLE
    Get-RequestedSources -Sources @('Source1', 'Source2') -ExcludeSources @('Source3') -IncludeTags @('Tag1')
 
    Description:
    Retrieves sources 'Source1' and 'Source2', excludes 'Source3', and includes sources with 'Tag1'.
 
    .EXAMPLE
    Get-RequestedSources -Sources @('SourceA', 'SourceB') -ExcludeTags @('TagX')
 
    Description:
    Retrieves sources 'SourceA' and 'SourceB', excluding sources with 'TagX'.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $Sources,
        [string[]] $ExcludeSources,
        [string[]] $IncludeTags,
        [string[]] $ExcludeTags
    )
    $NonWorking = [System.Collections.Generic.List[String]]::new()
    $Working = [System.Collections.Generic.List[String]]::new()
    $NonWorkingExclusions = [System.Collections.Generic.List[String]]::new()
    $WorkingExclusions = [System.Collections.Generic.List[String]]::new()
    foreach ($Source in $Sources) {
        $Found = $false
        foreach ($Key in $Script:TestimoConfiguration.Keys) {
            if ($Key -notin 'Types', 'Exclusions', 'Inclusions', 'Debug') {
                if ($Source -in $Script:TestimoConfiguration[$Key].Keys) {
                    $Found = $true
                    break
                }
            }
        }
        if ($Found) {
            $Working.Add($Source)
        } else {
            $NonWorking.Add($Source)
        }
    }
    foreach ($Source in $ExcludeSources) {
        $Found = $false
        foreach ($Key in $Script:TestimoConfiguration.Keys) {
            if ($Key -notin 'Types', 'Exclusions', 'Inclusions', 'Debug') {
                if ($Source -in $Script:TestimoConfiguration[$Key].Keys) {
                    $Found = $true
                    break
                }
            }
        }
        if ($Found) {
            $WorkingExclusions.Add($Source)
        } else {
            $NonWorkingExclusions.Add($Source)
        }
    }
    foreach ($Tag in $IncludeTags) {
        foreach ($Key in $Script:TestimoConfiguration.Keys) {
            if ($Key -notin 'Types', 'Exclusions', 'Inclusions', 'Debug') {
                foreach ($Source in $Script:TestimoConfiguration[$Key].Keys) {
                    if ($Tag -in $Script:TestimoConfiguration[$Key][$Source]['Source']['Details'].Tags) {
                        $Working.Add($Source)
                    }
                }
            }
        }
    }
    if ($IncludeTags.Count -gt 0) {
        Out-Informative -Text 'Following tags will be used' -Level 0 -Status $true -ExtendedValue ($IncludeTags -join ', ') -OverrideTextStatus "Tags"
    }
    if ($ExcludeTags.Count -gt 0) {
        Out-Informative -Text 'Following tags will be excluded' -Level 0 -Status $true -ExtendedValue ($ExcludeTags -join ', ') -OverrideTextStatus "Tags"
    }
    if ($Working.Count -gt 0) {
        Out-Informative -Text 'Following sources will be used' -Level 0 -Status $true -ExtendedValue ($Working -join ', ') -OverrideTextStatus "Valid Sources"
    }
    if ($NonWorking.Count -gt 0) {
        Out-Informative -Text 'Following sources were provided incorrectly (skipping)' -Level 0 -Status $false -ExtendedValue ($NonWorking -join ', ') -OverrideTextStatus "Failed Sources"
    }
    if ($WorkingExclusions.Count -gt 0) {
        Out-Informative -Text 'Following sources will be excluded' -Level 0 -Status $true -ExtendedValue ($WorkingExclusions -join ', ') -OverrideTextStatus "Valid Sources"
    }
    if ($NonWorkingExclusions.Count -gt 0) {
        Out-Informative -Text 'Following sources for exclusions were provided incorrectly (skipping)' -Level 0 -Status $false -ExtendedValue ($NonWorkingExclusions -join ', ') -OverrideTextStatus "Failed Sources"
    }
}
function Get-TestimoDomainControllers {
    <#
    .SYNOPSIS
    Retrieves domain controllers in a specified domain, with an option to skip read-only domain controllers.
 
    .DESCRIPTION
    This function retrieves domain controllers in the specified domain. It can skip read-only domain controllers if desired.
 
    .PARAMETER Domain
    Specifies the name of the domain to retrieve domain controllers from.
 
    .PARAMETER SkipRODC
    Indicates whether to skip read-only domain controllers.
 
    .EXAMPLE
    Get-TestimoDomainControllers -Domain "contoso.com"
    Retrieves all domain controllers in the "contoso.com" domain.
 
    .EXAMPLE
    Get-TestimoDomainControllers -Domain "contoso.com" -SkipRODC
    Retrieves all domain controllers in the "contoso.com" domain, excluding read-only domain controllers.
 
    #>

    [CmdletBinding()]
    param(
        [string] $Domain,
        [switch] $SkipRODC
    )
    try {
        $DC = Get-ADDomainController -Discover -DomainName $Domain
        $DomainControllers = Get-ADDomainController -Server $DC.HostName[0] -Filter * -ErrorAction Stop
        if ($SkipRODC) {
            $DomainControllers = $DomainControllers | Where-Object { $_.IsReadOnly -eq $false }
        }
        foreach ($_ in $DomainControllers) {
            if ($Script:TestimoConfiguration['Inclusions']['DomainControllers']) {
                if ($_ -in $Script:TestimoConfiguration['Inclusions']['DomainControllers']) {
                    [PSCustomObject] @{
                        Name  = $($_.HostName).ToLower()
                        IsPDC = $_.OperationMasterRoles -contains 'PDCEmulator'
                    }
                }
                # We skip checking for exclusions
                continue
            }
            if ($_.HostName -notin $Script:TestimoConfiguration['Exclusions']['DomainControllers']) {
                [PSCustomObject] @{
                    Name  = $($_.HostName).ToLower()
                    IsPDC = $_.OperationMasterRoles -contains 'PDCEmulator'
                }
            }
        }
    } catch {
        return
    }
}
function Get-TestimoSourcesStatus {
    <#
    .SYNOPSIS
    Retrieves the status of Testimo sources based on the specified scope.
 
    .DESCRIPTION
    This function retrieves the status of Testimo sources based on the specified scope. It checks if any Testimo source within the specified scope is enabled.
 
    .PARAMETER Scope
    Specifies the scope for which the Testimo sources status should be retrieved.
 
    .EXAMPLE
    Get-TestimoSourcesStatus -Scope "Global"
    Retrieves the status of Testimo sources within the Global scope.
 
    .EXAMPLE
    Get-TestimoSourcesStatus -Scope "Local"
    Retrieves the status of Testimo sources within the Local scope.
    #>

    [cmdletbinding()]
    param(
        [string] $Scope
    )
    $AllTests = foreach ($Source in $($Script:TestimoConfiguration.ActiveDirectory.Keys)) {
        if ($Scope -ne $Script:TestimoConfiguration.ActiveDirectory[$Source].Scope) {
            continue
        }
        $Script:TestimoConfiguration.ActiveDirectory["$Source"].Enable
    }
    $AllTests -contains $true
}
function Import-TestimoConfiguration {
    <#
    .SYNOPSIS
    Imports a Testimo configuration from various sources.
 
    .DESCRIPTION
    This function imports a Testimo configuration from different sources such as Hashtable, JSON file, or JSON content. It then updates the Testimo configuration based on the provided input.
 
    .PARAMETER Configuration
    Specifies the configuration object to be imported. This can be a Hashtable, a path to a JSON file, or JSON content.
 
    .EXAMPLE
    Import-TestimoConfiguration -Configuration $Hashtable
    Imports a Testimo configuration from a Hashtable.
 
    .EXAMPLE
    Import-TestimoConfiguration -Configuration "C:\Path\to\Configuration.json"
    Imports a Testimo configuration from a JSON file located at the specified path.
 
    .EXAMPLE
    Import-TestimoConfiguration -Configuration '{"Key": {"Enable": true, "Source": {"ExpectedOutput": "Output"}}}'
    Imports a Testimo configuration from JSON content.
 
    #>

    [CmdletBinding()]
    param(
        [Object] $Configuration
    )

    if ($Configuration) {
        if ($Configuration -is [System.Collections.IDictionary]) {
            $Option = 'Hashtable'
            $LoadedConfiguration = $Configuration
        } elseif ($Configuration -is [string]) {
            if (Test-Path -LiteralPath $Configuration) {
                $Option = 'File'
                $FileContent = Get-Content -LiteralPath $Configuration
            } else {
                $Option = 'JSON'
                $FileContent = $Configuration
            }
            try {
                $LoadedConfiguration = $FileContent | ConvertFrom-Json
            } catch {
                Out-Informative -OverrideTitle 'Testimo' -Text "Loading configuration from JSON failed. Skipping." -Level 0 -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ("Not JSON or syntax is incorrect.")
                return
            }
        } else {
            Out-Informative -OverrideTitle 'Testimo' -Text "Loading configuratio failed. Skipping." -Level 0 -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ("Not JSON/Hashtable or syntax is incorrect.")
        }
        Out-Informative -OverrideTitle 'Testimo' -Text "Using configuration provided by user" -Level 0 -Start

        if ($LoadedConfiguration -is [System.Collections.IDictionary]) {
            foreach ($Key in ($LoadedConfiguration).Keys) {
                if ($Script:TestimoConfiguration['ActiveDirectory'][$Key]) {
                    $Target = 'ActiveDirectory'
                } elseif ($Script:TestimoConfiguration['Office365'][$Key]) {
                    $Target = 'Office365'
                } else {
                    $Target = 'Unknown'
                }
                if ($Target -ne 'Unknown') {
                    $Script:TestimoConfiguration[$Target][$Key]['Enable'] = $LoadedConfiguration.$Key.Enable

                    if ($null -ne $LoadedConfiguration[$Key]['Source']) {
                        if ($null -ne $LoadedConfiguration[$Key]['Source']['ExpectedOutput']) {
                            $Script:TestimoConfiguration[$Target][$Key]['Source']['ExpectedOutput'] = $LoadedConfiguration.$Key['Source']['ExpectedOutput']
                        }
                        if ($null -ne $LoadedConfiguration[$Key]['Source']['Parameters']) {
                            foreach ($Parameter in [string] $LoadedConfiguration[$Key]['Source']['Parameters'].Keys) {
                                $Script:TestimoConfiguration[$Target][$Key]['Source']['Parameters'][$Parameter] = $LoadedConfiguration[$Key]['Source']['Parameters'][$Parameter]
                            }
                        }
                    }
                    foreach ($Test in $LoadedConfiguration.$Key.Tests.Keys) {
                        $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Enable'] = $LoadedConfiguration.$Key.Tests.$Test.Enable

                        if ($null -ne $LoadedConfiguration.$Key.Tests.$Test.Parameters.ExpectedValue) {
                            $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Parameters']['ExpectedValue'] = $LoadedConfiguration.$Key.Tests.$Test.Parameters.ExpectedValue
                        }
                        if ($null -ne $LoadedConfiguration.$Key.Tests.$Test.Parameters.ExpectedCount) {
                            $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Parameters']['ExpectedCount'] = $LoadedConfiguration.$Key.Tests.$Test.Parameters.ExpectedCount
                        }
                        if ($null -ne $LoadedConfiguration.$Key.Tests.$Test.Parameters.Property) {
                            $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Parameters']['Property'] = $LoadedConfiguration.$Key.Tests.$Test.Parameters.Property
                        }
                        if ($null -ne $LoadedConfiguration.$Key.Tests.$Test.Parameters.OperationType) {
                            $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Parameters']['OperationType'] = $LoadedConfiguration.$Key.Tests.$Test.Parameters.OperationType
                        }
                    }
                } else {

                }
            }
        } else {
            foreach ($Key in ($LoadedConfiguration).PSObject.Properties.Name) {
                if ($Script:TestimoConfiguration['ActiveDirectory'][$Key]) {
                    $Target = 'ActiveDirectory'
                } elseif ($Script:TestimoConfiguration['Office365'][$Key]) {
                    $Target = 'Office365'
                } else {
                    $Target = 'Unknown'
                }
                if ($Target -ne 'Unknown') {
                    $Script:TestimoConfiguration[$Target][$Key]['Enable'] = $LoadedConfiguration.$Key.Enable

                    if ($null -ne $LoadedConfiguration.$Key.'Source') {
                        if ($null -ne $LoadedConfiguration.$Key.'Source'.'ExpectedOutput') {
                            $Script:TestimoConfiguration[$Target][$Key]['Source']['ExpectedOutput'] = $LoadedConfiguration.$Key.'Source'.'ExpectedOutput'
                        }
                        if ($null -ne $LoadedConfiguration.$Key.'Source'.'Parameters') {
                            foreach ($Parameter in $LoadedConfiguration.$Key.'Source'.'Parameters'.PSObject.Properties.Name) {
                                $Script:TestimoConfiguration[$Target][$Key]['Source']['Parameters'][$Parameter] = $LoadedConfiguration.$Key.'Source'.'Parameters'.$Parameter
                            }
                        }
                    }

                    foreach ($Test in $LoadedConfiguration.$Key.Tests.PSObject.Properties.Name) {
                        $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Enable'] = $LoadedConfiguration.$Key.Tests.$Test.Enable

                        if ($null -ne $LoadedConfiguration.$Key.Tests.$Test.Parameters.ExpectedValue) {
                            $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Parameters']['ExpectedValue'] = $LoadedConfiguration.$Key.Tests.$Test.Parameters.ExpectedValue
                        }
                        if ($null -ne $LoadedConfiguration.$Key.Tests.$Test.Parameters.ExpectedCount) {
                            $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Parameters']['ExpectedCount'] = $LoadedConfiguration.$Key.Tests.$Test.Parameters.ExpectedCount
                        }
                        if ($null -ne $LoadedConfiguration.$Key.Tests.$Test.Parameters.Property) {
                            $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Parameters']['Property'] = $LoadedConfiguration.$Key.Tests.$Test.Parameters.Property
                        }
                        if ($null -ne $LoadedConfiguration.$Key.Tests.$Test.Parameters.OperationType) {
                            $Script:TestimoConfiguration[$Target][$Key]['Tests'][$Test]['Parameters']['OperationType'] = $LoadedConfiguration.$Key.Tests.$Test.Parameters.OperationType
                        }
                    }
                } else {

                }
            }
        }
        Out-Informative -OverrideTitle 'Testimo' -Status $null -Domain $Domain -DomainController $DomainController -ExtendedValue ("Configuration loaded from $Option") -End

    } else {
        Out-Informative -OverrideTitle 'Testimo' -Text "Using configuration defaults" -Level 0 -Status $null -ExtendedValue ("No configuration provided by user")
    }
}


function Initialize-TestimoTests {
    <#
    .SYNOPSIS
    Simple command that goes thru all the tests and makes sure minimal tests are "improved" to become standard test
 
    .DESCRIPTION
    Simple command that goes thru all the tests and makes sure minimal tests are "improved" to become standard test
 
    .EXAMPLE
    Initialize-TestimoTests
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param()

    foreach ($Key in $Script:TestimoConfiguration.Keys) {
        if ($Key -notin 'Types', 'Exclusions', 'Inclusions', 'Debug') {
            foreach ($Source in [string[]] $Script:TestimoConfiguration[$Key].Keys) {
                foreach ($TestName in [string[]] $Script:TestimoConfiguration[$Key][$Source].Tests.Keys) {
                    $TestValue = $Script:TestimoConfiguration[$Key][$Source].Tests.$TestName
                    if ($TestValue -is [System.Collections.IDictionary]) {
                        if ($TestValue.Details) {
                            if (-not $TestValue.Details.Category) {
                                if ($Script:TestimoConfiguration[$Key][$Source].Source.Details -and $Script:TestimoConfiguration[$Key][$Source].Source.Details.Category) {
                                    $TestValue.Details.Category = $Script:TestimoConfiguration[$Key][$Source].Source.Details.Category
                                }
                            }
                            if (-not $TestValue.Details.Category) {
                                if ($Script:TestimoConfiguration[$Key][$Source].Source.Details -and $Script:TestimoConfiguration[$Key][$Source].Source.Details.ActionType) {
                                    $TestValue.Details.ActionType = $Script:TestimoConfiguration[$Key][$Source].Source.Details.ActionType
                                }
                            }
                        }
                    } else {
                        # we use configuration as default category
                        $DefaultCategory = 'Configuration'
                        # but we also check if source has something different and we use it
                        if ($Script:TestimoConfiguration[$Key][$Source].Source.Details -and $Script:TestimoConfiguration[$Key][$Source].Source.Details.Category) {
                            $DefaultCategory = $Script:TestimoConfiguration[$Key][$Source].Source.Details.Category
                        }

                        # Overwrite the test value with the default settings
                        # This is to support basic paramters testing in a way that DSC does
                        $Script:TestimoConfiguration[$Key][$Source].Tests.$TestName = [ordered] @{
                            Enable     = $true
                            Name       = $TestName
                            Parameters = @{
                                Property = $TestName
                                OperationType = 'eq'
                                ExpectedValue = $TestValue
                            }
                            Details    = [ordered] @{
                                Category    = $DefaultCategory
                                Importance  = 5
                                ActionType  = 2
                                StatusTrue  = 1
                                StatusFalse = 5
                            }
                        }
                    }
                }
            }
        }
    }
}
function New-ChartData {
    <#
    .SYNOPSIS
    Creates a chart data structure based on the input results.
 
    .DESCRIPTION
    This function takes an array of results and generates a chart data structure that counts the occurrences of each assessment. If an assessment is null, it is categorized as 'Skipped'.
 
    .PARAMETER Results
    The array of results to generate the chart data from.
 
    .EXAMPLE
    $results = @(
        [PSCustomObject]@{ Assessment = 'Pass' },
        [PSCustomObject]@{ Assessment = 'Fail' },
        [PSCustomObject]@{ Assessment = 'Pass' },
        [PSCustomObject]@{ Assessment = $null },
        [PSCustomObject]@{ Assessment = 'Pass' }
    )
    New-ChartData -Results $results
 
    This example creates a chart data structure based on the provided results array.
 
    #>

    [cmdletBinding()]
    param(
        $Results
    )
    $ChartData = [ordered] @{}
    foreach ($Result in $Results) {
        if ($null -ne $Result.Assessment) {
            if (-not $ChartData[$Result.Assessment]) {
                $ChartData[$Result.Assessment] = [ordered] @{
                    Count = 0
                    Color = $Script:StatusToColors[$Result.Assessment]
                }
            }
            $ChartData[$Result.Assessment].Count++
        } else {
            # if for whatever reason result.assesment is null we need to improvise
            if (-not $ChartData['Skipped']) {
                $ChartData['Skipped'] = [ordered] @{
                    Count = 0
                    Color = $Script:StatusToColors['Skipped']
                }
            }
            $ChartData['Skipped'].Count++
        }
    }
    $ChartData
}
function Out-Begin {
    <#
    .SYNOPSIS
    Outputs formatted text based on specified parameters.
 
    .DESCRIPTION
    The Out-Begin function outputs formatted text to the console based on the provided Scope, Text, Level, Type, Domain, and DomainController parameters.
 
    .PARAMETER Scope
    Specifies the scope of the output. Valid values are 'Forest', 'Domain', or 'DC'.
 
    .PARAMETER Text
    Specifies the text to be displayed.
 
    .PARAMETER Level
    Specifies the level of the output.
 
    .PARAMETER Type
    Specifies the type of output. Default value is 't'.
 
    .PARAMETER Domain
    Specifies the domain for the output.
 
    .PARAMETER DomainController
    Specifies the domain controller for the output.
 
    .EXAMPLE
    Out-Begin -Scope 'Forest' -Text 'Sample text' -Level 1 -Type 't' -Domain 'ExampleDomain' -DomainController 'DC1'
    Outputs formatted text for the Forest scope with the specified parameters.
 
    .EXAMPLE
    Out-Begin -Scope 'Domain' -Text 'Error message' -Level 2 -Type 'e' -Domain 'AnotherDomain'
    Outputs an error message for the Domain scope with the specified parameters.
    #>

    [CmdletBinding()]
    param(
        [string] $Scope,
        [string] $Text,
        [int] $Level,
        [string] $Type = 't',
        [string] $Domain,
        [string] $DomainController
    )
    if ($Scope -in 'Forest', 'Domain', 'DC') {
        if ($Domain -and $DomainController) {
            if ($Type -eq 't') {
                [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow
            } elseif ($Type -eq 'e') {
                [ConsoleColor[]] $Color = [ConsoleColor]::Red, [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
            } elseif ($Type -eq 'e') {
                [ConsoleColor[]] $Color = [ConsoleColor]::Red, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
            } else {
                [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
            }
            $TestText = "[$Type]", "[$Domain] ", $Text
        } elseif ($DomainController) {
            # Shouldn't really happen
            Write-Warning "Out-Begin - Shouldn't happen - Fix me."
        } else {
            if ($Type -eq 't') {
                [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
            } elseif ($Type -eq 'e') {
                [ConsoleColor[]] $Color = [ConsoleColor]::Red, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
            } else {
                [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
            }
            $TestText = "[$Type]", "[Forest] ", $Text
        }
    } else {
        if ($Type -eq 't') {
            [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
        } elseif ($Type -eq 'e') {
            [ConsoleColor[]] $Color = [ConsoleColor]::Red, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
        } else {
            [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
        }
        $TestText = "[$Type]", "[$Scope] ", $Text
    }
    Write-Color -Text $TestText -Color $Color -StartSpaces $Level -NoNewLine
}
function Out-Failure {
    <#
    .SYNOPSIS
    Sends a failure status message with detailed information.
 
    .DESCRIPTION
    The Out-Failure function sends a failure status message with detailed information including the scope, text, level, extended value, domain, domain controller, reference ID, type, source, and test.
 
    .PARAMETER Scope
    Specifies the scope of the failure.
 
    .PARAMETER Text
    Specifies the text message associated with the failure.
 
    .PARAMETER Level
    Specifies the level of the failure.
 
    .PARAMETER ExtendedValue
    Specifies additional extended value information for the failure. Default is 'Input data not provided. Failing test.'.
 
    .PARAMETER Domain
    Specifies the domain associated with the failure.
 
    .PARAMETER DomainController
    Specifies the domain controller related to the failure.
 
    .PARAMETER ReferenceID
    Specifies a reference ID for the failure.
 
    .PARAMETER Type
    Specifies the type of failure. Valid values are 'e' (error), 'i' (information), or 't' (test). Default is 't'.
 
    .PARAMETER Source
    Specifies the source of the failure as a dictionary.
 
    .PARAMETER Test
    Specifies the test information related to the failure as a dictionary.
 
    .EXAMPLE
    Out-Failure -Scope "Global" -Text "Connection failed" -Level 3 -Domain "example.com" -DomainController "DC1" -ReferenceID "12345" -Type "e" -Source @{"SourceKey"="SourceValue"} -Test @{"TestKey"="TestValue"}
 
    Sends a failure status message with the specified parameters.
 
    #>

    [CmdletBinding()]
    param(
        [string] $Scope,
        [string] $Text,
        [int] $Level,
        [string] $ExtendedValue = 'Input data not provided. Failing test.',
        [string] $Domain,
        [string] $DomainController,
        [string] $ReferenceID,
        [validateSet('e', 'i', 't')][string] $Type = 't',
        [System.Collections.IDictionary] $Source,
        [System.Collections.IDictionary] $Test
    )
    Out-Begin -Scope $Scope -Text $Text -Level $Level -Domain $Domain -DomainController $DomainController -Type $Type
    Out-Status -Scope $Scope -Text $Text -Status $false -ExtendedValue $ExtendedValue -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $Source -Test $Test
}
function Out-Informative {
    <#
    .SYNOPSIS
    Outputs informative messages with customizable colors and status indicators.
 
    .DESCRIPTION
    The Out-Informative function is used to output informative messages with customizable colors and status indicators. It allows for specifying the level, title, text, status, extended value, and scope of the message.
 
    .PARAMETER Level
    Specifies the indentation level for the message.
 
    .PARAMETER OverrideTitle
    Specifies an optional title to override the default title.
 
    .PARAMETER OverrideTextStatus
    Specifies an optional text status to override the default status.
 
    .PARAMETER Domain
    Specifies the domain related to the message.
 
    .PARAMETER DomainController
    Specifies the domain controller related to the message.
 
    .PARAMETER Text
    Specifies the main text content of the message.
 
    .PARAMETER Status
    Specifies the status of the message (true for pass, false for fail, null for informative).
 
    .PARAMETER ExtendedValue
    Specifies additional extended value information for the message.
 
    .PARAMETER Start
    Indicates whether the message is the start of a section.
 
    .PARAMETER End
    Indicates whether the message is the end of a section.
 
    .PARAMETER Scope
    Specifies the scope of the message.
 
    .EXAMPLE
    Out-Informative -Level 1 -OverrideTitle 'Custom Title' -Text 'This is a custom message' -Status $true -ExtendedValue 'Additional info' -Scope 'Domain'
    Outputs a custom informative message with a specified title, text, pass status, extended value, and domain scope.
 
    .EXAMPLE
    Out-Informative -Level 0 -Text 'Default message' -Status $false
    Outputs a default informative message with a fail status.
 
    #>

    [CmdletBinding()]
    param(
        [int] $Level = 0,
        [string] $OverrideTitle,
        [string] $OverrideTextStatus,
        [string] $Domain,
        [string] $DomainController,
        [string] $Text,
        [nullable[bool]] $Status,
        [string] $ExtendedValue,
        [switch] $Start,
        [switch] $End,
        [string] $Scope
    )

    if ($Start -or (-not $Start -and -not $End)) {
        $Type = 'i'
        if ($Scope -in 'Forest', 'Domain', 'DC') {
            if ($Domain -and $DomainController) {
                [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::Yellow
                $TestText = "[$Type]", "[$Domain]", "[$($DomainController)] ", $Text
            } elseif ($Domain) {
                [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
                $TestText = "[$Type]", "[$Domain] ", $Text
            } elseif ($DomainController) {
                # Shouldn't really happen
                Write-Warning "Out-Begin - Shouldn't happen - Fix me."
            } else {
                [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
                if ($OverrideTitle) {
                    $TestText = "[$Type]", "[$OverrideTitle] ", $Text
                } else {
                    $TestText = "[$Type]", "[Forest] ", $Text
                }
            }
        } else {
            [ConsoleColor[]] $Color = [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow
            if ($OverrideTitle) {
                $TestText = "[$Type]", "[$OverrideTitle] ", $Text
            } else {
                if ($Scope) {
                    $TestText = "[$Type]", "[$Scope] ", $Text
                } else {
                    $TestText = "[$Type]", "[Testimo] ", $Text
                }
            }
        }
        Write-Color -Text $TestText -Color $Color -StartSpaces $Level -NoNewLine
    }
    if ($End -or (-not $Start -and -not $End)) {
        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]::Magenta, [ConsoleColor]::Cyan
        }
        if ($OverrideTextStatus) {
            $TextStatus = $OverrideTextStatus
        }
        if ($ExtendedValue) {
            Write-Color -Text ' [', $TextStatus, ']', " [", $ExtendedValue, "]" -Color $Color
        } else {
            Write-Color -Text ' [', $TextStatus, ']' -Color $Color
        }
    }
}
function Out-Skip {
    <#
    .SYNOPSIS
    Skips a specific test and updates the test summary.
 
    .DESCRIPTION
    The Out-Skip function is used to skip a specific test and update the test summary with the skipped test count. It also provides a reason for skipping the test.
 
    .PARAMETER Scope
    Specifies the scope of the test.
 
    .PARAMETER TestsSummary
    Specifies the summary of all tests.
 
    .PARAMETER Level
    Specifies the level of the test.
 
    .PARAMETER Domain
    Specifies the domain of the test.
 
    .PARAMETER DomainController
    Specifies the domain controller for the test.
 
    .PARAMETER Test
    Specifies the name of the test being skipped.
 
    .PARAMETER Source
    Specifies the source of the test.
 
    .PARAMETER Reason
    Specifies the reason for skipping the test. Default is 'Skipping - unmet dependency'.
 
    .EXAMPLE
    Out-Skip -Scope 'Integration' -TestsSummary $TestsSummary -Level 1 -Domain 'example.com' -DomainController 'DC1' -Test 'Test1' -Source 'ModuleA'
 
    Description
    -----------
    Skips the test named 'Test1' at level 1 in the 'Integration' scope for the domain 'example.com' using the domain controller 'DC1'. Updates the test summary with the skipped test count.
 
    #>

    [CmdletBinding()]
    param(
        [string] $Scope,
        [PSCustomobject] $TestsSummary,
        [int] $Level = 0,
        [string] $Domain,
        [string] $DomainController,
        [string] $Test,
        [string] $Source,
        [string] $Reason = 'Skipping - unmet dependency'

    )
    Out-Begin -Scope $Scope -Type 'i' -Text $Test -Level $Level -Domain $Domain -DomainController $DomainController

    Out-Status -Scope $Scope -Text $Test -Status $null -ExtendedValue $Reason -Domain $Domain -DomainController $DomainController -ReferenceID $Source

    $TestsSummary.Skipped = $TestsSummary.Skipped + 1
    $TestsSummary.Total = $TestsSummary.Failed + $TestsSummary.Passed + $TestsSummary.Skipped
    $TestsSummary
}
function Out-Status {
    <#
    .SYNOPSIS
    Outputs the status of a specific test or source.
 
    .DESCRIPTION
    The Out-Status function outputs the status of a specific test or source based on the provided parameters. It includes details such as the test type, importance, category, action type, and status translation.
 
    .PARAMETER Scope
    Specifies the scope of the test (e.g., 'Domain', 'DC').
 
    .PARAMETER TestID
    Specifies the unique identifier of the test.
 
    .PARAMETER Text
    Specifies additional text related to the test or source.
 
    .PARAMETER Status
    Specifies the status of the test or source.
 
    .PARAMETER Section
    Specifies the section to which the test or source belongs.
 
    .PARAMETER ExtendedValue
    Specifies any extended value associated with the test or source.
 
    .PARAMETER Domain
    Specifies the domain related to the test or source.
 
    .PARAMETER DomainController
    Specifies the domain controller related to the test or source.
 
    .PARAMETER Source
    An IDictionary containing details about the source.
 
    .PARAMETER Test
    An IDictionary containing details about the test.
 
    .PARAMETER ReferenceID
    Specifies the reference ID of the test or source.
 
    .EXAMPLE
    Out-Status -Scope 'Domain' -TestID '123' -Text 'Test description' -Status $true -Section 'Section1' -ExtendedValue 'Extended' -Domain 'example.com' -DomainController 'DC1' -Source $SourceDetails -Test $TestDetails -ReferenceID 'REF123'
    Outputs the status of a test in the 'Domain' scope with the specified details.
 
    .EXAMPLE
    Out-Status -Scope 'DC' -TestID '456' -Text 'Another test' -Status $false -Section 'Section2' -ExtendedValue 'Additional' -Domain 'example.com' -DomainController 'DC2' -Source $SourceDetails -Test $TestDetails -ReferenceID 'REF456'
    Outputs the status of a test in the 'DC' scope with the specified details.
    #>

    [CmdletBinding()]
    param(
        [string] $Scope,
        [string] $TestID,
        [string] $Text,
        [nullable[bool]] $Status,
        [string] $Section,
        [string] $ExtendedValue,
        [string] $Domain,
        [string] $DomainController,
        [System.Collections.IDictionary] $Source,
        [System.Collections.IDictionary] $Test,
        [string] $ReferenceID
    )
    if ($Domain -and $DomainController) {
        $TestType = 'Domain Controller'
        $TestText = "Domain Controller - $DomainController | $Text"
    } elseif ($Domain) {
        $TestType = 'Domain'
        $TestText = "Domain - $Domain | $Text"
    } else {
        $TestType = $Scope
        $TestText = "$Scope | $Text"
    }

    if ($Source -and -not $Test) {
        # This means we're dealing with source
        if (-not [string]::IsNullOrWhitespace($Source.Details.Importance)) {
            $ImportanceInformation = $Script:Importance[$Source.Details.Importance]
        } else {
            $ImportanceInformation = 'Not defined'
        }
        if (-not [string]::IsNullOrWhitespace($Source.Details.Category)) {
            $Category = $Source.Details.Category
        } else {
            $Category = 'Not defined'
        }
        if (-not [string]::IsNullOrWhitespace($Source.Details.ActionType)) {
            $Action = $Script:ActionType[$Source.Details.ActionType]
        } else {
            $Action = 'Not defined'
        }

        if ($null -ne $Source.Details.StatusTrue -and $null -ne $Source.Details.StatusFalse) {
            if ($Status -eq $true) {
                $StatusTranslation = $Script:StatusTranslation[$Source.Details.StatusTrue]
                $StatusColor = $Script:StatusTranslationConsoleColors[$Source.Details.StatusTrue]
            } elseif ($Status -eq $false) {
                $StatusTranslation = $Script:StatusTranslation[$Source.Details.StatusFalse]
                $StatusColor = $Script:StatusTranslationConsoleColors[$Source.Details.StatusFalse]
            } elseif ($null -eq $Status) {
                $StatusTranslation = $Script:StatusTranslation[0]
                $StatusColor = $Script:StatusTranslationConsoleColors[0]
            }
        } else {
            # We need to overwrite some values to better suite our reports
            #$StatusTranslation = $Status
            if ($Status -eq $true) {
                $StatusTranslation = $Script:StatusTranslation[1]
                $StatusColor = $Script:StatusTranslationConsoleColors[1]
            } elseif ($Status -eq $false) {
                $StatusTranslation = $Script:StatusTranslation[4]
                $StatusColor = $Script:StatusTranslationConsoleColors[4]
            } elseif ($null -eq $Status) {
                $StatusTranslation = $Script:StatusTranslation[0]
                $StatusColor = $Script:StatusTranslationConsoleColors[0]
            }
        }
    } else {
        if (-not [string]::IsNullOrWhitespace($Test.Details.Importance)) {
            $ImportanceInformation = $Script:Importance[$Test.Details.Importance]
        } else {
            $ImportanceInformation = 'Not defined'
        }
        if (-not [string]::IsNullOrWhitespace($Test.Details.Category)) {
            $Category = $Test.Details.Category
        } else {
            $Category = 'Not defined'
        }
        if (-not [string]::IsNullOrWhitespace($Test.Details.ActionType)) {
            $Action = $Script:ActionType[$Test.Details.ActionType]
        } else {
            $Action = 'Not defined'
        }
        if ($null -ne $Test.Details.StatusTrue -and $null -ne $Test.Details.StatusFalse) {
            if ($Status -eq $true) {
                $StatusTranslation = $Script:StatusTranslation[$Test.Details.StatusTrue]
                $StatusColor = $Script:StatusTranslationConsoleColors[$Test.Details.StatusTrue]
            } elseif ($Status -eq $false) {
                $StatusTranslation = $Script:StatusTranslation[$Test.Details.StatusFalse]
                $StatusColor = $Script:StatusTranslationConsoleColors[$Test.Details.StatusFalse]
            } elseif ($null -eq $Status) {
                $StatusTranslation = $Script:StatusTranslation[0]
                $StatusColor = $Script:StatusTranslationConsoleColors[0]
            }
        } else {
            # We need to overwrite some values to better suite our reports
            #$StatusTranslation = $Status
            if ($Status -eq $true) {
                $StatusTranslation = $Script:StatusTranslation[1]
                $StatusColor = $Script:StatusTranslationConsoleColors[1]
            } elseif ($Status -eq $false) {
                $StatusTranslation = $Script:StatusTranslation[4]
                $StatusColor = $Script:StatusTranslationConsoleColors[4]
            } elseif ($null -eq $Status) {
                $StatusTranslation = $Script:StatusTranslation[0]
                $StatusColor = $Script:StatusTranslationConsoleColors[0]
            }
        }
    }

    $Output = [PSCustomObject]@{
        Name             = $TestText
        Source           = $Source.Name
        DisplayName      = $Text
        Type             = $TestType
        Category         = $Category
        Assessment       = $StatusTranslation
        Status           = $Status
        Action           = $Action
        Importance       = $ImportanceInformation
        Extended         = $ExtendedValue
        Domain           = $Domain
        DomainController = $DomainController
    }
    if (-not $ReferenceID) {
        $Script:Reporting['Errors'].Add($Output)
    } else {
        if ($Scope -in 'Forest', 'Domain', 'DC') {
            if ($Domain -and $DomainController) {
                $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['Results'].Add($Output)
            } elseif ($Domain) {
                $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['Results'].Add($Output)
            } else {
                $Script:Reporting['Forest']['Tests'][$ReferenceID]['Results'].Add($Output)
            }
        } else {
            $Script:Reporting[$Scope]['Tests'][$ReferenceID]['Results'].Add($Output)
        }
    }
    if ($null -eq $StatusColor) {
        Write-Warning -Message "Status color for $StatusTranslation is not within -1 and 5 range. Test: $($Output.Name). Fix test!!"
        $StatusColor = [ConsoleColor]::Red
    }
    [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, $StatusColor, [ConsoleColor]::Cyan, [ConsoleColor]::Cyan, $StatusColor, [ConsoleColor]::Cyan
    if ($ExtendedValue) {
        Write-Color -Text ' [', $StatusTranslation, ']', " [", $ExtendedValue, "]" -Color $Color
    } else {
        Write-Color -Text ' [', $StatusTranslation, ']' -Color $Color
    }

    $Script:TestResults.Add($Output)
}
function Out-Summary {
    <#
    .SYNOPSIS
    Outputs a summary of the tests performed.
 
    .DESCRIPTION
    The Out-Summary function outputs a summary of the tests performed, including various details such as test results, execution time, and test counts.
 
    .PARAMETER Scope
    Specifies the scope of the tests (e.g., 'Forest', 'Domain', 'DC').
 
    .PARAMETER Time
    Specifies the stopwatch object to measure the time taken for the tests.
 
    .PARAMETER Text
    Specifies additional text to include in the summary.
 
    .PARAMETER Level
    Specifies the level of the summary.
 
    .PARAMETER Domain
    Specifies the domain for the tests.
 
    .PARAMETER DomainController
    Specifies the domain controller for the tests.
 
    .PARAMETER TestsSummary
    Specifies a custom object containing the summary of the tests.
 
    .EXAMPLE
    Out-Summary -Scope 'Domain' -Time $stopwatch -Text 'Additional information' -Level 1 -Domain 'example.com' -DomainController 'DC1' -TestsSummary $summaryObject
    Outputs a summary for the tests performed in the 'Domain' scope with the specified details.
 
    .EXAMPLE
    Out-Summary -Scope 'Forest' -Time $stopwatch -Text 'Detailed summary' -Level 2 -Domain 'example.com' -DomainController 'DC1' -TestsSummary $summaryObject
    Outputs a detailed summary for the tests performed in the 'Forest' scope with the specified details.
    #>

    [CmdletBinding()]
    param(
        [string] $Scope,
        [System.Diagnostics.Stopwatch] $Time,
        $Text,
        [int] $Level,
        [string] $Domain,
        [string] $DomainController,
        [PSCustomobject] $TestsSummary
    )
    $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
    $Type = 'i'
    if ($Scope -in 'Forest', 'Domain', 'DC') {
        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, # Type
                    [ConsoleColor]::DarkGray, # Domain
                    [ConsoleColor]::DarkGray, # Domain Controller
                    [ConsoleColor]::Yellow, # Text
                    [ConsoleColor]::Yellow, # [
                    [ConsoleColor]::DarkGray, # Time To Execute Text
                    [ConsoleColor]::Yellow, # Actual Time
                    [ConsoleColor]::DarkGray, # Bracket ]
                    [ConsoleColor]::DarkGray # Bracket [
                    [ConsoleColor]::Yellow, # Tests Total text
                    [ConsoleColor]::White, # Count Tests
                    [ConsoleColor]::Yellow # Tests Tests
                    [ConsoleColor]::Green # Tests passed
                    [ConsoleColor]::Yellow # Tests failed
                    [ConsoleColor]::Red # Tests failed count
                    [ConsoleColor]::Yellow # Tests skipped
                    [ConsoleColor]::Cyan # Tests skipped count
                )
            }
            $TestText = @(
                "[$Type]", # Yellow
                "[$Domain]", # DarkGray
                "[$($DomainController)] ", # DarkGray
                $Text, # Yellow
                ' [', # Yellow
                'Time to execute tests: ', # DarkGray
                $EndTime, # Yellow
                ']', # DarkGray
                '[', # DarkGray
                'Tests Total: ', # Yellow
            ($TestsSummary.Total), # White
                ', Passed: ', # Yellow
            ($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, # Type
                    [ConsoleColor]::DarkGray, # Domain
                    #[ConsoleColor]::DarkGray, # Domain Controller
                    [ConsoleColor]::Yellow, # Text
                    [ConsoleColor]::Yellow, # [
                    [ConsoleColor]::DarkGray, # Time To Execute Text
                    [ConsoleColor]::Yellow, # Actual Time
                    [ConsoleColor]::DarkGray, # Bracket ]
                    [ConsoleColor]::DarkGray # Bracket [
                    [ConsoleColor]::Yellow, # Tests Total text
                    [ConsoleColor]::White, # Count Tests
                    [ConsoleColor]::Yellow # Tests Tests
                    [ConsoleColor]::Green # Tests passed
                    [ConsoleColor]::Yellow # Tests failed
                    [ConsoleColor]::Red # Tests failed count
                    [ConsoleColor]::Yellow # Tests skipped
                    [ConsoleColor]::Cyan # Tests skipped count
                )
            }
            $TestText = @(
                "[$Type]", # Yellow
                "[$Domain] ", # DarkGray
                # "[$($DomainController)] ", # DarkGray
                $Text, # Yellow
                ' [', # Yellow
                'Time to execute tests: ', # DarkGray
                $EndTime, # Yellow
                ']', # DarkGray
                '[', # DarkGray
                'Tests Total: ', # Yellow
            ($TestsSummary.Total), # White
                ', Passed: ', # Yellow
            ($TestsSummary.Passed),
                ', Failed: ',
            ($TestsSummary.Failed),
                ', Skipped: ',
            ($TestsSummary.Skipped),
                ']'
            )
        } elseif ($DomainController) {
            # Shouldn't really happen
            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, # Type
                    [ConsoleColor]::DarkGray, # Domain / Forest
                    #[ConsoleColor]::DarkGray, # Domain Controller
                    [ConsoleColor]::Yellow, # Text
                    [ConsoleColor]::Yellow, # [
                    [ConsoleColor]::DarkGray, # Time To Execute Text
                    [ConsoleColor]::Yellow, # Actual Time
                    [ConsoleColor]::DarkGray, # Bracket ]
                    [ConsoleColor]::DarkGray # Bracket [
                    [ConsoleColor]::Yellow, # Tests Total text
                    [ConsoleColor]::White, # Count Tests
                    [ConsoleColor]::Yellow # Tests Tests
                    [ConsoleColor]::Green # Tests passed
                    [ConsoleColor]::Yellow # Tests failed
                    [ConsoleColor]::Red # Tests failed count
                    [ConsoleColor]::Yellow # Tests skipped
                    [ConsoleColor]::Cyan # Tests skipped count
                )
            }
            $TestText = @(
                "[$Type]", # Yellow
                "[Forest] ", # DarkGray
                # "[$($DomainController)] ", # DarkGray
                $Text, # Yellow
                ' [', # Yellow
                'Time to execute tests: ', # DarkGray
                $EndTime, # Yellow
                ']', # DarkGray
                '[', # DarkGray
                'Tests Total: ', # Yellow
            ($TestsSummary.Total), # White
                ', Passed: ', # Yellow
            ($TestsSummary.Passed),
                ', Failed: ',
            ($TestsSummary.Failed),
                ', Skipped: ',
            ($TestsSummary.Skipped),
                ']'
            )
        }
    } else {
        if ($Type -eq 't') {
            [ConsoleColor[]] $Color = [ConsoleColor]::Cyan, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray, [ConsoleColor]::Yellow, [ConsoleColor]::DarkGray
        } else {
            [ConsoleColor[]] $Color = @(
                [ConsoleColor]::Yellow, # Type
                [ConsoleColor]::DarkGray, # Domain / Forest
                #[ConsoleColor]::DarkGray, # Domain Controller
                [ConsoleColor]::Yellow, # Text
                [ConsoleColor]::Yellow, # [
                [ConsoleColor]::DarkGray, # Time To Execute Text
                [ConsoleColor]::Yellow, # Actual Time
                [ConsoleColor]::DarkGray, # Bracket ]
                [ConsoleColor]::DarkGray # Bracket [
                [ConsoleColor]::Yellow, # Tests Total text
                [ConsoleColor]::White, # Count Tests
                [ConsoleColor]::Yellow # Tests Tests
                [ConsoleColor]::Green # Tests passed
                [ConsoleColor]::Yellow # Tests failed
                [ConsoleColor]::Red # Tests failed count
                [ConsoleColor]::Yellow # Tests skipped
                [ConsoleColor]::Cyan # Tests skipped count
            )
        }
        $TestText = @(
            "[$Type]", # Yellow
            "[$Scope] ", # DarkGray
            # "[$($DomainController)] ", # DarkGray
            $Text, # Yellow
            ' [', # Yellow
            'Time to execute tests: ', # DarkGray
            $EndTime, # Yellow
            ']', # DarkGray
            '[', # DarkGray
            'Tests Total: ', # Yellow
        ($TestsSummary.Total), # White
            ', Passed: ', # Yellow
        ($TestsSummary.Passed),
            ', Failed: ',
        ($TestsSummary.Failed),
            ', Skipped: ',
        ($TestsSummary.Skipped),
            ']'
        )
    }
    Write-Color -Text $TestText -Color $Color -StartSpaces $Level
}
$Script:Importance = @{
    0  = 'Informational'
    1  = 'Negligible'
    2  = 'Very low'
    3  = 'Low'
    4  = 'Minor'
    5  = 'Moderate Low'
    6  = 'Moderate'
    7  = 'High'
    8  = 'Very High'
    9  = 'Significant'
    10 = 'Extreme'
}
$Script:StatusTranslation = @{
    -1 = 'Skipped'
    0  = 'Informational' # #4D9F6F # Low risk
    1  = 'Good'
    2  = 'Low' # #507DC6 # General Risk
    3  = 'Elevated' # #998D16 # Significant Risk
    4  = 'High' # #7A5928 High Risk
    5  = 'Severe' # #D65742 Server Risk
}

$Script:StatusToColors = @{
    'Skipped'       = 'DeepSkyBlue'
    'Informational' = 'CornflowerBlue'
    'Good'          = 'LawnGreen'
    'Low'           = 'ParisDaisy' # # General Risk
    'Elevated'      = 'SafetyOrange' # # Significant Risk
    'High'          = 'InternationalOrange' # High Risk
    'Severe'        = 'TorchRed' # Server Risk
    $true           = 'LawnGreen'
    $false          = 'TorchRed'
}

$Script:StatusTranslationColors = @{
    -1 = 'DeepSkyBlue'
    0  = 'CornflowerBlue'
    1  = 'LawnGreen'
    2  = 'ParisDaisy' # # General Risk
    3  = 'SafetyOrange' # # Significant Risk
    4  = 'InternationalOrange' # High Risk
    5  = 'TorchRed' # Server Risk
}
$Script:ActionType = @{
    0 = 'Informational'
    1 = 'Recommended'
    2 = 'Must Implement'
}
$Script:StatusTranslationConsoleColors = @{
    -1 = [System.ConsoleColor]::DarkBlue
    0  = [System.ConsoleColor]::DarkBlue
    1  = [System.ConsoleColor]::Green
    2  = [System.ConsoleColor]::Magenta # # General Risk
    3  = [System.ConsoleColor]::DarkMagenta # # Significant Risk
    4  = [System.ConsoleColor]::Red # High Risk
    5  = [System.ConsoleColor]::DarkRed # Server Risk
}

<#
$Script:WarningSystem = @{
    0 = 'All Clear'
    1 = 'Advice'
    2 = 'Watch and Act'
    3 = 'Emergency Warning'
}
$Script:PotentialImpact = @{
    0 = 'Neglible'
    1 = 'Minor'
    3 = 'Moderate'
    4 = 'Significant'
    5 = 'Severe'
}
$Script:Likelihood = @{
    0 = 'Very Unlikely'
    1 = 'Unlikely'
    2 = 'Possible'
    3 = 'Likely'
    4 = 'Very Likely'
}
$Script:Consequence = @{
    0 = 'Small'
    1 = 'Moderate'
    2 = 'Severe'
    3 = 'Catastrophic'
}
#>

function Set-TestsStatus {
    <#
    .SYNOPSIS
    Sets the status of tests based on provided parameters.
 
    .DESCRIPTION
    This function sets the status of tests based on the specified sources, tags, and exclusion criteria.
 
    .PARAMETER Sources
    Specifies an array of sources to include for test status setting.
 
    .PARAMETER ExcludeSources
    Specifies an array of sources to exclude for test status setting.
 
    .PARAMETER IncludeTags
    Specifies an array of tags to include for test status setting.
 
    .PARAMETER ExcludeTags
    Specifies an array of tags to exclude for test status setting.
 
    .EXAMPLE
    Set-TestsStatus -Sources Source1, Source2 -IncludeTags Tag1, Tag2
 
    Description:
    Sets the status of tests for Source1 and Source2 with tags Tag1 and Tag2 enabled.
 
    .EXAMPLE
    Set-TestsStatus -Sources Source3 -ExcludeSources Source4 -ExcludeTags Tag3
 
    Description:
    Sets the status of tests for Source3 with Source4 excluded and Tag3 disabled.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $Sources,
        [string[]] $ExcludeSources,
        [string[]] $IncludeTags,
        [string[]] $ExcludeTags
    )
    # we first disable all sources to make sure it's a clean start
    foreach ($Key in $Script:TestimoConfiguration.Keys) {
        if ($Key -notin 'Types', 'Exclusions', 'Inclusions', 'Debug') {
            foreach ($Source in $Script:TestimoConfiguration.$Key.Keys) {
                if ($Script:TestimoConfiguration[$Key][$Source]) {
                    $Script:TestimoConfiguration[$Key][$Source]['Enable'] = $false
                    $Script:TestimoConfiguration.Types[$Key] = $false
                }
            }
        }
    }
    # then we go thru the sources and enable them
    foreach ($Key in $Script:TestimoConfiguration.Keys) {
        if ($Key -notin 'Types', 'Exclusions', 'Inclusions', 'Debug') {
            foreach ($Tag in $IncludeTags) {
                if ($Script:TestimoConfiguration[$Key]) {
                    foreach ($Source in $Script:TestimoConfiguration[$Key].Keys) {
                        if ($Tag -in $Script:TestimoConfiguration[$Key][$Source]['Source']['Details'].Tags) {
                            $Script:TestimoConfiguration[$Key][$Source]['Enable'] = $true
                            $Script:TestimoConfiguration.Types[$Key] = $true
                        }
                    }
                }
            }
            foreach ($Source in $Sources) {
                if ($Script:TestimoConfiguration[$Key][$Source]) {
                    $Script:TestimoConfiguration[$Key][$Source]['Enable'] = $true
                    $Script:TestimoConfiguration.Types[$Key] = $true
                }
            }
            foreach ($Source in $ExcludeSources) {
                if ($Script:TestimoConfiguration[$Key][$Source]) {
                    $Script:TestimoConfiguration[$Key][$Source]['Enable'] = $false
                }
            }
            foreach ($Tag in $ExcludeTags) {
                if ($Script:TestimoConfiguration[$Key]) {
                    foreach ($Source in $Script:TestimoConfiguration[$Key].Keys) {
                        if ($Tag -in $Script:TestimoConfiguration[$Key][$Source]['Source']['Details'].Tags) {
                            $Script:TestimoConfiguration[$Key][$Source]['Enable'] = $false
                        }
                    }
                }
            }
        }
    }
}
function Start-TestimoEmail {
    <#
    .SYNOPSIS
    Sends an email with a summary of Active Directory tests.
 
    .DESCRIPTION
    This function sends an email with a summary of Active Directory tests. It allows customization of email settings such as From, To, CC, BCC, Server, Port, SSL, UserName, Password, Priority, and Subject.
 
    .PARAMETER From
    The email address from which the email will be sent.
 
    .PARAMETER To
    An array of email addresses to which the email will be sent.
 
    .PARAMETER CC
    An array of email addresses to be CC'd on the email.
 
    .PARAMETER BCC
    An array of email addresses to be BCC'd on the email.
 
    .PARAMETER Server
    The SMTP server used to send the email.
 
    .PARAMETER Port
    The port number of the SMTP server.
 
    .PARAMETER SSL
    Indicates whether SSL should be used for the email connection.
 
    .PARAMETER UserName
    The username for authentication with the SMTP server.
 
    .PARAMETER Password
    The password for authentication with the SMTP server.
 
    .PARAMETER PasswordAsSecure
    Indicates whether the password should be treated as a secure string.
 
    .PARAMETER PasswordFromFile
    Indicates whether the password should be read from a file.
 
    .PARAMETER Priority
    The priority of the email. Default is 'High'.
 
    .PARAMETER Subject
    The subject of the email. Default is '[Reporting Evotec] Summary of Active Directory Tests'.
 
    .EXAMPLE
    Start-TestimoEmail -From "sender@example.com" -To "recipient@example.com" -Server "mail.example.com" -Port 587 -SSL -UserName "username" -Password "password" -Priority "High" -Subject "Summary of Active Directory Tests"
 
    Sends an email with the specified parameters.
 
    #>

    [CmdletBinding()]
    param(
        [string] $From,
        [string[]] $To,
        [string[]] $CC,
        [string[]] $BCC,
        [string] $Server,
        [int] $Port,
        [switch] $SSL,
        [string] $UserName,
        [string] $Password,
        [switch] $PasswordAsSecure,
        [switch] $PasswordFromFile,
        [string] $Priority = 'High',
        [string] $Subject = '[Reporting Evotec] Summary of Active Directory Tests'
    )
    Email {
        EmailHeader {
            EmailFrom -Address $From
            EmailTo -Addresses $To
            EmailServer -Server $Server -UserName $UserName -Password $PasswordFromFile -PasswordAsSecure:$PasswordAsSecure -PasswordFromFile:$PasswordFromFile -Port 587 -SSL:$SSL
            EmailOptions -Priority $Priority -DeliveryNotifications Never
            EmailSubject -Subject $Subject
        }
        EmailBody -FontFamily 'Calibri' -Size 15 {
            #EmailText -Text "Summary of Active Directory Tests" -Color None, Blue -LineBreak

            EmailTable -DataTable $Results {
                EmailTableCondition -ComparisonType 'string' -Name 'Status' -Operator eq -Value 'True' -BackgroundColor Green -Color White -Inline -Row
                EmailTableCondition -ComparisonType 'string' -Name 'Status' -Operator ne -Value 'True' -BackgroundColor Red -Color White -Inline -Row
            } -HideFooter
        }
    } -AttachSelf -Supress $false
}
function Start-TestimoReport {
    <#
    .SYNOPSIS
    Generates a test report based on the provided test results.
 
    .DESCRIPTION
    This function generates a test report based on the provided test results. It allows customization of colors and conditions for displaying test results.
 
    .PARAMETER TestResults
    An IDictionary containing the test results to be included in the report.
 
    .PARAMETER FilePath
    The file path where the report will be saved. If not provided, a temporary file with an HTML extension will be created.
 
    .PARAMETER Online
    Indicates whether the report should be displayed online.
 
    .PARAMETER ShowHTML
    Indicates whether the report should be shown in HTML format.
 
    .PARAMETER HideSteps
    Indicates whether detailed steps should be hidden in the report.
 
    .PARAMETER AlwaysShowSteps
    Indicates whether detailed steps should always be shown in the report.
 
    .PARAMETER Scopes
    An array of scopes to be included in the report.
 
    .PARAMETER SplitReports
    Indicates whether the report should be split into multiple reports based on specified conditions.
 
    .EXAMPLE
    Start-TestimoReport -TestResults $TestResults -FilePath "C:\Reports\TestReport.html" -ShowHTML -AlwaysShowSteps
    Generates a test report using the provided test results, saves it to the specified file path, shows it in HTML format, and always displays detailed steps.
 
    .EXAMPLE
    Start-TestimoReport -TestResults $TestResults -FilePath "C:\Reports\TestReport.html" -Online -Scopes "Scope1", "Scope2" -SplitReports
    Generates a test report using the provided test results, displays it online, includes specified scopes, and splits the report based on conditions.
 
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $TestResults,
        [string] $FilePath,
        [switch] $Online,
        [switch] $ShowHTML,
        [switch] $HideSteps,
        [switch] $AlwaysShowSteps,
        [string[]] $Scopes,
        [switch] $SplitReports
    )
    if ($FilePath -eq '') {
        $FilePath = Get-FileName -Extension 'html' -Temporary
    }

    $ColorPassed = 'LawnGreen'
    $ColorSkipped = 'DeepSkyBlue'
    $ColorFailed = 'TorchRed'
    $ColorPassedText = 'Black'
    $ColorFailedText = 'Black'
    $ColorSkippedText = 'Black'

    $TestResults['Configuration'] = @{
        Colors = @{
            ColorPassed      = $ColorPassed
            ColorSkipped     = $ColorSkipped
            ColorFailed      = $ColorFailed
            ColorPassedText  = $ColorPassedText
            ColorFailedText  = $ColorFailedText
            ColorSkippedText = $ColorSkippedText
        }
    }
    $TestResults['Configuration']['ResultConditions'] = {
        #New-TableCondition -Name 'Status' -Value $true -BackgroundColor 'LawnGreen'
        # #New-TableCondition -Name 'Status' -Value $false -BackgroundColor 'Tomato'
        # New-TableCondition -Name 'Status' -Value $null -BackgroundColor 'DeepSkyBlue'
        foreach ($Status in $Script:StatusTranslation.Keys) {
            New-HTMLTableCondition -Name 'Assessment' -Value $Script:StatusTranslation[$Status] -BackgroundColor $Script:StatusTranslationColors[$Status] #-Row
        }
        New-HTMLTableCondition -Name 'Assessment' -Value $true -BackgroundColor $TestResults['Configuration']['Colors']['ColorPassed'] -Color $TestResults['Configuration']['Colors']['ColorPassedText'] #-Row
        New-HTMLTableCondition -Name 'Assessment' -Value $false -BackgroundColor $TestResults['Configuration']['Colors']['ColorFailed'] -Color $TestResults['Configuration']['Colors']['ColorFailedText'] #-Row
    }
    $TestResults['Configuration']['ResultConditionsEmail'] = {
        $Translations = @{
            -1 = 'Skipped'
            0  = 'Informational' # #4D9F6F # Low risk
            1  = 'Good'
            2  = 'Low' # #507DC6 # General Risk
            3  = 'Elevated' # #998D16 # Significant Risk
            4  = 'High' # #7A5928 High Risk
            5  = 'Severe' # #D65742 Server Risk
        }
        $TranslationsColors = @{
            -1 = 'DeepSkyBlue'
            0  = 'ElectricBlue'
            1  = 'LawnGreen'
            2  = 'ParisDaisy' # # General Risk
            3  = 'SafetyOrange' # # Significant Risk
            4  = 'InternationalOrange' # High Risk
            5  = 'TorchRed' # Server Risk
        }
        foreach ($Status in $Translations.Keys) {
            New-HTMLTableCondition -Name 'Assessment' -Value $Translations[$Status] -BackgroundColor $TranslationsColors[$Status] -Inline
        }
        New-HTMLTableCondition -Name 'Assessment' -Value $true -BackgroundColor 'LawnGreen' -Inline
        New-HTMLTableCondition -Name 'Assessment' -Value $false -BackgroundColor 'TorchRed' -Inline
    }
    # [Array] $PassedTests = $TestResults['Results'] | Where-Object { $_.Status -eq $true }
    # [Array] $FailedTests = $TestResults['Results'] | Where-Object { $_.Status -eq $false }
    # [Array] $SkippedTests = $TestResults['Results'] | Where-Object { $_.Status -ne $true -and $_.Status -ne $false }
    if ($SplitReports) {
        Start-TestimoReportHTMLWithSplit -TestResults $TestResults -FilePath $FilePath -Online:$Online -ShowHTML:$ShowHTML.IsPresent -HideSteps:$HideSteps.IsPresent -AlwaysShowSteps:$AlwaysShowSteps.IsPresent -Scopes $Scopes
    } else {
        Start-TestimoReportHTML -TestResults $TestResults -FilePath $FilePath -Online:$Online -ShowHTML:$ShowHTML.IsPresent -HideSteps:$HideSteps.IsPresent -AlwaysShowSteps:$AlwaysShowSteps.IsPresent -Scopes $Scopes
    }
}
function Start-TestimoReportHTML {
    <#
    .SYNOPSIS
    Generates an HTML report based on the provided test results.
 
    .DESCRIPTION
    This function generates an HTML report based on the test results provided. It allows customization of the report format and content.
 
    .PARAMETER TestResults
    Specifies the test results to be included in the report.
 
    .PARAMETER FilePath
    Specifies the file path where the HTML report will be saved.
 
    .PARAMETER Online
    Indicates whether the report should be viewable online.
 
    .PARAMETER ShowHTML
    Indicates whether to display the HTML content.
 
    .PARAMETER HideSteps
    Indicates whether to hide detailed steps in the report.
 
    .PARAMETER AlwaysShowSteps
    Indicates whether to always show detailed steps in the report.
 
    .PARAMETER Scopes
    Specifies the scopes to be included in the report.
 
    .EXAMPLE
    Start-TestimoReportHTML -TestResults $TestResults -FilePath "C:\Reports\TestReport.html" -Online -ShowHTML -Scopes "Forest", "Domains"
    Generates an HTML report based on the provided test results, saves it to "C:\Reports\TestReport.html", displays it online, and includes scopes "Forest" and "Domains".
 
    .NOTES
    File Name : Start-TestimoReportHTML.ps1
    Prerequisite : This function requires the New-HTML and Start-TimeLog functions.
    #>

    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $TestResults,
        [string] $FilePath,
        [switch] $Online,
        [switch] $ShowHTML,
        [switch] $HideSteps,
        [switch] $AlwaysShowSteps,
        [string[]] $Scopes

    )
    $Time = Start-TimeLog
    Out-Informative -OverrideTitle 'Testimo' -Text 'HTML Report Generation Started' -Level 0 -Status $null #-ExtendedValue $Script:Reporting['Version']

    New-HTML -FilePath $FilePath -Online:$Online {
        New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
        New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoin -ArrayJoinString ', ' -DateTimeFormat 'dd.MM.yyyy HH:mm:ss'
        New-HTMLTabStyle -BorderRadius 0px -BackgroundColorActive SlateGrey

        New-HTMLHeader {
            New-HTMLSection -Invisible {
                New-HTMLSection {
                    New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue
                } -JustifyContent flex-start -Invisible
                New-HTMLSection {
                    New-HTMLText -Text $Script:Reporting['Version'] -Color Blue
                } -JustifyContent flex-end -Invisible
            }
        }

        # Find amount of sources used. If just one, skip summary
        $NumberOfSourcesExecuted = 0
        #$NumberOfSourcesExecuted += $TestResults['Forest']['Tests'].Count
        foreach ($Key in $TestResults.Keys) {
            if ($Key -notin 'Version', 'Errors', 'Results', 'Summary', 'Domains', 'BySource', 'Configuration') {
                $NumberOfSourcesExecuted += $TestResults[$Key]['Tests'].Count
            }
        }
        foreach ($Domain in $TestResults['Domains'].Keys) {
            $NumberOfSourcesExecuted += $TestResults['Domains'][$Domain]['Tests'].Count
            $NumberOfSourcesExecuted += $TestResults['Domains'][$Domain]['DomainControllers'].Count
        }

        if ($NumberOfSourcesExecuted -gt 1) {
            Start-TestimoReportSummaryAdvanced -TestResults $TestResults
        }
        foreach ($Scope in $Scopes) {
            if ($TestResults[$Scope]['Tests'].Count -gt 0) {
                # There's at least 1 forest test - so lets go
                if ($NumberOfSourcesExecuted -eq 1) {
                    # there's just one forest test, and only 1 forest test in total so we don't need tabs
                    foreach ($Source in $TestResults[$Scope]['Tests'].Keys) {
                        $Name = $TestResults[$Scope]['Tests'][$Source]['Name']
                        $Data = $TestResults[$Scope]['Tests'][$Source]['Data']
                        $Information = $TestResults[$Scope]['Tests'][$Source]['Information']
                        $SourceCode = $TestResults[$Scope]['Tests'][$Source]['SourceCode']
                        $Results = $TestResults[$Scope]['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended
                        $WarningsAndErrors = $TestResults[$Scope]['Tests'][$Source]['WarningsAndErrors']
                        try {
                            Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -TestResults $TestResults -Type $Scope -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                        } catch {
                            Write-Warning -Message "Failed to generate report (1) for $Source in $Scope"
                        }
                    }
                } else {
                    New-HTMLTab -Name $Scope -IconBrands first-order {
                        foreach ($Source in $TestResults[$Scope]['Tests'].Keys) {
                            $Name = $TestResults[$Scope]['Tests'][$Source]['Name']
                            $Data = $TestResults[$Scope]['Tests'][$Source]['Data']
                            $Information = $TestResults[$Scope]['Tests'][$Source]['Information']
                            $SourceCode = $TestResults[$Scope]['Tests'][$Source]['SourceCode']
                            $Results = $TestResults[$Scope]['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended
                            $WarningsAndErrors = $TestResults[$Scope]['Tests'][$Source]['WarningsAndErrors']
                            try {
                                Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -TestResults $TestResults -Type $Scope -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                            } catch {
                                Write-Warning -Message "Failed to generate report (2) for $Source in $Scope"
                            }
                        }
                    }
                }
            }
        }
        if ($TestResults['Forest']['Tests'].Count -gt 0) {
            # There's at least 1 forest test - so lets go
            if ($NumberOfSourcesExecuted -eq 1) {
                # there's just one forest test, and only 1 forest test in total so we don't need tabs
                foreach ($Source in $TestResults['Forest']['Tests'].Keys) {
                    $Name = $TestResults['Forest']['Tests'][$Source]['Name']
                    $Data = $TestResults['Forest']['Tests'][$Source]['Data']
                    $Information = $TestResults['Forest']['Tests'][$Source]['Information']
                    $SourceCode = $TestResults['Forest']['Tests'][$Source]['SourceCode']
                    $Results = $TestResults['Forest']['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended
                    $WarningsAndErrors = $TestResults['Forest']['Tests'][$Source]['WarningsAndErrors']
                    try {
                        Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -TestResults $TestResults -Type 'Forest' -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                    } catch {
                        Write-Warning -Message "Failed to generate report (3) for $Source in $Scope"
                    }
                }
            } else {
                New-HTMLTab -Name 'Forest' -IconBrands first-order {
                    foreach ($Source in $TestResults['Forest']['Tests'].Keys) {
                        $Name = $TestResults['Forest']['Tests'][$Source]['Name']
                        $Data = $TestResults['Forest']['Tests'][$Source]['Data']
                        $Information = $TestResults['Forest']['Tests'][$Source]['Information']
                        $SourceCode = $TestResults['Forest']['Tests'][$Source]['SourceCode']
                        $Results = $TestResults['Forest']['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended
                        $WarningsAndErrors = $TestResults['Forest']['Tests'][$Source]['WarningsAndErrors']
                        try {
                            Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -TestResults $TestResults -Type 'Forest' -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                        } catch {
                            Write-Warning -Message "Failed to generate report (4) for $Source in $Scope"
                        }
                    }
                }
            }
        }
        $DomainsFound = @{}
        [Array] $ProcessDomains = foreach ($Domain in $TestResults['Domains'].Keys) {
            if ($TestResults['Domains'][$Domain]['Tests'].Count -gt 0) {
                $DomainsFound[$Domain] = $true
                $true
            }
        }
        if ($ProcessDomains -contains $true) {
            New-HTMLTab -Name 'Domains' -IconBrands wpbeginner {
                foreach ($Domain in $TestResults['Domains'].Keys) {
                    if ($TestResults['Domains'][$Domain]['Tests'].Count -gt 0 -or $TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) {
                        New-HTMLTab -Name "Domain $Domain" -IconBrands deskpro {
                            foreach ($Source in $TestResults['Domains'][$Domain]['Tests'].Keys) {
                                $Information = $TestResults['Domains'][$Domain]['Tests'][$Source]['Information']
                                $Name = $TestResults['Domains'][$Domain]['Tests'][$Source]['Name']
                                $Data = $TestResults['Domains'][$Domain]['Tests'][$Source]['Data']
                                $SourceCode = $TestResults['Domains'][$Domain]['Tests'][$Source]['SourceCode']
                                $Results = $TestResults['Domains'][$Domain]['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended, Domain
                                $WarningsAndErrors = $TestResults['Domains'][$Domain]['Tests'][$Source]['WarningsAndErrors']
                                try {
                                    Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -HideSteps:$HideSteps -TestResults $TestResults -Type 'Domain' -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                                } catch {
                                    Write-Warning -Message "Failed to generate report (5) for $Source in domain $Domain"
                                }
                            }
                        }
                    }
                }
            }
        }

        if ($TestResults['Domains'].Keys.Count -gt 0) {
            $DomainsFound = @{}
            [Array] $ProcessDomainControllers = foreach ($Domain in $TestResults['Domains'].Keys) {
                if ($TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) {
                    $DomainsFound[$Domain] = $true
                    $true
                }
            }
            if ($ProcessDomainControllers -contains $true) {
                New-HTMLTab -Name 'Domain Controllers' -IconRegular snowflake {
                    foreach ($Domain in $TestResults['Domains'].Keys) {
                        if ($TestResults['Domains'][$Domain]['Tests'].Count -gt 0 -or $TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) {
                            New-HTMLTab -Name "Domain $Domain" -IconBrands deskpro {
                                if ($TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) {
                                    #New-HTMLTabPanel -Orientation vertical {
                                    foreach ($DC in $TestResults['Domains'][$Domain]['DomainControllers'].Keys) {
                                        New-HTMLTab -TabName $DC -TextColor DarkSlateGray {
                                            #-HeaderText "Domain Controller - $DC" -HeaderBackGroundColor DarkSlateGray {
                                            New-HTMLContainer {
                                                foreach ($Source in $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'].Keys) {
                                                    $Information = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Information']
                                                    $Name = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Name']
                                                    $Data = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Data']
                                                    $SourceCode = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['SourceCode']
                                                    $Results = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended, Domain, DomainController
                                                    $WarningsAndErrors = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['WarningsAndErrors']
                                                    try {
                                                        Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -TestResults $TestResults -Type 'DC' -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                                                    } catch {
                                                        Write-Warning -Message "Failed to generate report (6) for $Source in $Domain for $DC"
                                                    }
                                                }
                                            }
                                        }
                                        #}
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    } -ShowHTML:$ShowHTML
    $TimeEnd = Stop-TimeLog -Time $Time
    Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report Saved to $FilePath" -Level 0 -Status $null -ExtendedValue $TimeEnd
}
function Start-TestimoReportHTMLWithSplit {
    <#
    .SYNOPSIS
    Generates an HTML report based on test results with the option to split by sources.
 
    .DESCRIPTION
    This function generates an HTML report based on the provided test results. It allows for splitting the report by different sources. The report can be saved to a specified file path and can be viewed online. Additional options include showing or hiding steps, always showing steps, and specifying scopes for the report.
 
    .PARAMETER TestResults
    Specifies the test results to generate the report from.
 
    .PARAMETER FilePath
    Specifies the file path to save the generated HTML report.
 
    .PARAMETER Online
    Indicates whether the report should be viewable online.
 
    .PARAMETER ShowHTML
    Switch to display the generated HTML report.
 
    .PARAMETER HideSteps
    Switch to hide steps in the report.
 
    .PARAMETER AlwaysShowSteps
    Switch to always show steps in the report.
 
    .PARAMETER Scopes
    Specifies the scopes to include in the report.
 
    .EXAMPLE
    Start-TestimoReportHTMLWithSplit -TestResults $TestResults -FilePath "C:\Reports\TestReport.html" -Online -ShowHTML -Scopes "Forest", "Domains"
    Generates an HTML report based on the test results stored in $TestResults, saves it to "C:\Reports\TestReport.html", makes it viewable online, displays the HTML report, and includes scopes "Forest" and "Domains".
 
    .EXAMPLE
    Start-TestimoReportHTMLWithSplit -TestResults $TestResults -FilePath "C:\Reports\TestReport.html" -HideSteps
    Generates an HTML report based on the test results stored in $TestResults, saves it to "C:\Reports\TestReport.html", and hides steps in the report.
    #>

    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $TestResults,
        [string] $FilePath,
        [switch] $Online,
        [switch] $ShowHTML,
        [switch] $HideSteps,
        [switch] $AlwaysShowSteps,
        [string[]] $Scopes
    )

    $DateName = $(Get-Date -f yyyy-MM-dd_HHmmss)
    $FileName = [io.path]::GetFileNameWithoutExtension($FilePath)
    $DirectoryName = [io.path]::GetDirectoryName($FilePath)

    # Find amount of sources used. If just one, skip summary
    $NumberOfSourcesExecuted = 0
    #$NumberOfSourcesExecuted += $TestResults['Forest']['Tests'].Count
    foreach ($Key in $TestResults.Keys) {
        if ($Key -notin 'Version', 'Errors', 'Results', 'Summary', 'Domains', 'BySource', 'Configuration') {
            $NumberOfSourcesExecuted += $TestResults[$Key]['Tests'].Count
        }
    }
    foreach ($Domain in $TestResults['Domains'].Keys) {
        $NumberOfSourcesExecuted += $TestResults['Domains'][$Domain]['Tests'].Count
        $NumberOfSourcesExecuted += $TestResults['Domains'][$Domain]['DomainControllers'].Count
    }

    foreach ($Scope in $Scopes) {
        if ($TestResults[$Scope]['Tests'].Count -gt 0) {
            foreach ($Source in $TestResults[$Scope]['Tests'].Keys) {
                $Time = Start-TimeLog
                $NewFileName = $FileName + '_' + $Source + "_" + $DateName + '.html'
                $FilePath = [io.path]::Combine($DirectoryName, $NewFileName)
                Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report for $Source Generation Started" -Level 0 -Status $null #-ExtendedValue $Script:Reporting['Version']
                New-HTML -FilePath $FilePath -Online:$Online {
                    New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
                    New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoin -ArrayJoinString ', ' -DateTimeFormat 'dd.MM.yyyy HH:mm:ss'
                    New-HTMLTabStyle -BorderRadius 0px -BackgroundColorActive SlateGrey

                    New-HTMLHeader {
                        New-HTMLSection -Invisible {
                            New-HTMLSection {
                                New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue
                            } -JustifyContent flex-start -Invisible
                            New-HTMLSection {
                                New-HTMLText -Text $Script:Reporting['Version'] -Color Blue
                            } -JustifyContent flex-end -Invisible
                        }
                    }
                    $Name = $TestResults[$Scope]['Tests'][$Source]['Name']
                    $Data = $TestResults[$Scope]['Tests'][$Source]['Data']
                    $Information = $TestResults[$Scope]['Tests'][$Source]['Information']
                    $SourceCode = $TestResults[$Scope]['Tests'][$Source]['SourceCode']
                    $Results = $TestResults[$Scope]['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended
                    $WarningsAndErrors = $TestResults[$Scope]['Tests'][$Source]['WarningsAndErrors']
                    try {
                        Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -TestResults $TestResults -Type $Scope -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                    } catch {
                        Write-Warning -Message "Failed to generate report (1) for $Source in $Scope"
                    }
                } -ShowHTML:$ShowHTML.IsPresent
                $TimeEnd = Stop-TimeLog -Time $Time
                Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report for $Source Saved to $FilePath" -Level 0 -Status $null -ExtendedValue $TimeEnd
            }
        }
    }
    if ($TestResults['Forest']['Tests'].Count -gt 0) {
        # there's just one forest test, and only 1 forest test in total so we don't need tabs
        foreach ($Source in $TestResults['Forest']['Tests'].Keys) {
            $Time = Start-TimeLog
            $NewFileName = $FileName + '_' + $Source + "_" + $DateName + '.html'
            $FilePath = [io.path]::Combine($DirectoryName, $NewFileName)
            Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report for $Source Generation Started" -Level 0 -Status $null #-ExtendedValue $Script:Reporting['Version']
            New-HTML -FilePath $FilePath -Online:$Online {
                New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
                New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoin -ArrayJoinString ', ' -DateTimeFormat 'dd.MM.yyyy HH:mm:ss'
                New-HTMLTabStyle -BorderRadius 0px -BackgroundColorActive SlateGrey

                New-HTMLHeader {
                    New-HTMLSection -Invisible {
                        New-HTMLSection {
                            New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue
                        } -JustifyContent flex-start -Invisible
                        New-HTMLSection {
                            New-HTMLText -Text $Script:Reporting['Version'] -Color Blue
                        } -JustifyContent flex-end -Invisible
                    }
                }
                $Name = $TestResults['Forest']['Tests'][$Source]['Name']
                $Data = $TestResults['Forest']['Tests'][$Source]['Data']
                $Information = $TestResults['Forest']['Tests'][$Source]['Information']
                $SourceCode = $TestResults['Forest']['Tests'][$Source]['SourceCode']
                $Results = $TestResults['Forest']['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended
                $WarningsAndErrors = $TestResults['Forest']['Tests'][$Source]['WarningsAndErrors']
                try {
                    Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -TestResults $TestResults -Type 'Forest' -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                } catch {
                    Write-Warning -Message "Failed to generate report (3) for $Source in $Scope"
                }
            } -ShowHTML:$ShowHTML.IsPresent
            $TimeEnd = Stop-TimeLog -Time $Time
            Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report for $Source Saved to $FilePath" -Level 0 -Status $null -ExtendedValue $TimeEnd
        }

    }
    $DomainsFound = @{}
    [Array] $ProcessDomains = foreach ($Domain in $TestResults['Domains'].Keys) {
        if ($TestResults['Domains'][$Domain]['Tests'].Count -gt 0) {
            $DomainsFound[$Domain] = $true
            $true
        }
    }
    if ($ProcessDomains -contains $true) {
        # Establish the sources we have for Domains
        $DomainSources = foreach ($Domain in $TestResults['Domains'].Keys) {
            foreach ($Source in $TestResults['Domains'][$Domain]['Tests'].Keys) {
                $Source
            }
        }
        foreach ($Source in $DomainSources | Sort-Object -Unique) {
            $Time = Start-TimeLog
            $NewFileName = $FileName + '_' + $Source + "_" + $DateName + '.html'
            $FilePath = [io.path]::Combine($DirectoryName, $NewFileName)
            Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report for $Source Generation Started" -Level 0 -Status $null #-ExtendedValue $Script:Reporting['Version']
            New-HTML -FilePath $FilePath -Online:$Online {
                New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
                New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoin -ArrayJoinString ', ' -DateTimeFormat 'dd.MM.yyyy HH:mm:ss'
                New-HTMLTabStyle -BorderRadius 0px -BackgroundColorActive SlateGrey

                New-HTMLHeader {
                    New-HTMLSection -Invisible {
                        New-HTMLSection {
                            New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue
                        } -JustifyContent flex-start -Invisible
                        New-HTMLSection {
                            New-HTMLText -Text $Script:Reporting['Version'] -Color Blue
                        } -JustifyContent flex-end -Invisible
                    }
                }

                foreach ($Domain in $TestResults['Domains'].Keys) {
                    if ($TestResults['Domains'][$Domain]['Tests'].Count -gt 0 -or $TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) {

                        $Information = $TestResults['Domains'][$Domain]['Tests'][$Source]['Information']
                        $Name = $TestResults['Domains'][$Domain]['Tests'][$Source]['Name']
                        $Data = $TestResults['Domains'][$Domain]['Tests'][$Source]['Data']
                        $SourceCode = $TestResults['Domains'][$Domain]['Tests'][$Source]['SourceCode']
                        $Results = $TestResults['Domains'][$Domain]['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended, Domain
                        $WarningsAndErrors = $TestResults['Domains'][$Domain]['Tests'][$Source]['WarningsAndErrors']

                        if ($Results.Status -notcontains $False) {
                            $Title = "$Domain 💚"
                        } else {
                            $Title = "$Domain 📛"
                        }

                        New-HTMLTab -Name "Domain $Title" -IconBrands deskpro {
                            try {
                                Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -HideSteps:$HideSteps -TestResults $TestResults -Type 'Domain' -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                            } catch {
                                Write-Warning -Message "Failed to generate report (5) for $Source in domain $Domain"
                            }

                        }
                    }
                }
                # }
            } -ShowHTML:$ShowHTML.IsPresent
            $TimeEnd = Stop-TimeLog -Time $Time
            Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report for $Source Saved to $FilePath" -Level 0 -Status $null -ExtendedValue $TimeEnd
        }
    }

    if ($TestResults['Domains'].Keys.Count -gt 0) {
        $DomainsFound = @{}
        [Array] $ProcessDomainControllers = foreach ($Domain in $TestResults['Domains'].Keys) {
            if ($TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) {
                $DomainsFound[$Domain] = $true
                $true
            }
        }
        if ($ProcessDomainControllers -contains $true) {
            # Establish the sources we have for Domain Controllers
            $DCSources = foreach ($Domain in $TestResults['Domains'].Keys) {
                foreach ($DC in $TestResults['Domains'][$Domain]['DomainControllers'].Keys) {
                    foreach ($Source in $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'].Keys) {
                        $Source
                    }
                }
            }
            foreach ($Source in $DCSources | Sort-Object -Unique) {
                $Time = Start-TimeLog
                $NewFileName = $FileName + '_' + $Source + "_" + $DateName + '.html'
                $FilePath = [io.path]::Combine($DirectoryName, $NewFileName)
                Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report for $Source Generation Started" -Level 0 -Status $null #-ExtendedValue $Script:Reporting['Version']
                New-HTML -FilePath $FilePath -Online:$Online {
                    New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
                    New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoin -ArrayJoinString ', ' -DateTimeFormat 'dd.MM.yyyy HH:mm:ss'
                    New-HTMLTabStyle -BorderRadius 0px -BackgroundColorActive SlateGrey

                    New-HTMLHeader {
                        New-HTMLSection -Invisible {
                            New-HTMLSection {
                                New-HTMLText -Text "Report generated on $(Get-Date)" -Color Blue
                            } -JustifyContent flex-start -Invisible
                            New-HTMLSection {
                                New-HTMLText -Text $Script:Reporting['Version'] -Color Blue
                            } -JustifyContent flex-end -Invisible
                        }
                    }

                    foreach ($Domain in $TestResults['Domains'].Keys) {
                        if ($TestResults['Domains'][$Domain]['Tests'].Count -gt 0 -or $TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) {
                            if ($TestResults['Domains'][$Domain]['DomainControllers'].Count -gt 0) {
                                foreach ($DC in $TestResults['Domains'][$Domain]['DomainControllers'].Keys) {
                                    $Information = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Information']
                                    $Name = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Name']
                                    $Data = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Data']
                                    $SourceCode = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['SourceCode']
                                    $Results = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['Results'] #| Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended, Domain, DomainController
                                    $WarningsAndErrors = $TestResults['Domains'][$Domain]['DomainControllers'][$DC]['Tests'][$Source]['WarningsAndErrors']

                                    if ($Results.Status -notcontains $False) {
                                        $Title = "$DC 💚"
                                    } else {
                                        $Title = "$DC 📛"
                                    }

                                    New-HTMLTab -TabName $Title -TextColor DarkSlateGray {
                                        New-HTMLContainer {
                                            try {
                                                Start-TestimoReportSection -Name $Name -Data $Data -Information $Information -SourceCode $SourceCode -Results $Results -WarningsAndErrors $WarningsAndErrors -TestResults $TestResults -Type 'DC' -AlwaysShowSteps:$AlwaysShowSteps.IsPresent
                                            } catch {
                                                Write-Warning -Message "Failed to generate report (6) for $Source in $Domain for $DC"
                                            }

                                        }
                                    }

                                }
                            }

                        }
                    }

                } -ShowHTML:$ShowHTML.IsPresent
                $TimeEnd = Stop-TimeLog -Time $Time
                Out-Informative -OverrideTitle 'Testimo' -Text "HTML Report for $Source Saved to $FilePath" -Level 0 -Status $null -ExtendedValue $TimeEnd
            }
        }
    }
}
function Start-TestimoReportSection {
    <#
    .SYNOPSIS
    Starts a section for generating a test report.
 
    .DESCRIPTION
    This function starts a section for generating a test report with detailed information and visualizations.
 
    .PARAMETER Name
    The name of the test report section.
 
    .PARAMETER Data
    An array containing the test data.
 
    .PARAMETER Information
    A dictionary containing additional information.
 
    .PARAMETER SourceCode
    The scriptblock of the source code used for testing.
 
    .PARAMETER Results
    An array containing the test results.
 
    .PARAMETER WarningsAndErrors
    An array containing any warnings or errors encountered during testing.
 
    .PARAMETER HideSteps
    A switch to hide detailed steps in the report.
 
    .PARAMETER AlwaysShowSteps
    A switch to always show detailed steps in the report.
 
    .PARAMETER TestResults
    A dictionary containing the detailed test results.
 
    .PARAMETER Type
    The type of test report section (e.g., 'Forest', 'DC', 'Domain', 'Office 365').
 
    .EXAMPLE
    Start-TestimoReportSection -Name "Forest Report" -Data $ForestData -Information $AdditionalInfo -SourceCode { Get-ForestData } -Results $ForestResults -Type 'Forest'
 
    Starts a new section in the test report for a forest assessment with the specified data, additional information, source code, results, and type.
 
    .EXAMPLE
    Start-TestimoReportSection -Name "Domain Report" -Data $DomainData -Information $AdditionalInfo -SourceCode { Get-DomainData } -Results $DomainResults -Type 'Domain'
 
    Starts a new section in the test report for a domain assessment with the specified data, additional information, source code, results, and type.
    #>

    [cmdletBinding()]
    param(
        [string] $Name,
        [Array] $Data,
        [System.Collections.IDictionary]$Information,
        [Scriptblock]$SourceCode,
        [Array] $Results,
        [Array] $WarningsAndErrors,
        [switch] $HideSteps,
        [switch] $AlwaysShowSteps,
        [System.Collections.IDictionary]$TestResults,
        [string] $Type
    )
    [Array] $FailedTestsSingular = $Results | Where-Object { $_.Status -eq $false }

    if ($Type -eq 'Forest') {
        $ResultsDisplay = $Results | Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended
    } elseif ($Type -eq 'DC') {
        $ResultsDisplay = $Results | Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended, Domain
    } elseif ($Type -eq 'Domain') {
        $ResultsDisplay = $Results | Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended, Domain, DomainController
    } else {
        # Office 365 and other scopes
        $ResultsDisplay = $Results | Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended
    }
    $ResultsCache = [ordered] @{}
    foreach ($Result in $ResultsDisplay) {
        $ResultsCache[$Result.DisplayName] = $Result
    }

    $ChartData = New-ChartData -Results $Results

    New-HTMLSection -HeaderText $Name -HeaderBackGroundColor CornflowerBlue -Direction column {
        New-HTMLSection -Invisible -Direction column {
            New-HTMLSection -HeaderText 'Information' {
                New-HTMLContainer {
                    New-HTMLChart {
                        foreach ($Key in $ChartData.Keys) {
                            New-ChartPie -Name $Key -Value $ChartData[$Key].Count -Color $ChartData[$Key].Color
                        }
                    } -Height 250
                    New-HTMLText -Text @(
                        "Below command was used to generate and asses current data that is visible in this report. "
                        "In case there are more information required feel free to confirm problems found yourself. "
                    ) -FontSize 10pt
                    if ($SourceCode) {
                        New-HTMLCodeBlock -Code $SourceCode -Style 'PowerShell' -Theme enlighter
                    } elseif ($Information.Source.DataCode) {
                        New-HTMLCodeBlock -Code $Information.Source.DataCode -Style 'PowerShell' -Theme enlighter
                    }
                    if ($WarningsAndErrors) {
                        New-HTMLSection -HeaderText 'Warnings & Errors' -HeaderBackGroundColor OrangePeel {
                            New-HTMLTable -DataTable $WarningsAndErrors -Filtering -PagingLength 7
                        }
                    }
                }
                New-HTMLContainer {
                    if ($Information.Source.Details) {
                        if ($Information.DataDescription) {
                            & $Information.DataDescription
                        } elseif ($Information.Source.Details.Description) {
                            New-HTMLText -Text $Information.Source.Details.Description -FontSize 10pt
                        }

                        $SummaryOfTests = foreach ($Test in $Information.Tests.Keys) {
                            if ($Information.Tests[$Test].Enable -eq $true -and $Information.Tests[$Test].Details.Description) {
                                New-HTMLListItem -FontSize 10pt -Text $Information.Tests[$Test].Name, " - ", $Information.Tests[$Test].Details.Description
                                if ($Information.Tests[$Test].Details.Resources) {
                                    New-HTMLList -FontSize 10pt {
                                        foreach ($Resource in $Information.Tests[$Test].Details.Resources) {
                                            if ($Resource.StartsWith('[')) {
                                                New-HTMLListItem -Text $Resource
                                            } else {
                                                # Since the link is given in pure form, we want to convert it to markdown link
                                                $Resource = "[$Resource]($Resource)"
                                                New-HTMLListItem -Text $Resource
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ($SummaryOfTests) {
                            New-HTMLList {
                                $SummaryOfTests
                            }
                        }

                        if ($Information.Source.Details.Resources) {
                            #New-HTMLText -LineBreak
                            New-HTMLText -Text 'Following resources may be helpful to understand this topic', ', please make sure to read those to understand this topic before following any instructions.' -FontSize 10pt -FontWeight bold, normal
                            New-HTMLList -FontSize 10pt {
                                foreach ($Resource in $Information.Source.Details.Resources) {
                                    if ($Resource.StartsWith('[')) {
                                        New-HTMLListItem -Text $Resource
                                    } else {
                                        # Since the link is given in pure form, we want to convert it to markdown link
                                        $Resource = "[$Resource]($Resource)"
                                        New-HTMLListItem -Text $Resource
                                    }
                                }
                            }
                        }
                    }
                    #New-HTMLText -FontSize 10pt -Text 'Summary of Test results for ', $Name -FontWeight bold
                    New-HTMLText -FontSize 10pt -Text @(
                        "In the table below you can find summary of tests executed in the "
                        $Name
                        " category. Each test has their "
                        "assessment level ", ', '
                        "importance level ", ' and '
                        "action ",
                        "defined. "
                        "Depending on the assessment, importance and action AD Team needs to investigate according to the steps provided including using their internal processes (for example SOP). "
                        "It's important to have an understanding what the test is trying to tell you and what solution is provided. "
                        "If you have doubts, or don't understand some test please consider talking to senior admins for guidance. "
                    ) -FontWeight normal, bold, normal, bold, normal, bold, normal, bold, normal, normal, normal, normal
                    New-HTMLTable -DataTable $ResultsDisplay {
                        & $TestResults['Configuration']['ResultConditions']
                    } -Filtering -PagingLength 10
                }
            }
        }
        # If there is no data to display we don't want to add empty table and section to the report. It makes no sense to take useful resources.
        if ($Data) {
            New-HTMLSection -HeaderText 'Data' {
                New-HTMLContainer {
                    if ($Information.DataInformation) {
                        & $Information.DataInformation
                    }
                    New-HTMLTable -DataTable $Data -Filtering {
                        if ($Information.DataHighlights) {
                            & $Information.DataHighlights
                        } else {
                            foreach ($Test in $Information.Tests.Values) {
                                if ($Test.Enable -eq $true) {
                                    if ($null -ne $Test.Parameters -and $Test.Parameters.ContainsKey('ExpectedValue')) {
                                        #$TemporaryResults = $ResultsCache[$Test.Name]
                                        #$StatusColor = $Script:StatusToColors[$TemporaryResults.Assessment]
                                        # We need to fix PSWriteHTML to support New-HTMLTableContent for javascript based content
                                        #New-HTMLTableContent -ColumnName $Test.Parameters.Property -RowIndex 1 -BackGroundColor $StatusColor
                                    }
                                }
                            }
                        }
                    } -PagingLength 7 -DateTimeSortingFormat 'DD.MM.YYYY HH:mm:ss' -WordBreak break-all -ScrollX -ExcludeProperty 'PropertyNames', 'AddedProperties', 'RemovedProperties', 'ModifiedProperties', 'PropertyCount'
                }
            }
        }
        if ($Information.Solution) {
            if (($HideSteps.IsPresent -eq $false -and $FailedTestsSingular.Count -gt 0) -or $AlwaysShowSteps.IsPresent) {
                New-HTMLSection -Name 'Solution' {
                    & $Information.Solution
                }
            }
        }
    }
}
function Start-TestimoReportSummary {
    <#
    .SYNOPSIS
    Generates a summary report for Testimo test results.
 
    .DESCRIPTION
    This function generates a summary report for Testimo test results. It creates a visual representation of test results including charts and tables.
 
    .PARAMETER TestResults
    Specifies the test results to be summarized.
 
    .EXAMPLE
    $TestResults = @{
        'Results' = @(
            @{ 'TestName' = 'Test1'; 'Status' = 'Passed' },
            @{ 'TestName' = 'Test2'; 'Status' = 'Failed' }
        )
        'Summary' = @{ 'Total' = 2 }
        'Configuration' = @{
            'Colors' = @{
                'ColorPassed' = 'Green'
                'ColorFailed' = 'Red'
                'ColorPassedText' = 'White'
                'ColorFailedText' = 'Black'
            }
        }
    }
    Start-TestimoReportSummary -TestResults $TestResults
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $TestResults
    )
    $ChartData = New-ChartData -Results $TestResults['Results']
    $TableData = [ordered] @{}
    foreach ($Chart in $ChartData.Keys) {
        $TableData[$Chart] = $ChartData[$Chart].Count
    }
    $TableData['Total'] = $TestResults['Summary'].Total
    $DisplayTableData = [PSCustomObject] $TableData

    New-HTMLTab -Name 'Summary' -IconBrands galactic-senate {
        New-HTMLSection -HeaderText "Tests results" -HeaderBackGroundColor DarkGray {
            New-HTMLContainer {
                New-HTMLChart {
                    #New-ChartPie -Name 'Passed' -Value ($PassedTests.Count) -Color $ColorPassed
                    #New-ChartPie -Name 'Failed' -Value ($FailedTests.Count) -Color $ColorFailed
                    #New-ChartPie -Name 'Skipped' -Value ($SkippedTests.Count) -Color $ColorSkipped
                    foreach ($Key in $ChartData.Keys) {
                        New-ChartPie -Name $Key -Value $ChartData[$Key].Count -Color $ChartData[$Key].Color
                    }
                }
                New-HTMLTable -DataTable $DisplayTableData -HideFooter -DisableSearch {
                    foreach ($Chart in $ChartData.Keys) {
                        New-HTMLTableContent -ColumnName $Chart -BackGroundColor $ChartData[$Chart].Color -Color Black
                    }
                    #New-HTMLTableContent -ColumnName 'Passed' -BackGroundColor $TestResults['Configuration']['Colors']['ColorPassed'] -Color $TestResults['Configuration']['Colors']['ColorPassedText']
                    #New-HTMLTableContent -ColumnName 'Failed' -BackGroundColor $TestResults['Configuration']['Colors']['ColorFailed'] -Color $TestResults['Configuration']['Colors']['ColorFailedText']
                    #New-HTMLTableContent -ColumnName 'Skipped' -BackGroundColor $TestResults['Configuration']['Colors']['ColorSkipped'] -Color $TestResults['Configuration']['Colors']['ColorSkippedText']
                } -DataStore HTML -Buttons @() -DisablePaging -DisableInfo -DisableOrdering
            } -Width '35%'
            New-HTMLContainer {
                New-HTMLText -Text @(
                    "Below you can find overall summary of all tests executed in this Testimo run."
                ) -FontSize 10pt

                $ResultsDisplay = $TestResults['Results'] | Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended, Domain, DomainController
                New-HTMLTable -DataTable $ResultsDisplay {
                    #New-HTMLTableCondition -Name 'Status' -Value $true -BackgroundColor $TestResults['Configuration']['Colors']['ColorPassed'] -Color $TestResults['Configuration']['Colors']['ColorPassedText'] #-Row
                    #New-HTMLTableCondition -Name 'Status' -Value $false -BackgroundColor $TestResults['Configuration']['Colors']['ColorFailed'] -Color $TestResults['Configuration']['Colors']['ColorFailedText'] #-Row
                    #New-HTMLTableCondition -Name 'Status' -Value $null -BackgroundColor $TestResults['Configuration']['Colors']['ColorSkipped'] -Color $TestResults['Configuration']['Colors']['ColorSkippedText'] #-Row
                    foreach ($Status in $Script:StatusTranslation.Keys) {
                        New-HTMLTableCondition -Name 'Assessment' -Value $Script:StatusTranslation[$Status] -BackgroundColor $Script:StatusTranslationColors[$Status] -Row
                    }
                    New-HTMLTableCondition -Name 'Assessment' -Value $true -BackgroundColor $TestResults['Configuration']['Colors']['ColorPassed'] -Color $TestResults['Configuration']['Colors']['ColorPassedText'] -Row
                    New-HTMLTableCondition -Name 'Assessment' -Value $false -BackgroundColor $TestResults['Configuration']['Colors']['ColorFailed'] -Color $TestResults['Configuration']['Colors']['ColorFailedText'] -Row
                } -Filtering
            }
        }
    }
}
function Start-TestimoReportSummaryAdvanced {
    <#
    .SYNOPSIS
    This function generates an advanced summary report for Testimo results.
 
    .DESCRIPTION
    Start-TestimoReportSummaryAdvanced function is used to create a detailed summary report based on the Testimo test results. It calculates and displays various statistics and visual representations of the test outcomes.
 
    .PARAMETER TestResults
    Specifies the test results data to be summarized.
 
    .EXAMPLE
    Start-TestimoReportSummaryAdvanced -TestResults $TestResults
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $TestResults
    )

    $ChartData = New-ChartData -Results $TestResults['Results']
    $TableData = [ordered] @{}
    $TableData['Total'] = 0
    foreach ($Chart in $ChartData.Keys) {
        $TableData[$Chart] = $ChartData[$Chart].Count
        $TableData['Total'] = $TableData['Total'] + $ChartData[$Chart].Count
    }
    $DisplayTableData = [PSCustomObject] $TableData

    New-HTMLTab -Name 'Summary' -IconBrands galactic-senate {
        New-HTMLSection -Invisible {
            New-HTMLContainer {
                New-HTMLPanel {
                    New-HTMLSummary -Title 'Testimo Summary' {
                        foreach ($Source in $TestResults.BySource.Keys) {
                            $SourceData = $TestResults.BySource[$Source]
                            $Results = $TestResults.BySource[$Source].Results

                            $CountBad = 0
                            $CountGood = 0
                            foreach ($Result in $Results) {
                                if ($Result.Assessment -in 'Informational', 'Good') {
                                    $CountGood++
                                } else {
                                    $CountBad++
                                }
                            }

                            if ($CountGood -gt 0 -and $CountBad -gt 0) {
                                $ItemConfiguration = @{
                                    IconColor = 'Orange'
                                    IconSolid = 'exclamation-circle'
                                }
                            } elseif ($CountBad -gt 0) {
                                $ItemConfiguration = @{
                                    IconColor = 'Red'
                                    IconSolid = 'window-close'
                                }
                            } else {
                                $ItemConfiguration = @{
                                    IconColor   = 'DarkPastelGreen'
                                    IconRegular = 'check-circle'
                                }
                            }
                            $NameOfItem = "$($SourceData.Name) (number of tests: $($Results.Count))"
                            Write-Verbose -Message "Generating SummaryItem for $NameOfItem"
                            New-HTMLSummaryItem -Text $NameOfItem {
                                foreach ($Result in $Results) {
                                    if ($Result.Assessment -in 'Informational', 'Good') {
                                        $ItemConfigurationTest = @{
                                            IconColor   = 'DarkPastelGreen'
                                            IconRegular = 'check-circle'
                                        }
                                    } else {
                                        $ItemConfigurationTest = @{
                                            IconColor = 'Red'
                                            IconSolid = 'window-close'
                                        }
                                    }
                                    New-HTMLSummaryItem -Text $Result.DisplayName {
                                        New-HTMLSummaryItemData -Text "type" -Value ($Result.Type -join ",")
                                        New-HTMLSummaryItemData -Text "category" -Value ($Result.Category -join ",")
                                        # New-HTMLSummaryItemData -Text "assesment" -Value $Result.Assessment
                                        # New-HTMLSummaryItemData -Text "action" -Value $Result.Action
                                        # New-HTMLSummaryItemData -Text "importance" -Value $Result.Importance
                                    } @ItemConfigurationTest
                                }
                            } @ItemConfiguration
                        }
                    }
                } -BorderRadius 0px
            } -Width '40%'
            New-HTMLContainer {
                New-HTMLPanel {
                    New-HTMLContainer {
                        New-HTMLChart {
                            #New-ChartPie -Name 'Passed' -Value ($PassedTests.Count) -Color $ColorPassed
                            #New-ChartPie -Name 'Failed' -Value ($FailedTests.Count) -Color $ColorFailed
                            #New-ChartPie -Name 'Skipped' -Value ($SkippedTests.Count) -Color $ColorSkipped
                            foreach ($Key in $ChartData.Keys) {
                                New-ChartPie -Name $Key -Value $ChartData[$Key].Count -Color $ChartData[$Key].Color
                            }
                        }
                        New-HTMLTable -DataTable $DisplayTableData -HideFooter -DisableSearch {
                            foreach ($Chart in $ChartData.Keys) {
                                New-HTMLTableContent -ColumnName $Chart -BackGroundColor $ChartData[$Chart].Color -Color Black
                            }
                            #New-HTMLTableContent -ColumnName 'Passed' -BackGroundColor $TestResults['Configuration']['Colors']['ColorPassed'] -Color $TestResults['Configuration']['Colors']['ColorPassedText']
                            #New-HTMLTableContent -ColumnName 'Failed' -BackGroundColor $TestResults['Configuration']['Colors']['ColorFailed'] -Color $TestResults['Configuration']['Colors']['ColorFailedText']
                            #New-HTMLTableContent -ColumnName 'Skipped' -BackGroundColor $TestResults['Configuration']['Colors']['ColorSkipped'] -Color $TestResults['Configuration']['Colors']['ColorSkippedText']
                        } -DataStore HTML -Buttons @() -DisablePaging -DisableInfo -DisableOrdering
                    }
                    New-HTMLContainer {
                        New-HTMLSection -HeaderText "Tests results" -HeaderBackGroundColor DarkGray {
                            $ResultsDisplay = $TestResults['Results'] | Select-Object -Property DisplayName, Type, Category, Assessment, Importance, Action, Extended, Domain, DomainController
                            New-HTMLTable -DataTable $ResultsDisplay {
                                #New-HTMLTableCondition -Name 'Status' -Value $true -BackgroundColor $TestResults['Configuration']['Colors']['ColorPassed'] -Color $TestResults['Configuration']['Colors']['ColorPassedText'] #-Row
                                #New-HTMLTableCondition -Name 'Status' -Value $false -BackgroundColor $TestResults['Configuration']['Colors']['ColorFailed'] -Color $TestResults['Configuration']['Colors']['ColorFailedText'] #-Row
                                #New-HTMLTableCondition -Name 'Status' -Value $null -BackgroundColor $TestResults['Configuration']['Colors']['ColorSkipped'] -Color $TestResults['Configuration']['Colors']['ColorSkippedText'] #-Row
                                foreach ($Status in $Script:StatusTranslation.Keys) {
                                    New-HTMLTableCondition -Name 'Assessment' -Value $Script:StatusTranslation[$Status] -BackgroundColor $Script:StatusTranslationColors[$Status] -Row
                                }
                                New-HTMLTableCondition -Name 'Assessment' -Value $true -BackgroundColor $TestResults['Configuration']['Colors']['ColorPassed'] -Color $TestResults['Configuration']['Colors']['ColorPassedText'] -Row
                                New-HTMLTableCondition -Name 'Assessment' -Value $false -BackgroundColor $TestResults['Configuration']['Colors']['ColorFailed'] -Color $TestResults['Configuration']['Colors']['ColorFailedText'] -Row
                            } -Filtering
                        }
                    }
                } -BorderRadius 0px
            }
        }
    }

}
function Start-Testing {
    <#
    .SYNOPSIS
    This function initiates the testing process based on the specified scope.
 
    .DESCRIPTION
    Start-Testing function is used to start the testing process with the provided parameters. It sets the appropriate levels based on the scope and retrieves configuration details for testing.
 
    .PARAMETER Execute
    Specifies the script block to execute during testing.
 
    .PARAMETER Scope
    Specifies the scope of the testing process (Forest, Domain, DC).
 
    .PARAMETER Domain
    Specifies the domain for the testing process.
 
    .PARAMETER DomainController
    Specifies the domain controller for the testing process.
 
    .PARAMETER IsPDC
    Specifies if the domain controller is a Primary Domain Controller.
 
    .PARAMETER ForestInformation
    Specifies information related to the forest for testing.
 
    .PARAMETER DomainInformation
    Specifies information related to the domain for testing.
 
    .PARAMETER ForestDetails
    Specifies additional details related to the forest for testing.
 
    .PARAMETER SkipRODC
    Skips Read-Only Domain Controller testing.
 
    .PARAMETER Variables
    Specifies additional variables for testing.
 
    .EXAMPLE
    Start-Testing -Execute { Write-Host "Testing in progress..." } -Scope "Domain" -Domain "example.com" -DomainController "DC1" -IsPDC $true -ForestInformation $ForestInfo -DomainInformation $DomainInfo -ForestDetails @{"Detail1"="Value1"} -SkipRODC -Variables @{"Var1"="Value1"}
    #>

    [CmdletBinding()]
    param(
        [ScriptBlock] $Execute,
        [string] $Scope,
        [string] $Domain,
        [string] $DomainController,
        [bool] $IsPDC,
        [Object] $ForestInformation,
        [Object] $DomainInformation,
        [System.Collections.IDictionary] $ForestDetails,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $Variables
    )
    $GlobalTime = Start-TimeLog

    if ($Scope -eq 'Forest') {
        $Level = 3
        $LevelTest = 6
        $LevelSummary = 3
        $LevelTestFailure = 6
        $Config = $Script:TestimoConfiguration['ActiveDirectory']
        $SummaryText = "Forest"
    } elseif ($Scope -eq 'Domain') {
        $Level = 6
        $LevelTest = 9
        $LevelSummary = 6
        $LevelTestFailure = 9
        $Config = $Script:TestimoConfiguration['ActiveDirectory']
        Write-Color
        $SummaryText = "Domain $Domain"
    } elseif ($Scope -eq 'DC') {
        $Level = 9
        $LevelTest = 12
        $LevelSummary = 9
        $LevelTestFailure = 12
        $Config = $Script:TestimoConfiguration['ActiveDirectory']
        $SummaryText = "Domain $Domain, $DomainController"
    } else {
        $Level = 3
        $LevelTest = 6
        $LevelSummary = 3
        $LevelTestFailure = 6
        $Config = $Script:TestimoConfiguration[$Scope]
        Write-Color
        $SummaryText = $Scope
    }
    # Build requirements variables
    [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

    Out-Informative -Scope $Scope -Text $SummaryText -Status $null -ExtendedValue '' -Domain $Domain -DomainController $DomainController -Level ($LevelSummary - 3)

    $TestsSummaryTogether = @(
        foreach ($Source in $Config.Keys) {
            if ($Scope -ne $Config[$Source].Scope) {
                continue
            }
            $CurrentSection = $Config[$Source]
            if ($null -eq $CurrentSection) {
                # Probably should write some tests
                Write-Warning "Source $Source in scope: $Scope is defined improperly. Please verify."
                continue
            }
            if ($CurrentSection['Enable'] -eq $true) {
                $Time = Start-TimeLog
                $CurrentSource = $CurrentSection['Source']
                #$CurrentTests = $CurrentSection['Tests']
                [Array] $AllTests = $CurrentSection['Tests'].Keys

                $ReferenceID = $Source #Get-RandomStringName -Size 8
                $TestsSummary = [PSCustomobject] @{
                    Passed  = 0
                    Failed  = 0
                    Skipped = 0
                    Total   = 0 # $AllTests.Count + 1 # +1 includes availability of data test
                }
                # build data output for extended results
                $TestOutput = [ordered] @{
                    Name             = $CurrentSource['Name']
                    SourceCode       = if ($CurrentSource['Data']) { $CurrentSource['Data'] } else { $null }
                    Details          = $CurrentSource['Details']
                    Results          = [System.Collections.Generic.List[PSCustomObject]]::new()
                    Domain           = $Domain
                    DomainController = $DomainController
                }

                # Lets divide tests results into by type Forest/Domain/Domain Controller
                if ($Scope -in 'Forest', 'Domain', 'DC') {
                    if ($Domain -and $DomainController) {
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID] = $TestOutput
                    } elseif ($Domain) {
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID] = $TestOutput
                    } else {
                        $Script:Reporting['Forest']['Tests'][$ReferenceID] = $TestOutput
                    }
                } else {
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID] = $TestOutput
                }

                # Lets divide tests by source (same content/different way to use later on)
                #if (-not $Script:Reporting['BySource'][$Source]) {
                # $Script:Reporting['BySource'][$Source] = [System.Collections.Generic.List[PSCustomObject]]::new()
                #}
                #$Script:Reporting['BySource'][$Source].Add($TestOutput)
                $Script:Reporting['BySource'][$Source] = $TestOutput
                if (-not $CurrentSection['Source']) {
                    Write-Warning "Source $Source in scope: $Scope is defined improperly. Please verify."
                    continue
                }

                # Check if requirements are met
                if ($CurrentSource['Requirements']) {
                    if ($null -ne $CurrentSource['Requirements']['IsDomainRoot']) {
                        if (-not $CurrentSource['Requirements']['IsDomainRoot'] -eq $IsDomainRoot) {
                            Out-Skip -Scope $Scope -Test $CurrentSource['Name'] -DomainController $DomainController -Domain $Domain -TestsSummary $TestsSummary -Source $ReferenceID -Level $Level
                            continue
                        }
                    }
                    if ($null -ne $CurrentSource['Requirements']['IsPDC']) {
                        if (-not $CurrentSource['Requirements']['IsPDC'] -eq $IsPDC) {
                            Out-Skip -Scope $Scope -Test $CurrentSource['Name'] -DomainController $DomainController -Domain $Domain -TestsSummary $TestsSummary -Source $ReferenceID -Level $Level
                            continue
                        }
                    }
                    if ($null -ne $CurrentSource['Requirements']['OperatingSystem']) {


                    }
                    if ($null -ne $CurrentSource['Requirements']['CommandAvailable']) {
                        [Array] $Commands = foreach ($Command in $CurrentSource['Requirements']['CommandAvailable']) {
                            $OutputCommand = Get-Command -Name $Command -ErrorAction SilentlyContinue
                            if (-not $OutputCommand) {
                                $false
                            }
                        }
                        if ($Commands -contains $false) {
                            $CommandsTested = $CurrentSource['Requirements']['CommandAvailable'] -join ', '
                            Out-Skip -Scope $Scope -Test $CurrentSource['Name'] -DomainController $DomainController -Domain $Domain -TestsSummary $TestsSummary -Source $ReferenceID -Level $Level -Reason "Skipping - At least one command unavailable ($CommandsTested)"
                            continue
                        }
                    }
                    if ($null -ne $CurrentSource['Requirements']['IsInternalForest']) {
                        if ($CurrentSource['Requirements']['IsInternalForest'] -eq $true) {
                            if ($ForestName) {
                                Out-Skip -Scope $Scope -Test $CurrentSource['Name'] -DomainController $DomainController -Domain $Domain -TestsSummary $TestsSummary -Source $ReferenceID -Level $Level -Reason "Skipping - External forest requested. Not supported test."
                                continue
                            }
                        }
                    }
                }
                # START - Execute TEST - By getting the Data SOURCE
                Out-Informative -Scope $Scope -Text $CurrentSource['Name'] -Level $Level -Domain $Domain -DomainController $DomainController -Start
                if ($CurrentSource['Parameters']) {
                    $SourceParameters = $CurrentSource['Parameters']
                } else {
                    $SourceParameters = @{}
                }
                if ($Scope -in 'Forest', 'Domain', 'DC') {
                    $SourceParameters['DomainController'] = $DomainController
                    if ($Scope -eq 'Forest') {
                        $SourceParameters['QueryServer'] = $ForestDetails['QueryServers']['Forest']['HostName'][0]
                    } else {
                        $SourceParameters['QueryServer'] = $ForestDetails['QueryServers'][$Domain]['HostName'][0]
                    }
                    $SourceParameters['Domain'] = $Domain
                    $SourceParameters['ForestDetails'] = $ForestDetails
                    $SourceParameters['ForestName'] = $ForestInformation.Name
                    $SourceParameters['DomainInformation'] = $DomainInformation
                    $SourceParameters['ForestInformation'] = $ForestInformation
                    $SourceParameters['SkipRODC'] = $SkipRODC.IsPresent # bool true/false
                } else {
                    $SourceParameters['Authorization'] = $Script:AuthorizationO365Cache
                    $SourceParameters['Session'] = Get-PSSession | Where-Object { $_.ComputerName -eq 'outlook.office365.com' -and $_.State -eq 'Opened' } | Select-Object -First 1
                }
                foreach ($Variable in $Variables.Keys) {
                    $SourceParameters[$Variable] = $Variables[$Variable]
                }
                if ($CurrentSource['Data'] -is [ScriptBlock]) {
                    if ($Script:TestimoConfiguration.Debug.ShowErrors) {
                        $OutputData = & $CurrentSource['Data'] -DomainController $DomainController -Domain $Domain
                        $OutputInvoke = @{
                            Output = $OutputData
                        }
                        $ErrorMessage = $null
                    } else {
                        $OutputInvoke = Invoke-CommandCustom -ScriptBlock $CurrentSource['Data'] -Parameter $SourceParameters -ReturnVerbose -ReturnError -ReturnWarning -AddParameter
                        if ($OutputInvoke.Error) {
                            $ErrorMessage = $OutputInvoke.Error.Exception.Message -replace "`n", " " -replace "`r", " "
                        } else {
                            $ErrorMessage = $null
                        }
                    }
                } else {
                    $OutputInvoke = @{
                        Output = $CurrentSource['DataOutput']
                    }
                }
                $WarningsAndErrors = @(
                    #if ($ShowWarning) {
                    foreach ($War in $OutputInvoke.Warning) {
                        [PSCustomObject] @{
                            Type       = 'Warning'
                            Comment    = $War
                            Reason     = ''
                            TargetName = ''
                        }
                    }
                    #}
                    #if ($ShowError) {
                    foreach ($Err in $OutputInvoke.Error) {
                        [PSCustomObject] @{
                            Type       = 'Error'
                            Comment    = $Err
                            Reason     = $Err.CategoryInfo.Reason
                            TargetName = $Err.CategoryInfo.TargetName
                        }
                    }
                    #}
                )
                Out-Informative -Scope $Scope -Text $CurrentSource['Name'] -Status $null -ExtendedValue $null -Domain $Domain -DomainController $DomainController -End
                # END - Execute TEST - By getting the Data SOURCE
                $Object = $OutputInvoke.Output
                if ($CurrentSource['Flatten'] -and $Object) {
                    $Object = $Object | ConvertTo-FlatObject
                }

                # Add data output to extended results
                if ($Scope -in 'Forest', 'Domain', 'DC') {
                    if ($Domain -and $DomainController) {
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['Data'] = $Object
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['Verbose'] = $OutputInvoke.Verbose
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['Warning'] = $OutputInvoke.Warning
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['Error'] = $OutputInvoke.Error
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['WarningsAndErrors'] = $WarningsAndErrors
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['DetailsTests'] = [ordered]@{ }
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['ResultsTests'] = [ordered]@{ }
                        $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['Information'] = $CurrentSection
                    } elseif ($Domain) {
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['Data'] = $Object
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['Verbose'] = $OutputInvoke.Verbose
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['Warning'] = $OutputInvoke.Warning
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['Error'] = $OutputInvoke.Error
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['WarningsAndErrors'] = $WarningsAndErrors
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['DetailsTests'] = [ordered]@{ }
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['ResultsTests'] = [ordered]@{ }
                        $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['Information'] = $CurrentSection
                    } else {
                        $Script:Reporting['Forest']['Tests'][$ReferenceID]['Data'] = $Object
                        $Script:Reporting['Forest']['Tests'][$ReferenceID]['Verbose'] = $OutputInvoke.Verbose
                        $Script:Reporting['Forest']['Tests'][$ReferenceID]['Warning'] = $OutputInvoke.Warning
                        $Script:Reporting['Forest']['Tests'][$ReferenceID]['Error'] = $OutputInvoke.Error
                        $Script:Reporting['Forest']['Tests'][$ReferenceID]['WarningsAndErrors'] = $WarningsAndErrors
                        $Script:Reporting['Forest']['Tests'][$ReferenceID]['DetailsTests'] = [ordered]@{ }
                        $Script:Reporting['Forest']['Tests'][$ReferenceID]['ResultsTests'] = [ordered]@{ }
                        $Script:Reporting['Forest']['Tests'][$ReferenceID]['Information'] = $CurrentSection
                    }
                } else {
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID]['Data'] = $Object
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID]['Verbose'] = $OutputInvoke.Verbose
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID]['Warning'] = $OutputInvoke.Warning
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID]['Error'] = $OutputInvoke.Error
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID]['WarningsAndErrors'] = $WarningsAndErrors
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID]['DetailsTests'] = [ordered]@{ }
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID]['ResultsTests'] = [ordered]@{ }
                    $Script:Reporting[$Scope]['Tests'][$ReferenceID]['Information'] = $CurrentSection
                }
                # If there's no output from Source Data all other tests will fail
                if ($ErrorMessage) {
                    $FailAllTests = $true
                    $ExtendedValue = $ErrorMessage -join "; "
                    Out-Failure -Scope $Scope -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue $ExtendedValue -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $CurrentSource
                    $TestsSummary.Failed = $TestsSummary.Failed + 1
                } elseif ($Object -and $CurrentSource['ExpectedOutput'] -eq $true) {
                    # Output is provided and we did expect it - passed test
                    $FailAllTests = $false
                    Out-Begin -Scope $Scope -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $CurrentSource['Name'] -Status $true -ExtendedValue 'Data is available' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $CurrentSource
                    $TestsSummary.Passed = $TestsSummary.Passed + 1

                } elseif ($Object -and $CurrentSource['ExpectedOutput'] -eq $false) {
                    # Output is provided, but we expected no output - failing test
                    $FailAllTests = $false
                    Out-Failure -Scope $Scope -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'Data is available. This is a bad thing' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $CurrentSource
                    $TestsSummary.Failed = $TestsSummary.Failed + 1

                } elseif ($Object -and $null -eq $CurrentSource['ExpectedOutput']) {
                    # Output is provided, but we weren't sure if there should be output or not
                    $FailAllTests = $false
                    Out-Begin -Scope $Scope -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $CurrentSource['Name'] -Status $null -ExtendedValue 'Data is available' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $CurrentSource
                    #$TestsSummary.Passed = $TestsSummary.Passed + 1
                    $TestsSummary.Skipped = $TestsSummary.Skipped + 1

                } elseif ($null -eq $Object -and $CurrentSource['ExpectedOutput'] -eq $true) {
                    # Output was not provided and we expected it
                    $FailAllTests = $true
                    Out-Failure -Scope $Scope -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'No data available' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $CurrentSource
                    $TestsSummary.Failed = $TestsSummary.Failed + 1
                } elseif ($null -eq $Object -and $CurrentSource['ExpectedOutput'] -eq $false) {
                    # This tests whether there was an output from Source or not.
                    # Sometimes it makes sense to ask for data and get null/empty in return
                    # you just need to make sure to define ExpectedOutput = $false in source definition
                    $FailAllTests = $false
                    Out-Begin -Scope $Scope -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $CurrentSource['Name'] -Status $true -ExtendedValue 'No data returned, which is a good thing' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $CurrentSource
                    $TestsSummary.Passed = $TestsSummary.Passed + 1
                } elseif ($null -eq $Object -and $null -eq $CurrentSource['ExpectedOutput']) {
                    # Output is not provided, but we weren't sure if there should be output or not
                    $FailAllTests = $false
                    Out-Begin -Scope $Scope -Text $CurrentSource['Name'] -Level $LevelTest -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $CurrentSource['Name'] -Status $null -ExtendedValue 'No data returned' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $CurrentSource
                    # $TestsSummary.Passed = $TestsSummary.Passed + 1
                    $TestsSummary.Skipped = $TestsSummary.Skipped + 1
                } else {
                    $FailAllTests = $true
                    Out-Failure -Scope $Scope -Text $CurrentSource['Name'] -Level $LevelTest -ExtendedValue 'No data available' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Source $CurrentSource
                    $TestsSummary.Failed = $TestsSummary.Failed + 1
                }

                foreach ($Test in $AllTests) {
                    # Add content with description of the test
                    if ($Scope -in 'Forest', 'Domain', 'DC') {
                        if ($Domain -and $DomainController) {
                            $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['DetailsTests'][$Test] = $CurrentSection['Tests'][$Test]['Details']
                        } elseif ($Domain) {
                            $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['DetailsTests'][$Test] = $CurrentSection['Tests'][$Test]['Details']
                        } else {
                            $Script:Reporting['Forest']['Tests'][$ReferenceID]['DetailsTests'][$Test] = $CurrentSection['Tests'][$Test]['Details']
                        }
                    } else {
                        $Script:Reporting[$Scope]['Tests'][$ReferenceID]['DetailsTests'][$Test] = $CurrentSection['Tests'][$Test]['Details']
                    }

                    $CurrentTest = $CurrentSection['Tests'][$Test]
                    if ($CurrentTest['Enable'] -eq $True) {
                        # Check for requirements
                        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) {
                            $testStepOneSplat = @{
                                Test             = $CurrentTest
                                Object           = $Object
                                Domain           = $Domain
                                DomainController = $DomainController
                                Level            = $LevelTest
                                TestName         = $CurrentTest['Name']
                                ReferenceID      = $ReferenceID
                                Requirements     = $CurrentTest['Requirements']
                                Scope            = $Scope
                            }
                            # We provide whatever parameters are available in Data Source to Tests (mainly for use within WhereObject)
                            #if ($CurrentSource['Parameters']) {
                            # $testStepOneSplat['Parameters'] = $CurrentSource['Parameters']
                            #}
                            if ($Scope -in 'Forest', 'Domain', 'DC') {
                                if ($Scope -eq 'Forest') {
                                    $testStepOneSplat['QueryServer'] = $ForestDetails['QueryServers']['Forest']['HostName'][0]
                                } else {
                                    $testStepOneSplat['QueryServer'] = $ForestDetails['QueryServers'][$Domain]['HostName'][0]
                                }
                                $testStepOneSplat['ForestDetails'] = $ForestDetails
                                $testStepOneSplat['ForestName'] = $ForestInformation.Name
                                $testStepOneSplat['DomainInformation'] = $DomainInformation
                                $testStepOneSplat['ForestInformation'] = $ForestInformation
                            }
                            $TestsResults = Test-StepOne @testStepOneSplat
                            $TestsSummary.Passed = $TestsSummary.Passed + ($TestsResults | Where-Object { $_ -eq $true }).Count
                            $TestsSummary.Failed = $TestsSummary.Failed + ($TestsResults | Where-Object { $_ -eq $false }).Count
                        } else {
                            $TestsResults = $null
                            $TestsSummary.Failed = $TestsSummary.Failed + 1
                            Out-Failure -Scope $Scope -Text $CurrentTest['Name'] -Level $LevelTestFailure -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -ExtendedValue 'Input data not provided. Failing test.' -Source $CurrentTest
                        }
                        if ($Scope -in 'Forest', 'Domain', 'DC') {
                            if ($Domain -and $DomainController) {
                                $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Tests'][$ReferenceID]['ResultsTests'][$Test] = $TestsResults
                            } elseif ($Domain) {
                                $Script:Reporting['Domains'][$Domain]['Tests'][$ReferenceID]['ResultsTests'][$Test] = $TestsResults
                            } else {
                                $Script:Reporting['Forest']['Tests'][$ReferenceID]['ResultsTests'][$Test] = $TestsResults
                            }
                        } else {
                            $Script:Reporting[$Scope]['Tests'][$ReferenceID]['ResultsTests'][$Test] = $TestsResults
                        }
                    } else {
                        $TestsSummary.Skipped = $TestsSummary.Skipped + 1
                    }
                }
                $TestsSummary.Total = $TestsSummary.Failed + $TestsSummary.Passed + $TestsSummary.Skipped
                $TestsSummary
                Out-Summary -Scope $Scope -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

    if ($Scope -in 'Forest', 'Domain', 'DC') {
        if ($Domain -and $DomainController) {
            $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DomainController]['Summary'] = $TestsSummaryFinal
        } elseif ($Domain) {
            $Script:Reporting['Domains'][$Domain]['Summary'] = $TestsSummaryFinal
        } else {
            if ($Scope -ne 'Forest') {
                $Script:Reporting['Summary'] = $TestsSummaryFinal
            } else {
                $Script:Reporting['Forest']['Summary'] = $TestsSummaryFinal
            }
        }
    } else {
        $Script:Reporting[$Scope]['Summary'] = $TestsSummaryFinal
    }
    Out-Summary -Scope $Scope -Text $SummaryText -Time $GlobalTime -Level ($LevelSummary - 3) -Domain $Domain -DomainController $DomainController -TestsSummary $TestsSummaryFinal
}
function Test-StepOne {
    <#
    .SYNOPSIS
    This function performs a specific test step with various parameters.
 
    .DESCRIPTION
    Test-StepOne function is used to execute a specific test step with the provided parameters. It allows testing different operations on objects and validating the results.
 
    .PARAMETER Scope
    Specifies the scope of the test step.
 
    .PARAMETER Test
    Specifies the test data to be used for the test step.
 
    .PARAMETER Domain
    Specifies the domain for the test operation.
 
    .PARAMETER DomainController
    Specifies the domain controller for the test operation.
 
    .PARAMETER Object
    Specifies the object on which the test operation will be performed.
 
    .PARAMETER TestName
    Specifies the name of the test step.
 
    .PARAMETER Level
    Specifies the level of the test step.
 
    .PARAMETER ReferenceID
    Specifies the reference ID for the test step.
 
    .PARAMETER Requirements
    Specifies additional requirements for the test step.
 
    .PARAMETER QueryServer
    Specifies the server to query for the test operation.
 
    .PARAMETER ForestDetails
    Specifies details related to the forest for the test operation.
 
    .PARAMETER DomainInformation
    Specifies information related to the domain for the test operation.
 
    .PARAMETER ForestInformation
    Specifies information related to the forest for the test operation.
 
    .PARAMETER ForestName
    Specifies the name of the forest for the test operation.
 
    .EXAMPLE
    Test-StepOne -Scope "Global" -Test $TestData -Domain "example.com" -DomainController "DC1" -Object $Object -TestName "Test1" -Level 1 -ReferenceID "Ref1" -Requirements @{"ExpectedOutput"=$true} -QueryServer "Server1" -ForestDetails @{"Detail1"="Value1"} -DomainInformation $DomainInfo -ForestInformation $ForestInfo -ForestName "Forest1"
    #>

    [CmdletBinding()]
    param(
        [string] $Scope,
        [System.Collections.IDictionary] $Test,
        [string] $Domain,
        [string] $DomainController,
        [Array] $Object,
        [string] $TestName,
        [int] $Level,
        [string] $ReferenceID,
        [System.Collections.IDictionary] $Requirements,
        [string] $QueryServer,
        [System.Collections.IDictionary] $ForestDetails,
        [object] $DomainInformation,
        [object] $ForestInformation,
        [string] $ForestName
    )

    [string] $OperationType = $Test.Parameters.OperationType
    if ($OperationType -eq '') { $OperationType = 'eq' }
    [string[]] $Property = $Test.Parameters.Property
    [string[]] $PropertyExtendedValue = $Test.Parameters.PropertyExtendedValue
    $ExpectedValue = $Test.Parameters.ExpectedValue
    [nullable[int]] $ExpectedCount = $Test.Parameters.ExpectedCount
    [scriptblock] $OverwriteName = $Test.Parameters.OverwriteName
    [scriptblock] $WhereObject = $Test.Parameters.WhereObject
    [nullable[bool]] $ExpectedResult = $Test.Parameters.ExpectedResult
    [nullable[bool]] $ExpectedOutput = $Test.Parameters.ExpectedOutput
    [string] $OperationResult = $Test.Parameters.OperationResult

    if ($Object) {
        if ($WhereObject) {
            $Object = $Object | Where-Object $WhereObject
        }
        if ($null -ne $Requirements) {
            if ($null -ne $Requirements['ExpectedOutput']) {
                #if ($Requirements['MustMatch']) {
                # $TestsSummary.Skipped = $TestsSummary.Skipped + 1
                # continue
                #}
            }
        }

        if ($null -eq $ExpectedCount) {
            # This checks for ExpectedResult/ExpectedOutput
            # The difference is that
            # - ExpectedResult (true/false) - when ExpectedResult is True it means we filtered our objected with Where and it still provided output
            # This allows us to not check each element in Array one by one, but just assume this in bundle
            # - ExpectedOutput (true/false) - when we do Where-Object but it's possible that Array won't contain what we are looking for, and at the same time, it's not a problem
            if ($null -eq $Object) {
                if ($ExpectedResult -eq $false) {
                    Out-Begin -Scope $Scope -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $TestName -Status $true -ExtendedValue "Data is not available. This is expected" -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Test $Test
                    return $true
                } elseif ($ExpectedResult -eq $true) {
                    Out-Begin -Scope $Scope -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $TestName -Status $false -ExtendedValue 'Data is not available. This is not expected' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Test $Test
                    return $false
                }
                # This checks for NULL after Where-Object
                # Data Source is not null, but after WHERE-Object becomes NULL - we need to fail this
                if ($null -eq $ExpectedOutput -or $ExpectedOutput -eq $true) {
                    Out-Begin -Scope $Scope -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $TestName -Status $false -ExtendedValue 'Data is not available' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Test $Test
                    return $false
                } elseif ($ExpectedOutput -eq $false) {
                    Out-Begin -Scope $Scope -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $TestName -Status $true -ExtendedValue "Data is not available, but it's not required" -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Test $Test
                    return $true
                }
            } else {
                if ($ExpectedResult -eq $false) {
                    Out-Begin -Scope $Scope -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $TestName -Status $false -ExtendedValue 'Data is available. This is not expected' -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Test $Test
                    return $false
                } elseif ($ExpectedResult -eq $true) {
                    Out-Begin -Scope $Scope -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController
                    Out-Status -Scope $Scope -Text $TestName -Status $true -ExtendedValue "Data is available. This is expected" -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Test $Test
                    return $true
                }
            }
        }

        if ($null -ne $ExpectedCount) {
            if ($OverwriteName) {
                $TestName = & $OverwriteName
            }
            Test-StepTwo -Scope $Scope -Test $Test -Object $Object -ExpectedCount $ExpectedCount -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult -ReferenceID $ReferenceID -ExpectedOutput $ExpectedOutput
        } else {
            if ($Test.Parameters.Bundle -eq $true) {
                # We treat Input as a whole rather than line one by line
                Test-StepTwo -Scope $Scope -Test $Test -Object $Object -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult -ReferenceID $ReferenceID -ExpectedOutput $ExpectedOutput
            } else {
                foreach ($_ in $Object) {
                    if ($OverwriteName) {
                        $TestName = & $OverwriteName
                    }
                    Test-StepTwo -Scope $Scope -Test $Test -Object $_ -OperationType $OperationType -TestName $TestName -Level $Level -Domain $Domain -DomainController $DomainController -Property $Property -ExpectedValue $ExpectedValue -PropertyExtendedValue $PropertyExtendedValue -OperationResult $OperationResult -ReferenceID $ReferenceID -ExpectedOutput $ExpectedOutput
                }
            }
        }
    }
}
function Test-StepTwo {
    <#
    .SYNOPSIS
    This function performs a specific test step with various parameters.
 
    .DESCRIPTION
    Test-StepTwo function is used to execute a specific test step with the provided parameters. It allows testing different operations on objects and validating the results.
 
    .PARAMETER Scope
    Specifies the scope of the test step.
 
    .PARAMETER Test
    Specifies the test data to be used for the test step.
 
    .PARAMETER Domain
    Specifies the domain for the test operation.
 
    .PARAMETER DomainController
    Specifies the domain controller for the test operation.
 
    .PARAMETER Object
    Specifies the object on which the test operation will be performed.
 
    .PARAMETER TestName
    Specifies the name of the test step.
 
    .PARAMETER OperationType
    Specifies the type of operation to be performed.
 
    .PARAMETER Level
    Specifies the level of the test step.
 
    .PARAMETER Property
    Specifies the property of the object to be tested.
 
    .PARAMETER PropertyExtendedValue
    Specifies the extended value of the property.
 
    .PARAMETER ExpectedValue
    Specifies the expected value of the test operation.
 
    .PARAMETER ExpectedCount
    Specifies the expected count of the test operation.
 
    .PARAMETER OperationResult
    Specifies the result of the test operation.
 
    .PARAMETER ReferenceID
    Specifies the reference ID for the test step.
 
    .PARAMETER ExpectedOutput
    Specifies the expected output of the test operation.
 
    .EXAMPLE
    Test-StepTwo -Scope "Global" -Test $TestData -Domain "example.com" -DomainController "DC1" -Object $Object -TestName "Test1" -OperationType "Read" -Level 1 -Property @("Property1") -PropertyExtendedValue @("ExtendedValue1") -ExpectedValue @("Value1") -ExpectedCount 1 -OperationResult "Success" -ReferenceID "Ref1" -ExpectedOutput $true
    #>

    [CmdletBinding()]
    param(
        [string] $Scope,
        [System.Collections.IDictionary] $Test,
        [string] $Domain,
        [string] $DomainController,
        [Array] $Object,
        [string] $TestName,
        [string] $OperationType,
        [int] $Level,
        [string[]] $Property,
        [string[]] $PropertyExtendedValue,
        [Array] $ExpectedValue,
        [nullable[int]] $ExpectedCount,
        [string] $OperationResult,
        [string] $ReferenceID,
        [nullable[bool]] $ExpectedOutput
    )
    Out-Begin -Scope $Scope -Text $TestName -Level $Level -Domain $Domain -DomainController $DomainController

    $TemporaryBoundParameters = $PSBoundParameters

    $ScriptBlock = {
        $Operators = @{
            'lt'          = 'Less Than'
            'gt'          = 'Greater Than'
            'le'          = 'Less Or Equal'
            'ge'          = 'Greater Or Equal'
            'eq'          = 'Equal'
            'contains'    = 'Contains'
            'notcontains' = 'Not contains'
            'like'        = 'Like'
            'match'       = 'Match'
            'notmatch'    = 'Not match'
            'notin'       = 'Not in'
            'in'          = 'Either Value'
        }
        [Object] $TestedValue = $Object
        foreach ($V in $Property) {
            $TestedValue = $TestedValue.$V
        }
        if ($null -ne $TestedValue -and $TestedValue.GetType().BaseType.Name -eq 'Enum') {
            $TestedValue = $TestedValue.ToString()
        }

        if ($TemporaryBoundParameters.ContainsKey('ExpectedCount')) {
            if ($null -eq $Object) {
                $TestedValueCount = 0
            } else {
                $TestedValueCount = $TestedValue.Count
            }
            if ($OperationType -eq 'lt') {
                $TestResult = $TestedValueCount -lt $ExpectedCount
            } elseif ($OperationType -eq 'gt') {
                $TestResult = $TestedValueCount -gt $ExpectedCount
            } elseif ($OperationType -eq 'ge') {
                $TestResult = $TestedValueCount -ge $ExpectedCount
            } elseif ($OperationType -eq 'le') {
                $TestResult = $TestedValueCount -le $ExpectedCount
            } elseif ($OperationType -eq 'like') {
                # Useless - doesn't make any sense
                $TestResult = $TestedValueCount -like $ExpectedCount
            } elseif ($OperationType -eq 'contains') {
                # Useless - doesn't make any sense
                $TestResult = $TestedValueCount -contains $ExpectedCount
            } elseif ($OperationType -eq 'in') {
                # Useless - doesn't make any sense
                $TestResult = $ExpectedCount -in $TestedValueCount
            } elseif ($OperationType -eq 'notin') {
                # Useless - doesn't make any sense
                $TestResult = $ExpectedCount -notin $TestedValueCount
            } else {
                $TestResult = $TestedValueCount -eq $ExpectedCount
            }
            $TextTestedValue = $TestedValueCount
            $TextExpectedValue = $ExpectedCount

        } elseif ($TemporaryBoundParameters.ContainsKey('ExpectedValue')) {
            $OutputValues = [System.Collections.Generic.List[Object]]::new()
            if ($null -eq $TestedValue -and $null -ne $ExpectedValue) {
                # if testedvalue is null and expected value is not null that means there's no sense in testing things
                # it should fail
                $TestResult = for ($i = 0; $i -lt $ExpectedValue.Count; $i++) {
                    $false # return fail

                    # We need to add this to be able to convert values as below for output purposes only.
                    if ($ExpectedValue[$i] -is [string] -and $ExpectedValue[$i] -like '*Get-Date*') {
                        [scriptblock] $DateConversion = [scriptblock]::Create($ExpectedValue[$i])
                        $CompareValue = & $DateConversion
                    } else {
                        $CompareValue = $ExpectedValue[$I]
                    }
                    # gather comparevalue for display purposes
                    $OutputValues.Add($CompareValue)

                }
                $TextExpectedValue = $OutputValues -join ', '
                $TextTestedValue = 'Null'
            } else {
                [Array] $TestResult = @(
                    if ($OperationType -eq 'notin') {
                        $ExpectedValue -notin $TestedValue
                        $TextExpectedValue = $ExpectedValue
                    } elseif ($OperationType -eq 'in') {
                        $TestedValue -in $ExpectedValue
                        $TextExpectedValue = $ExpectedValue -join ' or '
                    } else {
                        for ($i = 0; $i -lt $ExpectedValue.Count; $i++) {

                            # this check is introduced to convert Get-Date in ExpectedValue to proper values
                            # normally it wouldn't be nessecary but since we're exporting configuration to JSON
                            # it would export currentdatetime to JSON and we don't want that.
                            if ($ExpectedValue[$i] -is [string] -and $ExpectedValue[$i] -like '*Get-Date*') {
                                [scriptblock] $DateConversion = [scriptblock]::Create($ExpectedValue[$i])
                                $CompareValue = & $DateConversion
                            } else {
                                $CompareValue = $ExpectedValue[$I]
                            }
                            if ($TestedValue -is [System.Collections.ICollection] -or $TestedValue -is [Array]) {
                                $CompareObjects = Compare-Object -ReferenceObject $TestedValue -DifferenceObject $CompareValue -IncludeEqual
                                #$CompareObjects

                                if ($OperationType -eq 'eq') {
                                    if ($CompareObjects.SideIndicator -notcontains "=>" -and $CompareObjects.SideIndicator -notcontains "<=" -and $CompareObjects.SideIndicator -contains "==") {
                                        $true
                                    } else {
                                        $false
                                    }
                                } elseif ($OperationType -eq 'ne') {
                                    if ($CompareObjects.SideIndicator -contains "=>" -or $CompareObjects.SideIndicator -contains "<=") {
                                        $true
                                    } else {
                                        $false
                                    }
                                } else {
                                    # Not supported for arrays
                                    $null
                                }
                            } else {
                                if ($OperationType -eq 'lt') {
                                    $TestedValue -lt $CompareValue
                                } elseif ($OperationType -eq 'gt') {
                                    $TestedValue -gt $CompareValue
                                } elseif ($OperationType -eq 'ge') {
                                    $TestedValue -ge $CompareValue
                                } elseif ($OperationType -eq 'le') {
                                    $TestedValue -le $CompareValue
                                } elseif ($OperationType -eq 'like') {
                                    $TestedValue -like $CompareValue
                                } elseif ($OperationType -eq 'contains') {
                                    $TestedValue -contains $CompareValue
                                } elseif ($OperationType -eq 'notcontains') {
                                    $TestedValue -notcontains $CompareValue
                                } elseif ($OperationType -eq 'match') {
                                    $TestedValue -match $CompareValue
                                } elseif ($OperationType -eq 'notmatch') {
                                    $TestedValue -notmatch $CompareValue
                                } else {
                                    $TestedValue -eq $CompareValue
                                }
                            }
                            # gather comparevalue for display purposes
                            $OutputValues.Add($CompareValue)
                        }
                        if ($ExpectedValue.Count -eq 0) {
                            $TextExpectedValue = 'Null'
                        } else {
                            $TextExpectedValue = $OutputValues -join ', '
                        }
                    }
                    if ($null -eq $TestedValue) {
                        $TextTestedValue = 'Null'
                    } else {
                        $TextTestedValue = $TestedValue
                    }
                )
            }
        } else {
            if ($ExpectedOutput -eq $false) {
                [Array] $TestResult = @(
                    if ($null -eq $TestedValue) {
                        $true
                    } else {
                        $false
                    }
                )
                $TextExpectedValue = 'No output'
            } else {
                # Skipped tests
                $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])): $($TextExpectedValue)"
                } else {
                    $ReportResult = $false
                    if ($Test.Parameters.DisplayResult -ne $false) {
                        $ReportExtended = "Expected value ($($Operators[$OperationType])): $TextExpectedValue, Found value: $($TextTestedValue)"
                    } else {
                        $ReportExtended = "Expected value ($($Operators[$OperationType])): $TextExpectedValue"
                    }
                }
            } else {
                if ($TestResult -notcontains $false) {
                    $ReportResult = $true
                    $ReportExtended = "Expected value ($($Operators[$OperationType])): $($TextExpectedValue)"
                } else {
                    $ReportResult = $false
                    if ($Test.Parameters.DisplayResult -ne $false) {
                        $ReportExtended = "Expected value ($($Operators[$OperationType])): $TextExpectedValue, Found value: $($TextTestedValue)"
                    } else {
                        $ReportExtended = "Expected value ($($Operators[$OperationType])): $TextExpectedValue"
                    }
                }

            }
        }
        if ($PropertyExtendedValue.Count -gt 0) {
            $ReportExtended = $Object
            foreach ($V in $PropertyExtendedValue) {
                $ReportExtended = $ReportExtended.$V
            }
            $ReportExtended = $ReportExtended -join ', '
        }
        Out-Status -Scope $Scope -Text $TestName -Status $ReportResult -ExtendedValue $ReportExtended -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Test $Test
        return $ReportResult
    }

    if ($Script:TestimoConfiguration.Debug.ShowErrors) {
        & $ScriptBlock
    } else {
        try {
            & $ScriptBlock
        } catch {
            Out-Status -Scope $Scope -Text $TestName -Status $false -ExtendedValue $_.Exception.Message -Domain $Domain -DomainController $DomainController -ReferenceID $ReferenceID -Test $Test
            return $False
        }
    }
}

$Script:TestimoConfiguration = [ordered] @{
    Types           = [ordered]@{ }
    Exclusions      = [ordered] @{
        Domains           = @()
        DomainControllers = @()
    }
    Inclusions      = [ordered] @{
        Domains           = @()
        DomainControllers = @()
    }
    ActiveDirectory = [ordered]@{
        ForestBackup                                 = $Backup
        ForestDHCP                                   = $ForestDHCP
        ForestReplication                            = $Replication # this should work 2012+
        ForestReplicationStatus                      = $ReplicationStatus # Thi is based on repadmin / could be useful for Windows 2008R2
        ForestOptionalFeatures                       = $OptionalFeatures
        ForestSites                                  = $Sites
        ForestSiteLinks                              = $SiteLinks
        ForestSiteLinksConnections                   = $SiteLinksConnections
        ForestRoles                                  = $ForestFSMORoles
        ForestRootKDS                                = $RootKDS
        ForestSubnets                                = $ForestSubnets
        ForestOrphanedAdmins                         = $OrphanedAdmins
        ForestTombstoneLifetime                      = $TombstoneLifetime
        ForestTrusts                                 = $Trusts
        ForestConfigurationPartitionOwners           = $ForestConfigurationPartitionOwners
        ForestConfigurationPartitionOwnersContainers = $ForestConfigurationPartitionOwnersContainers
        ForestDuplicateSPN                           = $DuplicateSPN
        ForestVulnerableSchemaClass                  = $VulnerableSchemaClass
        DomainLDAP                                   = $DomainLDAP
        DomainMachineQuota                           = $MachineQuota
        DomainDomainControllers                      = $DomainDomainControllers
        DomainRoles                                  = $DomainFSMORoles
        DomainWellKnownFolders                       = $WellKnownFolders
        DomainPasswordComplexity                     = $PasswordComplexity
        DomainGroupPolicyAssessment                  = $GroupPolicyAssessment
        DomainGroupPolicyPermissions                 = $GroupPolicyPermissions
        DomainGroupPolicyPermissionConsistency       = $GroupPolicyPermissionConsistency
        DomainGroupPolicyOwner                       = $GroupPolicyOwner
        DomainGroupPolicyADM                         = $GroupPolicyADM
        DomainGroupPolicySysvol                      = $GroupPolicySysvol
        DomainOrphanedForeignSecurityPrincipals      = $OrphanedForeignSecurityPrincipals
        DomainOrganizationalUnitsEmpty               = $OrganizationalUnitsEmpty
        DomainOrganizationalUnitsProtected           = $OrganizationalUnitsProtected
        DomainNetLogonOwner                          = $NetLogonOwner
        DomainDNSScavengingForPrimaryDNSServer       = $DNSScavengingForPrimaryDNSServer
        DomainDNSForwaders                           = $DNSForwaders
        DomainDnsZonesAging                          = $DnsZonesAging
        #DomainSecurityAdministrator = $DomainSecurityAdministrator
        DomainSecurityComputers                      = $DomainSecurityComputers
        DomainSecurityDelegatedObjects               = $DomainSecurityDelegatedObjects
        DomainSecurityGroupsAccountOperators         = $SecurityGroupsAccountOperators
        DomainSecurityGroupsSchemaAdmins             = $SecurityGroupsSchemaAdmins
        DomainSecurityUsers                          = $SecurityUsers
        DomainSecurityUsersAcccountAdministrator     = $SecurityUsersAcccountAdministrator
        DomainSecurityKrbtgt                         = $SecurityKRBGT
        DomainSysVolDFSR                             = $SysVolDFSR
        DomainDNSZonesForest0ADEL                    = $DNSZonesForest0ADEL
        DomainDNSZonesDomain0ADEL                    = $DNSZonesDomain0ADEL
        DomainDHCPAuthorized                         = $DHCPAuthorized
        DomainComputersUnsupported                   = $ComputersUnsupported
        DomainComputersUnsupportedMainstream         = $ComputersUnsupportedMainstream
        DomainExchangeUsers                          = $ExchangeUsers
        DomainDuplicateObjects                       = $DuplicateObjects
        DCInformation                                = $Information
        DCWindowsRemoteManagement                    = $WindowsRemoteManagement
        DCEventLogs                                  = $EventLogs
        DCOperatingSystem                            = $OperatingSystem
        DCServices                                   = $Services
        DCLDAP                                       = $LDAP
        DCLDAPInsecureBindings                       = $LDAPInsecureBindings
        DCPingable                                   = $Pingable
        DCPorts                                      = $Ports
        DCRDPPorts                                   = $RDPPorts
        DCRDPSecurity                                = $RDPSecurity
        DCDiskSpace                                  = $DiskSpace
        DCTimeSettings                               = $TimeSettings
        DCTimeSynchronizationInternal                = $TimeSynchronizationInternal
        DCTimeSynchronizationExternal                = $TimeSynchronizationExternal
        DCNetworkCardSettings                        = $NetworkCardSettings
        DCWindowsUpdates                             = $WindowsUpdates
        DCWindowsRolesAndFeatures                    = $WindowsRolesAndFeatures
        DCWindowsFeaturesOptional                    = $WindowsFeaturesOptional
        DCDnsResolveInternal                         = $DNSResolveInternal
        DCDnsResolveExternal                         = $DNSResolveExternal
        DCDnsNameServes                              = $DNSNameServers
        DCSMBProtocols                               = $SMBProtocols
        DCSMBShares                                  = $SMBShares
        DCSMBSharesPermissions                       = $SMBSharesPermissions
        DCDFS                                        = $DFS
        DCNTDSParameters                             = $NTDSParameters
        DCGroupPolicySYSVOLDC                        = $GroupPolicySYSVOLDC
        DCLanManagerSettings                         = $LanManagerSettings
        DCDiagnostics                                = $Diagnostics
        DCLanManServer                               = $LanManServer
        DCMSSLegacy                                  = $MSSLegacy
        DCFileSystem                                 = $FileSystem
        DCNetSessionEnumeration                      = $NetSessionEnumeration
        DCServiceWINRM                               = $ServiceWINRM
        DCUNCHardenedPaths                           = $UNCHardenedPaths
        DCDNSForwaders                               = $DCDNSForwaders
    }
    #Office365 = [ordered]@{ }
    Debug           = [ordered] @{
        ShowErrors = $false
    }
}
function Compare-Testimo {
    <#
    .SYNOPSIS
    Compares two sets of baseline data for a specific test.
 
    .DESCRIPTION
    This function compares two sets of baseline data for a specific test. It allows you to compare the source and target data to identify any differences or changes.
 
    .PARAMETER Name
    Specifies the name of the test being compared.
 
    .PARAMETER DisplayName
    Specifies the display name of the test being compared.
 
    .PARAMETER Scope
    Specifies the scope of the test being compared.
 
    .PARAMETER Category
    Specifies the category of the test being compared. Default is 'Baseline'.
 
    .PARAMETER BaseLineSource
    Specifies the baseline source data for comparison.
 
    .PARAMETER BaseLineTarget
    Specifies the baseline target data for comparison.
 
    .PARAMETER BaseLineSourcePath
    Specifies the file path to the baseline source data in JSON format.
 
    .PARAMETER BaseLineTargetPath
    Specifies the file path to the baseline target data in JSON format.
 
    .PARAMETER ExcludeProperty
    Specifies an array of properties to exclude from the comparison.
 
    .EXAMPLE
    Example 1
    ----------------
    Compare-Testimo -Name "Test1" -Scope "Domain" -BaseLineSource $SourceData1 -BaseLineTarget $TargetData1
 
    .EXAMPLE
    Example 2
    ----------------
    Compare-Testimo -Name "Test2" -DisplayName "Test 2" -Scope "Forest" -Category "Security" -BaseLineSourcePath "C:\Baseline\Test2_Source.json" -BaseLineTargetPath "C:\Baseline\Test2_Target.json" -ExcludeProperty "Property1", "Property2"
    #>

    [cmdletbinding(DefaultParameterSetName = 'JSON')]
    param(
        [parameter(Mandatory, ParameterSetName = 'Object')]
        [parameter(Mandatory, ParameterSetName = 'JSON')]
        [string] $Name,

        [parameter(ParameterSetName = 'Object')]
        [parameter(ParameterSetName = 'JSON')]
        [parameter()][string] $DisplayName,

        [parameter(Mandatory, ParameterSetName = 'Object')]
        [parameter(Mandatory, ParameterSetName = 'JSON')]
        [string] $Scope,

        [parameter(ParameterSetName = 'Object')]
        [parameter(ParameterSetName = 'JSON')]
        [string] $Category = 'Baseline',

        [parameter(Mandatory, ParameterSetName = 'Object')][Object] $BaseLineSource,
        [parameter(Mandatory, ParameterSetName = 'Object')][Object] $BaseLineTarget,
        [parameter(Mandatory, ParameterSetName = 'JSON')][Object] $BaseLineSourcePath,
        [parameter(Mandatory, ParameterSetName = 'JSON')][Object] $BaseLineTargetPath,

        [parameter(ParameterSetName = 'Object')]
        [parameter(ParameterSetName = 'JSON')]
        [string[]] $ExcludeProperty
    )

    $IsDsc = $false
    if ($PSBoundParameters.ContainsKey("BaseLineSourcePath")) {
        $SourcePath = Get-Item -LiteralPath $BaseLineSourcePath -ErrorAction SilentlyContinue
        $TargetPath = Get-Item -LiteralPath $BaseLineTargetPath -ErrorAction SilentlyContinue
        if (-not $SourcePath -or -not $TargetPath) {
            Out-Informative -Text "Could not find baseline source or target. Invalid path. Skipping source $Name" -Level 0 -Status $null -ExtendedValue $null
            return
        }
        if ($SourcePath.Extension -eq '.json' -and $TargetPath.Extension -eq '.json') {
            $BaseLineSource = Get-Content -LiteralPath $BaseLineSourcePath -Raw | ConvertFrom-Json
            $BaseLineTarget = Get-Content -LiteralPath $BaseLineTargetPath -Raw | ConvertFrom-Json
            @{
                Name            = $Name
                DisplayName     = if ($DisplayName) { $DisplayName } else { $Name }
                Scope           = $Scope
                Category        = $Category
                BaseLineSource  = $BaseLineSource
                BaseLineTarget  = $BaseLineTarget
                ExcludeProperty = $ExcludeProperty
            }
        } elseif ($SourcePath.Extension -eq '.ps1' -and $TargetPath.Extension -eq '.ps1') {
            $IsDsc = $true
            $CommandExists = Get-Command -Name 'ConvertTo-DSCObject' -ErrorAction SilentlyContinue
            if ($CommandExists) {
                $DSCGroups = [ordered] @{}
                $DSCGroupsTarget = [ordered] @{}

                [Array] $BaseLineSource = ConvertTo-DSCObject -Path $BaseLineSourcePath
                [Array] $BaseLineTarget = ConvertTo-DSCObject -Path $BaseLineTargetPath

                foreach ($DSC in $BaseLineSource) {
                    if ($DSC.Keys -notcontains 'ResourceName') {
                        Out-Informative -Text "Reading DSC Source failed. Probably missing DSC module. File $BaseLineSourcePath" -Level 0 -Status $false -ExtendedValue $null
                        continue
                    }
                    if (-not $DSCGroups[$DSC.ResourceName]) {
                        $DSCGroups[$DSC.ResourceName] = [System.Collections.Generic.List[PSCustomObject]]::new()
                    }
                    try {
                        $DSCGroups[$DSC.ResourceName].Add([PSCustomObject] $DSC)
                    } catch {
                        Out-Informative -Text "Reading DSC Source failed. Probably missing DSC module. File $BaseLineSourcePath" -Level 0 -Status $false -ExtendedValue $null
                        continue
                    }
                }
                foreach ($DSC in $BaseLineTarget) {
                    if ($DSC.Keys -notcontains 'ResourceName') {
                        Out-Informative -Text "Reading DSC Target failed. Probably missing DSC module. File $BaseLineTargetPath" -Level 0 -Status $false -ExtendedValue $null
                        continue
                    }
                    if (-not $DSCGroupsTarget[$DSC.ResourceName]) {
                        $DSCGroupsTarget[$DSC.ResourceName] = [System.Collections.Generic.List[PSCustomObject]]::new()
                    }
                    $DSCGroupsTarget[$DSC.ResourceName].Add([PSCustomObject] $DSC)
                }

                foreach ($Source in $DSCGroups.Keys) {
                    if ($DSCGroups[$Source].Count -gt 1 -or $DSCGroupsTarget[$Source].Count -gt 1) {
                        # This is to handle arrays within objects like: AADConditionalAccessPolicy
                        # By default its hard to compare array to array because the usual way is to do it by index.
                        # So we're forcing an array to become single object with it's property
                        $NewSourceObject = [ordered] @{}
                        foreach ($DSC in $DSCGroups[$Source]) {
                            if ($DSC.DisplayName) {
                                $NewSourceObject[$DSC.DisplayName] = $DSC
                            } elseif ($DSC.Name) {
                                $NewSourceObject[$DSC.Name] = $DSC
                            } elseif ($DSC.Identity) {
                                $NewSourceObject[$DSC.Identity] = $DSC
                            } else {
                                $NewSourceObject[$DSC.ResourceName] = $DSC
                            }
                        }
                        $SourceObject = [PSCustomObject] $NewSourceObject

                        $NewTargetObject = [ordered] @{}
                        foreach ($DSC in $DSCGroupsTarget[$Source]) {
                            if ($DSC.DisplayName) {
                                $NewTargetObject[$DSC.DisplayName] = $DSC
                            } elseif ($DSC.Name) {
                                $NewTargetObject[$DSC.Name] = $DSC
                            } elseif ($DSC.Identity) {
                                $NewTargetObject[$DSC.Identity] = $DSC
                            } else {
                                $NewTargetObject[$DSC.ResourceName] = $DSC
                            }
                        }
                        $TargetObject = [PSCustomObject] $NewTargetObject

                        if ($TargetObject) {
                            @{
                                Name            = $Source
                                DisplayName     = $Source
                                Scope           = $Scope
                                Category        = $Category
                                BaseLineSource  = $SourceObject
                                BaseLineTarget  = $TargetObject
                                ExcludeProperty = $ExcludeProperty
                            }
                        } else {
                            @{
                                Name            = $Source
                                DisplayName     = $Source
                                Scope           = $Scope
                                Category        = $Category
                                BaseLineSource  = $SourceObject
                                BaseLineTarget  = $null
                                ExcludeProperty = $ExcludeProperty
                            }
                        }
                    } else {
                        # This is standard DSC comparison
                        if ($DSCGroupsTarget[$Source]) {
                            @{
                                Name            = $Source
                                DisplayName     = $Source
                                Scope           = $Scope
                                Category        = $Category
                                BaseLineSource  = if ($DSCGroups[$Source].Count -eq 1) { $DSCGroups[$Source][0] } else { $DSCGroups[$Source] }
                                BaseLineTarget  = if ($DSCGroupsTarget[$Source].Count -eq 1) { $DSCGroupsTarget[$Source][0] } else { $DSCGroupsTarget[$Source] }
                                ExcludeProperty = $ExcludeProperty
                            }
                        } else {
                            @{
                                Name            = $Source
                                DisplayName     = $Source
                                Scope           = $Scope
                                Category        = $Category
                                BaseLineSource  = if ($DSCGroups[$Source].Count -eq 1) { $DSCGroups[$Source][0] } else { $DSCGroups[$Source] }
                                BaseLineTarget  = $null
                                ExcludeProperty = $ExcludeProperty
                            }
                        }
                    }
                }
            } else {
                Out-Informative -Text "DSCParser is not available. Skipping source $Name" -Level 0 -Status $null -ExtendedValue $null
            }
        } else {
            Out-Informative -Text "Only PS1 (DSC) and JSON files are supported. Skipping source $Name" -Level 0 -Status $null -ExtendedValue $null
            return
        }
        if (-not $BaseLineSource -or -not $BaseLineTarget) {
            Out-Informative -Text "Loading BaseLineSource or BaseLineTarget didn't work. Skipping source $Name" -Level 0 -Status $null -ExtendedValue $null
            return
        }
    }
}
function Get-TestimoConfiguration {
    <#
    .SYNOPSIS
    Retrieves Testimo configuration details for Active Directory sources.
 
    .DESCRIPTION
    This function retrieves the Testimo configuration details for Active Directory sources. It organizes the configuration into a structured format for better readability and management.
 
    .PARAMETER AsJson
    Indicates whether to output the configuration as JSON format.
 
    .PARAMETER FilePath
    Specifies the file path to save the configuration.
 
    .EXAMPLE
    Example 1
    ----------------
    Get-TestimoConfiguration
 
    .EXAMPLE
    Example 2
    ----------------
    Get-TestimoConfiguration -AsJson -FilePath "C:\TestimoConfiguration.json"
    #>

    [CmdletBinding()]
    param(
        [switch] $AsJson,
        [string] $FilePath
    )
    $NewConfig = [ordered] @{ }
    foreach ($Source in ($Script:TestimoConfiguration.ActiveDirectory).Keys) {
        if (-not $Script:TestimoConfiguration['ActiveDirectory'][$Source]) {
            Out-Informative -Text "Configuration for $Source is not available. Skipping source $Source" -Level 0 -Status $null -ExtendedValue $null
            continue
        }
        $NewConfig[$Source] = [ordered] @{ }
        $NewConfig[$Source]['Enable'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Enable']
        if ($null -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Source']['ExpectedOutput']) {
            $NewConfig[$Source]['Source'] = [ordered] @{ }
            $NewConfig[$Source]['Source']['ExpectedOutput'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Source']['ExpectedOutput']
        }

        if ($null -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Source']['Parameters']) {
            $NewConfig[$Source]['Source'] = [ordered] @{ }
            $NewConfig[$Source]['Source']['Parameters'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Source']['Parameters']
        }
        $NewConfig[$Source]['Tests'] = [ordered] @{ }
        foreach ($Test in $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'].Keys) {
            $NewConfig[$Source]['Tests'][$Test] = [ordered] @{ }
            $NewConfig[$Source]['Tests'][$Test]['Enable'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Enable']
            $NewConfig[$Source]['Tests'][$Test]['Parameters'] = [ordered] @{ }

            if ($null -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']) {
                if ($null -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['Property']) {

                    if ($null -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['Property']) {
                        $NewConfig[$Source]['Tests'][$Test]['Parameters']['Property'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['Property']
                    }
                    if ($null -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['ExpectedValue']) {
                        $NewConfig[$Source]['Tests'][$Test]['Parameters']['ExpectedValue'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['ExpectedValue']
                    }
                    if ($null -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['ExpectedCount']) {
                        $NewConfig[$Source]['Tests'][$Test]['Parameters']['ExpectedCount'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['ExpectedCount']
                    }
                    if ($null -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['OperationType']) {
                        $NewConfig[$Source]['Tests'][$Test]['Parameters']['OperationType'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['OperationType']
                    }
                    #if ($nulle -ne $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['PropertyExtendedValue']) {
                    # $NewConfig[$Source]['Tests'][$Test]['Parameters']['PropertyExtendedValue'] = $Script:TestimoConfiguration['ActiveDirectory'][$Source]['Tests'][$Test]['Parameters']['PropertyExtendedValue']
                    #}
                }
            }
        }
    }
    if ($FilePath) {
        $NewConfig | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $FilePath
        return
    }
    if ($AsJSON) {
        return $NewConfig | ConvertTo-Json -Depth 10
    }
    return $NewConfig
}
function Get-TestimoSources {
    <#
    .SYNOPSIS
    Retrieves information about Testimo data sources.
 
    .DESCRIPTION
    This function retrieves detailed information about Testimo data sources based on the provided source names. It returns information such as source name, scope, tests available, area, category, tags, severity, risk level, description, resolution, and resources.
 
    .PARAMETER Sources
    Specifies an array of source names to retrieve information for.
 
    .PARAMETER SourcesOnly
    Indicates whether to return only the list of source names without additional details.
 
    .PARAMETER Enabled
    Indicates whether to retrieve information only for enabled sources.
 
    .PARAMETER Advanced
    Indicates whether to include advanced details for each source.
 
    .EXAMPLE
    Example 1
    ----------------
    Get-TestimoSources -Sources "DomainComputersUnsupported", "DomainDHCPAuthorized"
 
    .EXAMPLE
    Example 2
    ----------------
    Get-TestimoSources -Sources "DomainComputersUnsupported", "DomainDHCPAuthorized" -Advanced
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $Sources,
        [switch] $SourcesOnly,
        [switch] $Enabled,
        [switch] $Advanced
    )
    if (-not $Sources) {
        $Sources = $Script:TestimoConfiguration.ActiveDirectory.Keys
    }
    if ($SourcesOnly) {
        return $Sources
    }
    foreach ($S in $Sources) {
        $Object = [ordered]@{
            Source = $S
            Scope  = $Script:TestimoConfiguration.ActiveDirectory[$S].Scope
            Name   = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Name
            Tests  = $Script:TestimoConfiguration.ActiveDirectory[$S].Tests.Keys
        }
        $Object['Area'] = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Details.Area
        $Object['Category'] = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Details.Category
        $Object['Tags'] = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Details.Tags
        $Object['Severity'] = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Details.Severity
        $Object['RiskLevel'] = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Details.RiskLevel
        $Object['Description'] = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Details.Description
        $Object['Resolution'] = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Details.Resolution
        $Object['Resources'] = $Script:TestimoConfiguration.ActiveDirectory[$S].Source.Details.Resources
        if ($Advanced) {
            $Object['Advanced'] = $Script:TestimoConfiguration.ActiveDirectory[$S]
        }
        [PSCustomObject] $Object
    }
}
function Import-PrivateModule {
    <#
    .SYNOPSIS
    Imports a private module by name.
 
    .DESCRIPTION
    This function imports a private module by name. It attempts to import the module specified by the given name. If the module is not found in the standard module directories, it looks for the module in the loaded modules.
 
    .PARAMETER Name
    Specifies the name of the private module to import.
 
    .PARAMETER Portable
    Indicates whether the module should be imported as portable.
 
    .EXAMPLE
    Example 1
    ----------------
    Import-PrivateModule -Name "MyPrivateModule"
 
    .EXAMPLE
    Example 2
    ----------------
    Import-PrivateModule -Name "MyPrivateModule" -Portable
 
    #>

    [cmdletBinding()]
    param(
        [string] $Name,
        [switch] $Portable
    )
    try {
        $ADModule = Import-Module -PassThru -Name $Name -ErrorAction Stop
    } catch {
        if ($_.Exception.Message -like '*was not loaded because no valid module file was found in any module directory*') {
            $Module = Get-Module -Name $Name
            #$PSD1 = -join ($Name, ".psd1")
            #$Module = [io.path]::Combine($Module.ModuleBase, $PSD1)
            if ($Module) {
                $ADModule = Import-Module $Module -PassThru
            }
        }
    }
    $ADModule
}
function Invoke-Testimo {
    <#
    .SYNOPSIS
    Testimo simplifies Active Directory testing and reporting.
 
    .DESCRIPTION
    Testimo simplifies Active Directory testing and reporting. It provides a way to execute tests and generate HTML reports. It's a wrapper around other modules like PSWinDocumentation, PSSharedGoods, PSEventViewer, PSWriteHTML, ADEssentials, GPOZaurr, and more.
 
    .PARAMETER BaselineTests
    Specifies the baseline tests to be executed.
 
    .PARAMETER Sources
    Specifies the type of reports to be generated from a list of available reports.
 
    .PARAMETER ExcludeSources
    Specifies the type of report to be excluded from the list of available reports. By default, all reports are run.
 
    .PARAMETER ExcludeDomains
    Excludes specific domains from the search. By default, the entire forest is scanned.
 
    .PARAMETER IncludeDomains
    Includes only specific domains in the search. By default, the entire forest is scanned.
 
    .PARAMETER ExcludeDomainControllers
    Excludes specific domain controllers from the search. By default, no exclusions are made.
 
    .PARAMETER IncludeDomainControllers
    Includes only specific domain controllers in the search. By default, all domain controllers are included.
 
    .PARAMETER IncludeTags
    Includes only tests with specific tags. By default, all tests are included.
 
    .PARAMETER ExcludeTags
    Excludes tests with specific tags. By default, no tests are excluded.
 
    .PARAMETER ForestName
    Specifies the target forest to be tested. By default, the current forest is used.
 
    .PARAMETER PassThru
    Indicates whether to return created objects after the report is generated.
 
    .PARAMETER ShowErrors
    Specifies whether to display errors during the execution of the tests.
 
    .PARAMETER ExtendedResults
    Indicates whether to return more detailed information to the console.
 
    .PARAMETER Configuration
    Loads configuration settings from a file or an object.
 
    .PARAMETER FilePath
    Path where the HTML report will be saved. If not specified, the report will be saved in the temporary directory and the path will be displayed in console.
 
    .PARAMETER ShowReport
    Specifies whether to display the HTML report once the tests are completed.
 
    .PARAMETER HideHTML
    Specifies whether to prevent the HTML report from being displayed in the default browser upon completion.
 
    .PARAMETER HideSteps
    Specifies whether to exclude the steps in the report.
 
    .PARAMETER AlwaysShowSteps
    Specifies whether to always show the steps in the report.
 
    .PARAMETER SkipRODC
    Specifies whether to skip Read-Only Domain Controllers. By default, all domain controllers are included.
 
    .PARAMETER Online
    Specifies whether HTML files should use CSS/JS from the Internet (CDN). By default, CSS/JS is embedded in the HTML file.
 
    .PARAMETER ExternalTests
    Specifies external tests to be included.
 
    .PARAMETER Variables
    Specifies additional variables to be used during the tests.
 
    .PARAMETER SplitReports
    Specifies whether to split the report into multiple files, one for each report.
 
    .EXAMPLE
    Example 1
    ----------------
    Invoke-Testimo -Sources DCDiskSpace, DCFileSystem
 
    .EXAMPLE
    Example 2
    ----------------
    Invoke-Testimo -Sources DCDiskSpace, DCFileSystem -SplitReports -ReportPath "$PSScriptRoot\Reports\Testimo.html" -AlwaysShowSteps
    Invoke-Testimo -Sources DomainComputersUnsupported, DomainDuplicateObjects -SplitReports -ReportPath "$PSScriptRoot\Reports\Testimo.html" -AlwaysShowSteps
 
    .NOTES
    General notes
    #>

    [alias('Test-ImoAD', 'Test-IMO', 'Testimo')]
    [CmdletBinding()]
    param(
        [ScriptBlock] $BaselineTests,
        [alias('Type')][string[]] $Sources,
        [alias('ExludeType')] [string[]] $ExcludeSources,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [string[]] $IncludeDomains,
        [string[]] $IncludeDomainControllers,
        # this requires rebuild of all tests
        [string] $ForestName,
        [alias('ReturnResults')][switch] $PassThru,
        [switch] $ShowErrors,
        [switch] $ExtendedResults,
        [Object] $Configuration,
        [alias('ReportPath')][string] $FilePath,
        [Parameter(DontShow)][switch] $ShowReport,
        [switch] $HideHTML,
        [alias('HideSolution')][switch] $HideSteps,
        [alias('AlwaysShowSolution')][switch] $AlwaysShowSteps,
        [switch] $SkipRODC,
        [switch] $Online,
        [string[]] $ExternalTests,
        [System.Collections.IDictionary] $Variables,
        [switch] $SplitReports,
        [alias('Tags')][string[]] $IncludeTags,
        [string[]] $ExcludeTags
    )
    if ($ShowReport) {
        Write-Warning "Invoke-Testimo - Paramter ShowReport is deprecated. By default HTML report will open up after running Testimo. If you want to prevent that, use HideHTML switch instead. This message and parameter will be removed in future releases."
    }

    $Script:Reporting = [ordered] @{ }
    $Script:Reporting['Version'] = ''
    $Script:Reporting['Errors'] = [System.Collections.Generic.List[PSCustomObject]]::new()
    $Script:Reporting['Results'] = $null
    $Script:Reporting['Summary'] = [ordered] @{ }

    $TestimoVersion = Get-Command -Name 'Invoke-Testimo' -ErrorAction SilentlyContinue
    $ProgressPreference = 'SilentlyContinue'
    [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/evotecit/Testimo/releases")
    $ProgressPreference = 'Continue'
    $LatestVersion = $GitHubReleases[0]

    if (-not $LatestVersion.Errors) {
        if ($TestimoVersion.Version -eq $LatestVersion.Version) {
            $Script:Reporting['Version'] = "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)"
        } elseif ($TestimoVersion.Version -lt $LatestVersion.Version) {
            $Script:Reporting['Version'] = "Current: $($TestimoVersion.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?"
        } elseif ($TestimoVersion.Version -gt $LatestVersion.Version) {
            $Script:Reporting['Version'] = "Current: $($TestimoVersion.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!"
        }
    } else {
        $Script:Reporting['Version'] = "Current: $($TestimoVersion.Version)"
    }
    Out-Informative -OverrideTitle 'Testimo' -Text 'Version' -Level 0 -Status $null -ExtendedValue $Script:Reporting['Version']

    if ($BaselineTests) {
        $BaseLineTestsObjects = & $BaselineTests
        if ($BaseLineTestsObjects) {
            Add-TestimoBaseLines -BaseLineObjects $BaseLineTestsObjects
        }
    }

    Add-TestimoSources -Folder $ExternalTests

    if (-not $Script:DefaultSources) {
        $Script:DefaultSources = Get-TestimoSources -Enabled -SourcesOnly
    } else {
        Set-TestsStatus -Sources $Script:DefaultSources
    }

    # make sure that tests are initialized (small one line tests require more, default data)
    Initialize-TestimoTests

    Import-TestimoConfiguration -Configuration $Configuration

    $global:ProgressPreference = 'SilentlyContinue'
    $global:ErrorActionPreference = 'Stop'
    $Script:TestResults = [System.Collections.Generic.List[PSCustomObject]]::new()
    $Script:TestimoConfiguration.Debug.ShowErrors = $ShowErrors
    $Script:TestimoConfiguration.Exclusions.Domains = $ExcludeDomains
    $Script:TestimoConfiguration.Exclusions.DomainControllers = $ExcludeDomainControllers
    $Script:TestimoConfiguration.Inclusions.Domains = $IncludeDomains
    $Script:TestimoConfiguration.Inclusions.DomainControllers = $IncludeDomainControllers

    if (-not $Sources -and -not ($IncludeTags -or $ExcludeTags)) {
        $Sources = $Script:DefaultSources
    }
    Set-TestsStatus -Sources $Sources -ExcludeSources $ExcludeSources -IncludeTags $IncludeTags -ExcludeTags $ExcludeTags

    $Script:Reporting['Forest'] = [ordered] @{ }
    $Script:Reporting['Forest']['Summary'] = $null
    $Script:Reporting['Forest']['Tests'] = [ordered] @{ }
    $Script:Reporting['Domains'] = [ordered] @{ }
    $Scopes = $Script:TestimoConfiguration.Types.Keys
    foreach ($Scope in $Scopes) {
        $Script:Reporting[$Scope] = [ordered] @{ }
        $Script:Reporting[$Scope]['Summary'] = $null
        $Script:Reporting[$Scope]['Tests'] = [ordered] @{ }
    }
    $Script:Reporting['BySource'] = [ordered] @{}


    if ($Script:TestimoConfiguration.Inclusions.Domains) {
        Out-Informative -Text 'Only following Domains will be scanned' -Level 0 -Status $null -ExtendedValue ($Script:TestimoConfiguration.Inclusions.Domains -join ', ')
    }
    if ( $Script:TestimoConfiguration.Inclusions.DomainControllers) {
        Out-Informative -Text 'Only following Domain Controllers will be scanned' -Level 0 -Status $null -ExtendedValue ($Script:TestimoConfiguration.Inclusions.DomainControllers -join ', ')
    }
    # We only exclude if inclusion is not specified for Domains
    if ($Script:TestimoConfiguration.Exclusions.Domains -and -not $Script:TestimoConfiguration.Inclusions.Domains) {
        Out-Informative -Text 'Following Domains will be ignored' -Level 0 -Status $null -ExtendedValue ($Script:TestimoConfiguration.Exclusions.Domains -join ', ')
    }
    # We only exclude if inclusion is not specified for Domain Controllers
    if ( $Script:TestimoConfiguration.Exclusions.DomainControllers -and -not $Script:TestimoConfiguration.Inclusions.DomainControllers) {
        Out-Informative -Text 'Following Domain Controllers will be ignored' -Level 0 -Status $null -ExtendedValue ($Script:TestimoConfiguration.Exclusions.DomainControllers -join ', ')
    }

    Get-RequestedSources -Sources $Sources -ExcludeSources $ExcludeSources -IncludeTags $IncludeTags -ExcludeTags $ExcludeTags

    if ($Script:TestimoConfiguration['Types']['ActiveDirectory']) {
        $ForestDetails = Get-WinADForestDetails -WarningVariable ForestWarning -WarningAction SilentlyContinue -Forest $ForestName -ExcludeDomains $ExcludeDomains -IncludeDomains $IncludeDomains -IncludeDomainControllers $IncludeDomainControllers -ExcludeDomainControllers $ExcludeDomainControllers -SkipRODC:$SkipRODC -Extended
        if ($ForestDetails) {
            # Tests related to FOREST
            $null = Start-Testing -Scope 'Forest' -ForestInformation $ForestDetails.Forest -ForestDetails $ForestDetails -SkipRODC:$SkipRODC -Variables $Variables {
                # Tests related to DOMAIN
                foreach ($Domain in $ForestDetails.Domains) {
                    $Script:Reporting['Domains'][$Domain] = [ordered] @{ }
                    $Script:Reporting['Domains'][$Domain]['Summary'] = [ordered] @{ }
                    $Script:Reporting['Domains'][$Domain]['Tests'] = [ordered] @{ }
                    $Script:Reporting['Domains'][$Domain]['DomainControllers'] = [ordered] @{ }

                    if ($ForestDetails['DomainsExtended']["$Domain"]) {
                        Start-Testing -Scope 'Domain' -Domain $Domain -DomainInformation $ForestDetails['DomainsExtended']["$Domain"] -ForestInformation $ForestDetails.Forest -ForestDetails $ForestDetails -SkipRODC:$SkipRODC -Variables $Variables {
                            # Tests related to DOMAIN CONTROLLERS
                            if (Get-TestimoSourcesStatus -Scope 'DC') {
                                foreach ($DC in $ForestDetails['DomainDomainControllers'][$Domain]) {
                                    $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DC.HostName] = [ordered] @{ }
                                    $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DC.HostName]['Summary'] = [ordered] @{ }
                                    $Script:Reporting['Domains'][$Domain]['DomainControllers'][$DC.HostName]['Tests'] = [ordered] @{ }
                                    Start-Testing -Scope 'DC' -Domain $Domain -DomainController $DC.HostName -IsPDC $DC.IsPDC -DomainInformation $ForestDetails['DomainsExtended']["$Domain"] -ForestInformation $ForestDetails.Forest -ForestDetails $ForestDetails -Variables $Variables
                                }
                            }
                        }
                        #}
                    }
                }
            }
        } else {
            Write-Color -Text '[e]', '[Testimo] ', "Forest Information couldn't be gathered. ", "[", "Error", "] ", "[", $ForestWarning, "]" -Color Red, DarkGray, Yellow, Cyan, DarkGray, Cyan, Cyan, Red, Cyan
        }
    }
    foreach ($Scope in $Scopes | Where-Object { $_ -notin 'ActiveDirectory' }) {
        if ($Script:TestimoConfiguration['Types'][$Scope]) {
            $null = Start-Testing -Scope $Scope -Variables $Variables
        }
    }
    $Script:Reporting['Results'] = $Script:TestResults

    if ($PassThru -and $ExtendedResults) {
        $Script:Reporting
    } else {
        if ($PassThru) {
            $Script:TestResults
        }
    }
    if (-not $FilePath) {
        $FilePath = Get-FileName -Extension 'html' -Temporary
    }

    Start-TestimoReport -Scopes $Scopes -FilePath $FilePath -Online:$Online -ShowHTML:(-not $HideHTML.IsPresent) -TestResults $Script:Reporting -HideSteps:$HideSteps -AlwaysShowSteps:$AlwaysShowSteps -SplitReports:$SplitReports
}

[scriptblock] $SourcesAutoCompleter = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    @(
        $Script:TestimoConfiguration.ActiveDirectory.Keys
        $Script:TestimoConfiguration.Office365.Keys
    ) | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" }
}
Register-ArgumentCompleter -CommandName Invoke-Testimo -ParameterName Sources -ScriptBlock $SourcesAutoCompleter
Register-ArgumentCompleter -CommandName Invoke-Testimo -ParameterName ExcludeSources -ScriptBlock $SourcesAutoCompleter
Register-ArgumentCompleter -CommandName Get-TestimoSources -ParameterName Sources -ScriptBlock $SourcesAutoCompleter


# Export functions and aliases as required
Export-ModuleMember -Function @('Compare-Testimo','Get-TestimoConfiguration','Get-TestimoSources','Import-PrivateModule','Invoke-Testimo') -Alias @('Testimo','Test-IMO','Test-ImoAD')


# SIG # Begin signature block
# MIItsQYJKoZIhvcNAQcCoIItojCCLZ4CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCTAFMhgA9RwzCU
# O83L+BbgM7H9knhZck0JQ8KK0vZ3V6CCJrQwggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw
# aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK
# EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm
# dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu
# d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD
# eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1
# XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld
# QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS
# YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm
# M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT
# QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx
# fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
# VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq
# hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4
# XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ
# aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg
# X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk
# apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL
# FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy
# 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u
# KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54
# zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8
# 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8
# aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w
# ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS
# g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9
# /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn
# HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0
# VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f
# sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj
# gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0
# QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv
# mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T
# /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk
# 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r
# mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG
# CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
# Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV
# HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB
# AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp
# wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl
# zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ
# cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe
# Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j
# Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh
# IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6
# OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw
# N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR
# 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2
# VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ
# CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw
# MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k
# jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9
# NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9
# URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY
# E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS
# 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa
# wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w
# c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR
# Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2
# 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK
# ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC
# AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH
# BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6
# mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/
# SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY
# gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9
# kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ
# 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew
# Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm
# Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA
# SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr
# y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR
# ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGwjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/
# X+VhFjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAx
# MzIzNTk1OVowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAKNTRYcdg45brD5UsyPgz5/X5dLnXaEOCdwvSKOX
# ejsqnGfcYhVYwamTEafNqrJq3RApih5iY2nTWJw1cb86l+uUUI8cIOrHmjsvlmbj
# aedp/lvD1isgHMGXlLSlUIHyz8sHpjBoyoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7
# ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jWPl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PB
# uOZSlbVH13gpOWvgeFmX40QrStWVzu8IF+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu
# 6QXuqvYk9R28mxyyt1/f8O52fTGZZUdVnUokL6wrl76f5P17cz4y7lI0+9S769Sg
# LDSb495uZBkHNwGRDxy1Uc2qTGaDiGhiu7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUG
# FOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmzyrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZc
# ClhMAD6FaXXHg2TWdc2PEnZWpST618RrIbroHzSYLzrqawGw9/sqhux7UjipmAmh
# cbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U2
# 0clfCKRwo+wK8REuZODLIivK8SgTIUlRfgZm0zu++uuRONhRB8qUt+JQofM604qD
# y0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAW
# BgNVHSUBAf8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglg
# hkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0O
# BBYEFKW27xPn783QZKHVVqllMaPe1eNJMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEy
# NTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZT
# SEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAIEa1t6g
# qbWYF7xwjU+KPGic2CX/yyzkzepdIpLsjCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s
# 1+FgnCvt7T1IjrhrunxdvcJhN2hJd6PrkKoS1yeF844ektrCQDifXcigLiV4JZ0q
# BXqEKZi2V3mP2yZWK7Dzp703DNiYdk9WuVLCtp04qYHnbUFcjGnRuSvExnvPnPp4
# 4pMadqJpddNQ5EQSviANnqlE0PjlSXcIWiHFtM+YlRpUurm8wWkZus8W8oM3NG6w
# QSbd3lqXTzON1I13fXVFoaVYJmoDRd7ZULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4Z
# iQPq1JE3701S88lgIcRWR+3aEUuMMsOI5ljitts++V+wQtaP4xeR0arAVeOGv6wn
# LEHQmjNKqDbUuXKWfpd5OEhfysLcPTLfddY2Z1qJ+Panx+VPNTwAvb6cKmx5Adza
# ROY63jg7B145WPR8czFVoIARyxQMfq68/qTreWWqaNYiyjvrmoI1VygWy2nyMpqy
# 0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElGt9V/zLY4wNjsHPW2obhDLN9OTH0eaHDA
# dwrUAuBcYLso/zjlUlrWrBciI0707NMX+1Br/wd3H3GXREHJuEbTbDJ8WC9nR2Xl
# G3O2mflrLAZG70Ee8PBf4NvZrZCARK+AEEGKMIIHXzCCBUegAwIBAgIQB8JSdCgU
# otar/iTqF+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg
# Q29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAw
# MDAwMFoXDTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1p
# a2/FgsOzdzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYD
# VQQDDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmV
# OrRBVRQA8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVE
# h0C/Daehvxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNd
# GVXRYOLn47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0
# 235CN4RrW+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuA
# o3+jVB8wiUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw
# 8/FNzGNPlAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP
# 0ib98XLfQpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxi
# W4oHYO28eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFK
# RqwvSSr4fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKA
# BGoIqSW05nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQID
# AQABo4ICAzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYD
# VR0OBBYEFHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNV
# HSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQw
# OTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAy
# MUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0
# cDovL3d3dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggr
# BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBo
# dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2Rl
# U2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqG
# SIb3DQEBCwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50
# ZHzoWs6EBlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa
# 1W47YSrc5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2
# CbE3JroJf2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0
# djvQSx510MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N
# 9E8hUVevxALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgi
# zpwBasrxh6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38
# wwtaJ3KYD0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Y
# n8kQMB6/Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Z
# n3exUAKqG+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe
# 6nB6bSYHv8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGC
# BlMwggZPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS
# U0E0MDk2IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgB
# ZQMEAgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8G
# CSqGSIb3DQEJBDEiBCAqh7St1/kbVhBWlIoW9S+jZss9YwRdAGbdfKtbLl64FDAN
# BgkqhkiG9w0BAQEFAASCAgBlOH9Mz1tVmk5TXa38xOuB/mnlYD3W9nZxetRjLaCn
# r2UwrpDbrCxKZWhul7U61J3ptj12o7XvGwC1BfyrnTjcul73k4mlypoG/+/FYVpO
# IjNohjTGDZ3KNCdxH9N6N//XbdZvW2Ij9VlcZxMZXsQ++LXetwOe8kt0Ue0jtJP/
# EavvqHGFDKkaOns6dunt161XiBS40yTfxk4+3TIiD02RgCQCwa/mjvZGPWFfJhmI
# 7l6G/wJ7PuB0JfQ44S+W2imJWC/GeAabQVNkfX2EwBwL/YuKitc0IexfvhYPV9C0
# SlF+Ke93hKTUbmFgzH1EpJHDu0B1YwQV3fmhsXCeRBQFz5xXIRc2ozwJg7Rug02Q
# eUXWS51xdqPOFjqRu2ywhn6uQOP65NeQNiIPLDpq9FbPBUjQZ7hb89tzC5VnuZnZ
# qAZa/bDPnOuzP6dZGEuaROvsfzMlcwLk4a23AE1AD4F+aIRUON7TD6xG2/Ma/3iy
# GkSiPEAdYPAc52ob/tGeFyOWdw1WJD/KDz46LTDCyWh3SdPlmfXXFop2DJsfDlyi
# rmz5FHCEFZBTo5tNZWqZ6HYy44SYveELSPtG/5A6Z0GH+vPQGlkjDfmx010u6mGD
# TmsmA4KpBNCuX2RDPx4H6tdTerQud14vDTZQjL/Z1mTbI3+LlhMsYK2payeRSyuI
# 5aGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5
# pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN
# AQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA5MjgxNTA5MjdaMC8GCSqGSIb3DQEJBDEi
# BCD1khEmoVx6z5TKWboZyLde+S94Tgfqb+9ggmEFg8oXwTANBgkqhkiG9w0BAQEF
# AASCAgCOglWijo4ptIFoeo9etNlbHFkBUyXnwKk6cD7GcCCVWhiZpdLkyF2PNk4e
# w2DxgivVRfMG7c445a1Z2cbAQK3YHijOsZBbLtz69zvdmPSNDkbAA4Ue86UhidOi
# OFQXu5PRp7p73GLWEEHSeI7Yr5PWvhARPu1qHZfZCflmaUQ72oWTNkjbbYpo8ww9
# PU97h/FCH9almmgbUS5usXtcpfMMbiqA6BIHX/KOvb24G0itBEMyTvURc/i7Zfsx
# O2lZ/3AkfFZCmK1iD7z7W450mkasnoomynOcjmjZhkZMrJ3/+UK3UrDr5HeNIpn4
# gJVvqmS8xZMoW1AjDv06cJR6gr8csbU5+agEb6xuVvXie/TRKoeLg440DKbnHGf2
# wXARJUEZbjkL9ipC8EtCu5jZSrYx4dogwT3HnN4CbgLGMrN9wtZwjf+78M/A4Tar
# 9lN5Vqsm2DiDQseERACbLt3zUCYhipCmws9bCtglOIL5dS09NemxuLPS2bN7i16Z
# lyNvWEO8FMKgysuE4ViYolmJrp7/IFLByirBDmziPBgv7zw4F+nEb+eqGjBVJvI1
# 57eyA47T+QdXj+4m9aa+m4waHohdR+nDA4jRtFeNb0eicZw4GhuCLYPOkV4kq/Bo
# z12jVhAfi69z1bTQQpB4DbNfXXqeRSv6hS7UTOtTLF35oyhxww==
# SIG # End signature block