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]