GPOZaurr.psm1

function ConvertFrom-DistinguishedName { 
    <#
    .SYNOPSIS
    Converts a Distinguished Name to CN, OU, Multiple OUs or DC
 
    .DESCRIPTION
    Converts a Distinguished Name to CN, OU, Multiple OUs or DC
 
    .PARAMETER DistinguishedName
    Distinguished Name to convert
 
    .PARAMETER ToOrganizationalUnit
    Converts DistinguishedName to Organizational Unit
 
    .PARAMETER ToDC
    Converts DistinguishedName to DC
 
    .PARAMETER ToDomainCN
    Converts DistinguishedName to Domain CN
 
    .EXAMPLE
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToOrganizationalUnit
 
    Output:
    OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName
 
    Output:
    Przemyslaw Klys
 
    .EXAMPLE
    ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit -IncludeParent
 
    Output:
    OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz
    OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit
 
    Output:
    OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param([Parameter(ParameterSetName = 'ToOrganizationalUnit')]
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')]
        [Parameter(ParameterSetName = 'ToDC')]
        [Parameter(ParameterSetName = 'ToDomainCN')]
        [Parameter(ParameterSetName = 'Default')]
        [alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName,
        [Parameter(ParameterSetName = 'ToOrganizationalUnit')][switch] $ToOrganizationalUnit,
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][alias('ToMultipleOU')][switch] $ToMultipleOrganizationalUnit,
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][switch] $IncludeParent,
        [Parameter(ParameterSetName = 'ToDC')][switch] $ToDC,
        [Parameter(ParameterSetName = 'ToDomainCN')][switch] $ToDomainCN)
    Process {
        foreach ($Distinguished in $DistinguishedName) {
            if ($ToDomainCN) {
                $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
                $CN = $DN -replace ',DC=', '.' -replace "DC="
                if ($CN) { $CN }
            } elseif ($ToOrganizationalUnit) {
                $Value = [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value
                if ($Value) { $Value }
            } elseif ($ToMultipleOrganizationalUnit) {
                if ($IncludeParent) { $Distinguished }
                while ($true) {
                    $Distinguished = $Distinguished -replace '^.+?,(?=..=)'
                    if ($Distinguished -match '^DC=') { break }
                    $Distinguished
                }
            } elseif ($ToDC) {
                $Value = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
                if ($Value) { $Value }
            } else {
                $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$'
                $Found = $Distinguished -match $Regex
                if ($Found) { $Matches.cn }
            }
        }
    }
}
function ConvertFrom-SID { 
    <#
    .SYNOPSIS
    Small command that can resolve SID values
 
    .DESCRIPTION
    Small command that can resolve SID values
 
    .PARAMETER SID
    Value to resolve
 
    .PARAMETER OnlyWellKnown
    Only resolve SID when it's well know SID. Otherwise return $null
 
    .PARAMETER OnlyWellKnownAdministrative
    Only resolve SID when it's administrative well know SID. Otherwise return $null
 
    .PARAMETER DoNotResolve
    Uses only dicrionary values without querying AD
 
    .EXAMPLE
    ConvertFrom-SID -SID 'S-1-5-8', 'S-1-5-9', 'S-1-5-11', 'S-1-5-18', 'S-1-1-0' -DoNotResolve
 
    .NOTES
    General notes
    #>

    [cmdletbinding(DefaultParameterSetName = 'Standard')]
    param([Parameter(ParameterSetName = 'Standard')]
        [Parameter(ParameterSetName = 'OnlyWellKnown')]
        [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')]
        [string[]] $SID,
        [Parameter(ParameterSetName = 'OnlyWellKnown')][switch] $OnlyWellKnown,
        [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')][switch] $OnlyWellKnownAdministrative,
        [Parameter(ParameterSetName = 'Standard')][switch] $DoNotResolve)
    $WellKnownAdministrative = @{'S-1-5-18' = [PSCustomObject] @{Name = 'NT AUTHORITY\SYSTEM'
            SID                                                       = 'S-1-5-18'
            DomainName                                                = ''
            Type                                                      = 'WellKnownAdministrative'
            Error                                                     = ''
        }
        'S-1-5-32-544'                      = [PSCustomObject] @{Name = 'BUILTIN\Administrators'
            SID                                  = 'S-1-5-32-544'
            DomainName                           = ''
            Type                                 = 'WellKnownAdministrative'
            Error                                = ''
        }
    }
    $wellKnownSIDs = @{'S-1-0' = [PSCustomObject] @{Name = 'Null AUTHORITY'
            SID                                          = 'S-1-0'
            DomainName                                   = ''
            Type                                         = 'WellKnownGroup'
            Error                                        = ''
        }
        'S-1-0-0'              = [PSCustomObject] @{Name = 'NULL SID'
            SID                             = 'S-1-0-0'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-1'                = [PSCustomObject] @{Name = 'WORLD AUTHORITY'
            SID                           = 'S-1-1'
            DomainName                    = ''
            Type                          = 'WellKnownGroup'
            Error                         = ''
        }
        'S-1-1-0'              = [PSCustomObject] @{Name = 'Everyone'
            SID                             = 'S-1-1-0'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-2'                = [PSCustomObject] @{Name = 'LOCAL AUTHORITY'
            SID                           = 'S-1-2'
            DomainName                    = ''
            Type                          = 'WellKnownGroup'
            Error                         = ''
        }
        'S-1-2-0'              = [PSCustomObject] @{Name = 'LOCAL'
            SID                             = 'S-1-2-0'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-2-1'              = [PSCustomObject] @{Name = 'CONSOLE LOGON'
            SID                             = 'S-1-2-1'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-3'                = [PSCustomObject] @{Name = 'CREATOR AUTHORITY'
            SID                           = 'S-1-3'
            DomainName                    = ''
            Type                          = 'WellKnownGroup'
            Error                         = ''
        }
        'S-1-3-0'              = [PSCustomObject] @{Name = 'CREATOR OWNER'
            SID                             = 'S-1-3-0'
            DomainName                      = ''
            Type                            = 'WellKnownAdministrative'
            Error                           = ''
        }
        'S-1-3-1'              = [PSCustomObject] @{Name = 'CREATOR GROUP'
            SID                             = 'S-1-3-1'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-3-2'              = [PSCustomObject] @{Name = 'CREATOR OWNER SERVER'
            SID                             = 'S-1-3-2'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-3-3'              = [PSCustomObject] @{Name = 'CREATOR GROUP SERVER'
            SID                             = 'S-1-3-3'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-3-4'              = [PSCustomObject] @{Name = 'OWNER RIGHTS'
            SID                             = 'S-1-3-4'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-80-0'           = [PSCustomObject] @{Name = 'NT SERVICE\ALL SERVICES'
            SID                                = 'S-1-5-80-0'
            DomainName                         = ''
            Type                               = 'WellKnownGroup'
            Error                              = ''
        }
        'S-1-4'                = [PSCustomObject] @{Name = 'Non-unique Authority'
            SID                           = 'S-1-4'
            DomainName                    = ''
            Type                          = 'WellKnownGroup'
            Error                         = ''
        }
        'S-1-5'                = [PSCustomObject] @{Name = 'NT AUTHORITY'
            SID                           = 'S-1-5'
            DomainName                    = ''
            Type                          = 'WellKnownGroup'
            Error                         = ''
        }
        'S-1-5-1'              = [PSCustomObject] @{Name = 'NT AUTHORITY\DIALUP'
            SID                             = 'S-1-5-1'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-2'              = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK'
            SID                             = 'S-1-5-2'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-3'              = [PSCustomObject] @{Name = 'NT AUTHORITY\BATCH'
            SID                             = 'S-1-5-3'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-4'              = [PSCustomObject] @{Name = 'NT AUTHORITY\INTERACTIVE'
            SID                             = 'S-1-5-4'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-6'              = [PSCustomObject] @{Name = 'NT AUTHORITY\SERVICE'
            SID                             = 'S-1-5-6'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-7'              = [PSCustomObject] @{Name = 'NT AUTHORITY\ANONYMOUS LOGON'
            SID                             = 'S-1-5-7'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-8'              = [PSCustomObject] @{Name = 'NT AUTHORITY\PROXY'
            SID                             = 'S-1-5-8'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-9'              = [PSCustomObject] @{Name = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS'
            SID                             = 'S-1-5-9'
            DomainName                      = ''
            Type                            = 'WellKnownGroup'
            Error                           = ''
        }
        'S-1-5-10'             = [PSCustomObject] @{Name = 'NT AUTHORITY\SELF'
            SID                              = 'S-1-5-10'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-11'             = [PSCustomObject] @{Name = 'NT AUTHORITY\Authenticated Users'
            SID                              = 'S-1-5-11'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-12'             = [PSCustomObject] @{Name = 'NT AUTHORITY\RESTRICTED'
            SID                              = 'S-1-5-12'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-13'             = [PSCustomObject] @{Name = 'NT AUTHORITY\TERMINAL SERVER USER'
            SID                              = 'S-1-5-13'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-14'             = [PSCustomObject] @{Name = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON'
            SID                              = 'S-1-5-14'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-15'             = [PSCustomObject] @{Name = 'NT AUTHORITY\This Organization'
            SID                              = 'S-1-5-15'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-17'             = [PSCustomObject] @{Name = 'NT AUTHORITY\IUSR'
            SID                              = 'S-1-5-17'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-18'             = [PSCustomObject] @{Name = 'NT AUTHORITY\SYSTEM'
            SID                              = 'S-1-5-18'
            DomainName                       = ''
            Type                             = 'WellKnownAdministrative'
            Error                            = ''
        }
        'S-1-5-19'             = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK SERVICE'
            SID                              = 'S-1-5-19'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-20'             = [PSCustomObject] @{Name = 'NT AUTHORITY\NETWORK SERVICE'
            SID                              = 'S-1-5-20'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-32-544'         = [PSCustomObject] @{Name = 'BUILTIN\Administrators'
            SID                                  = 'S-1-5-32-544'
            DomainName                           = ''
            Type                                 = 'WellKnownAdministrative'
            Error                                = ''
        }
        'S-1-5-32-545'         = [PSCustomObject] @{Name = 'BUILTIN\Users'
            SID                                  = 'S-1-5-32-545'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-546'         = [PSCustomObject] @{Name = 'BUILTIN\Guests'
            SID                                  = 'S-1-5-32-546'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-547'         = [PSCustomObject] @{Name = 'BUILTIN\Power Users'
            SID                                  = 'S-1-5-32-547'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-548'         = [PSCustomObject] @{Name = 'BUILTIN\Account Operators'
            SID                                  = 'S-1-5-32-548'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-549'         = [PSCustomObject] @{Name = 'BUILTIN\Server Operators'
            SID                                  = 'S-1-5-32-549'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-550'         = [PSCustomObject] @{Name = 'BUILTIN\Print Operators'
            SID                                  = 'S-1-5-32-550'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-551'         = [PSCustomObject] @{Name = 'BUILTIN\Backup Operators'
            SID                                  = 'S-1-5-32-551'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-552'         = [PSCustomObject] @{Name = 'BUILTIN\Replicators'
            SID                                  = 'S-1-5-32-552'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-64-10'          = [PSCustomObject] @{Name = 'NT AUTHORITY\NTLM Authentication'
            SID                                 = 'S-1-5-64-10'
            DomainName                          = ''
            Type                                = 'WellKnownGroup'
            Error                               = ''
        }
        'S-1-5-64-14'          = [PSCustomObject] @{Name = 'NT AUTHORITY\SChannel Authentication'
            SID                                 = 'S-1-5-64-14'
            DomainName                          = ''
            Type                                = 'WellKnownGroup'
            Error                               = ''
        }
        'S-1-5-64-21'          = [PSCustomObject] @{Name = 'NT AUTHORITY\Digest Authentication'
            SID                                 = 'S-1-5-64-21'
            DomainName                          = ''
            Type                                = 'WellKnownGroup'
            Error                               = ''
        }
        'S-1-5-80'             = [PSCustomObject] @{Name = 'NT SERVICE'
            SID                              = 'S-1-5-80'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-5-83-0'           = [PSCustomObject] @{Name = 'NT VIRTUAL MACHINE\Virtual Machines'
            SID                                = 'S-1-5-83-0'
            DomainName                         = ''
            Type                               = 'WellKnownGroup'
            Error                              = ''
        }
        'S-1-16-0'             = [PSCustomObject] @{Name = 'Untrusted Mandatory Level'
            SID                              = 'S-1-16-0'
            DomainName                       = ''
            Type                             = 'WellKnownGroup'
            Error                            = ''
        }
        'S-1-16-4096'          = [PSCustomObject] @{Name = 'Low Mandatory Level'
            SID                                 = 'S-1-16-4096'
            DomainName                          = ''
            Type                                = 'WellKnownGroup'
            Error                               = ''
        }
        'S-1-16-8192'          = [PSCustomObject] @{Name = 'Medium Mandatory Level'
            SID                                 = 'S-1-16-8192'
            DomainName                          = ''
            Type                                = 'WellKnownGroup'
            Error                               = ''
        }
        'S-1-16-8448'          = [PSCustomObject] @{Name = 'Medium Plus Mandatory Level'
            SID                                 = 'S-1-16-8448'
            DomainName                          = ''
            Type                                = 'WellKnownGroup'
            Error                               = ''
        }
        'S-1-16-12288'         = [PSCustomObject] @{Name = 'High Mandatory Level'
            SID                                  = 'S-1-16-12288'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-16-16384'         = [PSCustomObject] @{Name = 'System Mandatory Level'
            SID                                  = 'S-1-16-16384'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-16-20480'         = [PSCustomObject] @{Name = 'Protected Process Mandatory Level'
            SID                                  = 'S-1-16-20480'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-16-28672'         = [PSCustomObject] @{Name = 'Secure Process Mandatory Level'
            SID                                  = 'S-1-16-28672'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-554'         = [PSCustomObject] @{Name = 'BUILTIN\Pre-Windows 2000 Compatible Access'
            SID                                  = 'S-1-5-32-554'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-555'         = [PSCustomObject] @{Name = 'BUILTIN\Remote Desktop Users'
            SID                                  = 'S-1-5-32-555'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-556'         = [PSCustomObject] @{Name = 'BUILTIN\Network Configuration Operators'
            SID                                  = 'S-1-5-32-556'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-557'         = [PSCustomObject] @{Name = 'BUILTIN\Incoming Forest Trust Builders'
            SID                                  = 'S-1-5-32-557'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-558'         = [PSCustomObject] @{Name = 'BUILTIN\Performance Monitor Users'
            SID                                  = 'S-1-5-32-558'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-559'         = [PSCustomObject] @{Name = 'BUILTIN\Performance Log Users'
            SID                                  = 'S-1-5-32-559'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-560'         = [PSCustomObject] @{Name = 'BUILTIN\Windows Authorization Access Group'
            SID                                  = 'S-1-5-32-560'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-561'         = [PSCustomObject] @{Name = 'BUILTIN\Terminal Server License Servers'
            SID                                  = 'S-1-5-32-561'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-562'         = [PSCustomObject] @{Name = 'BUILTIN\Distributed COM Users'
            SID                                  = 'S-1-5-32-562'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-569'         = [PSCustomObject] @{Name = 'BUILTIN\Cryptographic Operators'
            SID                                  = 'S-1-5-32-569'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-573'         = [PSCustomObject] @{Name = 'BUILTIN\Event Log Readers'
            SID                                  = 'S-1-5-32-573'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-574'         = [PSCustomObject] @{Name = 'BUILTIN\Certificate Service DCOM Access'
            SID                                  = 'S-1-5-32-574'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-575'         = [PSCustomObject] @{Name = 'BUILTIN\RDS Remote Access Servers'
            SID                                  = 'S-1-5-32-575'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-576'         = [PSCustomObject] @{Name = 'BUILTIN\RDS Endpoint Servers'
            SID                                  = 'S-1-5-32-576'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-577'         = [PSCustomObject] @{Name = 'BUILTIN\RDS Management Servers'
            SID                                  = 'S-1-5-32-577'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-578'         = [PSCustomObject] @{Name = 'BUILTIN\Hyper-V Administrators'
            SID                                  = 'S-1-5-32-578'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-579'         = [PSCustomObject] @{Name = 'BUILTIN\Access Control Assistance Operators'
            SID                                  = 'S-1-5-32-579'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
        'S-1-5-32-580'         = [PSCustomObject] @{Name = 'BUILTIN\Remote Management Users'
            SID                                  = 'S-1-5-32-580'
            DomainName                           = ''
            Type                                 = 'WellKnownGroup'
            Error                                = ''
        }
    }
    foreach ($S in $SID) {
        if ($OnlyWellKnownAdministrative) { if ($WellKnownAdministrative[$S]) { $WellKnownAdministrative[$S] } } elseif ($OnlyWellKnown) { if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } } else {
            if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } else {
                if ($DoNotResolve) {
                    if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512") {
                        [PSCustomObject] @{Name = $S
                            SID                 = $S
                            DomainName          = ''
                            Type                = 'Administrative'
                            Error               = ''
                        }
                    } else {
                        [PSCustomObject] @{Name = $S
                            SID                 = $S
                            DomainName          = ''
                            Error               = ''
                            Type                = 'NotAdministrative'
                        }
                    }
                } else {
                    try {
                        if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512") { $Type = 'Administrative' } else { $Type = 'NotAdministrative' }
                        $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value
                        [PSCustomObject] @{Name = $Name
                            SID                 = $S
                            DomainName          = (ConvertFrom-NetbiosName -Identity $Name).DomainName
                            Type                = $Type
                            Error               = ''
                        }
                    } catch {
                        [PSCustomObject] @{Name = $S
                            SID                 = $S
                            DomainName          = ''
                            Error               = $_.Exception.Message -replace [environment]::NewLine, ' '
                            Type                = 'Unknown'
                        }
                    }
                }
            }
        }
    }
}
function Convert-Identity { 
    <#
    .SYNOPSIS
    Small command that tries to resolve any given object
 
    .DESCRIPTION
    Small command that tries to resolve any given object - be it SID, DN, FSP or Netbiosname
 
    .PARAMETER Identity
    Type to resolve in form of Identity, DN, SID
 
    .PARAMETER SID
    Allows to pass SID directly, rather then going thru verification process
 
    .PARAMETER Name
    Allows to pass Name directly, rather then going thru verification process
 
    .EXAMPLE
    $Identity = @(
        'S-1-5-4'
        'S-1-5-4'
        'S-1-5-11'
        'S-1-5-32-549'
        'S-1-5-32-550'
        'S-1-5-32-548'
        'S-1-5-64-10'
        'S-1-5-64-14'
        'S-1-5-64-21'
        'S-1-5-18'
        'S-1-5-19'
        'S-1-5-32-544'
        'S-1-5-20-20-10-51' # Wrong SID
        'S-1-5-21-853615985-2870445339-3163598659-512'
        'S-1-5-21-3661168273-3802070955-2987026695-512'
        'S-1-5-21-1928204107-2710010574-1926425344-512'
        'CN=Test Test 2,OU=Users,OU=Production,DC=ad,DC=evotec,DC=pl'
        'Test Local Group'
        'przemyslaw.klys@evotec.pl'
        'test2'
        'NT AUTHORITY\NETWORK'
        'NT AUTHORITY\SYSTEM'
        'S-1-5-21-853615985-2870445339-3163598659-519'
        'TEST\some'
        'EVOTECPL\Domain Admins'
        'NT AUTHORITY\INTERACTIVE'
        'INTERACTIVE'
        'EVOTEC\Domain Admins'
        'EVOTECPL\Domain Admins'
        'Test\Domain Admins'
        'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # Valid
        'CN=S-1-5-21-1928204107-2710010574-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # not valid
        'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # cached
    )
 
    $TestOutput = Convert-Identity -Identity $Identity -Verbose
 
    Output:
 
    Name SID DomainName Type Error
    ---- --- ---------- ---- -----
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\Authenticated Users S-1-5-11 WellKnownGroup
    BUILTIN\Server Operators S-1-5-32-549 WellKnownGroup
    BUILTIN\Print Operators S-1-5-32-550 WellKnownGroup
    BUILTIN\Account Operators S-1-5-32-548 WellKnownGroup
    NT AUTHORITY\NTLM Authentication S-1-5-64-10 WellKnownGroup
    NT AUTHORITY\SChannel Authentication S-1-5-64-14 WellKnownGroup
    NT AUTHORITY\Digest Authentication S-1-5-64-21 WellKnownGroup
    NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative
    NT AUTHORITY\NETWORK SERVICE S-1-5-19 WellKnownGroup
    BUILTIN\Administrators S-1-5-32-544 WellKnownAdministrative
    S-1-5-20-20-10-51 S-1-5-20-20-10-51 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    EVOTECPL\TestingAD S-1-5-21-3661168273-3802070955-2987026695-1111 ad.evotec.pl NotAdministrative
    EVOTEC\Test Local Group S-1-5-21-853615985-2870445339-3163598659-3610 ad.evotec.xyz NotAdministrative
    EVOTEC\przemyslaw.klys S-1-5-21-853615985-2870445339-3163598659-1105 ad.evotec.xyz NotAdministrative
    test2 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    NT AUTHORITY\NETWORK S-1-5-2 WellKnownGroup
    NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative
    EVOTEC\Enterprise Admins S-1-5-21-853615985-2870445339-3163598659-519 ad.evotec.xyz Administrative
    TEST\some S-1-5-21-1928204107-2710010574-1926425344-1106 test.evotec.pl NotAdministrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    S-1-5-21-1928204107-2710010574-512 S-1-5-21-1928204107-2710010574-512 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'Identity')]
    param([parameter(ParameterSetName = 'Identity', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][string[]] $Identity,
        [parameter(ParameterSetName = 'SID', Mandatory)][System.Security.Principal.SecurityIdentifier[]] $SID,
        [parameter(ParameterSetName = 'Name', Mandatory)][string[]] $Name)
    Begin { if (-not $Script:GlobalCacheSidConvert) { $Script:GlobalCacheSidConvert = @{} } }
    Process {
        if ($Identity) {
            foreach ($Ident in $Identity) {
                $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+")
                if ($Script:GlobalCacheSidConvert[$Ident]) {
                    Write-Verbose "Convert-Identity - Processing $Ident (Cache)"
                    $Script:GlobalCacheSidConvert[$Ident]
                } elseif ($MatchRegex.Success) {
                    Write-Verbose "Convert-Identity - Processing $Ident (SID)"
                    if ($MatchRegex.Value -ne $Ident) { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $MatchRegex.Value } else { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $Ident }
                    $Script:GlobalCacheSidConvert[$Ident]
                } elseif ($Ident -like '*DC=*') {
                    Write-Verbose "Convert-Identity - Processing $Ident (DistinguishedName)"
                    try {
                        $Object = [adsi]"LDAP://$($Ident)"
                        $SIDValue = [System.Security.Principal.SecurityIdentifier]::new($Object.objectSid.Value, 0).Value
                        $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue
                    } catch {
                        $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Ident
                            SID                                                         = $null
                            DomainName                                                  = ''
                            Type                                                        = 'Unknown'
                            Error                                                       = $_.Exception.Message -replace [environment]::NewLine, ' '
                        }
                    }
                    $Script:GlobalCacheSidConvert[$Ident]
                } else {
                    Write-Verbose "Convert-Identity - Processing $Ident (Other)"
                    try {
                        $SIDValue = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value
                        $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue
                    } catch {
                        $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Ident
                            SID                                                         = $null
                            DomainName                                                  = ''
                            Type                                                        = 'Unknown'
                            Error                                                       = $_.Exception.Message -replace [environment]::NewLine, ' '
                        }
                    }
                    $Script:GlobalCacheSidConvert[$Ident]
                }
            }
        } else {
            if ($SID) {
                foreach ($S in $SID) {
                    if ($Script:GlobalCacheSidConvert[$S]) { $Script:GlobalCacheSidConvert[$S] } else {
                        $Script:GlobalCacheSidConvert[$S] = ConvertFrom-SID -SID $S
                        $Script:GlobalCacheSidConvert[$S]
                    }
                }
            } else {
                foreach ($Ident in $Name) {
                    if ($Script:GlobalCacheSidConvert[$Ident]) { $Script:GlobalCacheSidConvert[$Ident] } else {
                        $Script:GlobalCacheSidConvert[$Ident] = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value
                        $Script:GlobalCacheSidConvert[$Ident]
                    }
                }
            }
        }
    }
    End {}
}
function Copy-Dictionary { 
    [alias('Copy-Hashtable', 'Copy-OrderedHashtable')]
    [cmdletbinding()]
    param([System.Collections.IDictionary] $Dictionary)
    $ms = [System.IO.MemoryStream]::new()
    $bf = [System.Runtime.Serialization.Formatters.Binary.BinaryFormatter]::new()
    $bf.Serialize($ms, $Dictionary)
    $ms.Position = 0
    $clone = $bf.Deserialize($ms)
    $ms.Close()
    $clone
}
function Find-DatesCurrentDayMinuxDaysX {
    param($days)

    $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(- $Days)
    $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddMilliseconds(-1)
    $DateParameters = @{DateFrom = $DateTodayStart
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesCurrentHour { 
    $DateTodayStart = (Get-Date -Minute 0 -Second 0 -Millisecond 0)
    $DateTodayEnd = $DateTodayStart.AddHours(1)
    $DateParameters = @{DateFrom = $DateTodayStart
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesDayPrevious { 
    $DateToday = (Get-Date).Date
    $DateYesterday = $DateToday.AddDays(-1)
    $DateParameters = @{DateFrom = $DateYesterday
        DateTo                   = $dateToday
    }
    return $DateParameters
}
function Find-DatesDayToday { 
    $DateToday = (Get-Date).Date
    $DateTodayEnd = $DateToday.AddDays(1).AddSeconds(-1)
    $DateParameters = @{DateFrom = $DateToday
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesMonthCurrent { 
    $DateMonthFirstDay = (Get-Date -Day 1).Date
    $DateMonthLastDay = Get-Date $DateMonthFirstDay.AddMonths(1).AddSeconds(-1)
    $DateParameters = @{DateFrom = $DateMonthFirstDay
        DateTo                   = $DateMonthLastDay
    }
    return $DateParameters
}
function Find-DatesMonthPast {
    param([bool] $Force)

    $DateToday = (Get-Date).Date
    $DateMonthFirstDay = (Get-Date -Day 1).Date
    $DateMonthPreviousFirstDay = $DateMonthFirstDay.AddMonths(-1)
    if ($Force -eq $true -or $DateToday -eq $DateMonthFirstDay) {
        $DateParameters = @{DateFrom = $DateMonthPreviousFirstDay
            DateTo                   = $DateMonthFirstDay
        }
        return $DateParameters
    } else { return $null }
}
function Find-DatesPastHour { 
    $DateTodayEnd = Get-Date -Minute 0 -Second 0 -Millisecond 0
    $DateTodayStart = $DateTodayEnd.AddHours(-1)
    $DateParameters = @{DateFrom = $DateTodayStart
        DateTo                   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesQuarterCurrent {
    param([bool] $Force)

    $Today = (Get-Date)
    $Quarter = [Math]::Ceiling($Today.Month / 3)
    $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3))
    $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date
    $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1)
    $DateParameters = @{DateFrom = $StartDate
        DateTo                   = $EndDate
    }
    return $DateParameters
}
function Find-DatesQuarterLast {
    param([bool] $Force)

    $Today = (Get-Date).AddDays(-90)
    $Yesterday = ((Get-Date).AddDays(-1))
    $Quarter = [Math]::Ceiling($Today.Month / 3)
    $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3))
    $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date
    $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1)
    if ($Force -eq $true -or $Yesterday.Date -eq $EndDate.Date) {
        $DateParameters = @{DateFrom = $StartDate
            DateTo                   = $EndDate
        }
        return $DateParameters
    } else { return $null }
}
function Format-ToTitleCase { 
    <#
    .SYNOPSIS
    Formats string or number of strings to Title Case
 
    .DESCRIPTION
    Formats string or number of strings to Title Case allowing for prettty display
 
    .PARAMETER Text
    Sentence or multiple sentences to format
 
    .PARAMETER RemoveWhiteSpace
    Removes spaces after formatting string to Title Case.
 
    .PARAMETER RemoveChar
    Array of characters to remove
 
    .EXAMPLE
    Format-ToTitleCase 'me'
 
    Output:
    Me
 
    .EXAMPLE
    'me i feel good' | Format-ToTitleCase
 
    Output:
    Me I Feel Good
    Not Feel
 
    .EXAMPLE
    'me i feel', 'not feel' | Format-ToTitleCase
 
    Output:
    Me I Feel Good
    Not Feel
 
    .EXAMPLE
    Format-ToTitleCase -Text 'This is my thing' -RemoveWhiteSpace
 
    Output:
    ThisIsMyThing
 
    .EXAMPLE
    Format-ToTitleCase -Text "This is my thing: That - No I don't want all chars" -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
 
    .NOTES
    General notes
 
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)][string[]] $Text,
        [switch] $RemoveWhiteSpace,
        [string[]] $RemoveChar)
    Begin {}
    Process {
        $Conversion = foreach ($T in $Text) {
            $Output = (Get-Culture).TextInfo.ToTitleCase($T)
            foreach ($Char in $RemoveChar) { $Output = $Output -replace $Char }
            if ($RemoveWhiteSpace) { $Output = $Output -replace ' ', '' }
            $Output
        }
        $Conversion
    }
    End {}
}
function Get-ADACL { 
    [cmdletbinding()]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [alias('Identity')][Array] $ADObject,
        #[string] $Domain = $Env:USERDNSDOMAIN,
        #[Object] $Server,
        #[string] $ForestName,
        [switch] $Extended,
        [alias('ResolveTypes')][switch] $Resolve,
        [string] $Principal,
        [switch] $Inherited,
        [switch] $NotInherited,
        [switch] $Bundle,
        [System.Security.AccessControl.AccessControlType] $AccessControlType,
        [string[]] $IncludeObjectTypeName,
        [string[]] $IncludeInheritedObjectTypeName,
        [string[]] $ExcludeObjectTypeName,
        [string[]] $ExcludeInheritedObjectTypeName,
        [System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights,
        [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights,
        [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance,
        [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance,
        [switch] $ADRightsAsArray
    )
    Begin {
        if (-not $Script:ForestGUIDs) {
            Write-Verbose "Get-ADACL - Gathering Forest GUIDS"
            $Script:ForestGUIDs = Get-WinADForestGUIDs
        }
        if (-not $Script:ForestDetails) {
            Write-Verbose "Get-ADACL - Gathering Forest Details"
            $Script:ForestDetails = Get-WinADForestDetails
        }
    }
    Process {
        foreach ($Object in $ADObject) {
            $ADObjectData = $null
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                # if object already has proper security descriptor we don't need to do additional querying
                if ($Object.ntSecurityDescriptor) {
                    $ADObjectData = $Object
                }
                [string] $DistinguishedName = $Object.DistinguishedName
                [string] $CanonicalName = $Object.CanonicalName
                if ($CanonicalName) {
                    $CanonicalName = $CanonicalName.TrimEnd('/')
                }
                [string] $ObjectClass = $Object.ObjectClass
            } elseif ($Object -is [string]) {
                [string] $DistinguishedName = $Object
                [string] $CanonicalName = ''
                [string] $ObjectClass = ''
            } else {
                Write-Warning "Get-ADACL - Object not recognized. Skipping..."
                continue
            }
            <#
            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ','
            if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                Write-Verbose "Get-ADACL - Enabling PSDrives for $DistinguishedName to $DNConverted"
                New-ADForestDrives -ForestName $ForestName #-ObjectDN $Object
                if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    Write-Warning "Get-ADACL - Drive $DNConverted not mapped. Terminating..."
                    return
                }
            }
            Write-Verbose "Get-ADACL - Getting ACL from $DistinguishedName"
            try {
                $PathACL = "$DNConverted`:\$($DistinguishedName)"
                $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop
            } catch {
                Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)"
            }
            #>

            if (-not $ADObjectData) {
                $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $DistinguishedName
                $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0]
                try {
                    $ADObjectData = Get-ADObject -Identity $DistinguishedName -Properties ntSecurityDescriptor, CanonicalName -ErrorAction Stop -Server $QueryServer
                    # Since we already request an object we might as well use the data and overwrite it if people use the string
                    $ObjectClass = $ADObjectData.ObjectClass
                    $CanonicalName = $ADObjectData.CanonicalName
                    # Real ACL
                    $ACLs = $ADObjectData.ntSecurityDescriptor
                } catch {
                    Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)"
                    continue
                }
            } else {
                # Real ACL
                $ACLs = $ADObjectData.ntSecurityDescriptor
            }
            $AccessObjects = foreach ($ACL in $ACLs.Access) {
                [Array] $ADRights = $ACL.ActiveDirectoryRights -split ', '
                if ($AccessControlType) {
                    if ($ACL.AccessControlType -ne $AccessControlType) {
                        continue
                    }
                }
                if ($Inherited) {
                    if ($ACL.IsInherited -eq $false) {
                        # if it's not inherited and we require inherited lets continue
                        continue
                    }
                }
                if ($NotInherited) {
                    if ($ACL.IsInherited -eq $true) {
                        continue
                    }
                }
                if ($IncludeActiveDirectoryRights) {
                    $FoundInclude = $false
                    foreach ($Right in $ADRights) {
                        if ($IncludeActiveDirectoryRights -contains $Right) {
                            $FoundInclude = $true
                            break
                        }
                    }
                    if (-not $FoundInclude) {
                        continue
                    }
                }
                if ($ExcludeActiveDirectoryRights) {
                    foreach ($Right in $ADRights) {
                        $FoundExclusion = $false
                        if ($ExcludeActiveDirectoryRights -contains $Right) {
                            $FoundExclusion = $true
                            break
                        }
                        if ($FoundExclusion) {
                            continue
                        }
                    }
                }
                if ($IncludeActiveDirectorySecurityInheritance) {
                    if ($IncludeActiveDirectorySecurityInheritance -notcontains $ACL.InheritanceType) {
                        continue
                    }
                }
                if ($ExcludeActiveDirectorySecurityInheritance) {
                    if ($ExcludeActiveDirectorySecurityInheritance -contains $ACL.InheritanceType) {
                        continue
                    }
                }
                $IdentityReference = $ACL.IdentityReference.Value
                if ($Principal -and $Principal -ne $IdentityReference) {
                    continue
                }

                $ReturnObject = [ordered] @{ }
                $ReturnObject['DistinguishedName' ] = $DistinguishedName
                if ($CanonicalName) {
                    $ReturnObject['CanonicalName'] = $CanonicalName
                }
                if ($ObjectClass) {
                    $ReturnObject['ObjectClass'] = $ObjectClass
                }
                $ReturnObject['AccessControlType'] = $ACL.AccessControlType
                $ReturnObject['Principal'] = $IdentityReference
                if ($Resolve) {
                    $IdentityResolve = Get-WinADObject -Identity $IdentityReference -AddType -Verbose:$false
                    if (-not $IdentityResolve) {
                        #Write-Verbose "Get-ADACL - Reverting to Convert-Identity for $IdentityReference"
                        $ConvertIdentity = Convert-Identity -Identity $IdentityReference -Verbose:$false
                        $ReturnObject['PrincipalType'] = $ConvertIdentity.Type
                        # it's not really foreignSecurityPrincipal but can't tell what it is... # https://superuser.com/questions/1067246/is-nt-authority-system-a-user-or-a-group
                        $ReturnObject['PrincipalObjectType'] = 'foreignSecurityPrincipal'
                        $ReturnObject['PrincipalObjectDomain'] = $ConvertIdentity.DomainName
                        $ReturnObject['PrincipalObjectSid'] = $ConvertIdentity.SID
                    } else {
                        if ($ReturnObject['Principal']) {
                            $ReturnObject['Principal'] = $IdentityResolve.Name
                        }
                        $ReturnObject['PrincipalType'] = $IdentityResolve.Type
                        $ReturnObject['PrincipalObjectType'] = $IdentityResolve.ObjectClass
                        $ReturnObject['PrincipalObjectDomain' ] = $IdentityResolve.DomainName
                        $ReturnObject['PrincipalObjectSid'] = $IdentityResolve.ObjectSID
                    }
                    if (-not $ReturnObject['PrincipalObjectDomain']) {
                        $ReturnObject['PrincipalObjectDomain'] = ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDomainCN
                    }
                }
                $ReturnObject['ObjectTypeName'] = $Script:ForestGUIDs["$($ACL.objectType)"]
                $ReturnObject['InheritedObjectTypeName'] = $Script:ForestGUIDs["$($ACL.inheritedObjectType)"]
                if ($IncludeObjectTypeName) {
                    if ($IncludeObjectTypeName -notcontains $ReturnObject['ObjectTypeName']) {
                        continue
                    }
                }
                if ($IncludeInheritedObjectTypeName) {
                    if ($IncludeInheritedObjectTypeName -notcontains $ReturnObject['InheritedObjectTypeName']) {
                        continue
                    }
                }
                if ($ExcludeObjectTypeName) {
                    if ($ExcludeObjectTypeName -contains $ReturnObject['ObjectTypeName']) {
                        continue
                    }
                }
                if ($ExcludeInheritedObjectTypeName) {
                    if ($ExcludeInheritedObjectTypeName -contains $ReturnObject['InheritedObjectTypeName']) {
                        continue
                    }
                }
                if ($ADRightsAsArray) {
                    $ReturnObject['ActiveDirectoryRights'] = $ADRights
                } else {
                    $ReturnObject['ActiveDirectoryRights'] = $ACL.ActiveDirectoryRights
                }
                $ReturnObject['InheritanceType'] = $ACL.InheritanceType
                $ReturnObject['IsInherited'] = $ACL.IsInherited

                if ($Extended) {
                    $ReturnObject['ObjectType'] = $ACL.ObjectType
                    $ReturnObject['InheritedObjectType'] = $ACL.InheritedObjectType
                    $ReturnObject['ObjectFlags'] = $ACL.ObjectFlags
                    $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags
                    $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags
                }
                if ($Bundle) {
                    $ReturnObject['Bundle'] = $ACL
                }
                [PSCustomObject] $ReturnObject
            }
            if ($Bundle) {
                [PSCustomObject] @{
                    DistinguishedName = $DistinguishedName
                    CanonicalName     = $Object.CanonicalName
                    ACL               = $ACLs
                    ACLAccessRules    = $AccessObjects
                    Path              = $PathACL
                }
            } else {
                $AccessObjects
            }
        }
    }
    End {}
}
function Get-ADACLOwner { 
    <#
    .SYNOPSIS
    Gets owner from given Active Directory object
 
    .DESCRIPTION
    Gets owner from given Active Directory object
 
    .PARAMETER ADObject
    Active Directory object to get owner from
 
    .PARAMETER Resolve
    Resolves owner to provide more details about said owner
 
    .PARAMETER IncludeACL
    Include additional ACL information along with owner
 
    .PARAMETER IncludeOwnerType
    Include only specific Owner Type, by default all Owner Types are included
 
    .PARAMETER ExcludeOwnerType
    Exclude specific Owner Type, by default all Owner Types are included
 
    .EXAMPLE
    Get-ADACLOwner -ADObject 'CN=Policies,CN=System,DC=ad,DC=evotec,DC=xyz' -Resolve | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [alias('Identity')][Array] $ADObject,
        [switch] $Resolve,
        [alias('AddACL')][switch] $IncludeACL,
        [validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $IncludeOwnerType,
        [validateSet('WellKnownAdministrative', 'Administrative', 'NotAdministrative', 'Unknown')][string[]] $ExcludeOwnerType
        #,
        # [System.Collections.IDictionary] $ADAdministrativeGroups,

        # [alias('ForestName')][string] $Forest,
        # [string[]] $ExcludeDomains,
        # [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        # [System.Collections.IDictionary] $ExtendedForestInformation
    )
    Begin {
        #if (-not $Script:ADAdministrativeGroups -and $Resolve) {
        #Write-Verbose "Get-GPOZaurrOwner - Getting ADAdministrativeGroups"
        #$ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        #$ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation
        #}
        if (-not $Script:ForestDetails) {
            Write-Verbose "Get-ADACL - Gathering Forest Details"
            $Script:ForestDetails = Get-WinADForestDetails
        }
    }
    Process {
        foreach ($Object in $ADObject) {
            $ADObjectData = $null
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                # if object already has proper security descriptor we don't need to do additional querying
                if ($Object.ntSecurityDescriptor) {
                    $ADObjectData = $Object
                }
                [string] $DistinguishedName = $Object.DistinguishedName
                [string] $CanonicalName = $Object.CanonicalName
                [string] $ObjectClass = $Object.ObjectClass
            } elseif ($Object -is [string]) {
                [string] $DistinguishedName = $Object
                [string] $CanonicalName = ''
                [string] $ObjectClass = ''
            } else {
                Write-Warning "Get-ADACLOwner - Object not recognized. Skipping..."
                continue
            }
            <#
            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ','
            if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                Write-Verbose "Get-ADACLOwner - Enabling PSDrives for $DistinguishedName to $DNConverted"
                New-ADForestDrives -ForestName $ForestName # -ObjectDN $DistinguishedName
                if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..."
                    return
                }
            }
            $PathACL = "$DNConverted`:\$($DistinguishedName)"
            #>


            try {
                #$ACLs = Get-Acl -Path $PathACL -ErrorAction Stop
                if (-not $ADObjectData) {
                    $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $DistinguishedName
                    $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0]
                    try {
                        $ADObjectData = Get-ADObject -Identity $DistinguishedName -Properties ntSecurityDescriptor, CanonicalName, ObjectClass -ErrorAction Stop -Server $QueryServer
                        # Since we already request an object we might as well use the data and overwrite it if people use the string
                        $ObjectClass = $ADObjectData.ObjectClass
                        $CanonicalName = $ADObjectData.CanonicalName
                        # Real ACL
                        $ACLs = $ADObjectData.ntSecurityDescriptor
                    } catch {
                        Write-Warning "Get-ADACL - Path $PathACL - Error: $($_.Exception.Message)"
                        continue
                    }
                } else {
                    # Real ACL
                    $ACLs = $ADObjectData.ntSecurityDescriptor
                }
                $Hash = [ordered] @{
                    DistinguishedName = $DistinguishedName
                    CanonicalName     = $CanonicalName
                    ObjectClass       = $ObjectClass
                    Owner             = $ACLs.Owner
                }
                $ErrorMessage = ''
            } catch {
                $ACLs = $null
                $Hash = [ordered] @{
                    DistinguishedName = $DistinguishedName
                    CanonicalName     = $CanonicalName
                    ObjectClass       = $ObjectClass
                    Owner             = $null
                }
                $ErrorMessage = $_.Exception.Message
            }
            if ($IncludeACL) {
                $Hash['ACLs'] = $ACLs
            }
            if ($Resolve) {
                #$Identity = ConvertTo-Identity -Identity $Hash.Owner -ExtendedForestInformation $ForestInformation -ADAdministrativeGroups $ADAdministrativeGroups
                if ($null -eq $Hash.Owner) {
                    $Identity = $null
                } else {
                    $Identity = Convert-Identity -Identity $Hash.Owner -Verbose:$false
                }
                if ($Identity) {
                    $Hash['OwnerName'] = $Identity.Name
                    $Hash['OwnerSid'] = $Identity.SID
                    $Hash['OwnerType'] = $Identity.Type
                } else {
                    $Hash['OwnerName'] = ''
                    $Hash['OwnerSid'] = ''
                    $Hash['OwnerType'] = ''
                }

                if ($PSBoundParameters.ContainsKey('IncludeOwnerType')) {
                    if ($Hash['OwnerType'] -in $IncludeOwnerType) {

                    } else {
                        continue
                    }
                }
                if ($PSBoundParameters.ContainsKey('ExcludeOwnerType')) {
                    if ($Hash['OwnerType'] -in $ExcludeOwnerType) {
                        continue
                    }
                }
            }
            $Hash['Error'] = $ErrorMessage
            [PSCustomObject] $Hash
        }
    }
    End { }
}
function Get-ADADministrativeGroups { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Type
    Parameter description
 
    .PARAMETER Forest
    Parameter description
 
    .PARAMETER ExcludeDomains
    Parameter description
 
    .PARAMETER IncludeDomains
    Parameter description
 
    .PARAMETER ExtendedForestInformation
    Parameter description
 
    .EXAMPLE
    Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins
 
    Output (Where VALUE is Get-ADGroup output):
    Name Value
    ---- -----
    ByNetBIOS {EVOTEC\Domain Admins, EVOTEC\Enterprise Admins, EVOTECPL\Domain Admins}
    ad.evotec.xyz {DomainAdmins, EnterpriseAdmins}
    ad.evotec.pl {DomainAdmins}
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param([parameter(Mandatory)][validateSet('DomainAdmins', 'EnterpriseAdmins')][string[]] $Type,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    $ADDictionary = [ordered] @{}
    $ADDictionary['ByNetBIOS'] = [ordered] @{}
    $ADDictionary['BySID'] = [ordered] @{}
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $ADDictionary[$Domain] = [ordered] @{}
        $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        $DomainInformation = Get-ADDomain -Server $QueryServer
        if ($Type -contains 'DomainAdmins') {
            Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-512'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_
                $ADDictionary[$Domain]['DomainAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)"
                $ADDictionary['BySID'][$_.SID.Value] = $_ }
        }
    }
    foreach ($Domain in $ForestInformation.Forest.Domains) {
        if (-not $ADDictionary[$Domain]) { $ADDictionary[$Domain] = [ordered] @{} }
        if ($Type -contains 'EnterpriseAdmins') {
            $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
            $DomainInformation = Get-ADDomain -Server $QueryServer
            Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-519'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_
                $ADDictionary[$Domain]['EnterpriseAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)"
                $ADDictionary['BySID'][$_.SID.Value] = $_ }
        }
    }
    return $ADDictionary
}
function Get-Computer { 
    [cmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Application',
            'BIOS', 'CPU', 'RAM', 'Disk', 'DiskLogical',
            'Network', 'NetworkFirewall',
            'OperatingSystem', 'Services', 'System', 'Startup', 'Time', 'WindowsUpdates')][string[]] $Type,
        [switch] $AsHashtable)
    Begin {}
    Process {
        foreach ($Computer in $ComputerName) {
            $OutputObject = [ordered] @{}
            if ($Type -contains 'Application' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Application for $Computer"
                $Application = Get-ComputerApplication -ComputerName $Computer
                $OutputObject['Application'] = $Application
            }
            if ($Type -contains 'BIOS' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing BIOS for $Computer"
                $BIOS = Get-ComputerBios -ComputerName $Computer
                $OutputObject['BIOS'] = $BIOS
            }
            if ($Type -contains 'CPU' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing CPU for $Computer"
                $CPU = Get-ComputerCPU -ComputerName $Computer
                $OutputObject['CPU'] = $CPU
            }
            if ($Type -contains 'RAM' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing RAM for $Computer"
                $RAM = Get-ComputerRAM -ComputerName $Computer
                $OutputObject['RAM'] = $RAM
            }
            if ($Type -contains 'Disk' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Disk for $Computer"
                $Disk = Get-ComputerDisk -ComputerName $Computer
                $OutputObject['Disk'] = $Disk
            }
            if ($Type -contains 'DiskLogical' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing DiskLogical for $Computer"
                $DiskLogical = Get-ComputerDiskLogical -ComputerName $Computer
                $OutputObject['DiskLogical'] = $DiskLogical
            }
            if ($Type -contains 'OperatingSystem' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing OperatingSystem for $Computer"
                $OperatingSystem = Get-ComputerOperatingSystem -ComputerName $Computer
                $OutputObject['OperatingSystem'] = $OperatingSystem
            }
            if ($Type -contains 'Network' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Network for $Computer"
                $Network = Get-ComputerNetwork -ComputerName $Computer
                $OutputObject['Network'] = $Network
            }
            if ($Type -contains 'NetworkFirewall' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing NetworkFirewall for $Computer"
                $NetworkFirewall = Get-ComputerNetwork -ComputerName $Computer -NetworkFirewallOnly
                $OutputObject['NetworkFirewall'] = $NetworkFirewall
            }
            if ($Type -contains 'RDP' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing RDP for $Computer"
                $RDP = Get-ComputerRDP -ComputerName $Computer
                $OutputObject['RDP'] = $RDP
            }
            if ($Type -contains 'Services' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Services for $Computer"
                $Services = Get-ComputerService -ComputerName $Computer
                $OutputObject['Services'] = $Services
            }
            if ($Type -contains 'System' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing System for $Computer"
                $System = Get-ComputerSystem -ComputerName $Computer
                $OutputObject['System'] = $System
            }
            if ($Type -contains 'Startup' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Startup for $Computer"
                $Startup = Get-ComputerStartup -ComputerName $Computer
                $OutputObject['Startup'] = $Startup
            }
            if ($Type -contains 'Time' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Time for $Computer"
                $Time = Get-ComputerTime -TimeTarget $Computer
                $OutputObject['Time'] = $Time
            }
            if ($Type -contains 'Tasks' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Tasks for $Computer"
                $Tasks = Get-ComputerTask -ComputerName $Computer
                $OutputObject['Tasks'] = $Tasks
            }
            if ($Type -contains 'WindowsUpdates' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing WindowsUpdates for $Computer"
                $WindowsUpdates = Get-ComputerWindowsUpdates -ComputerName $Computer
                $OutputObject['WindowsUpdates'] = $WindowsUpdates
            }
            if ($AsHashtable) { $OutputObject } else { [PSCustomObject] $OutputObject }
        }
    }
}
function Get-FileMetaData { 
    <#
    .SYNOPSIS
    Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file
 
    .DESCRIPTION
    Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file
 
    .PARAMETER File
    FileName or FileObject
 
    .EXAMPLE
    Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties
 
    .EXAMPLE
    Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Where-Object { $_.Attributes -like '*Hidden*' } | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties
 
    .NOTES
    #>

    [CmdletBinding()]
    param ([Parameter(Position = 0, ValueFromPipeline)][Object] $File,
        [ValidateSet('None', 'MACTripleDES', 'MD5', 'RIPEMD160', 'SHA1', 'SHA256', 'SHA384', 'SHA512')][string] $HashAlgorithm = 'None',
        [switch] $Signature,
        [switch] $AsHashTable)
    Process {
        foreach ($F in $File) {
            $MetaDataObject = [ordered] @{}
            if ($F -is [string]) {
                if ($F -and (Test-Path -LiteralPath $F)) {
                    $FileInformation = Get-ItemProperty -Path $F
                    if ($FileInformation -is [System.IO.DirectoryInfo]) { continue }
                } else {
                    Write-Warning "Get-FileMetaData - Doesn't exists. Skipping $F."
                    continue
                }
            } elseif ($F -is [System.IO.DirectoryInfo]) { continue } elseif ($F -is [System.IO.FileInfo]) { $FileInformation = $F } else {
                Write-Warning "Get-FileMetaData - Only files are supported. Skipping $F."
                continue
            }
            $ShellApplication = New-Object -ComObject Shell.Application
            $ShellFolder = $ShellApplication.Namespace($FileInformation.Directory.FullName)
            $ShellFile = $ShellFolder.ParseName($FileInformation.Name)
            $MetaDataProperties = [ordered] @{}
            0..400 | ForEach-Object -Process { $DataValue = $ShellFolder.GetDetailsOf($null, $_)
                $PropertyValue = (Get-Culture).TextInfo.ToTitleCase($DataValue.Trim()).Replace(' ', '')
                if ($PropertyValue -ne '') { $MetaDataProperties["$_"] = $PropertyValue } }
            foreach ($Key in $MetaDataProperties.Keys) {
                $Property = $MetaDataProperties[$Key]
                $Value = $ShellFolder.GetDetailsOf($ShellFile, [int] $Key)
                if ($Property -in 'Attributes', 'Folder', 'Type', 'SpaceFree', 'TotalSize', 'SpaceUsed') { continue }
                If (($null -ne $Value) -and ($Value -ne '')) { $MetaDataObject["$Property"] = $Value }
            }
            if ($FileInformation.VersionInfo) {
                $SplitInfo = ([string] $FileInformation.VersionInfo).Split([char]13)
                foreach ($Item in $SplitInfo) {
                    $Property = $Item.Split(":").Trim()
                    if ($Property[0] -and $Property[1] -ne '') { if ($Property[1] -in 'False', 'True') { $MetaDataObject["$($Property[0])"] = [bool] $Property[1] } else { $MetaDataObject["$($Property[0])"] = $Property[1] } }
                }
            }
            $MetaDataObject["Attributes"] = $FileInformation.Attributes
            $MetaDataObject['IsReadOnly'] = $FileInformation.IsReadOnly
            $MetaDataObject['IsHidden'] = $FileInformation.Attributes -like '*Hidden*'
            $MetaDataObject['IsSystem'] = $FileInformation.Attributes -like '*System*'
            if ($Signature) {
                $DigitalSignature = Get-AuthenticodeSignature -FilePath $FileInformation.Fullname
                $MetaDataObject['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject
                $MetaDataObject['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer
                $MetaDataObject['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber
                $MetaDataObject['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore
                $MetaDataObject['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter
                $MetaDataObject['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint
                $MetaDataObject['SignatureStatus'] = $DigitalSignature.Status
                $MetaDataObject['IsOSBinary'] = $DigitalSignature.IsOSBinary
            }
            if ($HashAlgorithm -ne 'None') { $MetaDataObject[$HashAlgorithm] = (Get-FileHash -LiteralPath $FileInformation.FullName -Algorithm $HashAlgorithm).Hash }
            if ($AsHashTable) { $MetaDataObject } else { [PSCustomObject] $MetaDataObject }
        }
    }
}
function Get-FileName { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Extension
    Parameter description
 
    .PARAMETER Temporary
    Parameter description
 
    .PARAMETER TemporaryFileOnly
    Parameter description
 
    .EXAMPLE
    Get-FileName -Temporary
    Output: 3ymsxvav.tmp
 
    .EXAMPLE
 
    Get-FileName -Temporary
    Output: C:\Users\pklys\AppData\Local\Temp\tmpD74C.tmp
 
    .EXAMPLE
 
    Get-FileName -Temporary -Extension 'xlsx'
    Output: C:\Users\pklys\AppData\Local\Temp\tmp45B6.xlsx
 
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string] $Extension = 'tmp',
        [switch] $Temporary,
        [switch] $TemporaryFileOnly)
    if ($Temporary) { return "$($([System.IO.Path]::GetTempFileName()).Replace('.tmp','')).$Extension" }
    if ($TemporaryFileOnly) { return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension" }
}
function Get-FileOwner { 
    [cmdletBinding()]
    param([Array] $Path,
        [switch] $Recursive,
        [switch] $JustPath,
        [switch] $Resolve,
        [switch] $AsHashTable)
    Begin {}
    Process {
        foreach ($P in $Path) {
            if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P }
            if ($FullPath -and (Test-Path -Path $FullPath)) {
                if ($JustPath) {
                    $FullPath | ForEach-Object -Process { $ACL = Get-Acl -Path $_
                        $Object = [ordered]@{FullName = $_
                            Owner                     = $ACL.Owner
                        }
                        if ($Resolve) {
                            $Identity = Convert-Identity -Identity $ACL.Owner
                            if ($Identity) {
                                $Object['OwnerName'] = $Identity.Name
                                $Object['OwnerSid'] = $Identity.SID
                                $Object['OwnerType'] = $Identity.Type
                            } else {
                                $Object['OwnerName'] = ''
                                $Object['OwnerSid'] = ''
                                $Object['OwnerType'] = ''
                            }
                        }
                        if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } }
                } else {
                    Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -Force | ForEach-Object -Process { $File = $_
                        $ACL = Get-Acl -Path $File.FullName
                        $Object = [ordered] @{FullName = $_.FullName
                            Extension                  = $_.Extension
                            CreationTime               = $_.CreationTime
                            LastAccessTime             = $_.LastAccessTime
                            LastWriteTime              = $_.LastWriteTime
                            Attributes                 = $_.Attributes
                            Owner                      = $ACL.Owner
                        }
                        if ($Resolve) {
                            $Identity = Convert-Identity -Identity $ACL.Owner
                            if ($Identity) {
                                $Object['OwnerName'] = $Identity.Name
                                $Object['OwnerSid'] = $Identity.SID
                                $Object['OwnerType'] = $Identity.Type
                            } else {
                                $Object['OwnerName'] = ''
                                $Object['OwnerSid'] = ''
                                $Object['OwnerType'] = ''
                            }
                        }
                        if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } }
                }
            }
        }
    }
    End {}
}
function Get-FilePermission { 
    [alias('Get-PSPermissions', 'Get-FilePermissions')]
    [cmdletBinding()]
    param([Array] $Path,
        [switch] $Inherited,
        [switch] $NotInherited,
        [switch] $ResolveTypes,
        [switch] $Extended,
        [switch] $IncludeACLObject,
        [switch] $AsHashTable,
        [System.Security.AccessControl.FileSystemSecurity] $ACLS)
    foreach ($P in $Path) {
        if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P }
        $TestPath = Test-Path -Path $FullPath
        if ($TestPath) {
            if (-not $ACLS) {
                try { $ACLS = (Get-Acl -Path $FullPath -ErrorAction Stop) } catch {
                    Write-Warning -Message "Get-FilePermission - Can't access $FullPath. Error $($_.Exception.Message)"
                    continue
                }
            }
            $Output = foreach ($ACL in $ACLS.Access) {
                if ($Inherited) { if ($ACL.IsInherited -eq $false) { continue } }
                if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } }
                $TranslateRights = Convert-GenericRightsToFileSystemRights -OriginalRights $ACL.FileSystemRights
                $ReturnObject = [ordered] @{}
                $ReturnObject['Path' ] = $FullPath
                $ReturnObject['AccessControlType'] = $ACL.AccessControlType
                if ($ResolveTypes) {
                    $Identity = Convert-Identity -Identity $ACL.IdentityReference
                    if ($Identity) {
                        $ReturnObject['Principal'] = $ACL.IdentityReference
                        $ReturnObject['PrincipalName'] = $Identity.Name
                        $ReturnObject['PrincipalSid'] = $Identity.Sid
                        $ReturnObject['PrincipalType'] = $Identity.Type
                    } else {
                        $ReturnObject['Principal'] = $Identity
                        $ReturnObject['PrincipalName'] = ''
                        $ReturnObject['PrincipalSid'] = ''
                        $ReturnObject['PrincipalType'] = ''
                    }
                } else { $ReturnObject['Principal'] = $ACL.IdentityReference.Value }
                $ReturnObject['FileSystemRights'] = $TranslateRights
                $ReturnObject['IsInherited'] = $ACL.IsInherited
                if ($Extended) {
                    $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags
                    $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags
                }
                if ($IncludeACLObject) {
                    $ReturnObject['ACL'] = $ACL
                    $ReturnObject['AllACL'] = $ACLS
                }
                if ($AsHashTable) { $ReturnObject } else { [PSCustomObject] $ReturnObject }
            }
            $Output
        } else { Write-Warning "Get-PSPermissions - Path $Path doesn't exists. Skipping." }
    }
}
function Get-GitHubLatestRelease { 
    [CmdLetBinding()]
    param([alias('ReleasesUrl')][uri] $Url)
    $ProgressPreference = 'SilentlyContinue'
    Try {
        [Array] $JsonOutput = (Invoke-WebRequest -Uri $Url -ErrorAction Stop | ConvertFrom-Json)
        foreach ($JsonContent in $JsonOutput) {
            [PSCustomObject] @{PublishDate = [DateTime] $JsonContent.published_at
                CreatedDate                = [DateTime] $JsonContent.created_at
                PreRelease                 = [bool] $JsonContent.prerelease
                Version                    = [version] ($JsonContent.name -replace 'v', '')
                Tag                        = $JsonContent.tag_name
                Branch                     = $JsonContent.target_commitish
                Errors                     = ''
            }
        }
    } catch {
        [PSCustomObject] @{PublishDate = $null
            CreatedDate                = $null
            PreRelease                 = $null
            Version                    = $null
            Tag                        = $null
            Branch                     = $null
            Errors                     = $_.Exception.Message
        }
    }
    $ProgressPreference = 'Continue'
}
function Get-PSRegistry { 
    [cmdletbinding()]
    param([alias('Path')][string[]] $RegistryPath,
        [string[]] $ComputerName = $Env:COMPUTERNAME)
    $RootKeyDictionary = @{HKEY_CLASSES_ROOT = 2147483648
        HKCR                                 = 2147483648
        HKEY_CURRENT_USER                    = 2147483649
        HKCU                                 = 2147483649
        HKEY_LOCAL_MACHINE                   = 2147483650
        HKLM                                 = 2147483650
        HKEY_USERS                           = 2147483651
        HKU                                  = 2147483651
        HKEY_CURRENT_CONFIG                  = 2147483653
        HKCC                                 = 2147483653
        HKEY_DYN_DATA                        = 2147483654
        HKDD                                 = 2147483654
    }
    $TypesDictionary = @{'1' = 'GetStringValue'
        '2'                  = 'GetExpandedStringValue'
        '3'                  = 'GetBinaryValue'
        '4'                  = 'GetDWORDValue'
        '7'                  = 'GetMultiStringValue'
        '11'                 = 'GetQWORDValue'
    }
    $Dictionary = @{'HKCR:' = 'HKEY_CLASSES_ROOT'
        'HKCU:'             = 'HKEY_CURRENT_USER'
        'HKLM:'             = 'HKEY_LOCAL_MACHINE'
        'HKU:'              = 'HKEY_USERS'
        'HKCC:'             = 'HKEY_CURRENT_CONFIG'
        'HKDD:'             = 'HKEY_DYN_DATA'
    }
    [uint32] $RootKey = $null
    [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName
    foreach ($Registry in $RegistryPath) {
        If ($Registry -like '*:*') {
            foreach ($Key in $Dictionary.Keys) {
                if ($Registry.StartsWith($Key, [System.StringComparison]::CurrentCultureIgnoreCase)) {
                    $Registry = $Registry -replace $Key, $Dictionary[$Key]
                    break
                }
            }
        }
        for ($ComputerSplit = 0; $ComputerSplit -lt $Computers.Count; $ComputerSplit++) {
            if ($Computers[$ComputerSplit].Count -gt 0) {
                $Arguments = foreach ($_ in $RootKeyDictionary.Keys) {
                    if ($Registry.StartsWith($_, [System.StringComparison]::CurrentCultureIgnoreCase)) {
                        $RootKey = [uint32] $RootKeyDictionary[$_]
                        @{hDefKey       = [uint32] $RootKeyDictionary[$_]
                            sSubKeyName = $Registry.substring($_.Length + 1)
                        }
                        break
                    }
                }
                try {
                    if ($ComputerSplit -eq 0) {
                        $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments -Verbose:$false -ErrorAction Stop
                        $OutputKeys = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumKey -Arguments $Arguments -Verbose:$false -ErrorAction Stop
                    } else {
                        $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments -ComputerName $Computers[$ComputerSplit] -Verbose:$false -ErrorAction Stop
                        $OutputKeys = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumKey -ComputerName $Computers[$ComputerSplit] -Arguments $Arguments -Verbose:$false -ErrorAction Stop
                    }
                } catch {
                    $RegistryOutput = [ordered] @{PSConnection = $false
                        PSError                                = $true
                    }
                    if (-not $RegistryOutput['PSComputerName']) { if ($ComputerSplit -eq 0) { $RegistryOutput['PSComputerName'] = $ENV:COMPUTERNAME } else { $RegistryOutput['PSComputerName'] = $Entry.PSComputerName } } else { if ($ComputerSplit -eq 0) { $RegistryOutput['ComputerName'] = $ENV:COMPUTERNAME } else { $RegistryOutput['ComputerName'] = $Entry.PSComputerName } }
                    if (-not $RegistryOutput['PSSubKeys']) { $RegistryOutput['PSSubKeys'] = $OutputKeys.sNames } else { $RegistryOutput['SubKeys'] = $OutputKeys.sNames }
                    $RegistryOutput['PSPath'] = $Registry
                    [PSCustomObject] $RegistryOutput
                    continue
                }
                foreach ($Entry in $Output2) {
                    $RegistryOutput = [ordered] @{PSConnection = $true }
                    if ($Entry.ReturnValue -ne 0) { $RegistryOutput['PSError'] = $true } else {
                        $RegistryOutput['PSError'] = $false
                        $Types = $Entry.Types
                        $Names = $Entry.sNames
                        for ($i = 0; $i -lt $Names.Count; $i++) {
                            $Arguments['sValueName'] = $Names[$i]
                            $MethodName = $TypesDictionary["$($Types[$i])"]
                            try { if ($ComputerSplit -eq 0) { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -Verbose:$false -ErrorAction Stop } else { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -ComputerName $Entry.PSComputerName -Verbose:$false -ErrorAction Stop } } catch { $Values = $null }
                            if ($null -ne $Values.sValue) { if ($Names[$i]) { $RegistryOutput[$Names[$i]] = $Values.sValue } else { $RegistryOutput['DefaultKey'] = $Values.sValue } } elseif ($null -ne $Values.uValue) { if ($Names[$i]) { $RegistryOutput[$Names[$i]] = $Values.uValue } else { $RegistryOutput['DefaultKey'] = $Values.sValue } }
                        }
                    }
                    if (-not $RegistryOutput['PSComputerName']) { if ($ComputerSplit -eq 0) { $RegistryOutput['PSComputerName'] = $ENV:COMPUTERNAME } else { $RegistryOutput['PSComputerName'] = $Entry.PSComputerName } } else { if ($ComputerSplit -eq 0) { $RegistryOutput['ComputerName'] = $ENV:COMPUTERNAME } else { $RegistryOutput['ComputerName'] = $Entry.PSComputerName } }
                    if (-not $RegistryOutput['PSSubKeys']) { $RegistryOutput['PSSubKeys'] = $OutputKeys.sNames } else { $RegistryOutput['SubKeys'] = $OutputKeys.sNames }
                    $RegistryOutput['PSPath'] = $Registry
                    [PSCustomObject] $RegistryOutput
                }
            }
        }
    }
}
function Get-WinADDuplicateObject { 
    [alias('Get-WinADForestObjectsConflict')]
    [CmdletBinding()]
    Param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string] $PartialMatchDistinguishedName,
        [string[]] $IncludeObjectClass,
        [string[]] $ExcludeObjectClass,
        [switch] $Extended,
        [switch] $NoPostProcessing
    )
    # Based on https://gallery.technet.microsoft.com/scriptcenter/Get-ADForestConflictObjects-4667fa37
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $DC = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        #Get conflict objects
        $getADObjectSplat = @{
            LDAPFilter  = "(|(cn=*\0ACNF:*)(ou=*CNF:*))"
            Properties  = 'DistinguishedName', 'ObjectClass', 'DisplayName', 'SamAccountName', 'Name', 'ObjectCategory', 'WhenCreated', 'WhenChanged', 'ProtectedFromAccidentalDeletion', 'ObjectGUID'
            Server      = $DC
            SearchScope = 'Subtree'
        }
        $Objects = Get-ADObject @getADObjectSplat
        foreach ($_ in $Objects) {
            # Lets allow users to filter on it
            if ($ExcludeObjectClass) {
                if ($ExcludeObjectClass -contains $_.ObjectClass) {
                    continue
                }
            }
            if ($IncludeObjectClass) {
                if ($IncludeObjectClass -notcontains $_.ObjectClass) {
                    continue
                }
            }
            if ($PartialMatchDistinguishedName) {
                if ($_.DistinguishedName -notlike $PartialMatchDistinguishedName) {
                    continue
                }
            }
            if ($NoPostProcessing) {
                $_
                continue
            }
            $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToDomainCN
            # Lets create separate objects for different purpoeses
            $ConflictObject = [ordered] @{
                ConflictDN          = $_.DistinguishedName
                ConflictWhenChanged = $_.WhenChanged
                DomainName          = $DomainName
                ObjectClass         = $_.ObjectClass
            }
            $LiveObjectData = [ordered] @{
                LiveDn          = "N/A"
                LiveWhenChanged = "N/A"
            }
            $RestData = [ordered] @{
                DisplayName                     = $_.DisplayName
                Name                            = $_.Name.Replace("`n", ' ')
                SamAccountName                  = $_.SamAccountName
                ObjectCategory                  = $_.ObjectCategory
                WhenCreated                     = $_.WhenCreated
                WhenChanged                     = $_.WhenChanged
                ProtectedFromAccidentalDeletion = $_.ProtectedFromAccidentalDeletion
                ObjectGUID                      = $_.ObjectGUID.Guid
            }
            if ($Extended) {
                $LiveObject = $null
                $ConflictObject = $ConflictObject + $LiveObjectData + $RestData
                #See if we are dealing with a 'cn' conflict object
                if (Select-String -SimpleMatch "\0ACNF:" -InputObject $ConflictObject.ConflictDn) {
                    #Split the conflict object DN so we can remove the conflict notation
                    $SplitConfDN = $ConflictObject.ConflictDn -split "0ACNF:"
                    #Remove the conflict notation from the DN and try to get the live AD object
                    try {
                        $LiveObject = Get-ADObject -Identity "$($SplitConfDN[0].TrimEnd("\"))$($SplitConfDN[1].Substring(36))" -Properties WhenChanged -Server $DC -ErrorAction Stop
                    } catch { }
                    if ($LiveObject) {
                        $ConflictObject.LiveDN = $LiveObject.DistinguishedName
                        $ConflictObject.LiveWhenChanged = $LiveObject.WhenChanged
                    }
                } else {
                    #Split the conflict object DN so we can remove the conflict notation for OUs
                    $SplitConfDN = $ConflictObject.ConflictDn -split "CNF:"
                    #Remove the conflict notation from the DN and try to get the live AD object
                    try {
                        $LiveObject = Get-ADObject -Identity "$($SplitConfDN[0])$($SplitConfDN[1].Substring(36))" -Properties WhenChanged -Server $DC -ErrorAction Stop
                    } catch { }
                    if ($LiveObject) {
                        $ConflictObject.LiveDN = $LiveObject.DistinguishedName
                        $ConflictObject.LiveWhenChanged = $LiveObject.WhenChanged
                    }
                }
            } else {
                $ConflictObject = $ConflictObject + $RestData
            }
            [PSCustomObject] $ConflictObject
        }
    }
}
function Get-WinADForestDetails { 
    [CmdletBinding()]
    param([alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [string] $Filter = '*',
        [switch] $TestAvailability,
        [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All',
        [int[]] $Ports = 135,
        [int] $PortsTimeout = 100,
        [int] $PingCount = 1,
        [switch] $Extended,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    if ($Global:ProgressPreference -ne 'SilentlyContinue') {
        $TemporaryProgress = $Global:ProgressPreference
        $Global:ProgressPreference = 'SilentlyContinue'
    }
    if (-not $ExtendedForestInformation) {
        $Findings = [ordered] @{}
        try { if ($Forest) { $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest } else { $ForestInformation = Get-ADForest -ErrorAction Stop } } catch {
            Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)"
            return
        }
        if (-not $ForestInformation) { return }
        $Findings['Forest'] = $ForestInformation
        $Findings['ForestDomainControllers'] = @()
        $Findings['QueryServers'] = @{}
        $Findings['DomainDomainControllers'] = @{}
        [Array] $Findings['Domains'] = foreach ($Domain in $ForestInformation.Domains) {
            if ($IncludeDomains) {
                if ($Domain -in $IncludeDomains) { $Domain.ToLower() }
                continue
            }
            if ($Domain -notin $ExcludeDomains) { $Domain.ToLower() }
        }
        [Array] $DomainsActive = foreach ($Domain in $Findings['Forest'].Domains) {
            try {
                $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop
                $OrderedDC = [ordered] @{Domain = $DC.Domain
                    Forest                      = $DC.Forest
                    HostName                    = [Array] $DC.HostName
                    IPv4Address                 = $DC.IPv4Address
                    IPv6Address                 = $DC.IPv6Address
                    Name                        = $DC.Name
                    Site                        = $DC.Site
                }
            } catch {
                Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)"
                continue
            }
            if ($Domain -eq $Findings['Forest']['Name']) { $Findings['QueryServers']['Forest'] = $OrderedDC }
            $Findings['QueryServers']["$Domain"] = $OrderedDC
            $Domain
        }
        [Array] $Findings['Domains'] = foreach ($Domain in $Findings['Domains']) {
            if ($Domain -notin $DomainsActive) {
                Write-Warning "Get-WinADForestDetails - Domain $Domain doesn't seem to be active (no DCs). Skipping."
                continue
            }
            $Domain
        }
        [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
            $QueryServer = $Findings['QueryServers'][$Domain]['HostName'][0]
            [Array] $AllDC = try {
                try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop } catch {
                    Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)"
                    continue
                }
                foreach ($S in $DomainControllers) {
                    if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } }
                    if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } }
                    $Server = [ordered] @{Domain = $Domain
                        HostName                 = $S.HostName
                        Name                     = $S.Name
                        Forest                   = $ForestInformation.RootDomain
                        Site                     = $S.Site
                        IPV4Address              = $S.IPV4Address
                        IPV6Address              = $S.IPV6Address
                        IsGlobalCatalog          = $S.IsGlobalCatalog
                        IsReadOnly               = $S.IsReadOnly
                        IsSchemaMaster           = ($S.OperationMasterRoles -contains 'SchemaMaster')
                        IsDomainNamingMaster     = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                        IsPDC                    = ($S.OperationMasterRoles -contains 'PDCEmulator')
                        IsRIDMaster              = ($S.OperationMasterRoles -contains 'RIDMaster')
                        IsInfrastructureMaster   = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                        OperatingSystem          = $S.OperatingSystem
                        OperatingSystemVersion   = $S.OperatingSystemVersion
                        OperatingSystemLong      = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion
                        LdapPort                 = $S.LdapPort
                        SslPort                  = $S.SslPort
                        DistinguishedName        = $S.ComputerObjectDN
                        Pingable                 = $null
                        WinRM                    = $null
                        PortOpen                 = $null
                        Comment                  = ''
                    }
                    if ($TestAvailability) {
                        if ($Test -eq 'All' -or $Test -like 'Ping*') { $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount }
                        if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status }
                        if ($Test -eq 'All' -or '*PortOpen*') { $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status }
                    }
                    [PSCustomObject] $Server
                }
            } catch {
                [PSCustomObject]@{Domain     = $Domain
                    HostName                 = ''
                    Name                     = ''
                    Forest                   = $ForestInformation.RootDomain
                    IPV4Address              = ''
                    IPV6Address              = ''
                    IsGlobalCatalog          = ''
                    IsReadOnly               = ''
                    Site                     = ''
                    SchemaMaster             = $false
                    DomainNamingMasterMaster = $false
                    PDCEmulator              = $false
                    RIDMaster                = $false
                    InfrastructureMaster     = $false
                    LdapPort                 = ''
                    SslPort                  = ''
                    DistinguishedName        = ''
                    Pingable                 = $null
                    WinRM                    = $null
                    PortOpen                 = $null
                    Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                }
            }
            if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC }
            [Array] $Findings['DomainDomainControllers'][$Domain]
        }
        if ($Extended) {
            $Findings['DomainsExtended'] = @{}
            $Findings['DomainsExtendedNetBIOS'] = @{}
            foreach ($DomainEx in $Findings['Domains']) {
                try {
                    $Findings['DomainsExtended'][$DomainEx] = Get-ADDomain -Server $Findings['QueryServers'][$DomainEx].HostName[0] | ForEach-Object { [ordered] @{AllowedDNSSuffixes = $_.AllowedDNSSuffixes | ForEach-Object -Process { $_ }
                            ChildDomains                                                                                                                                              = $_.ChildDomains | ForEach-Object -Process { $_ }
                            ComputersContainer                                                                                                                                        = $_.ComputersContainer
                            DeletedObjectsContainer                                                                                                                                   = $_.DeletedObjectsContainer
                            DistinguishedName                                                                                                                                         = $_.DistinguishedName
                            DNSRoot                                                                                                                                                   = $_.DNSRoot
                            DomainControllersContainer                                                                                                                                = $_.DomainControllersContainer
                            DomainMode                                                                                                                                                = $_.DomainMode
                            DomainSID                                                                                                                                                 = $_.DomainSID.Value
                            ForeignSecurityPrincipalsContainer                                                                                                                        = $_.ForeignSecurityPrincipalsContainer
                            Forest                                                                                                                                                    = $_.Forest
                            InfrastructureMaster                                                                                                                                      = $_.InfrastructureMaster
                            LastLogonReplicationInterval                                                                                                                              = $_.LastLogonReplicationInterval
                            LinkedGroupPolicyObjects                                                                                                                                  = $_.LinkedGroupPolicyObjects | ForEach-Object -Process { $_ }
                            LostAndFoundContainer                                                                                                                                     = $_.LostAndFoundContainer
                            ManagedBy                                                                                                                                                 = $_.ManagedBy
                            Name                                                                                                                                                      = $_.Name
                            NetBIOSName                                                                                                                                               = $_.NetBIOSName
                            ObjectClass                                                                                                                                               = $_.ObjectClass
                            ObjectGUID                                                                                                                                                = $_.ObjectGUID
                            ParentDomain                                                                                                                                              = $_.ParentDomain
                            PDCEmulator                                                                                                                                               = $_.PDCEmulator
                            PublicKeyRequiredPasswordRolling                                                                                                                          = $_.PublicKeyRequiredPasswordRolling | ForEach-Object -Process { $_ }
                            QuotasContainer                                                                                                                                           = $_.QuotasContainer
                            ReadOnlyReplicaDirectoryServers                                                                                                                           = $_.ReadOnlyReplicaDirectoryServers | ForEach-Object -Process { $_ }
                            ReplicaDirectoryServers                                                                                                                                   = $_.ReplicaDirectoryServers | ForEach-Object -Process { $_ }
                            RIDMaster                                                                                                                                                 = $_.RIDMaster
                            SubordinateReferences                                                                                                                                     = $_.SubordinateReferences | ForEach-Object -Process { $_ }
                            SystemsContainer                                                                                                                                          = $_.SystemsContainer
                            UsersContainer                                                                                                                                            = $_.UsersContainer
                        } }
                    $NetBios = $Findings['DomainsExtended'][$DomainEx]['NetBIOSName']
                    $Findings['DomainsExtendedNetBIOS'][$NetBios] = $Findings['DomainsExtended'][$DomainEx]
                } catch {
                    Write-Warning "Get-WinADForestDetails - Error gathering Domain Information for domain $DomainEx - $($_.Exception.Message)"
                    continue
                }
            }
        }
        if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress }
        $Findings
    } else {
        $Findings = Copy-DictionaryManual -Dictionary $ExtendedForestInformation
        [Array] $Findings['Domains'] = foreach ($_ in $Findings.Domains) {
            if ($IncludeDomains) {
                if ($_ -in $IncludeDomains) { $_.ToLower() }
                continue
            }
            if ($_ -notin $ExcludeDomains) { $_.ToLower() }
        }
        foreach ($_ in [string[]] $Findings.DomainDomainControllers.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainDomainControllers.Remove($_) } }
        foreach ($_ in [string[]] $Findings.DomainsExtended.Keys) {
            if ($_ -notin $Findings.Domains) {
                $Findings.DomainsExtended.Remove($_)
                $NetBiosName = $Findings.DomainsExtended.$_.'NetBIOSName'
                if ($NetBiosName) { $Findings.DomainsExtendedNetBIOS.Remove($NetBiosName) }
            }
        }
        [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
            [Array] $AllDC = foreach ($S in $Findings.DomainDomainControllers["$Domain"]) {
                if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } }
                if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } }
                $S
            }
            if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC }
            [Array] $Findings['DomainDomainControllers'][$Domain]
        }
        $Findings
    }
}
function Get-WinADObject { 
    <#
    .SYNOPSIS
    Gets Active Directory Object
 
    .DESCRIPTION
    Returns Active Directory Object (Computers, Groups, Users or ForeignSecurityPrincipal) using ADSI
 
    .PARAMETER Identity
    Identity of an object. It can be SamAccountName, SID, DistinguishedName or multiple other options
 
    .PARAMETER DomainName
    Choose domain name the objects resides in. This is optional for most objects
 
    .PARAMETER Credential
    Parameter description
 
    .PARAMETER IncludeGroupMembership
    Queries for group members when object is a group
 
    .PARAMETER IncludeAllTypes
    Allows functions to return all objects types and not only Computers, Groups, Users or ForeignSecurityPrincipal
 
    .EXAMPLE
    An example
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][Array] $Identity,
        [string] $DomainName,
        [pscredential] $Credential,
        #[switch] $IncludeDeletedObjects,
        [switch] $IncludeGroupMembership,
        [switch] $IncludeAllTypes,
        [switch] $AddType,
        [switch] $Cache,
        [string[]] $Properties
        #[switch] $ResolveType
    )
    Begin {
        if ($Cache -and -not $Script:CacheObjectsWinADObject) {
            $Script:CacheObjectsWinADObject = @{}
        }
        # This is purely for calling group workaround
        Add-Type -AssemblyName System.DirectoryServices.AccountManagement

        $GroupTypes = @{
            '2'           = @{
                Name  = 'Distribution Group - Global' # distribution
                Type  = 'Distribution'
                Scope = 'Global'
            }
            '4'           = @{
                Name  = 'Distribution Group - Domain Local' # distribution
                Type  = 'Distribution'
                Scope = 'Domain local'
            }
            '8'           = @{
                Name  = 'Distribution Group - Universal'
                Type  = 'Distribution'
                Scope = 'Universal'
            }
            '-2147483640' = @{
                Name  = 'Security Group - Universal'
                Type  = 'Security'
                Scope = 'Universal'
            }
            '-2147483643' = @{
                Name  = 'Security Group - Builtin Local' # Builtin local Security Group
                Type  = 'Security'
                Scope = 'Builtin local'
            }
            '-2147483644' = @{
                Name  = 'Security Group - Domain Local'
                Type  = 'Security'
                Scope = 'Domain local'
            }
            '-2147483646' = @{
                Name  = 'Security Group - Global' # security
                Type  = 'Security'
                Scope = 'Global'
            }
        }
    }
    process {
        foreach ($Ident in $Identity) {
            $ResolvedIdentity = $null
            # If it's an object we need to make sure we pass only DN
            if ($Ident.DistinguishedName) {
                $Ident = $Ident.DistinguishedName
            }
            # we reset domain name to it's given value if at all
            $TemporaryName = $Ident
            $TemporaryDomainName = $DomainName

            # Since we change $Ident below to different names we need to be sure we use original query for cache
            if ($Cache -and $Script:CacheObjectsWinADObject[$TemporaryName]) {
                Write-Verbose "Get-WinADObject - Requesting $TemporaryName from Cache"
                $Script:CacheObjectsWinADObject[$TemporaryName]
                continue
            }
            <#
            # Now we need to asses what kind of object is it
            # this is important as we accept SID, DN, ForeignSID, ForeignSecurityPrincipals or even DOMAIN\Account
            if (Test-IsDistinguishedName -Identity $Ident) {
                if ([Regex]::IsMatch($Ident, "S-\d-\d+-(\d+-){1,14}\d+")) {
                    # lets save it's value because we may need it if it's NT AUTHORITY
                    $TemporaryName = $Ident
                    $SIDConversion = Convert-Identity -Identity $Ident
                    $TemporaryDomainName = $SIDConversion.DomainName
                    $Ident = $SIDConversion.SID
                } else {
                    # We check if identity is DN and if so we provide Domain Name
                    $TemporaryDomainName = ConvertFrom-DistinguishedName -DistinguishedName $Ident -ToDomainCN
                }
            } elseif ($Ident -like "*\*") {
                # lets save it's value because we may need it if it's NT AUTHORITY
                $TemporaryName = $Ident
                $NetbiosConversion = Convert-Identity -Identity $Ident
                if ($NetbiosConversion.SID) {
                    $TemporaryDomainName = $NetbiosConversion.DomainName
                    $Ident = $NetbiosConversion.SID
                } else {
                    # It happens that sometimes things like EVOTECPL\Print Operators are not resolved, we try different method
                    $NetbiosConversion = ConvertFrom-NetbiosName -Identity $Ident
                    if ($NetbiosConversion.DomainName) {
                        $TemporaryDomainName = $NetbiosConversion.DomainName
                        $Ident = $NetbiosConversion.Name
                    }
                }
                # if no conditions happen, we let it as is
                # We do nothing, because we were not able to process DomainName so maybe something else is going on
            } elseif ([Regex]::IsMatch($Ident, "^S-\d-\d+-(\d+-){1,14}\d+$")) {
                # This is for converting sids, including foreign ones
                $SIDConversion = Convert-Identity -Identity $Ident
                if ($SIDConversion.DomainName) {
                    $TemporaryDomainName = $SIDConversion.DomainName
                    #$Ident = $NetbiosConversion.Name
                }
            }
            #>

            # if Domain Name is provided we don't check for anything as it's most likely already good Ident value
            if (-not $TemporaryDomainName) {
                $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+")
                if ($MatchRegex.Success) {
                    $ResolvedIdentity = ConvertFrom-SID -SID $MatchRegex.Value
                    $TemporaryDomainName = $ResolvedIdentity.DomainName
                    $Ident = $MatchRegex.Value
                } elseif ($Ident -like '*\*') {
                    $ResolvedIdentity = Convert-Identity -Identity $Ident
                    if ($ResolvedIdentity.SID) {
                        $TemporaryDomainName = $ResolvedIdentity.DomainName
                        $Ident = $ResolvedIdentity.SID
                    } else {
                        $NetbiosConversion = ConvertFrom-NetbiosName -Identity $Ident
                        if ($NetbiosConversion.DomainName) {
                            $TemporaryDomainName = $NetbiosConversion.DomainName
                            $Ident = $NetbiosConversion.Name
                        }
                    }
                } elseif ($Ident -like '*DC=*') {
                    $DNConversion = ConvertFrom-DistinguishedName -DistinguishedName $Ident -ToDomainCN
                    $TemporaryDomainName = $DNConversion
                } elseif ($Ident -like '*@*') {
                    $CNConversion = $Ident -split '@', 2
                    $TemporaryDomainName = $CNConversion[1]
                    $Ident = $CNConversion[0]
                } elseif ($Ident -like '*.*') {
                    $ResolvedIdentity = Convert-Identity -Identity $Ident
                    if ($ResolvedIdentity.SID) {
                        $TemporaryDomainName = $ResolvedIdentity.DomainName
                        $Ident = $ResolvedIdentity.SID
                    } else {
                        $CNConversion = $Ident -split '\.', 2
                        $Ident = $CNConversion[0]
                        $TemporaryDomainName = $CNConversion[1]
                    }
                }

                <#
                if ([Regex]::IsMatch($Ident, "S-\d-\d+-(\d+-|){1,14}\d+") -or $Ident -like '*\*' -or $Ident -like "*@*" -or $Ident -like '*.*' -or $Ident -like '*DC=*') {
                    $ResolvedIdentity = Convert-Identity -Identity $Ident #-Verbose
                    if ($ResolvedIdentity.SID) {
                        #if (-not $TemporaryDomainName) {
                        $TemporaryDomainName = $ResolvedIdentity.DomainName
                        #}
                        $Ident = $ResolvedIdentity.SID
                    } else {
                        # It happens that sometimes things like EVOTECPL\Print Operators are not resolved, we try different method
                        if ($Ident -like "*\*") {
                            $NetbiosConversion = ConvertFrom-NetbiosName -Identity $Ident
                            if ($NetbiosConversion.DomainName) {
                                #if (-not $TemporaryDomainName) {
                                $TemporaryDomainName = $NetbiosConversion.DomainName
                                # }
                                $Ident = $NetbiosConversion.Name
                            }
                        } elseif ($Ident -like '*@*') {
                            $CNConversion = $Ident -split '@', 2
                            $Ident = $CNConversion[0]
                            #if (-not $TemporaryDomainName) {
                            $TemporaryDomainName = $CNConversion[1]
                            #}
                        } elseif ($Ident -like '*.*') {
                            $CNConversion = $Ident -split '\.', 2
                            $Ident = $CNConversion[0]
                            #if (-not $TemporaryDomainName) {
                            $TemporaryDomainName = $CNConversion[1]
                            #}
                        } else {
                            # if nothing helpeed we leave it as is
                        }
                    }
                }
                #>

            }


            # Building up ADSI call
            $Search = [System.DirectoryServices.DirectorySearcher]::new()
            #$Search.SizeLimit = $SizeLimit
            if ($TemporaryDomainName) {
                try {
                    $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain', $TemporaryDomainName)
                } catch {
                    Write-Warning "Get-WinADObject - Building context failed ($TemporaryDomainName), error: $($_.Exception.Message)"
                }
            } else {
                try {
                    $Context = [System.DirectoryServices.AccountManagement.PrincipalContext]::new('Domain')
                } catch {
                    Write-Warning "Get-WinADObject - Building context failed, error: $($_.Exception.Message)"
                }
            }
            #Convert Identity Input String to HEX, if possible
            Try {
                $IdentityGUID = ""
                ([System.Guid]$Ident).ToByteArray() | ForEach-Object { $IdentityGUID += $("\{0:x2}" -f $_) }
            } Catch {
                $IdentityGUID = "null"
            }
            # Building search filter
            $Search.filter = "(|(DistinguishedName=$Ident)(Name=$Ident)(SamAccountName=$Ident)(UserPrincipalName=$Ident)(objectGUID=$IdentityGUID)(objectSid=$Ident))"

            if ($TemporaryDomainName) {
                $Search.SearchRoot = "LDAP://$TemporaryDomainName"
            }
            if ($PSBoundParameters['Credential']) {
                $Cred = [System.DirectoryServices.DirectoryEntry]::new("LDAP://$TemporaryDomainName", $($Credential.UserName), $($Credential.GetNetworkCredential().password))
                $Search.SearchRoot = $Cred
            }
            Write-Verbose "Get-WinADObject - Requesting $Ident ($TemporaryDomainName)"
            try {
                $SearchResults = $($Search.FindAll())
            } catch {
                if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                    throw "Get-WinADObject - Requesting $Ident ($TemporaryDomainName) failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))"
                } else {
                    Write-Warning "Get-WinADObject - Requesting $Ident ($TemporaryDomainName) failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine,''))"
                    continue
                }
            }

            if ($SearchResults.Count -lt 1) {
                if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                    throw "Requesting $Ident ($TemporaryDomainName) failed with no results."
                }
            }

            foreach ($Object in $SearchResults) {
                $UAC = Convert-UserAccountControl -UserAccountControl ($Object.properties.useraccountcontrol -as [string])
                $ObjectClass = ($Object.properties.objectclass -as [array])[-1]
                if ($ObjectClass -notin 'group', 'computer', 'user', 'foreignSecurityPrincipal' -and (-not $IncludeAllTypes)) {
                    Write-Warning "Get-WinADObject - Unsupported object ($Ident) of type $ObjectClass. Only user,computer,group and foreignSecurityPrincipal is supported."
                    continue
                }
                $Members = $Object.properties.member -as [array]
                if ($ObjectClass -eq 'group') {
                    # we only do this additional step when requested. It's not nessecary for day to day use but can hurt performance real bad for normal use cases
                    # This was especially visible for group with 50k members and Get-WinADObjectMember which doesn't even require this data
                    if ($IncludeGroupMembership) {
                        # This is weird case but for some reason $Object.properties.member doesn't always return all values
                        # the workaround is to do additional query for group and assing it
                        $GroupMembers = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context, $Ident).Members
                        #if ($GroupMembers.Count -ne $Members.Count) {
                        #Write-Warning "Get-WinADObject - Weird. Members count different."
                        #}
                        [Array] $Members = foreach ($Member in $GroupMembers) {
                            if ($Member.DistinguishedName) {
                                $Member.DistinguishedName
                            } elseif ($Member.DisplayName) {
                                $Member.DisplayName
                            } else {
                                $Member.Sid
                            }
                        }
                    }
                }
                $ObjectDomainName = ConvertFrom-DistinguishedName -DistinguishedName ($Object.properties.distinguishedname -as [string]) -ToDomainCN
                $DisplayName = $Object.properties.displayname -as [string]
                $SamAccountName = $Object.properties.samaccountname -as [string]
                $Name = $Object.properties.name -as [string]

                if ($ObjectClass -eq 'foreignSecurityPrincipal' -and $DisplayName -eq '') {
                    # If object is foreignSecurityPrincipal (which shouldn't happen at this point) we need to set it to temporary name we
                    # used before. Usually this is to fix 'NT AUTHORITY\INTERACTIVE'
                    # I have no clue if there's better way to do it
                    <#
                    if ($TemporaryName) {
                        $DisplayName = $TemporaryName
                        # We try to make the output similar to what is reported by Get-ADGroupMember
                    } else {
                        # But sometimes 'NT AUTHORITY\INTERACTIVE' can be searched via SID which would not hit any conditions above
                        # So we try our suprt
                        $TemporaryName = Convert-Identity -Identity $Ident
                        if ($TemporaryName -is [string]) {
                            $DisplayName = $TemporaryName
                        }
                    }
                    #>

                    #if ($TemporaryName) {
                    # $DisplayName = $TemporaryName
                    #} else {
                    $DisplayName = $ResolvedIdentity.Name
                    #}
                    if ($DisplayName -like '*\*') {
                        $NetbiosWithName = $DisplayName -split '\\'
                        if ($NetbiosWithName.Count -eq 2) {
                            #$NetbiosName = $NetbiosWithName[0]
                            $NetbiosUser = $NetbiosWithName[1]
                            $Name = $NetbiosUser
                            $SamAccountName = $NetbiosUser
                        } else {
                            $Name = $DisplayName
                        }
                    } else {
                        $Name = $DisplayName
                    }
                }

                $GroupType = $Object.properties.grouptype -as [string]
                if ($Object.Properties.objectsid) {
                    try {
                        $ObjectSID = [System.Security.Principal.SecurityIdentifier]::new($Object.Properties.objectsid[0], 0).Value
                    } catch {
                        Write-Warning "Get-WinADObject - Getting objectsid failed, error: $($_.Exception.Message)"
                        $ObjectSID = $null
                    }
                } else {
                    $ObjectSID = $null
                }

                $ReturnObject = [ordered] @{
                    DisplayName         = $DisplayName
                    Name                = $Name
                    SamAccountName      = $SamAccountName
                    ObjectClass         = $ObjectClass
                    Enabled             = if ($ObjectClass -eq 'group') { $null } else { $UAC -notcontains 'ACCOUNTDISABLE' }
                    PasswordNeverExpire = if ($ObjectClass -eq 'group') { $null } else { $UAC -contains 'DONT_EXPIRE_PASSWORD' }
                    DomainName          = $ObjectDomainName
                    Distinguishedname   = $Object.properties.distinguishedname -as [string]
                    #Adspath = $Object.properties.adspath -as [string]
                    WhenCreated         = $Object.properties.whencreated -as [string]
                    WhenChanged         = $Object.properties.whenchanged -as [string]
                    #Deleted = $Object.properties.isDeleted -as [string]
                    #Recycled = $Object.properties.isRecycled -as [string]
                    UserPrincipalName   = $Object.properties.userprincipalname -as [string]
                    ObjectSID           = $ObjectSID
                    MemberOf            = $Object.properties.memberof -as [array]
                    Members             = $Members
                    DirectReports       = $Object.Properties.directreports
                    GroupScopedType     = $GroupTypes[$GroupType].Name
                    GroupScope          = $GroupTypes[$GroupType].Scope
                    GroupType           = $GroupTypes[$GroupType].Type
                    #Administrative = if ($Object.properties.admincount -eq '1') { $true } else { $false }
                    #Type = $ResolvedIdentity.Type
                    Description         = $Object.properties.description -as [string]
                }

                if ($Properties -contains 'LastLogonDate') {
                    $LastLogon = [int64] $Object.properties.lastlogontimestamp[0]
                    if ($LastLogon -ne 9223372036854775807) {
                        $ReturnObject['LastLogonDate'] = [datetime]::FromFileTimeUtc($LastLogon)
                    } else {
                        $ReturnObject['LastLogonDate'] = $null
                    }
                }
                if ($Properties -contains 'PasswordLastSet') {
                    $PasswordLastSet = [int64] $Object.properties.pwdlastset[0]
                    if ($PasswordLastSet -ne 9223372036854775807) {
                        $ReturnObject['PasswordLastSet'] = [datetime]::FromFileTimeUtc($PasswordLastSet)
                    } else {
                        $ReturnObject['PasswordLastSet'] = $null
                    }
                }
                if ($Properties -contains 'AccountExpirationDate') {
                    $ExpirationDate = [int64] $Object.properties.accountexpires[0]
                    if ($ExpirationDate -ne 9223372036854775807) {
                        $ReturnObject['AccountExpirationDate'] = [datetime]::FromFileTimeUtc($ExpirationDate)
                    } else {
                        $ReturnObject['AccountExpirationDate'] = $null
                    }
                }

                if ($AddType) {
                    if (-not $ResolvedIdentity) {
                        # This is purely to get special types
                        $ResolvedIdentity = ConvertFrom-SID -SID $ReturnObject['ObjectSID']
                    }
                    $ReturnObject['Type'] = $ResolvedIdentity.Type
                }
                if ($ReturnObject['Type'] -eq 'WellKnownAdministrative') {
                    if (-not $TemporaryDomainName) {
                        # This is so BUILTIN\Administrators would not report domain name that's always related to current one, while it could be someone expects it to be from different forest
                        # this is to mainly address issues with Get-ADACL IdentityReference returning data that's hard to manage otherwise
                        $ReturnObject['DomainName'] = ''
                    }
                }

                <#
                $LastLogon = $Object.properties.lastlogon -as [string]
                if ($LastLogon) {
                    $LastLogonDate = [datetime]::FromFileTime($LastLogon)
                } else {
                    $LastLogonDate = $null
                }
 
                $AccountExpires = $Object.Properties.accountexpires -as [string]
                $AccountExpiresDate = ConvertTo-Date -accountExpires $AccountExpires
 
                $PasswordLastSet = $Object.Properties.pwdlastset -as [string]
                if ($PasswordLastSet) {
                    $PasswordLastSetDate = [datetime]::FromFileTime($PasswordLastSet)
                } else {
                    $PasswordLastSetDate = $null
                }
                $BadPasswordTime = $Object.Properties.badpasswordtime -as [string]
                if ($BadPasswordTime) {
                    $BadPasswordDate = [datetime]::FromFileTime($BadPasswordTime)
                } else {
                    $BadPasswordDate = $null
                }
 
                $ReturnObject['LastLogonDate'] = $LastLogonDate
                $ReturnObject['PasswordLastSet'] = $PasswordLastSetDate
                $ReturnObject['BadPasswordTime'] = $BadPasswordDate
                $ReturnObject['AccountExpiresDate'] = $AccountExpiresDate
                #>

                if ($Cache) {
                    $Script:CacheObjectsWinADObject[$TemporaryName] = [PSCustomObject] $ReturnObject
                    $Script:CacheObjectsWinADObject[$TemporaryName]
                } else {
                    [PSCustomObject] $ReturnObject
                }
            }
        }
    }
}
function Get-WinADSharePermission { 
    [cmdletBinding(DefaultParameterSetName = 'Path')]
    param(
        [Parameter(ParameterSetName = 'Path', Mandatory)][string] $Path,
        [Parameter(ParameterSetName = 'ShareType', Mandatory)][validateset('NetLogon', 'SYSVOL')][string[]] $ShareType,
        [switch] $Owner,
        [string[]] $Name,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    if ($ShareType) {
        $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        foreach ($Domain in $ForestInformation.Domains) {
            $Path = -join ("\\", $Domain, "\$ShareType")
            @(Get-Item -Path $Path -Force) + @(Get-ChildItem -Path $Path -Recurse:$true -Force -ErrorAction SilentlyContinue -ErrorVariable Err) | ForEach-Object -Process {
                if ($Owner) {
                    $Output = Get-FileOwner -JustPath -Path $_ -Resolve -AsHashTable
                    $Output['Attributes'] = $_.Attributes
                    [PSCustomObject] $Output
                } else {
                    $Output = Get-FilePermission -Path $_ -ResolveTypes -Extended -AsHashTable
                    foreach ($O in $Output) {
                        $O['Attributes'] = $_.Attributes
                        [PSCustomObject] $O
                    }
                }
            }
        }
    } else {
        if ($Path -and (Test-Path -Path $Path)) {
            @(Get-Item -Path $Path -Force) + @(Get-ChildItem -Path $Path -Recurse:$true -Force -ErrorAction SilentlyContinue -ErrorVariable Err) | ForEach-Object -Process {
                if ($Owner) {
                    $Output = Get-FileOwner -JustPath -Path $_ -Resolve -AsHashTable -Verbose
                    $Output['Attributes'] = $_.Attributes
                    [PSCustomObject] $Output
                } else {
                    $Output = Get-FilePermission -Path $_ -ResolveTypes -Extended -AsHashTable
                    foreach ($O in $Output) {
                        $O['Attributes'] = $_.Attributes
                        [PSCustomObject] $O
                    }
                }
            }
        }
    }
    foreach ($e in $err) {
        Write-Warning "Get-WinADSharePermission - $($e.Exception.Message) ($($e.CategoryInfo.Reason))"
    }
}
function Remove-ADACL { 
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [alias('Identity')][Array] $ADObject,
        [Array] $ACL,
        [string] $Principal,
        [System.DirectoryServices.ActiveDirectoryRights] $AccessRule,
        [System.Security.AccessControl.AccessControlType] $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow
    )
    if (-not $Script:ForestDetails) {
        Write-Verbose "Remove-ADACL - Gathering Forest Details"
        $Script:ForestDetails = Get-WinADForestDetails
    }
    if ($PSBoundParameters.ContainsKey('ADObject')) {
        foreach ($Object in $ADObject) {
            $MYACL = Get-ADACL -ADObject $Object -Verbose -NotInherited -Bundle
            foreach ($SubACL in $MYACL) {
                $removePrivateACLSplat = @{
                    ACL               = $SubACL
                    Principal         = $Principal
                    AccessRule        = $AccessRule
                    AccessControlType = $AccessControlType
                    WhatIf            = $WhatIfPreference
                }
                Remove-EmptyValue -Hashtable $removePrivateACLSplat
                Remove-PrivateACL @removePrivateACLSplat
            }
        }
    } elseif ($PSBoundParameters.ContainsKey('ACL')) {
        foreach ($SubACL in $ACL) {
            $removePrivateACLSplat = @{
                ACL               = $SubACL
                Principal         = $Principal
                AccessRule        = $AccessRule
                AccessControlType = $AccessControlType
                WhatIf            = $WhatIfPreference
            }
            Remove-EmptyValue -Hashtable $removePrivateACLSplat
            Remove-PrivateACL @removePrivateACLSplat
        }
    }
}
function Remove-EmptyValue { 
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary)
    foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } }
    if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } }
}
function Select-Properties { 
    <#
    .SYNOPSIS
    Allows for easy selecting property names from one or multiple objects
 
    .DESCRIPTION
    Allows for easy selecting property names from one or multiple objects. This is especially useful with using AllProperties parameter where we want to make sure to get all properties from all objects.
 
    .PARAMETER Objects
    One or more objects
 
    .PARAMETER Property
    Properties to include
 
    .PARAMETER ExcludeProperty
    Properties to exclude
 
    .PARAMETER AllProperties
    All unique properties from all objects
 
    .PARAMETER PropertyNameReplacement
    Default property name when object has no properties
 
    .EXAMPLE
    $Object1 = [PSCustomobject] @{
        Name1 = '1'
        Name2 = '3'
        Name3 = '5'
    }
    $Object2 = [PSCustomobject] @{
        Name4 = '2'
        Name5 = '6'
        Name6 = '7'
    }
 
    Select-Properties -Objects $Object1, $Object2 -AllProperties
 
    #OR:
 
    $Object1, $Object2 | Select-Properties -AllProperties -ExcludeProperty Name6 -Property Name3
 
    .EXAMPLE
    $Object3 = [Ordered] @{
        Name1 = '1'
        Name2 = '3'
        Name3 = '5'
    }
    $Object4 = [Ordered] @{
        Name4 = '2'
        Name5 = '6'
        Name6 = '7'
    }
 
    Select-Properties -Objects $Object3, $Object4 -AllProperties
 
    $Object3, $Object4 | Select-Properties -AllProperties
 
    .NOTES
    General notes
    #>

    [CmdLetBinding()]
    param([Array][Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] $Objects,
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [switch] $AllProperties,
        [string] $PropertyNameReplacement = '*')
    Begin {
        function Select-Unique {
            [CmdLetBinding()]
            param([System.Collections.IList] $Object)
            $New = $Object.ToLower() | Select-Object -Unique
            $Selected = foreach ($_ in $New) {
                $Index = $Object.ToLower().IndexOf($_)
                if ($Index -ne -1) { $Object[$Index] }
            }
            $Selected
        }
        $ObjectsList = [System.Collections.Generic.List[Object]]::new()
    }
    Process { foreach ($Object in $Objects) { $ObjectsList.Add($Object) } }
    End {
        if ($ObjectsList.Count -eq 0) {
            Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.'
            return
        }
        if ($ObjectsList[0] -is [System.Collections.IDictionary]) {
            if ($AllProperties) {
                [Array] $All = foreach ($_ in $ObjectsList) { $_.Keys }
                $FirstObjectProperties = Select-Unique -Object $All
            } else { $FirstObjectProperties = $ObjectsList[0].Keys }
            if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) {
                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($Property -contains $_ -and $ExcludeProperty -notcontains $_) {
                        $_
                        continue
                    }
                }
            } elseif ($Property.Count -gt 0) {
                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($Property -contains $_) {
                        $_
                        continue
                    }
                }
            } elseif ($ExcludeProperty.Count -gt 0) {
                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($ExcludeProperty -notcontains $_) {
                        $_
                        continue
                    }
                }
            }
        } elseif ($ObjectsList[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') { $FirstObjectProperties = $PropertyNameReplacement } else {
            if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty } elseif ($Property.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property $Property } elseif ($ExcludeProperty.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty }
            if ($AllProperties) {
                [Array] $All = foreach ($_ in $ObjectsList) { $_.PSObject.Properties.Name }
                $FirstObjectProperties = Select-Unique -Object $All
            } else { $FirstObjectProperties = $ObjectsList[0].PSObject.Properties.Name }
        }
        $FirstObjectProperties
    }
}
function Set-ADACLOwner { 
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)][alias('Identity')][Array] $ADObject,
        [Parameter(Mandatory)][string] $Principal
    )
    Begin {
        if ($Principal -is [string]) {
            if ($Principal -like '*/*') {
                $SplittedName = $Principal -split '/'
                [System.Security.Principal.IdentityReference] $PrincipalIdentity = [System.Security.Principal.NTAccount]::new($SplittedName[0], $SplittedName[1])
            } else {
                [System.Security.Principal.IdentityReference] $PrincipalIdentity = [System.Security.Principal.NTAccount]::new($Principal)
            }
        } else {
            # Not yet ready
            return
        }
    }
    Process {
        foreach ($Object in $ADObject) {
            #$ADObjectData = $null
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                # if object already has proper security descriptor we don't need to do additional querying
                #if ($Object.ntSecurityDescriptor) {
                # $ADObjectData = $Object
                #}
                [string] $DistinguishedName = $Object.DistinguishedName
                [string] $CanonicalName = $Object.CanonicalName
                [string] $ObjectClass = $Object.ObjectClass
            } elseif ($Object -is [string]) {
                [string] $DistinguishedName = $Object
                [string] $CanonicalName = ''
                [string] $ObjectClass = ''
            } else {
                Write-Warning "Set-ADACLOwner - Object not recognized. Skipping..."
                continue
            }

            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToDC) -replace '=' -replace ','
            if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                Write-Verbose "Set-ADACLOwner - Enabling PSDrives for $DistinguishedName to $DNConverted"
                New-ADForestDrives -ForestName $ForestName # -ObjectDN $DistinguishedName
                if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..."
                    continue
                }
            }
            $PathACL = "$DNConverted`:\$($DistinguishedName)"
            try {
                $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop
            } catch {
                Write-Warning "Get-ADACL - Path $DistinguishedName / $PathACL - Error: $($_.Exception.Message)"
                continue
            }
            <#
            if (-not $ADObjectData) {
                try {
                    $ADObjectData = Get-ADObject -Identity $DistinguishedName -Properties ntSecurityDescriptor -ErrorAction Stop
                    $ACLs = $ADObjectData.ntSecurityDescriptor
                } catch {
                    Write-Warning "Get-ADACL - Path $DistinguishedName - Error: $($_.Exception.Message)"
                    continue
                }
            }
            #>

            $CurrentOwner = $ACLs.Owner
            Write-Verbose "Set-ADACLOwner - Changing owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName)"
            try {
                $ACLs.SetOwner($PrincipalIdentity)
            } catch {
                Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName): $($_.Exception.Message)"
                continue
            }
            try {
                #Set-ADObject -Identity $DistinguishedName -Replace @{ ntSecurityDescriptor = $ACLs } -ErrorAction Stop
                Set-Acl -Path $PathACL -AclObject $ACLs -ErrorAction Stop
            } catch {
                Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $PrincipalIdentity for $($DistinguishedName): $($_.Exception.Message)"
            }
            # }
        }
    }
    End {

    }
}
function Set-FileOwner { 
    [cmdletBinding(SupportsShouldProcess)]
    param([Array] $Path,
        [switch] $Recursive,
        [string] $Owner,
        [string[]] $Exlude,
        [switch] $JustPath)
    Begin {}
    Process {
        foreach ($P in $Path) {
            if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P }
            $OwnerTranslated = [System.Security.Principal.NTAccount]::new($Owner)
            if ($FullPath -and (Test-Path -Path $FullPath)) {
                if ($JustPath) {
                    $FullPath | ForEach-Object -Process { $File = $_
                        try { $ACL = Get-Acl -Path $File -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" }
                        if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) {
                            if ($PSCmdlet.ShouldProcess($File, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) {
                                try {
                                    $ACL.SetOwner($OwnerTranslated)
                                    Set-Acl -Path $File -AclObject $ACL -ErrorAction Stop
                                } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" }
                            }
                        } }
                } else {
                    Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -ErrorAction SilentlyContinue -ErrorVariable err | ForEach-Object -Process { $File = $_
                        try { $ACL = Get-Acl -Path $File.FullName -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" }
                        if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) {
                            if ($PSCmdlet.ShouldProcess($File.FullName, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) {
                                try {
                                    $ACL.SetOwner($OwnerTranslated)
                                    Set-Acl -Path $File.FullName -AclObject $ACL -ErrorAction Stop
                                } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" }
                            }
                        } }
                    foreach ($e in $err) { Write-Warning "Set-FileOwner - Errors processing $($e.Exception.Message) ($($e.CategoryInfo.Reason))" }
                }
            }
        }
    }
    End {}
}
function Start-TimeLog { 
    [CmdletBinding()]
    param()
    [System.Diagnostics.Stopwatch]::StartNew()
}
function Stop-TimeLog { 
    [CmdletBinding()]
    param ([Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time,
        [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner',
        [switch] $Continue)
    Begin {}
    Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } }
    End {
        if (-not $Continue) { $Time.Stop() }
        return $TimeToExecute
    }
}
function Write-Color { 
    <#
    .SYNOPSIS
        Write-Color is a wrapper around Write-Host.
 
        It provides:
        - Easy manipulation of colors,
        - Logging output to file (log)
        - Nice formatting options out of the box.
 
    .DESCRIPTION
        Author: przemyslaw.klys at evotec.pl
        Project website: https://evotec.xyz/hub/scripts/write-color-ps1/
        Project support: https://github.com/EvotecIT/PSWriteColor
 
        Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    .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
    # Added in 0.5
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    wc -t "my text" -c yellow -b green
    wc -text "my text" -c red
 
    .NOTES
        Additional Notes:
        - TimeFormat https://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
    #>

    [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)
    $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 } }
    if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } }
    if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } }
    if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline }
    if ($Text.Count -ne 0) {
        if ($Color.Count -ge $Text.Count) { 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 }
    if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } }
    if ($Text.Count -and $LogFile) {
        $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) { $PSCmdlet.WriteError($_) } else { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)" } }
        } Until ($Saved -eq $true -or $Retry -ge $LogRetry)
    }
}
function ConvertFrom-NetbiosName { 
    [cmdletBinding()]
    param([Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)]
        [string[]] $Identity)
    process {
        foreach ($Ident in $Identity) {
            if ($Ident -like '*\*') {
                $NetbiosWithObject = $Ident -split "\\"
                if ($NetbiosWithObject.Count -eq 2) {
                    $LDAPQuery = ([ADSI]"LDAP://$($NetbiosWithObject[0])")
                    $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $LDAPQuery.distinguishedName -ToDomainCN
                    [PSCustomObject] @{DomainName = $DomainName
                        Name                      = $NetbiosWithObject[1]
                    }
                } else {
                    [PSCustomObject] @{DomainName = ''
                        Name                      = $Ident
                    }
                }
            } else {
                [PSCustomObject] @{DomainName = ''
                    Name                      = $Ident
                }
            }
        }
    }
}
function Convert-GenericRightsToFileSystemRights { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER OriginalRights
    Parameter description
 
    .EXAMPLE
    An example
 
    .NOTES
 
    .LINK
    Improved https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/
 
    #>

    [cmdletBinding()]
    param([System.Security.AccessControl.FileSystemRights] $OriginalRights)
    Begin {
        $FileSystemRights = [System.Security.AccessControl.FileSystemRights]
        $GenericRights = @{GENERIC_READ = 0x80000000
            GENERIC_WRITE               = 0x40000000
            GENERIC_EXECUTE             = 0x20000000
            GENERIC_ALL                 = 0x10000000
            FILTER_GENERIC              = 0x0FFFFFFF
        }
        $MappedGenericRights = @{FILE_GENERIC_EXECUTE = $FileSystemRights::ExecuteFile -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::ReadAttributes -bor $FileSystemRights::Synchronize
            FILE_GENERIC_READ                         = $FileSystemRights::ReadAttributes -bor $FileSystemRights::ReadData -bor $FileSystemRights::ReadExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize
            FILE_GENERIC_WRITE                        = $FileSystemRights::AppendData -bor $FileSystemRights::WriteAttributes -bor $FileSystemRights::WriteData -bor $FileSystemRights::WriteExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize
            FILE_GENERIC_ALL                          = $FileSystemRights::FullControl
        }
    }
    Process {
        $MappedRights = [System.Security.AccessControl.FileSystemRights]::new()
        if ($OriginalRights -band $GenericRights.GENERIC_EXECUTE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_EXECUTE }
        if ($OriginalRights -band $GenericRights.GENERIC_READ) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_READ }
        if ($OriginalRights -band $GenericRights.GENERIC_WRITE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_WRITE }
        if ($OriginalRights -band $GenericRights.GENERIC_ALL) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_ALL }
(($OriginalRights -bAND $GenericRights.FILTER_GENERIC) -bOR $MappedRights) -as $FileSystemRights
    }
    End {}
}
function ConvertTo-OperatingSystem { 
    <#
    .SYNOPSIS
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .DESCRIPTION
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .PARAMETER OperatingSystem
    Operating System as returned by Active Directory
 
    .PARAMETER OperatingSystemVersion
    Operating System Version as returned by Active Directory
 
    .EXAMPLE
    $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object {
        $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
        Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force
        $_
    }
    $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table
 
    .EXAMPLE
    $Registry = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    ConvertTo-OperatingSystem -OperatingSystem $Registry.ProductName -OperatingSystemVersion $Registry.CurrentBuildNumber
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string] $OperatingSystem,
        [string] $OperatingSystemVersion)
    if ($OperatingSystem -like 'Windows 10*' -or $OperatingSystem -like 'Windows 11*') {
        $Systems = @{'10.0 (22000)' = 'Windows 11 21H2'
            '10.0 (19043)'          = 'Windows 10 21H1'
            '10.0 (19042)'          = 'Windows 10 20H2'
            '10.0 (19041)'          = 'Windows 10 2004'
            '10.0 (18898)'          = 'Windows 10 Insider Preview'
            '10.0 (18363)'          = "Windows 10 1909"
            '10.0 (18362)'          = "Windows 10 1903"
            '10.0 (17763)'          = "Windows 10 1809"
            '10.0 (17134)'          = "Windows 10 1803"
            '10.0 (16299)'          = "Windows 10 1709"
            '10.0 (15063)'          = "Windows 10 1703"
            '10.0 (14393)'          = "Windows 10 1607"
            '10.0 (10586)'          = "Windows 10 1511"
            '10.0 (10240)'          = "Windows 10 1507"
            '10.0.22000'            = 'Windows 11 21H2'
            '10.0.19043'            = 'Windows 10 21H1'
            '10.0.19042'            = 'Windows 10 20H2'
            '10.0.19041'            = 'Windows 10 2004'
            '10.0.18898'            = 'Windows 10 Insider Preview'
            '10.0.18363'            = "Windows 10 1909"
            '10.0.18362'            = "Windows 10 1903"
            '10.0.17763'            = "Windows 10 1809"
            '10.0.17134'            = "Windows 10 1803"
            '10.0.16299'            = "Windows 10 1709"
            '10.0.15063'            = "Windows 10 1703"
            '10.0.14393'            = "Windows 10 1607"
            '10.0.10586'            = "Windows 10 1511"
            '10.0.10240'            = "Windows 10 1507"
            '22000'                 = 'Windows 11 21H2'
            '19043'                 = 'Windows 10 21H1'
            '19042'                 = 'Windows 10 20H2'
            '19041'                 = 'Windows 10 2004'
            '18898'                 = 'Windows 10 Insider Preview'
            '18363'                 = "Windows 10 1909"
            '18362'                 = "Windows 10 1903"
            '17763'                 = "Windows 10 1809"
            '17134'                 = "Windows 10 1803"
            '16299'                 = "Windows 10 1709"
            '15063'                 = "Windows 10 1703"
            '14393'                 = "Windows 10 1607"
            '10586'                 = "Windows 10 1511"
            '10240'                 = "Windows 10 1507"
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } elseif ($OperatingSystem -like 'Windows Server*') {
        $Systems = @{'10.0 (20348)' = 'Windows Server 2022'
            '10.0 (19042)'          = 'Windows Server 2019 20H2'
            '10.0 (19041)'          = 'Windows Server 2019 2004'
            '10.0 (18363)'          = 'Windows Server 2019 1909'
            '10.0 (18362)'          = "Windows Server 2019 1903"
            '10.0 (17763)'          = "Windows Server 2019 1809"
            '10.0 (17134)'          = "Windows Server 2016 1803"
            '10.0 (14393)'          = "Windows Server 2016 1607"
            '6.3 (9600)'            = 'Windows Server 2012 R2'
            '6.1 (7601)'            = 'Windows Server 2008 R2'
            '5.2 (3790)'            = 'Windows Server 2003'
            '10.0.20348'            = 'Windows Server 2022'
            '10.0.19042'            = 'Windows Server 2019 20H2'
            '10.0.19041'            = 'Windows Server 2019 2004'
            '10.0.18363'            = 'Windows Server 2019 1909'
            '10.0.18362'            = "Windows Server 2019 1903"
            '10.0.17763'            = "Windows Server 2019 1809"
            '10.0.17134'            = "Windows Server 2016 1803"
            '10.0.14393'            = "Windows Server 2016 1607"
            '6.3.9600'              = 'Windows Server 2012 R2'
            '6.1.7601'              = 'Windows Server 2008 R2'
            '5.2.3790'              = 'Windows Server 2003'
            '20348'                 = 'Windows Server 2022'
            '19042'                 = 'Windows Server 2019 20H2'
            '19041'                 = 'Windows Server 2019 2004'
            '18363'                 = 'Windows Server 2019 1909'
            '18362'                 = "Windows Server 2019 1903"
            '17763'                 = "Windows Server 2019 1809"
            '17134'                 = "Windows Server 2016 1803"
            '14393'                 = "Windows Server 2016 1607"
            '9600'                  = 'Windows Server 2012 R2'
            '7601'                  = 'Windows Server 2008 R2'
            '3790'                  = 'Windows Server 2003'
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } else { $System = $OperatingSystem }
    if ($System) { $System } else { 'Unknown' }
}
function Convert-UserAccountControl { 
    [cmdletBinding()]
    param([alias('UAC')][int] $UserAccountControl,
        [string] $Separator)
    $UserAccount = [ordered] @{"SCRIPT"  = 1
        "ACCOUNTDISABLE"                 = 2
        "HOMEDIR_REQUIRED"               = 8
        "LOCKOUT"                        = 16
        "PASSWD_NOTREQD"                 = 32
        "ENCRYPTED_TEXT_PWD_ALLOWED"     = 128
        "TEMP_DUPLICATE_ACCOUNT"         = 256
        "NORMAL_ACCOUNT"                 = 512
        "INTERDOMAIN_TRUST_ACCOUNT"      = 2048
        "WORKSTATION_TRUST_ACCOUNT"      = 4096
        "SERVER_TRUST_ACCOUNT"           = 8192
        "DONT_EXPIRE_PASSWORD"           = 65536
        "MNS_LOGON_ACCOUNT"              = 131072
        "SMARTCARD_REQUIRED"             = 262144
        "TRUSTED_FOR_DELEGATION"         = 524288
        "NOT_DELEGATED"                  = 1048576
        "USE_DES_KEY_ONLY"               = 2097152
        "DONT_REQ_PREAUTH"               = 4194304
        "PASSWORD_EXPIRED"               = 8388608
        "TRUSTED_TO_AUTH_FOR_DELEGATION" = 16777216
        "PARTIAL_SECRETS_ACCOUNT"        = 67108864
    }
    $Output = foreach ($_ in $UserAccount.Keys) {
        $binaryAnd = $UserAccount[$_] -band $UserAccountControl
        if ($binaryAnd -ne "0") { $_ }
    }
    if ($Separator) { $Output -join $Separator } else { $Output }
}
function Copy-DictionaryManual { 
    [CmdletBinding()]
    param([System.Collections.IDictionary] $Dictionary)
    $clone = @{}
    foreach ($Key in $Dictionary.Keys) {
        $value = $Dictionary.$Key
        $clonedValue = switch ($Dictionary.$Key) {
            { $null -eq $_ } {
                $null
                continue
            }
            { $_ -is [System.Collections.IDictionary] } {
                Copy-DictionaryManual -Dictionary $_
                continue
            }
            { $type = $_.GetType()
                $type.IsPrimitive -or $type.IsValueType -or $_ -is [string] } {
                $_
                continue
            }
            default { $_ | Select-Object -Property * }
        }
        if ($value -is [System.Collections.IList]) { $clone[$Key] = @($clonedValue) } else { $clone[$Key] = $clonedValue }
    }
    $clone
}
function Get-ComputerApplication { 
    <#
    .SYNOPSIS
    Get software installed on computer or server
 
    .DESCRIPTION
    Get software installed on computer or server
 
    .PARAMETER ComputerName
    Specifies computer on which you want to run the operation.
 
    .EXAMPLE
    Get-ComputerApplications -Verbose | Format-Table
 
    .EXAMPLE
    Get-ComputerApplications -Verbose -ComputerName AD1, AD2 | Format-Table
 
    .NOTES
    General notes
    #>

    [alias('Get-ComputerApplications')]
    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME)
    $ScriptBlock = { $objapp1 = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
        $objapp2 = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
        $app1 = $objapp1 | Select-Object Displayname, Displayversion , Publisher, Installdate, @{Expression = { 'x64' }; Label = 'WindowsType' }
        $app2 = $objapp2 | Select-Object Displayname, Displayversion , Publisher, Installdate, @{Expression = { 'x86' }; Label = 'WindowsType' } | Where-Object { -NOT (([string]$_.displayname).contains('Security Update for Microsoft') -or ([string]$_.displayname).contains('Update for Microsoft')) }
        $app = $app1 + $app2
        $app | Where-Object { $null -ne $_.Displayname } }
    foreach ($Computer in $ComputerName) {
        try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Computer }
        if ($Computer -eq $Env:COMPUTERNAME -or $Computer -eq $LocalComputerDNSName) { $Parameters = @{ScriptBlock = $ScriptBlock } } else {
            $Parameters = @{ComputerName = $Computer
                ScriptBlock              = $ScriptBlock
            }
        }
        try { $Data = Invoke-Command @Parameters } catch {
            Write-Warning "Get-ComputerApplication - No data for computer $Computer"
            continue
        }
        foreach ($Information in $Data) {
            if ($Information.Installdate) {
                try { $InstallDate = [datetime]::ParseExact($Information.Installdate, 'yyyyMMdd', $null) } catch {
                    Write-Verbose "Get-ComputerApplication - InstallDate $($Information.Installdate) couldn't be converted."
                    $InstallDate = $null
                }
            } else { $InstallDate = $null }
            [PSCustomObject] @{DisplayName = $Information.DisplayName
                Version                    = $Information.DisplayVersion
                Publisher                  = $Information.Publisher
                Installdate                = $InstallDate
                Type                       = $Information.WindowsType
                ComputerName               = $Computer
            }
        }
    }
}
function Get-ComputerBios { 
    [CmdletBinding()]
    param([string] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All)
    [string] $Class = 'win32_bios'
    if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'PSComputerName', 'Status', 'Version', 'PrimaryBIOS', 'Manufacturer', 'ReleaseDate', 'SerialNumber', 'SMBIOSBIOSVersion', 'SMBIOSMajorVersion', 'SMBIOSMinorVersion', 'SystemBiosMajorVersion', 'SystemBiosMinorVersion' }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { $Information } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                [PSCustomObject] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                    Status                      = $Data.Status
                    Version                     = $Data.Version
                    VersionBIOS                 = -join ($Data.SMBIOSMajorVersion, ".", $Data.SMBIOSMinorVersion, ".", $Data.SystemBiosMajorVersion, ".", $Data.SystemBiosMinorVersion)
                    PrimaryBIOS                 = $Data.PrimaryBIOS
                    Manufacturer                = $Data.Manufacturer
                    ReleaseDate                 = $Data.ReleaseDate
                }
            }
        }
    }
}
function Get-ComputerCPU { 
    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All)
    [string] $Class = 'win32_processor'
    if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'PSComputerName', 'Name', 'DeviceID', 'Caption', 'SystemName', 'CurrentClockSpeed', 'MaxClockSpeed', 'ProcessorID', 'ThreadCount', 'Architecture', 'Status', 'LoadPercentage', 'L3CacheSize', 'Manufacturer', 'VirtualizationFirmwareEnabled', 'NumberOfCores', 'NumberOfEnabledCore', 'NumberOfLogicalProcessors' }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { $Information } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                [PSCustomObject] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                    Name                        = $Data.Name
                    DeviceID                    = $Data.DeviceID
                    Caption                     = $Data.Caption
                    CurrentClockSpeed           = $Data.CurrentClockSpeed
                    MaxClockSpeed               = $Data.MaxClockSpeed
                    ProcessorID                 = $Data.ProcessorID
                    ThreadCount                 = $Data.ThreadCount
                    Architecture                = $Data.Architecture
                    Status                      = $Data.Status
                    LoadPercentage              = $Data.LoadPercentage
                    Manufacturer                = $Data.Manufacturer
                    NumberOfCores               = $Data.NumberOfCores
                    NumberOfEnabledCore         = $Data.NumberOfEnabledCore
                    NumberOfLogicalProcessors   = $Data.NumberOfLogicalProcessors
                }
            }
        }
    }
}
function Get-ComputerDisk { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER ComputerName
    Parameter description
 
    .PARAMETER Protocol
    Parameter description
 
    .PARAMETER All
    Parameter description
 
    .EXAMPLE
    Get-ComputerDisk -ComputerName AD1, AD2, EVO1, AD2019 | Format-Table -AutoSize *
 
    Output:
    WARNING: Get-ComputerSystem - No data for computer AD2019. Most likely an error on receiving side.
 
    ComputerName Index Model Caption SerialNumber Description MediaType FirmwareRevision Partitions SizeGB PNPDeviceID
    ------------ ----- ----- ------- ------------ ----------- --------- ---------------- ---------- ------ -----------
    AD1 0 Microsoft Virtual Disk Microsoft Virtual Disk Disk drive Fixed hard disk media 1.0 3 127 SCSI\DISK&VEN_MSFT&PROD_VIRTUAL_DISK\000000
    AD2 0 Microsoft Virtual Disk Microsoft Virtual Disk Disk drive Fixed hard disk media 1.0 3 127 SCSI\DISK&VEN_MSFT&PROD_VIRTUAL_DISK\000000
    EVO1 0 WDC WD30EFRX-68AX9N0 WDC WD30EFRX-68AX9N0 WD-WMC1T2351095 Disk drive Fixed hard disk media 80.00A80 1 2795 SCSI\DISK&VEN_WDC&PROD_WD30EFRX-68AX9N0\4&191557A4&0&000000
    EVO1 2 Samsung SSD 950 PRO 512GB Samsung SSD 950 PRO 512GB 0025_3857_61B0_0EF2. Disk drive Fixed hard disk media 2B0Q 3 477 SCSI\DISK&VEN_NVME&PROD_SAMSUNG_SSD_950\5&35365596&0&000000
    EVO1 1 Samsung SSD 860 EVO 500GB Samsung SSD 860 EVO 500GB S3Z2NB0K176976A Disk drive Fixed hard disk media RVT01B6Q 1 466 SCSI\DISK&VEN_SAMSUNG&PROD_SSD\4&191557A4&0&000100
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All)
    [string] $Class = 'win32_diskdrive'
    if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Index', 'Model', 'Caption', 'SerialNumber', 'Description', 'MediaType', 'FirmwareRevision', 'Partitions', 'Size', 'PNPDeviceID', 'PSComputerName' }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { $Information } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                [PSCustomObject] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                    Index                       = $Data.Index
                    Model                       = $Data.Model
                    Caption                     = $Data.Caption
                    SerialNumber                = if ($Data.SerialNumber) { $Data.SerialNumber.Trim() } else { '' }
                    Description                 = $Data.Description
                    MediaType                   = $Data.MediaType
                    FirmwareRevision            = $Data.FirmwareRevision
                    Partitions                  = $Data.Partitions
                    SizeGB                      = $Data.Size / 1Gb -as [int]
                    PNPDeviceID                 = $Data.PNPDeviceID
                }
            }
        }
    }
}
function Get-ComputerDiskLogical { 
    <#
    .SYNOPSIS
    Getting drive space
 
    .DESCRIPTION
    Long description
 
    .PARAMETER ComputerName
    Parameter description
 
    .PARAMETER Protocol
    Parameter description
 
    .PARAMETER RoundingPlaceRoundingPlace
 
    .PARAMETER RoundingPlace
 
    .PARAMETER OnlyLocalDisk
    Parameter description
 
    .PARAMETER All
    Parameter description
 
    .EXAMPLE
    Get-ComputerDiskLogical -ComputerName AD1, AD2, EVOWIN -OnlyLocalDisk | ft -AutoSize
 
    Output:
 
    ComputerName DeviceID DriveType ProviderName FreeSpace UsedSpace TotalSpace FreePercent UsedPercent VolumeName
    ------------ -------- --------- ------------ --------- --------- ---------- ----------- ----------- ----------
    AD2 C: Local Disk 96,96 29,49 126,45 76,68 23,32
    AD1 C: Local Disk 103,17 23,28 126,45 81,59 18,41
    EVOWIN C: Local Disk 133,31 343,03 476,34 27,99 72,01
    EVOWIN D: Local Disk 2433 361,4 2794,39 87,07 12,93 Media
    EVOWIN E: Local Disk 66,05 399,7 465,75 14,18 85,82 Testing Environment
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [string][ValidateSet('GB', 'TB', 'MB')] $Size = 'GB',
        [int] $RoundingPlace = 2,
        [int] $RoundingPlacePercent = 2,
        [switch] $OnlyLocalDisk,
        [switch] $All)
    [string] $Class = 'win32_logicalDisk'
    if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'DeviceID', 'DriveType', 'ProviderName', 'FreeSpace', 'Size', 'VolumeName', 'PSComputerName' }
    $DriveType = @{'0' = 'Unknown'
        '1'            = 'No Root Directory'
        '2'            = 'Removable Disk'
        '3'            = 'Local Disk'
        '4'            = 'Network Drive'
        '5'            = 'Compact Disc'
        '6'            = 'RAM Disk'
    }
    $Divider = "1$Size"
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { $Information } else {
        $Output = foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                [PSCustomObject] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                    DeviceID                    = $Data.DeviceID
                    DriveType                   = $DriveType["$($Data.DriveType)"]
                    ProviderName                = $Data.ProviderName
                    FreeSpace                   = [Math]::Round($Data.FreeSpace / $Divider, $RoundingPlace)
                    UsedSpace                   = [Math]::Round(($Data.Size - $Data.FreeSpace) / $Divider, $RoundingPlace)
                    TotalSpace                  = [Math]::Round($Data.Size / $Divider, $RoundingPlace)
                    FreePercent                 = if ($Data.Size -gt 0) { [Math]::round(($Data.FreeSpace / $Data.Size) * 100, $RoundingPlacePercent) } else { '0' }
                    UsedPercent                 = if ($Data.Size -gt 0) { [Math]::round((($Data.Size - $Data.FreeSpace) / $Data.Size) * 100, $RoundingPlacePercent) } else { '0' }
                    VolumeName                  = $Data.VolumeName
                }
            }
        }
        if ($OnlyLocalDisk) { $Output | Where-Object { $_.DriveType -eq 'Local Disk' } } else { $Output }
    }
}
function Get-ComputerNetwork { 
    [alias('Get-ComputerNetworkCard')]
    <#
    .SYNOPSIS
 
    .DESCRIPTION
    Long description
 
    .PARAMETER ComputerName
    Parameter description
 
    .PARAMETER NetworkFirewallOnly
    Parameter description
 
    .PARAMETER NetworkFirewallSummaryOnly
    Parameter description
 
    .EXAMPLE
 
    Get-ComputerNetworkCard -ComputerName AD1, AD2, AD3
 
    Output
 
    Name NetworkCardName NetworkCardIndex FirewallProfile FirewallStatus IPv4Connectivity IPv6Connectivity Caption Description ElementName DefaultInboundAction DefaultOutboundAction AllowInboundRules AllowLocalFirewallRules AllowLocalIPsecRules AllowUserApps AllowUserPorts AllowUnicastResponseToMulticast NotifyOnListen EnableStealthModeForIPsec LogFileName LogMaxSizeKilobytes LogAllowed LogBlo
                                                                                                                                                                                                                                                                                                                                                                                                                                                                        cked
    ---- --------------- ---------------- --------------- -------------- ---------------- ---------------- ------- ----------- ----------- -------------------- --------------------- ----------------- ----------------------- -------------------- ------------- -------------- ------------------------------- -------------- ------------------------- ----------- ------------------- ---------- ------
    ad.evotec.xyz vEthernet (External Switch) 13 DomainAuthenticated True Internet NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
    Network 2 Ethernet 2 2 Private True Internet NoTraffic Block Allow NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
    Network Ethernet 2 Private True LocalNetwork NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
    ad.evotec.xyz Ethernet 5 3 DomainAuthenticated False Internet NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
    Network 2 Ethernet 4 12 Private False LocalNetwork NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
 
    .EXAMPLE
 
    Get-ComputerNetworkCard -ComputerName EVOWIN -NetworkFirewallOnly
 
    PSComputerName Profile Enabled DefaultInboundAction DefaultOutboundAction AllowInboundRules AllowLocalFirewallRules AllowLocalIPsecRules AllowUserApps AllowUserPorts AllowUnicastResponseToMulticast NotifyOnListen EnableStealthModeForIPsec LogMaxSizeKilobytes LogAllowed LogBlocked LogIgnored Caption Description ElementName InstanceID DisabledInterfaceAliases LogFileName Name CimClass
    -------------- ------- ------- -------------------- --------------------- ----------------- ----------------------- -------------------- ------------- -------------- ------------------------------- -------------- ------------------------- ------------------- ---------- ---------- ---------- ------- ----------- ----------- ---------- ------------------------ ----------- ---- --------
    EVOWIN Domain True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Domain {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Domain root/stand...
    EVOWIN Private True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Private {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Private root/stand...
    EVOWIN Public True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Public {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Public root/stand...
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [switch] $NetworkFirewallOnly,
        [switch] $NetworkFirewallSummaryOnly,
        [alias('Joiner')][string] $Splitter)
    [Array] $CollectionComputers = $ComputerName.Where({ $_ -eq $Env:COMPUTERNAME }, 'Split')
    $Firewall = @{}
    $NetworkFirewall = @(if ($CollectionComputers[0].Count -gt 0) {
            $Firewall[$Env:COMPUTERNAME] = @{}
            $Output = Get-NetFirewallProfile
            foreach ($_ in $Output) {
                Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -Type NoteProperty -Force
                $_
                if ($_.Name -eq 'Domain') { $Firewall[$Env:COMPUTERNAME]['DomainAuthenticated'] = $_ } else { $Firewall[$Env:COMPUTERNAME][$($_.Name)] = $_ }
            }
        }
        if ($CollectionComputers[1].Count -gt 0) {
            foreach ($_ in $CollectionComputers[1]) { $Firewall[$_] = @{} }
            $Output = Get-NetFirewallProfile -CimSession $CollectionComputers[1]
            foreach ($_ in $Output) { if ($_.Name -eq 'Domain') { $Firewall[$_.PSComputerName]['DomainAuthenticated'] = $_ } else { $Firewall[$_.PSComputerName][$($_.Name)] = $_ } }
        })
    if ($NetworkFirewallOnly) { return $NetworkFirewall }
    if ($NetworkFirewallSummaryOnly) { return $Firewall }
    $NetworkCards = @(if ($CollectionComputers[0].Count -gt 0) {
            $Output = Get-NetConnectionProfile
            foreach ($_ in $Output) {
                Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -Type NoteProperty -Force
                $_
            }
        }
        if ($CollectionComputers[1].Count -gt 0) { Get-NetConnectionProfile -CimSession $CollectionComputers[1] })
    foreach ($_ in $NetworkCards) {
        $NetworkCardsConfiguration = Get-CimData -ComputerName $ComputerName -Class 'Win32_NetworkAdapterConfiguration'
        $CurrentCard = foreach ($Configuration in $NetworkCardsConfiguration) { if ($_.PSComputerName -eq $Configuration.PSComputerName) { if ($Configuration.InterfaceIndex -eq $_.InterfaceIndex) { $Configuration } } }
        $NetbiosTCPIP = @{'0' = 'Default'
            '1'               = 'Enabled'
            '2'               = 'Disabled'
        }
        [PSCustomObject] @{Name             = $_.Name
            NetworkCardName                 = $_.InterfaceAlias
            NetworkCardIndex                = $_.InterfaceIndex
            FirewallProfile                 = $_.NetworkCategory
            FirewallStatus                  = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].'Enabled'
            IPAddress                       = $CurrentCard.IPAddress
            IPGateway                       = $CurrentCard.DefaultIPGateway
            IPSubnet                        = $CurrentCard.IPSubnet
            IPv4Connectivity                = $_.IPv4Connectivity
            IPv6Connectivity                = $_.IPv6Connectivity
            DNSServerSearchOrder            = $CurrentCard.DNSServerSearchOrder
            DNSDomainSuffixSearchOrder      = $CurrentCard.DNSDomainSuffixSearchOrder
            FullDNSRegistrationEnabled      = $CurrentCard.FullDNSRegistrationEnabled
            DHCPEnabled                     = $CurrentCard.DHCPEnabled
            DHCPServer                      = $CurrentCard.DHCPServer
            DHCPLeaseObtained               = $CurrentCard.DHCPLeaseObtained
            NetBIOSOverTCPIP                = $NetBiosTCPIP["$($CurrentCard.TcpipNetbiosOptions)"]
            Caption                         = $_.Caption
            Description                     = $_.Description
            ElementName                     = $_.ElementName
            DefaultInboundAction            = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].DefaultInboundAction
            DefaultOutboundAction           = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].DefaultOutboundAction
            AllowInboundRules               = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowInboundRules
            AllowLocalFirewallRules         = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowLocalFirewallRules
            AllowLocalIPsecRules            = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowLocalIPsecRules
            AllowUserApps                   = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUserApps
            AllowUserPorts                  = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUserPorts
            AllowUnicastResponseToMulticast = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUnicastResponseToMulticast
            NotifyOnListen                  = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].NotifyOnListen
            EnableStealthModeForIPsec       = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].EnableStealthModeForIPsec
            LogFileName                     = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogFileName
            LogMaxSizeKilobytes             = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogMaxSizeKilobytes
            LogAllowed                      = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogAllowed
            LogBlocked                      = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogBlocked
            LogIgnored                      = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogIgnored
            ComputerName                    = $_.PSComputerName
        }
    }
}
function Get-ComputerOperatingSystem { 
    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All)
    [string] $Class = 'win32_operatingsystem'
    if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Caption', 'Manufacturer', 'InstallDate', 'OSArchitecture', 'Version', 'SerialNumber', 'BootDevice', 'WindowsDirectory', 'CountryCode', 'OSLanguage', 'OSProductSuite', 'PSComputerName', 'LastBootUpTime', 'LocalDateTime' }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { $Information } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                [PSCustomObject] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                    OperatingSystem             = $Data.Caption
                    OperatingSystemVersion      = ConvertTo-OperatingSystem -OperatingSystem $Data.Caption -OperatingSystemVersion $Data.Version
                    OperatingSystemBuild        = $Data.Version
                    Manufacturer                = $Data.Manufacturer
                    OSArchitecture              = $Data.OSArchitecture
                    OSLanguage                  = ConvertFrom-LanguageCode -LanguageCode $Data.OSLanguage
                    OSProductSuite              = [Microsoft.PowerShell.Commands.OSProductSuite] $($Data.OSProductSuite)
                    InstallDate                 = $Data.InstallDate
                    LastBootUpTime              = $Data.LastBootUpTime
                    LocalDateTime               = $Data.LocalDateTime
                    SerialNumber                = $Data.SerialNumber
                    BootDevice                  = $Data.BootDevice
                    WindowsDirectory            = $Data.WindowsDirectory
                    CountryCode                 = $Data.CountryCode
                }
            }
        }
    }
}
function Get-ComputerRAM { 
    [CmdletBinding()]
    param([string] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All,
        [switch] $Extended)
    [string] $Class = 'Win32_physicalmemory '
    if ($All) { [string] $Properties = '*' } else {
        [string[]] $Properties = @('InstallDate'
            'Manufacturer'
            'Model'
            'OtherIdentifyingInfo'
            'PartNumber'
            'PoweredOn'
            'SerialNumber'
            'SKU'
            'Tag'
            'Version'
            'HotSwappable'
            'Removable'
            'Replaceable'
            'FormFactor'
            'BankLabel'
            'Capacity'
            'InterleavePosition'
            'MemoryType'
            'Speed'
            'ConfiguredClockSpeed'
            'ConfiguredVoltage'
            'DeviceLocator'
            'MaxVoltage'
            'MinVoltage'
            'SMBIOSMemoryType'
            'TypeDetail'
            'PSComputerName')
    }
    $FormFactor = @{'0' = 'Unknown'
        '1'             = 'Other'
        '2'             = 'SIP'
        '3'             = 'DIP'
        '4'             = 'ZIP'
        '5'             = 'SOJ'
        '6'             = 'Proprietary'
        '7'             = 'SIMM'
        '8'             = 'DIMM'
        '9'             = 'TSOP'
        '10'            = 'PGA'
        '11'            = 'RIMM'
        '12'            = 'SODIMM'
        '13'            = 'SRIMM'
        '14'            = 'SMD'
        '15'            = 'SSMP'
        '16'            = 'QFP'
        '17'            = 'TQFP'
        '18'            = 'SOIC'
        '19'            = 'LCC'
        '20'            = 'PLCC'
        '21'            = 'BGA'
        '22'            = 'FPBGA'
        '23'            = 'LGA'
    }
    $TypeDetails = @{'1' = 'Reserved'
        '2'              = 'Other'
        '4'              = 'Unknown'
        '8'              = 'Fast-paged'
        '16'             = 'Static column'
        '32'             = 'Pseudo-static'
        '64'             = 'RAMBUS'
        '128'            = 'Synchronous'
        '256'            = 'CMOS'
        '512'            = 'EDO'
        '1024'           = 'Window DRAM'
        '2048'           = 'Cache DRAM'
        '4096'           = 'Non-volatile'
    }
    $InterleavePosition = @{'0' = "Non-Interleaved"
        '1'                     = "First Position"
        '2'                     = "Second Position"
    }
    $MemoryType = @{'0' = "Unknown"
        '1'             = "Other"
        '2'             = "DRAM"
        '3'             = "Synchronous DRAM"
        '4'             = "Cache DRAM"
        '5'             = "EDO"
        '6'             = "EDRAM"
        '7'             = "VRAM"
        '8'             = "SRAM"
        '9'             = "ROM"
        '10'            = "ROM"
        '11'            = "FLASH"
        '12'            = "EEPROM"
        '13'            = "FEPROM"
        '14'            = "EPROM"
        '15'            = "CDRAM"
        '16'            = "3DRAM"
        '17'            = "SDRAM"
        '18'            = "SGRAM"
        '19'            = "RDRAM"
        '20'            = "DDR"
    }
    $MemoryTypeSMBIOS = @{'0' = 'Unknown'
        '1'                   = 'Other'
        '2'                   = 'DRAM'
        '3'                   = 'Synchronous DRAM'
        '4'                   = 'Cache DRAM'
        '5'                   = 'EDO'
        '6'                   = 'EDRAM'
        '7'                   = 'VRAM'
        '8'                   = 'SRAM'
        '9'                   = 'RAM'
        '10'                  = 'ROM'
        '11'                  = 'Flash'
        '12'                  = 'EEPROM'
        '13'                  = 'FEPROM'
        '14'                  = 'EPROM'
        '15'                  = 'CDRAM'
        '16'                  = '3DRAM'
        '17'                  = 'SDRAM'
        '18'                  = 'SGRAM'
        '19'                  = 'RDRAM'
        '20'                  = 'DDR'
        '21'                  = 'DDR2'
        '22'                  = 'DDR2 FB-DIMM'
        '24'                  = 'DDR3'
        '25'                  = 'FBD2'
        '26'                  = 'DDR4'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { $Information } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                $Ram = [ordered] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                    Manufacturer                = $Data.Manufacturer
                    FormFactor                  = $FormFactor["$($Data.FormFactor)"]
                    SMBIOSMemoryType            = $MemoryTypeSMBIOS["$($Data.SMBIOSMemoryType)"]
                    Size                        = [math]::round($Data.Capacity / 1GB, 2)
                    Speed                       = $Data.Speed
                    InterleavePosition          = $InterleavePosition["$($Data.InterleavePosition)"]
                    MemoryType                  = $MemoryType["$($Data.MemoryType)"]
                    TypeDetail                  = $TypeDetails["$($Data.TypeDetail)"]
                    PartNumber                  = $Data.PartNumber
                    DeviceLocator               = $Data.DeviceLocator
                }
                if ($Extended) {
                    $RamExtended = [ordered] @{InstallDate = $Data.InstallDate
                        Model                              = $Data.Model
                        OtherIdentifyingInfo               = $Data.OtherIdentifyingInfo
                        PoweredOn                          = $Data.PoweredOn
                        SerialNumber                       = $Data.SerialNumber
                        SKU                                = $Data.SKU
                        Tag                                = $Data.Tag
                        Version                            = $Data.Version
                        HotSwappable                       = $Data.HotSwappable
                        Removable                          = $Data.Removable
                        Replaceable                        = $Data.Replaceable
                        BankLabel                          = $Data.BankLabel
                        ConfiguredClockSpeed               = $Data.ConfiguredClockSpeed
                        ConfiguredVoltage                  = $Data.ConfiguredVoltage
                        MaxVoltage                         = $Data.MaxVoltage
                        MinVoltage                         = $Data.MinVoltage
                    }
                    [PSCustomObject] ($Ram + $RamExtended)
                } else { [PSCustomObject] $Ram }
            }
        }
    }
}
function Get-ComputerRDP { 
    [alias('Get-RDPSecurity')]
    [cmdletbinding()]
    param([string[]] $ComputerName)
    $Output = Get-CimData -Class 'Win32_TSGeneralSetting' -NameSpace 'root\cimv2\terminalservices' -ComputerName $ComputerName
    foreach ($_ in $Output) {
        $EncryptionLevels = @{'1' = 'Low'
            '2'                   = 'Medium / Client Compatible'
            '3'                   = 'High'
            '4'                   = 'FIPS Compliant'
        }
        $PolicyConfiguredBy = @{'0' = 'Server'
            '1'                     = 'Group policy'
            '2'                     = 'Default'
        }
        $SecurityLayers = @{'1' = 'RDP Security Layer'
            '2'                 = 'Negotiate'
            '3'                 = 'SSL'
            '4'                 = 'NEWTBD'
        }
        $HashType = @{'0' = 'Not valid'
            '1'           = 'Self-signed'
            '2'           = 'Custom'
        }
        $Connectivity = Test-ComputerPort -ComputerName $_.PSComputerName -PortTCP 3389 -WarningAction SilentlyContinue
        [PSCustomObject] @{ComputerName            = $_.PSComputerName
            Name                                   = $_.TerminalName
            Connectivity                           = $Connectivity.Status
            ConnectivitySummary                    = $Connectivity.Summary
            SecurityLayer                          = $SecurityLayers["$($_.SecurityLayer)"]
            MinimalEncryptionLevel                 = $EncryptionLevels["$($_.MinEncryptionLevel)"]
            MinimalEncryptionLevelValue            = $_.MinEncryptionLevel
            PolicySourceUserAuthenticationRequired = $PolicyConfiguredBy["$($_.PolicySourceUserAuthenticationRequired)"]
            PolicySourceMinimalEncryptionLevel     = $PolicyConfiguredBy["$($_.PolicySourceMinEncryptionLevel)"]
            PolicySourceSecurityLayer              = $PolicyConfiguredBy["$($_.PolicySourceSecurityLayer)"]
            CertificateName                        = $_.CertificateName
            CertificateThumbprint                  = $_.SSLCertificateSHA1Hash
            CertificateType                        = $HashType["$($_.SSLCertificateSHA1HashType)"]
            Transport                              = $_.Transport
            Protocol                               = $_.TerminalProtocol
            UserAuthenticationRequired             = [bool] $_.UserAuthenticationRequired
            WindowsAuthentication                  = [bool] $_.WindowsAuthentication
        }
    }
}
function Get-ComputerService { 
    [alias('Get-ComputerServices')]
    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME)
    Process {
        foreach ($Computer in $ComputerName) {
            $Services = Get-PSService -ComputerName $Computer | Select-Object ComputerName, Name, Displayname, Status, StartType
            $Services
        }
    }
}
function Get-ComputerSplit { 
    [CmdletBinding()]
    param([string[]] $ComputerName)
    if ($null -eq $ComputerName) { $ComputerName = $Env:COMPUTERNAME }
    try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Env:COMPUTERNAME }
    $ComputersLocal = $null
    [Array] $Computers = foreach ($Computer in $ComputerName) {
        if ($Computer -eq '' -or $null -eq $Computer) { $Computer = $Env:COMPUTERNAME }
        if ($Computer -ne $Env:COMPUTERNAME -and $Computer -ne $LocalComputerDNSName) { $Computer } else { $ComputersLocal = $Computer }
    }
    , @($ComputersLocal, $Computers)
}
function Get-ComputerStartup { 
    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All)
    [string] $Class = 'win32_startupCommand'
    if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Caption', 'Description', 'Command', 'Location', 'Name', 'User', 'UserSID', 'PSComputerName' }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { $Information } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                [PSCustomObject] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                    Caption                     = $Data.Caption
                    Description                 = $Data.Description
                    Command                     = $Data.Command
                    Location                    = $Data.Location
                    Name                        = $Data.Name
                    User                        = $Data.User
                    UserSID                     = $Data.UserSID
                }
            }
        }
    }
}
function Get-ComputerSystem { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER ComputerName
    Parameter description
 
    .PARAMETER Protocol
    Parameter description
 
    .PARAMETER All
    Parameter description
 
    .EXAMPLE
    Get-ComputerSystem -ComputerName AD1, AD2, EVO1, ADFFS | ft -a *
 
    Output:
    WARNING: Get-ComputerSystem - No data for computer ADFFS. Most likely an error on receiving side.
    ComputerName Name Manufacturer Domain Model Systemtype PrimaryOwnerName PCSystemType PartOfDomain CurrentTimeZone BootupState SystemFamily Roles
    ------------ ---- ------------ ------ ----- ---------- ---------------- ------------ ------------ --------------- ----------- ------------ -----
    AD1 AD1 Microsoft Corporation ad.evotec.xyz Virtual Machine x64-based PC Windows User 1 True 60 Normal boot Virtual Machine LM_Workstation, LM_Server, Primary_Domain_Controller, Timesource, NT, DFS
    AD2 AD2 Microsoft Corporation ad.evotec.xyz Virtual Machine x64-based PC Windows User 1 True 60 Normal boot Virtual Machine LM_Workstation, LM_Server, Backup_Domain_Controller, Timesource, NT, DFS
    EVO1 EVO1 MSI ad.evotec.xyz MS-7980 x64-based PC 1 True 60 Normal boot Default string LM_Workstation, LM_Server, SQLServer, NT, Potential_Browser, Master_Browser
 
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All)
    [string] $Class = 'Win32_ComputerSystem'
    if ($All) { $Properties = '*' } else { $Properties = 'PSComputerName', 'Name', 'Manufacturer' , 'Domain', 'Model' , 'Systemtype', 'PrimaryOwnerName', 'PCSystemType', 'PartOfDomain', 'CurrentTimeZone', 'BootupState', 'Roles', 'SystemFamily' }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { $Information } else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                [PSCustomObject] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                    Name                        = $Data.Name
                    Manufacturer                = $Data.Manufacturer
                    Domain                      = $Data.Domain
                    Model                       = $Data.Model
                    Systemtype                  = $Data.Systemtype
                    PrimaryOwnerName            = $Data.PrimaryOwnerName
                    PCSystemType                = [Microsoft.PowerShell.Commands.PCSystemType] $Data.PCSystemType
                    PartOfDomain                = $Data.PartOfDomain
                    CurrentTimeZone             = $Data.CurrentTimeZone
                    BootupState                 = $Data.BootupState
                    SystemFamily                = $Data.SystemFamily
                    Roles                       = $Data.Roles -join ', '
                }
            }
        }
    }
}
function Get-ComputerTask { 
    <#
    .SYNOPSIS
    Get Task Schedule information
 
    .DESCRIPTION
    Get Task Schedule information
 
    .PARAMETER ComputerName
    Specifies computer on which you want to run the operation.
 
    .EXAMPLE
    Get-ComputerTask | Format-Table
 
    .NOTES
    General notes
    #>

    [alias('Get-ComputerTasks')]
    [cmdletbinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME)
    foreach ($Computer in $ComputerName) {
        try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Computer }
        if ($Computer -eq $Env:COMPUTERNAME -or $Computer -eq $LocalComputerDNSName) { $TaskParameters = @{} } else { $TaskParameters = @{CimSession = $Computer } }
        $Tasks = Get-ScheduledTask @TaskParameters
        foreach ($Task in $Tasks) {
            $Info = $Task | Get-ScheduledTaskInfo @TaskParameters
            $Actions = foreach ($_ in $Task.Actions) { -join ($_.Execute, $_.Arguments) }
            [PSCustomObject] @{ComputerName             = $Computer
                TaskName                                = $Task.TaskName
                TaskPath                                = $Task.TaskPath
                State                                   = $Task.State
                Actions                                 = $Actions
                Author                                  = $Task.Author
                Date                                    = $Task.Date
                Description                             = $Task.Description
                Documentation                           = $Task.Documentation
                PrincipalDisplayName                    = $Task.Principal.DisplayName
                PrincipalUserID                         = $Task.Principal.UserID
                PrincipalGroupID                        = $Task.Principal.GroupID
                PrincipalLogonType                      = $Task.Principal.LogonType
                PrincipalRunLevel                       = $Task.Principal.RunLevel
                PrincipalProcessTokenSidType            = $Task.Principal.ProcessTokenSidType
                PrincipalRequiredPrivilege              = $Task.Principal.RequiredPrivilege
                SettingsAllowDemandStart                = $Task.Settings.AllowDemandStart
                SettingsAllowHardTerminate              = $Task.Settings.AllowHardTerminate
                SettingsCompatibility                   = $Task.Settings.Compatibility
                SettingsDeleteExpiredTaskAfter          = $Task.Settings.DeleteExpiredTaskAfter
                SettingsDisallowStartIfOnBatteries      = $Task.Settings.DisallowStartIfOnBatteries
                SettingsEnabled                         = $Task.Settings.Enabled
                SettingsExecutionTimeLimit              = $Task.Settings.ExecutionTimeLimit
                SettingsHidden                          = $Task.Settings.Hidden
                SettingsIdleSettings                    = $Task.Settings.IdleSettings
                SettingsMultipleInstances               = $Task.Settings.MultipleInstances
                SettingsNetworkSettings                 = $Task.Settings.NetworkSettings
                SettingsPriority                        = $Task.Settings.Priority
                SettingsRestartCount                    = $Task.Settings.RestartCount
                SettingsRestartInterval                 = $Task.Settings.RestartInterval
                SettingsRunOnlyIfIdle                   = $Task.Settings.RunOnlyIfIdle
                SettingsRunOnlyIfNetworkAvailable       = $Task.Settings.RunOnlyIfNetworkAvailable
                SettingsStartWhenAvailable              = $Task.Settings.StartWhenAvailable
                SettingsStopIfGoingOnBatteries          = $Task.Settings.StopIfGoingOnBatteries
                SettingsWakeToRun                       = $Task.Settings.WakeToRun
                SettingsDisallowStartOnRemoteAppSession = $Task.Settings.DisallowStartOnRemoteAppSession
                SettingsUseUnifiedSchedulingEngine      = $Task.Settings.UseUnifiedSchedulingEngine
                SettingsMaintenanceSettings             = $Task.Settings.MaintenanceSettings
                SettingsVolatile                        = $Task.Settings.volatile
                Source                                  = $Task.Source
                URI                                     = $Task.URI
                Version                                 = $Task.Version
                LastRunTime                             = $Info.LastRunTime
                LastTaskResult                          = $Info.LastTaskResult
                NextRunTime                             = $Info.NextRunTime
                NumberOfMissedRuns                      = $Info.NumberOfMissedRuns
            }
        }
    }
}
function Get-ComputerTime { 
    <#
    .SYNOPSIS
    #
 
    .DESCRIPTION
    Long description
 
    .PARAMETER TimeSource
    Parameter description
 
    .PARAMETER Domain
    Parameter description
 
    .PARAMETER TimeTarget
    Parameter description
 
    .PARAMETER ForceCIM
 
    .PARAMETER ToLocal
 
    .EXAMPLE
 
    Get-ComputerTime -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize
    Get-ComputerTime -TimeSource AD1 -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize
    Get-ComputerTime -TimeSource 'pool.ntp.org' -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize
 
    Output
 
    Name LocalDateTime RemoteDateTime InstallTime LastBootUpTime TimeDifferenceMinutes TimeDifferenceSeconds TimeDifferenceMilliseconds TimeSourceName
    ---- ------------- -------------- ----------- -------------- --------------------- --------------------- -------------------------- --------------
    AD2 13.08.2019 23:40:26 13.08.2019 23:40:26 30.05.2018 18:30:48 09.08.2019 18:40:31 8,33333333333333E-05 0,005 5 AD1.ad.evotec.xyz
    AD3 13.08.2019 23:40:26 13.08.2019 17:40:26 26.05.2019 17:30:17 09.08.2019 18:40:30 0,000266666666666667 0,016 16 AD1.ad.evotec.xyz
    EVOWin 13.08.2019 23:40:26 13.08.2019 23:40:26 24.05.2019 22:46:45 09.08.2019 18:40:06 6,66666666666667E-05 0,004 4 AD1.ad.evotec.xyz
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string] $TimeSource,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [alias('ComputerName')][string[]] $TimeTarget = $ENV:COMPUTERNAME,
        [switch] $ForceCIM)
    if (-not $TimeSource) { $TimeSource = (Get-ADDomainController -Discover -Service PrimaryDC -DomainName $Domain).HostName }
    if ($ForceCIM) {
        $TimeSourceInformation = Get-CimData -ComputerName $TimeSource -Class 'win32_operatingsystem'
        if ($TimeSourceInformation.LocalDateTime) { $TimeSourceInformation = $TimeSourceInformation.LocalDateTime } else { $TimeSourceInformation = $null }
    } else { $TimeSourceInformation = Get-ComputerTimeNtp -Server $TimeSource -ToLocal }
    $TimeTargetInformationCache = @{}
    $TimeTargetInformation = Get-CimData -ComputerName $TimeTarget -Class 'win32_operatingsystem'
    foreach ($_ in $TimeTargetInformation) { $TimeTargetInformationCache[$_.PSComputerName] = $_ }
    $TimeLocalCache = @{}
    $TimeLocal = Get-CimData -ComputerName $TimeTarget -Class 'Win32_LocalTime'
    foreach ($_ in $TimeLocal) { $TimeLocalCache[$_.PSComputerName] = $_ }
    $AllResults = foreach ($Computer in $TimeTarget) {
        $WMIComputerTime = $TimeLocalCache[$Computer]
        $WMIComputerTarget = $TimeTargetInformationCache[$Computer]
        if ($WMIComputerTime -and $WMIComputerTime.Year -and $WMIComputerTime.Month) { $RemoteDateTime = Get-Date -Year $WMIComputerTime.Year -Month $WMIComputerTime.Month -Day $WMIComputerTime.Day -Hour $WMIComputerTime.Hour -Minute $WMIComputerTime.Minute -Second $WMIComputerTime.Second } else { $RemoteDateTIme = '' }
        if ($WMIComputerTarget.LocalDateTime -and $TimeSourceInformation) {
            $Result = New-TimeSpan -Start $TimeSourceInformation -End $WMIComputerTarget.LocalDateTime
            [PSCustomObject] @{Name        = $Computer
                LocalDateTime              = $WMIComputerTarget.LocalDateTime
                RemoteDateTime             = $RemoteDateTime
                InstallTime                = $WMIComputerTarget.InstallDate
                LastBootUpTime             = $WMIComputerTarget.LastBootUpTime
                TimeDifferenceMinutes      = if ($Result.TotalMinutes -lt 0) { ($Result.TotalMinutes * -1) } else { $Result.TotalMinutes }
                TimeDifferenceSeconds      = if ($Result.TotalSeconds -lt 0) { ($Result.TotalSeconds * -1) } else { $Result.TotalSeconds }
                TimeDifferenceMilliseconds = if ($Result.TotalMilliseconds -lt 0) { ($Result.TotalMilliseconds * -1) } else { $Result.TotalMilliseconds }
                TimeSourceName             = $TimeSource
                Status                     = ''
            }
        } else {
            [PSCustomObject] @{Name        = $Computer
                LocalDateTime              = $WMIComputerTarget.LocalDateTime
                RemoteDateTime             = $RemoteDateTime
                InstallTime                = $WMIComputerTarget.InstallDate
                LastBootUpTime             = $WMIComputerTarget.LastBootUpTime
                TimeDifferenceMinutes      = $null
                TimeDifferenceSeconds      = $null
                TimeDifferenceMilliseconds = $null
                TimeSourceName             = $TimeSource
                Status                     = 'Unable to get time difference.'
            }
        }
    }
    $AllResults
}
function Get-ComputerWindowsUpdates { 
    [CmdletBinding()]
    param([string[]] $ComputerName = $Env:COMPUTERNAME)
    $Data = Get-HotFix -ComputerName $ComputerName | Select-Object Description , HotFixId , InstallDate, InstalledBy, InstalledOn, Caption, PSComputerName, Status, FixComments, ServicePackInEffect, Name, Site, Containerr
    return $Data
}
function Get-WinADForestGUIDs { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER Domain
    Parameter description
 
    .PARAMETER RootDSE
    Parameter description
 
    .PARAMETER DisplayNameKey
    Parameter description
 
    .EXAMPLE
    Get-WinADForestGUIDs
 
    .EXAMPLE
    Get-WinADForestGUIDs -DisplayNameKey
 
    .NOTES
    General notes
    #>

    [alias('Get-WinADDomainGUIDs')]
    [cmdletbinding()]
    param([string] $Domain = $Env:USERDNSDOMAIN,
        [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE,
        [switch] $DisplayNameKey)
    if ($null -eq $RootDSE) { $RootDSE = Get-ADRootDSE -Server $Domain }
    $GUID = @{}
    $GUID.Add('00000000-0000-0000-0000-000000000000', 'All')
    $Schema = Get-ADObject -SearchBase $RootDSE.schemaNamingContext -LDAPFilter '(schemaIDGUID=*)' -Properties name, schemaIDGUID
    foreach ($S in $Schema) { if ($DisplayNameKey) { $GUID["$($S.name)"] = $(([System.GUID]$S.schemaIDGUID).Guid) } else { $GUID["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.name } }
    $Extended = Get-ADObject -SearchBase "CN=Extended-Rights,$($RootDSE.configurationNamingContext)" -LDAPFilter '(objectClass=controlAccessRight)' -Properties name, rightsGUID
    foreach ($S in $Extended) { if ($DisplayNameKey) { $GUID["$($S.name)"] = $(([System.GUID]$S.rightsGUID).Guid) } else { $GUID["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.name } }
    return $GUID
}
function New-ADForestDrives { 
    [cmdletbinding()]
    param(
        [string] $ForestName,
        [string] $ObjectDN
    )
    if (-not $Global:ADDrivesMapped) {
        if ($ForestName) {
            $Forest = Get-ADForest -Identity $ForestName
        } else {
            $Forest = Get-ADForest
        }
        if ($ObjectDN) {
            $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $ObjectDN -ToDC) -replace '=' -replace ','
            if (-not(Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                try {
                    if ($Server) {
                        $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Server.Hostname[0] -Scope Global -WhatIf:$false
                        Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $($Server.Hostname[0])"
                    } else {
                        $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Domain -Scope Global -WhatIf:$false
                    }
                } catch {
                    Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $($Server.Hostname[0])"
                }
            }
        } else {
            foreach ($Domain in $Forest.Domains) {
                try {
                    $Server = Get-ADDomainController -Discover -DomainName $Domain -Writable
                    $DomainInformation = Get-ADDomain -Server $Server.Hostname[0]
                } catch {
                    Write-Warning "New-ADForestDrives - Can't process domain $Domain - $($_.Exception.Message)"
                    continue
                }
                $ObjectDN = $DomainInformation.DistinguishedName
                $DNConverted = (ConvertFrom-DistinguishedName -DistinguishedName $ObjectDN -ToDC) -replace '=' -replace ','
                if (-not(Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    try {
                        if ($Server) {
                            $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Server.Hostname[0] -Scope Global -WhatIf:$false
                            Write-Verbose "New-ADForestDrives - Mapped drive $Domain / $Server"
                        } else {
                            $null = New-PSDrive -Name $DNConverted -Root '' -PSProvider ActiveDirectory -Server $Domain -Scope Global -WhatIf:$false
                        }
                    } catch {
                        Write-Warning "New-ADForestDrives - Couldn't map new AD psdrive for $Domain / $Server $($_.Exception.Message)"
                    }
                }
            }
        }
        $Global:ADDrivesMapped = $true
    }
}
function Remove-PrivateACL { 
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [PSCustomObject] $ACL,
        [string] $Principal,
        [System.DirectoryServices.ActiveDirectoryRights] $AccessRule,
        [System.Security.AccessControl.AccessControlType] $AccessControlType
    )
    $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $ACL.DistinguishedName
    $QueryServer = $Script:ForestDetails['QueryServers'][$DomainName].HostName[0]

    if ($Principal -like '*-*-*-*') {
        $Identity = [System.Security.Principal.SecurityIdentifier]::new($Principal)
    } else {
        [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal)
    }
    $OutputRequiresCommit = @(
        if (-not $AccessRule) {
            # if access rule is not defined this means we want to remove user/group totally
            #Write-Verbose "Remove-ADACL - Removing access for $($ACLAccessRule.Principal) / $($ACLAccessRule.ActiveDirectoryRights)"
            Write-Verbose "Remove-ADACL - Removing access for $($Identity) / $AccessControlType / All Rights"
            try {
                $ACL.ACL.RemoveAccess($Identity, $AccessControlType)
                $true
            } catch {
                Write-Warning "Remove-ADACL - Removing permissions for $($ACL.DistinguishedName) failed: $($_.Exception.Message)"
                $false
            }
        } else {
            # if access rule is defined with just remove access rule we want to remove
            foreach ($Rule in $AccessRule) {
                $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $Rule, $AccessControlType)
                Write-Verbose "Remove-ADACL - Removing access for $($AccessRuleToRemove.IdentityReference) / $AccessControlType / $($AccessRuleToRemove.ActiveDirectoryRights)"
                try {
                    $ACL.ACL.RemoveAccessRule($AccessRuleToRemove)
                    $true
                } catch {
                    Write-Warning "Remove-ADACL - Removing permissions for $($ACL.DistinguishedName) failed: $($_.Exception.Message)"
                    $false
                }
            }
        }
    )
    if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) {
        Write-Verbose "Remove-ADACL - Saving permissions for $($ACL.DistinguishedName)"
        try {
            Set-ADObject -Identity $ACL.DistinguishedName -Replace @{ ntSecurityDescriptor = $ACL.ACL } -ErrorAction Stop -Server $QueryServer
            # Set-Acl -Path $ACL.Path -AclObject $ACL.ACL -ErrorAction Stop
        } catch {
            Write-Warning "Remove-ADACL - Saving permissions for $($ACL.DistinguishedName) failed: $($_.Exception.Message)"
        }
    } elseif ($OutputRequiresCommit -contains $false) {
        Write-Warning "Remove-ADACL - Skipping saving permissions for $($ACL.DistinguishedName) due to errors."
    }
}
function Test-ComputerPort { 
    [CmdletBinding()]
    param ([alias('Server')][string[]] $ComputerName,
        [int[]] $PortTCP,
        [int[]] $PortUDP,
        [int]$Timeout = 5000)
    begin {
        if ($Global:ProgressPreference -ne 'SilentlyContinue') {
            $TemporaryProgress = $Global:ProgressPreference
            $Global:ProgressPreference = 'SilentlyContinue'
        }
    }
    process {
        foreach ($Computer in $ComputerName) {
            foreach ($P in $PortTCP) {
                $Output = [ordered] @{'ComputerName' = $Computer
                    'Port'                           = $P
                    'Protocol'                       = 'TCP'
                    'Status'                         = $null
                    'Summary'                        = $null
                    'Response'                       = $null
                }
                $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue
                if ($TcpClient.TcpTestSucceeded) {
                    $Output['Status'] = $TcpClient.TcpTestSucceeded
                    $Output['Summary'] = "TCP $P Successful"
                } else {
                    $Output['Status'] = $false
                    $Output['Summary'] = "TCP $P Failed"
                    $Output['Response'] = $Warnings
                }
                [PSCustomObject]$Output
            }
            foreach ($P in $PortUDP) {
                $Output = [ordered] @{'ComputerName' = $Computer
                    'Port'                           = $P
                    'Protocol'                       = 'UDP'
                    'Status'                         = $null
                    'Summary'                        = $null
                }
                $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P)
                $UdpClient.Client.ReceiveTimeout = $Timeout
                $Encoding = [System.Text.ASCIIEncoding]::new()
                $byte = $Encoding.GetBytes("Evotec")
                [void]$UdpClient.Send($byte, $byte.length)
                $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0)
                try {
                    $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint)
                    [string]$Data = $Encoding.GetString($Bytes)
                    If ($Data) {
                        $Output['Status'] = $true
                        $Output['Summary'] = "UDP $P Successful"
                        $Output['Response'] = $Data
                    }
                } catch {
                    $Output['Status'] = $false
                    $Output['Summary'] = "UDP $P Failed"
                    $Output['Response'] = $_.Exception.Message
                }
                $UdpClient.Close()
                $UdpClient.Dispose()
                [PSCustomObject]$Output
            }
        }
    }
    end { if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } }
}
function Test-WinRM { 
    [CmdletBinding()]
    param ([alias('Server')][string[]] $ComputerName)
    $Output = foreach ($Computer in $ComputerName) {
        $Test = [PSCustomObject] @{Output = $null
            Status                        = $null
            ComputerName                  = $Computer
        }
        try {
            $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop
            $Test.Status = $true
        } catch { $Test.Status = $false }
        $Test
    }
    $Output
}
function ConvertFrom-LanguageCode { 
    [cmdletBinding()]
    param([string] $LanguageCode)
    $LanguageCodeDictionary = @{'1' = "Arabic"
        '4'                         = "Chinese (Simplified)?? China"
        '9'                         = "English"
        '1025'                      = "Arabic (Saudi Arabia)"
        '1026'                      = "Bulgarian"
        '1027'                      = "Catalan"
        '1028'                      = "Chinese (Traditional) Taiwan"
        '1029'                      = "Czech"
        '1030'                      = "Danish"
        '1031'                      = "German (Germany)"
        '1032'                      = "Greek"
        '1033'                      = "English (United States)"
        '1034'                      = "Spanish (Traditional Sort)"
        '1035'                      = "Finnish"
        '1036'                      = "French (France)"
        '1037'                      = "Hebrew"
        '1038'                      = "Hungarian"
        '1039'                      = "Icelandic"
        '1040'                      = "Italian (Italy)"
        '1041'                      = "Japanese"
        '1042'                      = "Korean"
        '1043'                      = "Dutch (Netherlands)"
        '1044'                      = "Norwegian (Bokmal)"
        '1045'                      = "Polish"
        '1046'                      = "Portuguese (Brazil)"
        '1047'                      = "Rhaeto-Romanic"
        '1048'                      = "Romanian"
        '1049'                      = "Russian"
        '1050'                      = "Croatian"
        '1051'                      = "Slovak"
        '1052'                      = "Albanian"
        '1053'                      = "Swedish"
        '1054'                      = "Thai"
        '1055'                      = "Turkish"
        '1056'                      = "Urdu"
        '1057'                      = "Indonesian"
        '1058'                      = "Ukrainian"
        '1059'                      = "Belarusian"
        '1060'                      = "Slovenian"
        '1061'                      = "Estonian"
        '1062'                      = "Latvian"
        '1063'                      = "Lithuanian"
        '1065'                      = "Persian"
        '1066'                      = "Vietnamese"
        '1069'                      = "Basque (Basque)"
        '1070'                      = "Serbian"
        '1071'                      = "Macedonian (FYROM)"
        '1072'                      = "Sutu"
        '1073'                      = "Tsonga"
        '1074'                      = "Tswana"
        '1076'                      = "Xhosa"
        '1077'                      = "Zulu"
        '1078'                      = "Afrikaans"
        '1080'                      = "Faeroese"
        '1081'                      = "Hindi"
        '1082'                      = "Maltese"
        '1084'                      = "Scottish Gaelic (United Kingdom)"
        '1085'                      = "Yiddish"
        '1086'                      = "Malay (Malaysia)"
        '2049'                      = "Arabic (Iraq)"
        '2052'                      = "Chinese (Simplified) PRC"
        '2055'                      = "German (Switzerland)"
        '2057'                      = "English (United Kingdom)"
        '2058'                      = "Spanish (Mexico)"
        '2060'                      = "French (Belgium)"
        '2064'                      = "Italian (Switzerland)"
        '2067'                      = "Dutch (Belgium)"
        '2068'                      = "Norwegian (Nynorsk)"
        '2070'                      = "Portuguese (Portugal)"
        '2072'                      = "Romanian (Moldova)"
        '2073'                      = "Russian (Moldova)"
        '2074'                      = "Serbian (Latin)"
        '2077'                      = "Swedish (Finland)"
        '3073'                      = "Arabic (Egypt)"
        '3076'                      = "Chinese Traditional (Hong Kong SAR)"
        '3079'                      = "German (Austria)"
        '3081'                      = "English (Australia)"
        '3082'                      = "Spanish (International Sort)"
        '3084'                      = "French (Canada)"
        '3098'                      = "Serbian (Cyrillic)"
        '4097'                      = "Arabic (Libya)"
        '4100'                      = "Chinese Simplified (Singapore)"
        '4103'                      = "German (Luxembourg)"
        '4105'                      = "English (Canada)"
        '4106'                      = "Spanish (Guatemala)"
        '4108'                      = "French (Switzerland)"
        '5121'                      = "Arabic (Algeria)"
        '5127'                      = "German (Liechtenstein)"
        '5129'                      = "English (New Zealand)"
        '5130'                      = "Spanish (Costa Rica)"
        '5132'                      = "French (Luxembourg)"
        '6145'                      = "Arabic (Morocco)"
        '6153'                      = "English (Ireland)"
        '6154'                      = "Spanish (Panama)"
        '7169'                      = "Arabic (Tunisia)"
        '7177'                      = "English (South Africa)"
        '7178'                      = "Spanish (Dominican Republic)"
        '8193'                      = "Arabic (Oman)"
        '8201'                      = "English (Jamaica)"
        '8202'                      = "Spanish (Venezuela)"
        '9217'                      = "Arabic (Yemen)"
        '9226'                      = "Spanish (Colombia)"
        '10241'                     = "Arabic (Syria)"
        '10249'                     = "English (Belize)"
        '10250'                     = "Spanish (Peru)"
        '11265'                     = "Arabic (Jordan)"
        '11273'                     = "English (Trinidad)"
        '11274'                     = "Spanish (Argentina)"
        '12289'                     = "Arabic (Lebanon)"
        '12298'                     = "Spanish (Ecuador)"
        '13313'                     = "Arabic (Kuwait)"
        '13322'                     = "Spanish (Chile)"
        '14337'                     = "Arabic (U.A.E.)"
        '14346'                     = "Spanish (Uruguay)"
        '15361'                     = "Arabic (Bahrain)"
        '15370'                     = "Spanish (Paraguay)"
        '16385'                     = "Arabic (Qatar)"
        '16394'                     = "Spanish (Bolivia)"
        '17418'                     = "Spanish (El Salvador)"
        '18442'                     = "Spanish (Honduras)"
        '19466'                     = "Spanish (Nicaragua)"
        '20490'                     = "Spanish (Puerto Rico)"
    }
    $Output = $LanguageCodeDictionary[$LanguageCode]
    if ($Output) { $Output } else { "Unknown (Undocumented)" }
}
function Get-CimData { 
    <#
    .SYNOPSIS
    Helper function for retreiving CIM data from local and remote computers
 
    .DESCRIPTION
    Helper function for retreiving CIM data from local and remote computers
 
    .PARAMETER ComputerName
    Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM).
 
    .PARAMETER Protocol
    Specifies the protocol to use. The acceptable values for this parameter are: DCOM, Default, or Wsman.
 
    .PARAMETER Class
    Specifies the name of the CIM class for which to retrieve the CIM instances. You can use tab completion to browse the list of classes, because PowerShell gets a list of classes from the local WMI server to provide a list of class names.
 
    .PARAMETER Properties
    Specifies a set of instance properties to retrieve. Use this parameter when you need to reduce the size of the object returned, either in memory or over the network. The object returned also contains the key properties even if you have not listed them using the Property parameter. Other properties of the class are present but they are not populated.
 
    .EXAMPLE
    Get-CimData -Class 'win32_bios' -ComputerName AD1,EVOWIN
 
    Get-CimData -Class 'win32_bios'
 
    # Get-CimClass to get all classes
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([parameter(Mandatory)][string] $Class,
        [string] $NameSpace = 'root\cimv2',
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [string[]] $Properties = '*')
    $ExcludeProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties', 'SystemCreationClassName', 'CreationClassName'
    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName
    $CimObject = @(# requires removal of this property for query
        [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' }
        $Computers = $ComputersSplit[1]
        if ($Computers.Count -gt 0) {
            if ($Protocol = 'Default') { Get-CimInstance -ClassName $Class -ComputerName $Computers -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } else {
                $Option = New-CimSessionOption -Protocol $Protocol
                $Session = New-CimSession -ComputerName $Computers -SessionOption $Option -ErrorAction SilentlyContinue
                $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
                $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue
                $Info
            }
        }
        foreach ($E in $ErrorsToProcess) { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" }
        $Computers = $ComputersSplit[0]
        if ($Computers.Count -gt 0) {
            $Info = Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsLocal | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
            $Info | Add-Member -Name 'PSComputerName' -Value $Computers -MemberType NoteProperty -Force
            $Info
        }
        foreach ($E in $ErrorsLocal) { Write-Warning -Message "Get-CimData - No data for computer $($Env:COMPUTERNAME). Failed with errror: $($E.Exception.Message)" })
    $CimObject
}
function Get-ComputerTimeNtp { 
    <#
        .Synopsis
        Gets (Simple) Network Time Protocol time (SNTP/NTP, rfc-1305, rfc-2030) from a specified server
        .DESCRIPTION
        This function connects to an NTP server on UDP port 123 and retrieves the current NTP time.
        Selected components of the returned time information are decoded and returned in a PSObject.
        .PARAMETER Server
        The NTP Server to contact. Uses pool.ntp.org by default.
        .EXAMPLE
        Get-NtpTime uk.pool.ntp.org
        Gets time from the specified server.
        .EXAMPLE
        Get-NtpTime | fl *
        Get time from default server (pool.ntp.org) and displays all output object attributes.
        .FUNCTIONALITY
        Gets NTP time from a specified server.
 
        .NOTES
        Author https://github.com/ChrisWarwick/PowerShell-NTP-Time
        Slightly simplified for different usage scenarios
    #>

    [CmdletBinding()]
    Param ([String]$Server = 'pool.ntp.org',
        [switch]$ToLocal)
    $StartOfEpoch = New-Object DateTime(1900, 1, 1, 0, 0, 0, [DateTimeKind]::Utc)
    [Byte[]]$NtpData = , 0 * 48
    $NtpData[0] = 0x1B
    $Socket = [Net.Sockets.Socket]::new([Net.Sockets.AddressFamily]::InterNetwork, [Net.Sockets.SocketType]::Dgram, [Net.Sockets.ProtocolType]::Udp)
    $Socket.SendTimeOut = 2000
    $Socket.ReceiveTimeOut = 2000
    Try { $Socket.Connect($Server, 123) } Catch {
        $_.Error
        Write-Warning "Get-ComputerTimeNtp - Failed to connect to server $Server"
        return
    }
    $t1 = Get-Date
    Try {
        [Void]$Socket.Send($NtpData)
        [Void]$Socket.Receive($NtpData)
    } Catch {
        Write-Warning "Get-ComputerTimeNtp - Failed to communicate with server $Server"
        return
    }
    $t4 = Get-Date
    $Socket.Shutdown("Both")
    $Socket.Close()
    $LI = ($NtpData[0] -band 0xC0) -shr 6
    If ($LI -eq 3) {
        Write-Warning 'Get-ComputerTimeNtp - Alarm condition from server (clock not synchronized)'
        return
    }
    $IntPart = [BitConverter]::ToUInt32($NtpData[43..40], 0)
    $FracPart = [BitConverter]::ToUInt32($NtpData[47..44], 0)
    $t3ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)
    $IntPart = [BitConverter]::ToUInt32($NtpData[35..32], 0)
    $FracPart = [BitConverter]::ToUInt32($NtpData[39..36], 0)
    $t2ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)
    $t1ms = ([TimeZoneInfo]::ConvertTimeToUtc($t1) - $StartOfEpoch).TotalMilliseconds
    $t4ms = ([TimeZoneInfo]::ConvertTimeToUtc($t4) - $StartOfEpoch).TotalMilliseconds
    $Offset = (($t2ms - $t1ms) + ($t3ms - $t4ms)) / 2
    [DateTime] $NTPDateTime = $StartOfEpoch.AddMilliseconds($t4ms + $Offset)
    if ($ToLocal) { $NTPDateTime.ToLocalTime() } else { $NTPDateTime }
}
function Get-PSService { 
    <#
    .SYNOPSIS
    Alternative way to Get-Service
 
    .DESCRIPTION
    Alternative way to Get-Service which works using CIM queries
 
    .PARAMETER ComputerName
    ComputerName(s) to query for services
 
    .PARAMETER Protocol
    Protocol to use to gather services
 
    .PARAMETER Service
    Limit output to just few services
 
    .PARAMETER All
    Return all data without filtering
 
    .PARAMETER Extended
    Return more data
 
    .EXAMPLE
    Get-PSService -ComputerName AD1, AD2, AD3, AD4 -Service 'Dnscache', 'DNS', 'PeerDistSvc', 'WebClient', 'Netlogon'
 
    .EXAMPLE
    Get-PSService -ComputerName AD1, AD2 -Extended
 
    .EXAMPLE
    Get-PSService
 
    .EXAMPLE
    Get-PSService -Extended
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param([alias('Computer', 'Computers')][string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [alias('Services')][string[]] $Service,
        [switch] $All,
        [switch] $Extended)
    [string] $Class = 'win32_service'
    [string] $Properties = '*'
    if ($Service) {
        $CachedServices = @{}
        foreach ($S in $Service) { $CachedServices[$S] = $true }
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) { if ($Service) { foreach ($Entry in $Information) { if ($CachedServices[$Entry.Name]) { $Entry } } } else { $Information } } else {
        foreach ($Data in $Information) {
            if ($Service) { if (-not $CachedServices[$Data.Name]) { continue } }
            $OutputService = [ordered] @{ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME }
                Status                                = $Data.State
                Name                                  = $Data.Name
                ServiceType                           = $Data.ServiceType
                StartType                             = $Data.StartMode
                DisplayName                           = $Data.DisplayName
            }
            if ($Extended) {
                $OutputServiceExtended = [ordered] @{StatusOther = $Data.Status
                    ExitCode                                     = $Data.ExitCode
                    DesktopInteract                              = $Data.DesktopInteract
                    ErrorControl                                 = $Data.ErrorControl
                    PathName                                     = $Data.PathName
                    Caption                                      = $Data.Caption
                    Description                                  = $Data.Description
                    Started                                      = $Data.Started
                    SystemName                                   = $Data.SystemName
                    AcceptPause                                  = $Data.AcceptPause
                    AcceptStop                                   = $Data.AcceptStop
                    ServiceSpecificExitCode                      = $Data.ServiceSpecificExitCode
                    StartName                                    = $Data.StartName
                    TagId                                        = $Data.TagId
                    CheckPoint                                   = $Data.CheckPoint
                    DelayedAutoStart                             = $Data.DelayedAutoStart
                    ProcessId                                    = $Data.ProcessId
                    WaitHint                                     = $Data.WaitHint
                }
                [PSCustomObject] ($OutputService + $OutputServiceExtended)
            } else { [PSCustomObject] $OutputService }
        }
    }
}
function ConvertFrom-XMLRSOP {
    [cmdletBinding()]
    param(
        [System.Xml.XmlElement]$Content,
        [string] $ResultsType,
        # [Microsoft.GroupPolicy.GPRsop] $ResultantSetPolicy,
        [string] $Splitter = [System.Environment]::NewLine
    )
    $GPOPrimary = [ordered] @{
        Summary              = $null
        SummaryDetails       = [System.Collections.Generic.List[PSCustomObject]]::new()
        SummaryDownload      = $null
        #ResultantSetPolicy = $ResultantSetPolicy

        GroupPolicies        = $null
        GroupPoliciesLinks   = $null
        GroupPoliciesApplied = $null
        GroupPoliciesDenied  = $null
        Results              = [ordered]@{}
    }

    $Object = [ordered] @{
        ReadTime           = [DateTime] $Content.ReadTime
        ComputerName       = $Content.$ResultsType.Name
        DomainName         = $Content.$ResultsType.Domain
        OrganizationalUnit = $Content.$ResultsType.SOM
        Site               = $Content.$ResultsType.Site
        GPOTypes           = $Content.$ResultsType.ExtensionData.Name.'#text' -join $Splitter
        SlowLink           = if ($Content.$ResultsType.SlowLink -eq 'true') { $true } else { $false };
    }

    $GPOPrimary['Summary'] = $Object
    [Array] $GPOPrimary['SecurityGroups'] = foreach ($Group in $Content.$ResultsType.SecurityGroup) {
        [PSCustomObject] @{
            Name = $Group.Name.'#Text'
            SID  = $Group.SID.'#Text'
        }
    }
    [Array] $GPOPrimary['GroupPolicies'] = foreach ($GPO in $Content.$ResultsType.GPO) {

        <#
        $EventsReason = @{
            'NOTAPPLIED-EMPTY' = 'Not Applied (Empty)'
            'DENIED-WMIFILTER' = 'Denied (WMI Filter)'
            'DENIED-SECURITY' = 'Denied (Security)'
        }
        #>

        # Lets translate CSE extensions as some didn't translate automatically
        $ExtensionName = $GPO.ExtensionName | ForEach-Object {
            ConvertFrom-CSExtension -CSE $_ -Limited
        }
        $GPOObject = [PSCustomObject] @{
            Name           = $GPO.Name
            #Path = $GPO.Path
            GUID           = $GPO.Path.Identifier.'#text'
            DomainName     = if ($GPO.Path.Domain.'#text') { $GPO.Path.Domain.'#text' } else { 'Local Policy' };
            #VersionDirectory = $GPO.VersionDirectory
            #VersionSysvol = $GPO.VersionSysvol
            Revision       = -join ('AD (', $GPO.VersionDirectory, '), SYSVOL (', $GPO.VersionSysvol, ')')
            IsValid        = if ($GPO.IsValid -eq 'true') { $true } else { $false };
            Status         = if ($GPO.FilterAllowed -eq 'true' -and $GPO.AccessDenied -eq 'false') { 'Applied' } else { 'Denied' };
            FilterAllowed  = if ($GPO.FilterAllowed -eq 'true') { $true } else { $false };
            AccessAllowed  = if ($GPO.AccessDenied -eq 'true') { $false } else { $true };
            FilterName     = $GPO.FilterName  # : Test
            ExtensionName  = ($ExtensionName | Sort-Object -Unique) -join '; '
            # This isn't really pretty for large amount of links but can be useful for assesing things
            SOMOrder       = $GPO.Link.SOMOrder -join '; '
            AppliedOrder   = $GPO.Link.AppliedOrder -join '; '
            LinkOrder      = $GPO.Link.LinkOrder -join '; '
            Enabled        = ($GPO.Link.Enabled | ForEach-Object { if ($_ -eq 'true') { $true } else { $false }; }) -join '; '
            Enforced       = ($GPO.Link.NoOverride | ForEach-Object { if ($_ -eq 'true') { $true } else { $false }; }) -join '; ' # : true
            SecurityFilter = $GPO.SecurityFilter -join '; ' # SecurityFilter : {NT AUTHORITY\Authenticated Users, EVOTEC\GDS-TestGroup3}
            FilterId       = $GPO.FilterID # : MSFT_SomFilter.ID="{ff08bc72-dae6-4890-b4cf-85a9c3b00056}",Domain="ad.evotec.xyz"
            Links          = $GPO.Link.SOMPath -join '; '
        }
        $GPOObject
    }
    [Array] $GPOPrimary['GroupPoliciesLinks'] = foreach ($GPO in $Content.$ResultsType.GPO) {
        foreach ($Link in $GPO.Link) {
            [PSCustomObject] @{
                DisplayName  = $GPO.Name
                DomainName   = $GPO.Path.Domain.'#text'
                GUID         = $GPO.Path.Identifier.'#text'
                SOMPath      = $Link.SOMPath      # : ad.evotec.xyz
                SOMOrder     = $Link.SOMOrder     # : 2
                AppliedOrder = $Link.AppliedOrder # : 0
                LinkOrder    = $Link.LinkOrder    # : 4
                Enabled      = if ($Link.Enabled -eq 'true') { $true } else { $false }; # : true
                Enforced     = if ($Link.NoOverride -eq 'true') { $true } else { $false }; # : true
            }
        }
    }

    [Array] $GPOPrimary['ScopeOfManagement'] = foreach ($SOM in $Content.$ResultsType.SearchedSOM) {
        [PSCustomObject] @{
            Path              = $SOM.Path
            Type              = $SOM.Type
            Order             = $SOM.Order
            BlocksInheritance = if ($SOM.BlocksInheritance -eq 'true') { $true } else { $false };
            Blocked           = if ($SOM.Blocked -eq 'true') { $true } else { $false };
            Reason            = if ($SOM.Reason -eq 'true') { $true } else { $false };
        }
    }
    [Array] $GPOPrimary['ExtensionStatus'] = foreach ($Details in $Content.$ResultsType.ExtensionStatus) {
        [PSCustomObject] @{
            Name          = $Details.Name          # : Registry
            Identifier    = $Details.Identifier    # : {35378EAC-683F-11D2-A89A-00C04FBBCFA2}
            BeginTime     = $Details.BeginTime     # : 2020-04-02T12:05:10
            EndTime       = $Details.EndTime       # : 2020-04-02T12:05:10
            LoggingStatus = $Details.LoggingStatus # : Complete
            Error         = $Details.Error         # : 0
        }
    }
    [Array] $GPOPrimary['ExtensionData'] = $Content.$ResultsType.ExtensionData.Extension


    foreach ($Single in $Content.$ResultsType.EventsDetails.SinglePassEventsDetails) {
        $GPOPrimary['Results']["$($Single.ActivityId)"] = [ordered] @{}

        $GPOPrimary['Results']["$($Single.ActivityId)"]['SummaryDetails'] = [Ordered] @{
            ActivityId                      = $Single.ActivityId                      # : {6400d0bf-ac88-4ee6-b2c2-ca2cbbab0695}
            ProcessingTrigger               = $Single.ProcessingTrigger               # : Periodic
            ProcessingAppMode               = $Single.ProcessingAppMode               # : Background
            LinkSpeedInKbps                 = $Single.LinkSpeedInKbps                 # : 0
            SlowLinkThresholdInKbps         = $Single.SlowLinkThresholdInKbps         # : 500
            DomainControllerName            = $Single.DomainControllerName            # : AD1.ad.evotec.xyz
            DomainControllerIPAddress       = $Single.DomainControllerIPAddress       # : 192.168.240.189
            PolicyProcessingMode            = $Single.PolicyProcessingMode            # : None
            PolicyElapsedTimeInMilliseconds = $Single.PolicyElapsedTimeInMilliseconds # : 1202
            ErrorCount                      = $Single.ErrorCount                      # : 0
            WarningCount                    = $Single.WarningCount                    # : 0
        }

        $GPOPrimary['SummaryDetails'].Add([PSCustomObject] $GPOPrimary['Results']["$($Single.ActivityId)"]['SummaryDetails'])

        [Array] $GPOPrimary['Results']["$($Single.ActivityId)"]['ProcessingTime'] = foreach ($Details in $Single.ExtensionProcessingTime) {
            [PSCustomObject] @{
                ExtensionName             = $Details.ExtensionName
                ExtensionGuid             = $Details.ExtensionGuid
                ElapsedTimeInMilliseconds = $Details.ElapsedTimeInMilliseconds
                ProcessedTimeStamp        = $Details.ProcessedTimeStamp
            }
        }


        $EventsLevel = @{
            '5' = 'Verbose'
            '4' = 'Informational'
            '3' = 'Warning'
            '2' = 'Error'
            '1' = 'Critical'
            '0' = 'LogAlways'
        }
        $EventsReason = @{
            'NOTAPPLIED-EMPTY' = 'Not Applied (Empty)'
            'DENIED-WMIFILTER' = 'Denied (WMI Filter)'
            'DENIED-SECURITY'  = 'Denied (Security)'
        }

        [Array] $GPOPrimary['Results']["$($Single.ActivityId)"]['Events'] = foreach ($Event in $Single.EventRecord) {
            [xml] $EventDetails = $Event.EventXML
            $EventInformation = [ordered] @{
                Description   = $Event.EventDescription
                Provider      = $EventDetails.Event.System.Provider.Name      # : Provider
                ProviderGUID  = $EventDetails.Event.System.Provider.Guid
                EventID       = $EventDetails.Event.System.EventID       # : 4006
                Version       = $EventDetails.Event.System.Version       # : 1
                Level         = $EventsLevel[$EventDetails.Event.System.Level]        # : 4
                Task          = $EventDetails.Event.System.Task          # : 0
                Opcode        = $EventDetails.Event.System.Opcode        # : 1
                Keywords      = $EventDetails.Event.System.Keywords      # : 0x4000000000000000
                TimeCreated   = [DateTime] $EventDetails.Event.System.TimeCreated.SystemTime   # : TimeCreated, 2020-08-09T20:16:44.5668052Z
                EventRecordID = $EventDetails.Event.System.EventRecordID # : 10641325
                Correlation   = $EventDetails.Event.System.Correlation.ActivityID   # : Correlation
                Execution     = -join ("ProcessID: ", $EventDetails.Event.System.Execution.ProcessID, " ThreadID: ", $EventDetails.Event.System.Execution.ThreadID)       # : Execution
                Channel       = $EventDetails.Event.System.Channel       # : Microsoft-Windows-GroupPolicy / Operational
                Computer      = $EventDetails.Event.System.Computer      # : AD1.ad.evotec.xyz
                Security      = $EventDetails.Event.System.Security.UserID      # : Security
            }
            foreach ($Entry in $EventDetails.Event.EventData.Data) {
                $EventInformation["$($Entry.Name)"] = $Entry.'#text'
            }
            [PSCustomObject] $EventInformation
        }

        # Lets build events by ID, this will be useful for better/easier processing
        $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID'] = [ordered] @{}
        $GroupedEvents = $GPOPrimary['Results']["$($Single.ActivityId)"]['Events'] | Group-Object -Property EventId
        foreach ($Events in $GroupedEvents) {
            $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID'][$Events.Name] = $Events.Group
        }

        $GPOPrimary['Results']["$($Single.ActivityId)"]['GroupPoliciesApplied'] = & {
            if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5312']) {
                [xml] $GPODetailsApplied = -join ('<Details>', $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5312'].GPOinfoList, '</Details>')
                foreach ($GPO in $GPODetailsApplied.Details.GPO) {
                    $ReturnObject = [ordered] @{
                        GUID        = $GPO.ID         # : { 4E1F9C70-1DDB-4AB6-BBA3-14A8E07F0B4B }
                        DisplayName = $GPO.Name       # : DC | Event Log Settings
                        Version     = $GPO.Version    # : 851981
                        Link        = $GPO.SOM        # : LDAP: / / OU = Domain Controllers, DC = ad, DC = evotec, DC = xyz
                        SysvolPath  = $GPO.FSPath     # : \\ad.evotec.xyz\SysVol\ad.evotec.xyz\Policies\ { 4E1F9C70-1DDB-4AB6-BBA3-14A8E07F0B4B }\Machine
                        #GPOTypes = $GPO.Extensions -join '; ' # : [ { 35378EAC-683F-11D2-A89A-00C04FBBCFA2 } { D02B1F72 - 3407 - 48AE-BA88-E8213C6761F1 }]
                    }
                    $TranslatedExtensions = foreach ($Extension in $GPO.Extensions) {
                        ConvertFrom-CSExtension -CSE $Extension -Limited
                    }
                    $ReturnObject['GPOTypes'] = $TranslatedExtensions -join '; '
                    [PSCustomObject] $ReturnObject
                }
            }
        }
        $GPOPrimary['Results']["$($Single.ActivityId)"]['GroupPoliciesDenied'] = & {
            if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5312']) {
                [xml] $GPODetailsDenied = -join ('<Details>', $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5313'].GPOinfoList, '</Details>')
                foreach ($GPO in $GPODetailsDenied.Details.GPO) {
                    [PSCustomObject] @{
                        GUID        = $GPO.ID      #: { 6AC1786C-016F-11D2-945F-00C04fB984F9 }
                        DisplayName = $GPO.Name    #: Default Domain Controllers Policy
                        Version     = $GPO.Version #: 131074
                        Link        = $GPO.SOM     #: LDAP: / / OU = Domain Controllers, DC = ad, DC = evotec, DC = xyz
                        SysvolPath  = $GPO.FSPath  #: \\ad.evotec.xyz\sysvol\ad.evotec.xyz\Policies\ { 6AC1786C-016F-11D2-945F-00C04fB984F9 }\Machine
                        Reason      = $EventsReason["$($GPO.Reason)"]  #: DENIED-WMIFILTER
                    }
                }
            }
        }

        $GPOPrimary['Results']["$($Single.ActivityId)"]['SummaryDownload'] = & {
            if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126']) {
                [PSCustomObject] @{
                    IsBackgroundProcessing  = if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].IsBackgroundProcessing -eq 'true') { $true } else { $false }; # : true
                    IsAsyncProcessing       = if ($GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].IsAsyncProcessing -eq 'true') { $true } else { $false }; # : false
                    Downloaded              = $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].NumberOfGPOsDownloaded               # : 7
                    Applicable              = $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].NumberOfGPOsApplicable               # : 6
                    DownloadTimeMiliseconds = $GPOPrimary['Results']["$($Single.ActivityId)"]['EventsByID']['5126'].GPODownloadTimeElapsedInMilliseconds # : 375
                }

            }
        }
    }
    $GPOPrimary
}
function ConvertTo-XMLAccountPolicy {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = @(
            $Settings = [ordered]@{
                DisplayName           = $GPO.DisplayName
                DomainName            = $GPO.DomainName
                GUID                  = $GPO.GUID
                GpoType               = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                ClearTextPassword     = 'Not Set'
                LockoutBadCount       = 'Not Set'
                LockoutDuration       = 'Not Set'
                MaximumPasswordAge    = 'Not Set'
                MinimumPasswordAge    = 'Not Set'
                MinimumPasswordLength = 'Not Set'
                PasswordComplexity    = 'Not Set'
                PasswordHistorySize   = 'Not Set'
                ResetLockoutCount     = 'Not Set'
                MaxClockSkew          = 'Not Set'
                MaxRenewAge           = 'Not Set'
                MaxServiceAge         = 'Not Set'
                MaxTicketAge          = 'Not Set'
                TicketValidateClient  = 'Not Set'
            }
            foreach ($GPOEntry in $GPO.DataSet) {
                if ($GPOEntry.SettingBoolean) {
                    $Settings[$($GPOEntry.Name)] = if ($GPOEntry.SettingBoolean -eq 'true') { 'Enabled' } elseif ($GPOEntry.SettingBoolean -eq 'false') { 'Disabled' } else { 'Not set' };
                } elseif ($GPOEntry.SettingNumber) {
                    $Settings[$($GPOEntry.Name)] = [int64] $GPOEntry.SettingNumber
                }
            }
            [PSCustomObject] $Settings
        )


        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {



        $CreateGPO = [ordered]@{
            DisplayName           = $GPO.DisplayName
            DomainName            = $GPO.DomainName
            GUID                  = $GPO.GUID
            GpoType               = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            ClearTextPassword     = 'Not Set'
            LockoutBadCount       = 'Not Set'
            LockoutDuration       = 'Not Set'
            MaximumPasswordAge    = 'Not Set'
            MinimumPasswordAge    = 'Not Set'
            MinimumPasswordLength = 'Not Set'
            PasswordComplexity    = 'Not Set'
            PasswordHistorySize   = 'Not Set'
            ResetLockoutCount     = 'Not Set'
            MaxClockSkew          = 'Not Set'
            MaxRenewAge           = 'Not Set'
            MaxServiceAge         = 'Not Set'
            MaxTicketAge          = 'Not Set'
            TicketValidateClient  = 'Not Set'
        }
        foreach ($GPOEntry in $GPO.DataSet) {
            if ($GPOEntry.SettingBoolean) {
                $CreateGPO[$($GPOEntry.Name)] = if ($GPOEntry.SettingBoolean -eq 'true') { 'Enabled' } elseif ($GPOEntry.SettingBoolean -eq 'false') { 'Disabled' } else { 'Not set' };
            } elseif ($GPOEntry.SettingNumber) {
                $CreateGPO[$($GPOEntry.Name)] = [int64] $GPOEntry.SettingNumber
            }
        }
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    }
}
function ConvertTo-XMLAudit {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    $SettingType = @{
        '0' = 'No Auditing'
        '1' = 'Success'
        '2' = 'Failure'
        '3' = 'Success, Failure'
    }
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = @(
            $Settings = [ordered]@{
                AuditAccountLogon                        = 'Not configured'
                AuditAccountManage                       = 'Not configured'
                AuditDSAccess                            = 'Not configured'
                AuditLogonEvents                         = 'Not configured'
                AuditObjectAccess                        = 'Not configured'
                AuditPolicyChange                        = 'Not configured'
                AuditPrivilegeUse                        = 'Not configured'
                AuditProcessTracking                     = 'Not configured'
                AuditSystemEvents                        = 'Not configured'
                # Advanced Policies
                AuditAccountLockout                      = 'Not configured'
                AuditApplicationGenerated                = 'Not configured'
                AuditApplicationGroupManagement          = 'Not configured'
                AuditAuditPolicyChange                   = 'Not configured'
                AuditAuthenticationPolicyChange          = 'Not configured'
                AuditAuthorizationPolicyChange           = 'Not configured'
                AuditCentralAccessPolicyStaging          = 'Not configured'
                AuditCertificationServices               = 'Not configured'
                AuditComputerAccountManagement           = 'Not configured'
                AuditCredentialValidation                = 'Not configured'
                AuditDetailedDirectoryServiceReplication = 'Not configured'
                AuditDetailedFileShare                   = 'Not configured'
                AuditDirectoryServiceAccess              = 'Not configured'
                AuditDirectoryServiceChanges             = 'Not configured'
                AuditDirectoryServiceReplication         = 'Not configured'
                AuditDistributionGroupManagement         = 'Not configured'
                AuditDPAPIActivity                       = 'Not configured'
                AuditFileShare                           = 'Not configured'
                AuditFileSystem                          = 'Not configured'
                AuditFilteringPlatformConnection         = 'Not configured'
                AuditFilteringPlatformPacketDrop         = 'Not configured'
                AuditFilteringPlatformPolicyChange       = 'Not configured'
                AuditGroupMembership                     = 'Not configured'
                AuditHandleManipulation                  = 'Not configured'
                AuditIPsecDriver                         = 'Not configured'
                AuditIPsecExtendedMode                   = 'Not configured'
                AuditIPsecMainMode                       = 'Not configured'
                AuditIPsecQuickMode                      = 'Not configured'
                AuditKerberosAuthenticationService       = 'Not configured'
                AuditKerberosServiceTicketOperations     = 'Not configured'
                AuditKernelObject                        = 'Not configured'
                AuditLogoff                              = 'Not configured'
                AuditLogon                               = 'Not configured'
                AuditMPSSVCRuleLevelPolicyChange         = 'Not configured'
                AuditNetworkPolicyServer                 = 'Not configured'
                AuditNonSensitivePrivilegeUse            = 'Not configured'
                AuditOtherAccountLogonEvents             = 'Not configured'
                AuditOtherAccountManagementEvents        = 'Not configured'
                AuditOtherLogonLogoffEvents              = 'Not configured'
                AuditOtherObjectAccessEvents             = 'Not configured'
                AuditOtherPolicyChangeEvents             = 'Not configured'
                AuditOtherPrivilegeUseEvents             = 'Not configured'
                AuditOtherSystemEvents                   = 'Not configured'
                AuditPNPActivity                         = 'Not configured'
                AuditProcessCreation                     = 'Not configured'
                AuditProcessTermination                  = 'Not configured'
                AuditRegistry                            = 'Not configured'
                AuditRemovableStorage                    = 'Not configured'
                AuditRPCEvents                           = 'Not configured'
                AuditSAM                                 = 'Not configured'
                AuditSecurityGroupManagement             = 'Not configured'
                AuditSecurityStateChange                 = 'Not configured'
                AuditSecuritySystemExtension             = 'Not configured'
                AuditSensitivePrivilegeUse               = 'Not configured'
                AuditSpecialLogon                        = 'Not configured'
                AuditSystemIntegrity                     = 'Not configured'
                AuditUserDeviceClaims                    = 'Not configured'
                AuditUserAccountManagement               = 'Not configured'
            }
            foreach ($GPOEntry in $GPO.DataSet) {
                if ($GPOEntry.PolicyTarget) {
                    # Category = 'AuditSettings', Settings = 'AuditSetting'
                    $Category = $GPOEntry.SubcategoryName -replace ' ', '' -replace '-', '' -replace '/', ''
                    if ($Settings["$($Category)"]) {
                        $Settings["$($Category)"] = $SettingType["$($GPOEntry.SettingValue)"]
                    }
                } else {
                    # Category = 'SecuritySettings', Settings = 'Audit'
                    $SuccessAttempts = try { [bool]::Parse($GPOEntry.SuccessAttempts) } catch { $null };
                    $FailureAttempts = try { [bool]::Parse($GPOEntry.FailureAttempts) } catch { $null };
                    if ($SuccessAttempts -and $FailureAttempts) {
                        $Setting = 'Success, Failure'
                    } elseif ($SuccessAttempts) {
                        $Setting = 'Success'
                    } elseif ($FailureAttempts) {
                        $Setting = 'Failure'
                    } else {
                        $Setting = 'Not configured'
                    }
                    $Settings["$($GPOEntry.Name)"] = $Setting
                }
            }
            [PSCustomObject] $Settings
        )

        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        $CreateGPO = [ordered]@{
            DisplayName                              = $GPO.DisplayName
            DomainName                               = $GPO.DomainName
            GUID                                     = $GPO.GUID
            GpoType                                  = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            AuditAccountLogon                        = 'Not configured'
            AuditAccountManage                       = 'Not configured'
            AuditDSAccess                            = 'Not configured'
            AuditLogonEvents                         = 'Not configured'
            AuditObjectAccess                        = 'Not configured'
            AuditPolicyChange                        = 'Not configured'
            AuditPrivilegeUse                        = 'Not configured'
            AuditProcessTracking                     = 'Not configured'
            AuditSystemEvents                        = 'Not configured'
            # Advanced Policies
            AuditAccountLockout                      = 'Not configured'
            AuditApplicationGenerated                = 'Not configured'
            AuditApplicationGroupManagement          = 'Not configured'
            AuditAuditPolicyChange                   = 'Not configured'
            AuditAuthenticationPolicyChange          = 'Not configured'
            AuditAuthorizationPolicyChange           = 'Not configured'
            AuditCentralAccessPolicyStaging          = 'Not configured'
            AuditCertificationServices               = 'Not configured'
            AuditComputerAccountManagement           = 'Not configured'
            AuditCredentialValidation                = 'Not configured'
            AuditDetailedDirectoryServiceReplication = 'Not configured'
            AuditDetailedFileShare                   = 'Not configured'
            AuditDirectoryServiceAccess              = 'Not configured'
            AuditDirectoryServiceChanges             = 'Not configured'
            AuditDirectoryServiceReplication         = 'Not configured'
            AuditDistributionGroupManagement         = 'Not configured'
            AuditDPAPIActivity                       = 'Not configured'
            AuditFileShare                           = 'Not configured'
            AuditFileSystem                          = 'Not configured'
            AuditFilteringPlatformConnection         = 'Not configured'
            AuditFilteringPlatformPacketDrop         = 'Not configured'
            AuditFilteringPlatformPolicyChange       = 'Not configured'
            AuditGroupMembership                     = 'Not configured'
            AuditHandleManipulation                  = 'Not configured'
            AuditIPsecDriver                         = 'Not configured'
            AuditIPsecExtendedMode                   = 'Not configured'
            AuditIPsecMainMode                       = 'Not configured'
            AuditIPsecQuickMode                      = 'Not configured'
            AuditKerberosAuthenticationService       = 'Not configured'
            AuditKerberosServiceTicketOperations     = 'Not configured'
            AuditKernelObject                        = 'Not configured'
            AuditLogoff                              = 'Not configured'
            AuditLogon                               = 'Not configured'
            AuditMPSSVCRuleLevelPolicyChange         = 'Not configured'
            AuditNetworkPolicyServer                 = 'Not configured'
            AuditNonSensitivePrivilegeUse            = 'Not configured'
            AuditOtherAccountLogonEvents             = 'Not configured'
            AuditOtherAccountManagementEvents        = 'Not configured'
            AuditOtherLogonLogoffEvents              = 'Not configured'
            AuditOtherObjectAccessEvents             = 'Not configured'
            AuditOtherPolicyChangeEvents             = 'Not configured'
            AuditOtherPrivilegeUseEvents             = 'Not configured'
            AuditOtherSystemEvents                   = 'Not configured'
            AuditPNPActivity                         = 'Not configured'
            AuditProcessCreation                     = 'Not configured'
            AuditProcessTermination                  = 'Not configured'
            AuditRegistry                            = 'Not configured'
            AuditRemovableStorage                    = 'Not configured'
            AuditRPCEvents                           = 'Not configured'
            AuditSAM                                 = 'Not configured'
            AuditSecurityGroupManagement             = 'Not configured'
            AuditSecurityStateChange                 = 'Not configured'
            AuditSecuritySystemExtension             = 'Not configured'
            AuditSensitivePrivilegeUse               = 'Not configured'
            AuditSpecialLogon                        = 'Not configured'
            AuditSystemIntegrity                     = 'Not configured'
            AuditUserDeviceClaims                    = 'Not configured'
            AuditUserAccountManagement               = 'Not configured'
        }
        foreach ($GPOEntry in $GPO.DataSet) {
            if ($GPOEntry.PolicyTarget) {
                # Category = 'AuditSettings', Settings = 'AuditSetting'
                $Category = $GPOEntry.SubcategoryName -replace ' ', '' -replace '-', '' -replace '/', ''
                if ($CreateGPO["$($Category)"]) {
                    $CreateGPO["$($Category)"] = $SettingType["$($GPOEntry.SettingValue)"]
                }
            } else {
                # Category = 'SecuritySettings', Settings = 'Audit'
                $SuccessAttempts = try { [bool]::Parse($GPOEntry.SuccessAttempts) } catch { $null };
                $FailureAttempts = try { [bool]::Parse($GPOEntry.FailureAttempts) } catch { $null };
                if ($SuccessAttempts -and $FailureAttempts) {
                    $Setting = 'Success, Failure'
                } elseif ($SuccessAttempts) {
                    $Setting = 'Success'
                } elseif ($FailureAttempts) {
                    $Setting = 'Failure'
                } else {
                    $Setting = 'Not configured'
                }
                $CreateGPO["$($GPOEntry.Name)"] = $Setting
            }
        }
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    }
}
function ConvertTo-XMLCertificates {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [string[]] $Category,
        [switch] $SingleObject
    )
    $SkipNames = ('Name', 'LocalName', 'NamespaceURI', 'Prefix', 'NodeType', 'ParentNode', 'OwnerDocument', 'IsEmpty', 'Attributes', 'HasAttributes', 'SchemaInfo', 'InnerXml', 'InnerText', 'NextSibling', 'PreviousSibling', 'ChildNodes', 'FirstChild', 'LastChild', 'HasChildNodes', 'IsReadOnly', 'OuterXml', 'BaseURI', 'PreviousText')
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Setting in $GPO.DataSet) {
            $SettingName = $Setting.Name -split ":"
            $MySettings = [ordered] @{
                CreatedTime         = $GPO.CreatedTime         # : 06.06.2020 18:03:36
                ModifiedTime        = $GPO.ModifiedTime        # : 17.06.2020 16:08:10
                ReadTime            = $GPO.ReadTime            # : 13.08.2020 10:15:37
                SecurityDescriptor  = $GPO.SecurityDescriptor  # : SecurityDescriptor
                FilterDataAvailable = $GPO.FilterDataAvailable # : True
            }
            $Name = $SettingName[1]
            #$Name = Format-ToTitleCase -Text $Setting.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
            $MySettings['Name'] = $Name # $Setting.Name

            ConvertTo-XMLNested -CreateGPO $MySettings -Setting $Setting -SkipNames $SkipNames #-Name $Name

            if ($MySettings.Data) {
                $bytes = $MySettings.Data -replace '\r?\n' -split '(?<=\G.{2})' -ne '' -replace '^', '0x' -as [byte[]]
                $CertificateData = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes)

                $MySettings['NotBefore'] = $CertificateData.NotBefore
                $MySettings['NotAfter'] = $CertificateData.NotAfter
                $MySettings['HasPrivateKey'] = $CertificateData.HasPrivateKey
                $MySettings['Thumbprint'] = $CertificateData.Thumbprint
                $MySettings['SerialNumber'] = $CertificateData.SerialNumber
                $MySettings['Version'] = $CertificateData.Version
                $MySettings['Handle'] = $CertificateData.Handle
                $MySettings['SignatureAlgorithm'] = $CertificateData.SignatureAlgorithm.Value
                $MySettings['SignatureAlgorithmName'] = $CertificateData.SignatureAlgorithm.FriendlyName
                $MySettings['KeyUsages'] = $CertificateData.Extensions.KeyUsages
                $MySettings.Remove('Data')
            }

            [PSCustomObject] $MySettings
        }

        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Setting in $GPO.DataSet) {
            $CreateGPO = [ordered]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
            }
            $SettingName = $Setting.Name -split ":"
            $CreateGPO['CreatedTime'] = $GPO.CreatedTime         # : 06.06.2020 18:03:36
            $CreateGPO['ModifiedTime'] = $GPO.ModifiedTime        # : 17.06.2020 16:08:10
            $CreateGPO['ReadTime'] = $GPO.ReadTime            # : 13.08.2020 10:15:37
            $CreateGPO['SecurityDescriptor'] = $GPO.SecurityDescriptor  # : SecurityDescriptor
            $CreateGPO['FilterDataAvailable'] = $GPO.FilterDataAvailable # : True

            $Name = $SettingName[1]
            $CreateGPO['Name'] = $Name # $Setting.Name

            ConvertTo-XMLNested -CreateGPO $CreateGPO -Setting $Setting -SkipNames $SkipNames #-Name $Name

            if ($CreateGPO.Data) {
                $bytes = $CreateGPO.Data -replace '\r?\n' -split '(?<=\G.{2})' -ne '' -replace '^', '0x' -as [byte[]]
                $CertificateData = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes)

                $CreateGPO['NotBefore'] = $CertificateData.NotBefore
                $CreateGPO['NotAfter'] = $CertificateData.NotAfter
                $CreateGPO['HasPrivateKey'] = $CertificateData.HasPrivateKey
                $CreateGPO['Thumbprint'] = $CertificateData.Thumbprint
                $CreateGPO['SerialNumber'] = $CertificateData.SerialNumber
                $CreateGPO['Version'] = $CertificateData.Version
                $CreateGPO['Handle'] = $CertificateData.Handle
                $CreateGPO['SignatureAlgorithm'] = $CertificateData.SignatureAlgorithm.Value
                $CreateGPO['SignatureAlgorithmName'] = $CertificateData.SignatureAlgorithm.FriendlyName
                $CreateGPO['KeyUsages'] = $CertificateData.Extensions.KeyUsages
                $CreateGPO.Remove('Data')
            }
            $CreateGPO['Filters'] = $Setting.Filters
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLDriveMapSettings {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Entry in $GPO.DataSet.Drive) {
            [PSCustomObject] @{
                Changed         = [DateTime] $Entry.changed
                #uid = $Entry.uid
                GPOSettingOrder = $Entry.GPOSettingOrder
                Filter          = $Entry.Filter

                Name            = $Entry.Name
                Status          = $Entry.status
                Action          = $Script:Actions["$($Entry.Properties.action)"]
                ThisDrive       = $Entry.Properties.thisDrive
                AllDrives       = $Entry.Properties.allDrives
                UserName        = $Entry.Properties.userName
                Path            = $Entry.Properties.path
                Label           = $Entry.Properties.label
                Persistent      = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent };
                UseLetter       = if ($Entry.Properties.useLetter -eq '1') { $true } elseif ($Entry.Properties.useLetter -eq '0') { $false } else { $Entry.Properties.useLetter };
                Letter          = $Entry.Properties.letter
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Entry in $GPO.DataSet.Drive) {
            $CreateGPO = [ordered]@{
                DisplayName     = $GPO.DisplayName
                DomainName      = $GPO.DomainName
                GUID            = $GPO.GUID
                GpoType         = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                Changed         = [DateTime] $Entry.changed
                #uid = $Entry.uid
                GPOSettingOrder = $Entry.GPOSettingOrder
                Filter          = $Entry.Filter

                Name            = $Entry.Name
                Status          = $Entry.status
                Action          = $Script:Actions["$($Entry.Properties.action)"]
                ThisDrive       = $Entry.Properties.thisDrive
                AllDrives       = $Entry.Properties.allDrives
                UserName        = $Entry.Properties.userName
                Path            = $Entry.Properties.path
                Label           = $Entry.Properties.label
                Persistent      = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent };
                UseLetter       = if ($Entry.Properties.useLetter -eq '1') { $true } elseif ($Entry.Properties.useLetter -eq '0') { $false } else { $Entry.Properties.useLetter };
                Letter          = $Entry.Properties.letter
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLEventLog {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO
    )
    $RetionPeriod = @{
        '0' = 'Overwrite events as needed'
        '1' = 'Overwrite events by days'
        '2' = 'Do not overwrite events (Clear logs manually)'
    }
    $CreateGPO = [ordered]@{
        DisplayName                        = $GPO.DisplayName
        DomainName                         = $GPO.DomainName
        GUID                               = $GPO.GUID
        GpoType                            = $GPO.GpoType
        #GpoCategory = $GPOEntry.GpoCategory
        #GpoSettings = $GPOEntry.GpoSettings
        ApplicationAuditLogRetentionPeriod = $null
        ApplicationMaximumLogSize          = $null
        ApplicationRestrictGuestAccess     = $null
        ApplicationRetentionDays           = $null
        SystemAuditLogRetentionPeriod      = $null
        SystemMaximumLogSize               = $null
        SystemRestrictGuestAccess          = $null
        SystemRetentionDays                = $null
        SecurityAuditLogRetentionPeriod    = $null
        SecurityMaximumLogSize             = $null
        SecurityRestrictGuestAccess        = $null
        SecurityRetentionDays              = $null
    }
    foreach ($GPOEntry in $GPO.DataSet) {
        if ($GPOEntry.SettingBoolean) {
            $CreateGPO["$($GPOEntry.Log)$($GPOEntry.Name)"] = if ($GPOEntry.SettingBoolean -eq 'true') { 'Enabled' } elseif ($GPOEntry.SettingBoolean -eq 'false') { 'Disabled' } else { 'Not set' };
        } elseif ($GPOEntry.SettingNumber) {
            if ($GPOEntry.Name -eq 'AuditLogRetentionPeriod') {
                if ($GPOEntry.SettingNumber) {
                    $CreateGPO["$($GPOEntry.Log)$($GPOEntry.Name)"] = $RetionPeriod[$($GPOEntry.SettingNumber)]
                } else {
                    # Won't happen?
                    $CreateGPO["$($GPOEntry.Log)$($GPOEntry.Name)"] = $GPOEntry.SettingNumber
                }
            } else {
                $CreateGPO["$($GPOEntry.Log)$($GPOEntry.Name)"] = $GPOEntry.SettingNumber
            }
        }
    }
    $CreateGPO['Linked'] = $GPO.Linked
    $CreateGPO['LinksCount'] = $GPO.LinksCount
    $CreateGPO['Links'] = $GPO.Links
    [PSCustomObject] $CreateGPO
}
function ConvertTo-XMLFolderRedirection {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    # Redirection types a stored as GUID in GPOs. This hash is used to translate into readable text.
    $FolderID = @{
        "{1777F761-68AD-4D8A-87BD-30B759FA33DD}" = "Favorites"
        "{FDD39AD0-238F-46AF-ADB4-6C85480369C7}" = "Documents"
        "{33E28130-4E1E-4676-835A-98395C3BC3BB}" = "Pictures"
        "{4BD8D571-6D19-48D3-BE97-422220080E43}" = "Music"
        "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}" = "Videos"
        "{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}" = "AppDataRoaming"
        "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}" = "Desktop"
        "{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}" = "StartMenu"
        "{374DE290-123F-4565-9164-39C4925E467B}" = "Downloads"
        "{4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4}" = "Saved Games"
        "{56784854-C6CB-462B-8169-88E350ACB882}" = "Contacts"
        "{7D1D3A04-DEBB-4115-95CF-2F29DA2920DA}" = "Searches"
        "{BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968}" = "Links"
    }
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Folder in $GPO.DataSet) {
            foreach ($Location in $Folder.Location) {
                [PSCustomObject] @{
                    ID                        = $Folder.ID
                    FolderType                = $FolderID[$Folder.Id]
                    DestinationPath           = $Location.DestinationPath
                    SecuritySID               = $Location.SecurityGroup.SID.'#text'
                    SecurityName              = $Location.SecurityGroup.Name.'#text'
                    GrantExclusiveRights      = if ($Folder.GrantExclusiveRights -eq 'true') { $true } else { $false }
                    MoveContents              = if ($Folder.MoveContents -eq 'true') { $true } else { $false }
                    FollowParent              = if ($Folder.FollowParent -eq 'true') { $true } else { $false }
                    ApplyToDownLevel          = if ($Folder.ApplyToDownLevel -eq 'true') { $true } else { $false }
                    DoNotCare                 = if ($Folder.DoNotCare -eq 'true') { $true } else { $false }
                    RedirectToLocal           = if ($Folder.RedirectToLocal -eq 'true') { $true } else { $false }
                    PolicyRemovalBehavior     = $Folder.PolicyRemovalBehavior     # : LeaveContents
                    ConfigurationControl      = if ($Folder.ConfigurationControl -eq 'GP') { 'Group Policy' } else { $Folder.ConfigurationControl }      # : GP
                    PrimaryComputerEvaluation = $Folder.PrimaryComputerEvaluation # : PrimaryComputerPolicyDisabled
                }
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Folder in $GPO.DataSet) {
            foreach ($Location in $Folder.Location) {
                $CreateGPO = [ordered]@{
                    DisplayName               = $GPO.DisplayName
                    DomainName                = $GPO.DomainName
                    GUID                      = $GPO.GUID
                    GpoType                   = $GPO.GpoType
                    Id                        = $Folder.Id
                    FolderType                = $FolderID[$Folder.Id]
                    DestinationPath           = $Location.DestinationPath
                    SecuritySID               = $Location.SecurityGroup.SID.'#text'
                    SecurityName              = $Location.SecurityGroup.Name.'#text'
                    GrantExclusiveRights      = if ($Folder.GrantExclusiveRights -eq 'true') { $true } else { $false }
                    MoveContents              = if ($Folder.MoveContents -eq 'true') { $true } else { $false }
                    FollowParent              = if ($Folder.FollowParent -eq 'true') { $true } else { $false }
                    ApplyToDownLevel          = if ($Folder.ApplyToDownLevel -eq 'true') { $true } else { $false }
                    DoNotCare                 = if ($Folder.DoNotCare -eq 'true') { $true } else { $false }
                    RedirectToLocal           = if ($Folder.RedirectToLocal -eq 'true') { $true } else { $false }
                    PolicyRemovalBehavior     = $Folder.PolicyRemovalBehavior     # : LeaveContents
                    ConfigurationControl      = if ($Folder.ConfigurationControl -eq 'GP') { 'Group Policy' } else { $Folder.ConfigurationControl }      # : GP
                    PrimaryComputerEvaluation = $Folder.PrimaryComputerEvaluation # : PrimaryComputerPolicyDisabled
                    Linked                    = $GPO.Linked
                    LinksCount                = $GPO.LinksCount
                    Links                     = $GPO.Links
                }
                [PSCustomObject] $CreateGPO
            }
        }
    }
}
function ConvertTo-XMLGenericPolicy {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [string[]] $Category,
        [switch] $SingleObject
    )
    $UsedNames = [System.Collections.Generic.List[string]]::new()
    [Array] $Policies = foreach ($Cat in $Category) {
        $GPO.DataSet | Where-Object { $_.Category -like $Cat }
    }
    if ($Policies.Count -gt 0) {
        if ($SingleObject) {
            $CreateGPO = [ordered]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                Count       = 0
                Settings    = $null
            }

            [Array] $CreateGPO['Settings'] = @(
                $Settings = [ordered] @{}
                foreach ($Policy in $Policies) {
                    #if ($Policy.Category -notlike $Category) {
                    # We check again for Category because one GPO can have multiple categories
                    # First check checks GPO globally,
                    # continue
                    #}

                    $Name = Format-ToTitleCase -Text $Policy.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
                    $Settings[$Name] = $Policy.State

                    foreach ($Setting in @('DropDownList', 'Numeric', 'EditText', 'Text', 'CheckBox', 'ListBox')) {
                        if ($Policy.$Setting) {
                            foreach ($Value in $Policy.$Setting) {
                                if ($Value.Name) {
                                    $SubName = Format-ToTitleCase -Text $Value.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
                                    $SubName = -join ($Name, $SubName)
                                    if ($SubName -notin $UsedNames) {
                                        $UsedNames.Add($SubName)
                                    } else {
                                        $TimesUsed = $UsedNames | Group-Object | Where-Object { $_.Name -eq $SubName }
                                        $NumberToUse = $TimesUsed.Count + 1
                                        # We add same name 2nd and 3rd time to make sure we count properly
                                        $UsedNames.Add($SubName)
                                        # We now build property name based on amnount of times
                                        $SubName = -join ($SubName, "$NumberToUse")
                                    }
                                    if ($Value.Value -is [string]) {
                                        $Settings["$SubName"] = $Value.Value
                                    } elseif ($Value.Value -is [System.Xml.XmlElement]) {

                                        <#
                            if ($null -eq $Value.Value.Name) {
                                # Shouldn't happen but lets see
                                Write-Verbose $Value
                            } else {
                                $CreateGPO["$SubName"] = $Value.Value.Name
                            }
 
                            #>

                                        if ($Value.Value.Element) {
                                            $Settings["$SubName"] = $Value.Value.Element.Data -join '; '
                                        } elseif ($null -eq $Value.Value.Name) {
                                            # Shouldn't happen but lets see
                                            Write-Verbose "Tracking $Value"
                                        } else {
                                            $Settings["$SubName"] = $Value.Value.Name
                                        }

                                    } elseif ($Value.State) {
                                        $Settings["$SubName"] = $Value.State
                                    } elseif ($null -eq $Value.Value) {
                                        # This is most likely Setting 'Text
                                        # Do nothing, usually it's just a text to display
                                        #Write-Verbose "Skipping value for display because it's empty. Name: $($Value.Name)"
                                    } else {
                                        # shouldn't happen
                                        Write-Verbose $Value
                                    }
                                }
                            }
                        }
                    }


                }
                [PSCustomObject] $Settings
            )

            $CreateGPO['Count'] = $CreateGPO['Settings'].Count
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        } else {

            $CreateGPO = [ordered]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
            }
            foreach ($Policy in $Policies) {
                #if ($Policy.Category -notlike $Category) {
                # We check again for Category because one GPO can have multiple categories
                # First check checks GPO globally,
                # continue
                #}
                $Name = Format-ToTitleCase -Text $Policy.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
                $CreateGPO[$Name] = $Policy.State

                foreach ($Setting in @('DropDownList', 'Numeric', 'EditText', 'Text', 'CheckBox', 'ListBox')) {
                    if ($Policy.$Setting) {
                        foreach ($Value in $Policy.$Setting) {
                            if ($Value.Name) {
                                $SubName = Format-ToTitleCase -Text $Value.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
                                $SubName = -join ($Name, $SubName)
                                if ($SubName -notin $UsedNames) {
                                    $UsedNames.Add($SubName)
                                } else {
                                    $TimesUsed = $UsedNames | Group-Object | Where-Object { $_.Name -eq $SubName }
                                    $NumberToUse = $TimesUsed.Count + 1
                                    # We add same name 2nd and 3rd time to make sure we count properly
                                    $UsedNames.Add($SubName)
                                    # We now build property name based on amnount of times
                                    $SubName = -join ($SubName, "$NumberToUse")
                                }
                                if ($Value.Value -is [string]) {
                                    $CreateGPO["$SubName"] = $Value.Value
                                } elseif ($Value.Value -is [System.Xml.XmlElement]) {

                                    <#
                                if ($null -eq $Value.Value.Name) {
                                    # Shouldn't happen but lets see
                                    Write-Verbose $Value
                                } else {
                                    $CreateGPO["$SubName"] = $Value.Value.Name
                                }
 
                                #>

                                    if ($Value.Value.Element) {
                                        $CreateGPO["$SubName"] = $Value.Value.Element.Data -join '; '
                                    } elseif ($null -eq $Value.Value.Name) {
                                        # Shouldn't happen but lets see
                                        Write-Verbose "Tracking $Value"
                                    } else {
                                        $CreateGPO["$SubName"] = $Value.Value.Name
                                    }

                                } elseif ($Value.State) {
                                    $CreateGPO["$SubName"] = $Value.State
                                } elseif ($null -eq $Value.Value) {
                                    # This is most likely Setting 'Text
                                    # Do nothing, usually it's just a text to display
                                    #Write-Verbose "Skipping value for display because it's empty. Name: $($Value.Name)"
                                } else {
                                    # shouldn't happen
                                    Write-Verbose $Value
                                }
                            }
                        }
                    }
                }
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
            #}
        }
    }
}
function ConvertTo-XMLGenericPublicKey {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [string[]] $Category,
        [switch] $SingleObject
    )
    $SkipNames = ('Name', 'LocalName', 'NamespaceURI', 'Prefix', 'NodeType', 'ParentNode', 'OwnerDocument', 'IsEmpty', 'Attributes', 'HasAttributes', 'SchemaInfo', 'InnerXml', 'InnerText', 'NextSibling', 'PreviousSibling', 'ChildNodes', 'FirstChild', 'LastChild', 'HasChildNodes', 'IsReadOnly', 'OuterXml', 'BaseURI', 'PreviousText')
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Setting in $GPO.DataSet) {
            $SettingName = $Setting.Name -split ":"
            $MySettings = [ordered] @{
                CreatedTime         = $GPO.CreatedTime         # : 06.06.2020 18:03:36
                ModifiedTime        = $GPO.ModifiedTime        # : 17.06.2020 16:08:10
                ReadTime            = $GPO.ReadTime            # : 13.08.2020 10:15:37
                SecurityDescriptor  = $GPO.SecurityDescriptor  # : SecurityDescriptor
                FilterDataAvailable = $GPO.FilterDataAvailable # : True
            }
            $Name = $SettingName[1]
            #$Name = Format-ToTitleCase -Text $Setting.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
            $MySettings['Name'] = $Name # $Setting.Name

            ConvertTo-XMLNested -CreateGPO $MySettings -Setting $Setting -SkipNames $SkipNames #-Name $Name
            [PSCustomObject] $MySettings
        }

        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Setting in $GPO.DataSet) {
            $CreateGPO = [ordered]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
            }
            $SettingName = $Setting.Name -split ":"
            $CreateGPO['CreatedTime'] = $GPO.CreatedTime         # : 06.06.2020 18:03:36
            $CreateGPO['ModifiedTime'] = $GPO.ModifiedTime        # : 17.06.2020 16:08:10
            $CreateGPO['ReadTime'] = $GPO.ReadTime            # : 13.08.2020 10:15:37
            $CreateGPO['SecurityDescriptor'] = $GPO.SecurityDescriptor  # : SecurityDescriptor
            $CreateGPO['FilterDataAvailable'] = $GPO.FilterDataAvailable # : True

            $Name = $SettingName[1]
            #$Name = Format-ToTitleCase -Text $Setting.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
            $CreateGPO['Name'] = $Name # $Setting.Name
            #$CreateGPO['GPOSettingOrder'] = $Setting.GPOSettingOrder

            #foreach ($Property in ($Setting.Properties | Get-Member -MemberType Properties).Name) {

            ConvertTo-XMLNested -CreateGPO $CreateGPO -Setting $Setting -SkipNames $SkipNames #-Name $Name
            <#
        $Properties = $Setting.PSObject.Properties.Name | Where-Object { $_ -notin $SkipNames }
        foreach ($Property in $Properties) {
            If ($Property -eq 'Value') {
                if ($Setting.$Property) {
                    #$SubProperties = $Setting.$Property.PSObject.Properties.Name
                    if ($Setting.$Property.Name) {
                        $Name = $Setting.$Property.Name
                    } else {
                        $Name = 'Value'
                    }
                    if ($Setting.$Property.Number) {
                        $CreateGPO[$Name] = $Setting.$Property.Number
                    } elseif ($Setting.$Property.String) {
                        $CreateGPO[$Name] = $Setting.$Property.String
                    } else {
                        throw
                    }
                }
            } else {
                $Name = Format-CamelCaseToDisplayName -Text $Property #-RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
                if ($Setting.$Property -is [System.Xml.XmlElement]) {
                    $SubPropeties = $Setting.$Property.PSObject.Properties.Name | Where-Object { $_ -notin $SkipNames }
 
 
 
                } else {
                    $CreateGPO[$Name] = $Setting.$Property
                }
            }
        }
        #>

            $CreateGPO['Filters'] = $Setting.Filters
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLGenericSecuritySettings {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [string[]] $Category
    )
    $SkipNames = ('Name', 'LocalName', 'NamespaceURI', 'Prefix', 'NodeType', 'ParentNode', 'OwnerDocument', 'IsEmpty', 'Attributes', 'HasAttributes', 'SchemaInfo', 'InnerXml', 'InnerText', 'NextSibling', 'PreviousSibling', 'Value', 'ChildNodes', 'FirstChild', 'LastChild', 'HasChildNodes', 'IsReadOnly', 'OuterXml', 'BaseURI', 'PreviousText')
    #$UsedNames = [System.Collections.Generic.List[string]]::new()
    [Array] $Settings = foreach ($Cat in $Category) {
        $GPO.DataSet | Where-Object { $null -ne $_.$Cat }
    }

    if ($Settings.Count -gt 0) {
        foreach ($Cat in $Category) {
            foreach ($Setting in $Settings.$Cat) {
                $CreateGPO = [ordered]@{
                    DisplayName = $GPO.DisplayName
                    DomainName  = $GPO.DomainName
                    GUID        = $GPO.GUID
                    GpoType     = $GPO.GpoType
                    #GpoCategory = $GPOEntry.GpoCategory
                    #GpoSettings = $GPOEntry.GpoSettings
                }
                #$Name = Format-ToTitleCase -Text $Setting.Name -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
                $CreateGPO['Name'] = $Setting.Name
                $CreateGPO['GPOSettingOrder'] = $Setting.GPOSettingOrder
                #foreach ($Property in ($Setting.Properties | Get-Member -MemberType Properties).Name) {

                $Properties = $Setting.Properties.PSObject.Properties.Name | Where-Object { $_ -notin $SkipNames }
                foreach ($Property in $Properties) {

                    $Name = Format-CamelCaseToDisplayName -Text $Property #-RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
                    $CreateGPO[$Name] = $Setting.Properties.$Property
                }
                $CreateGPO['Filters'] = $Setting.Filters

                $CreateGPO['Linked'] = $GPO.Linked
                $CreateGPO['LinksCount'] = $GPO.LinksCount
                $CreateGPO['Links'] = $GPO.Links
                [PSCustomObject] $CreateGPO
            }
        }
    }

}
function ConvertTo-XMLLocalGroups {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )

    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        if (-not $GPO.DataSet.Group) {
            continue
        }
        [Array] $CreateGPO['Settings'] = foreach ($Group in $GPO.DataSet.Group) {
            # We're mostly interested in Members
            [Array] $Members = foreach ($Member in $Group.Properties.Members.Member) {
                [ordered] @{
                    MemberName   = $Member.Name
                    MemberAction = $Member.Action
                    MemberSID    = $Member.SID
                }
            }
            # if we have no members we create dummy object to make sure we can use foreach below
            if ($Members.Count -eq 0) {
                $Members = @(
                    [ordered] @{
                        MemberName   = $null
                        MemberAction = $null
                        MemberSID    = $null
                    }
                )
            }
            foreach ($Member in $Members) {
                $GroupObject = [ordered]@{
                    Changed         = [DateTime] $Group.Changed
                    GPOSettingOrder = $Group.GPOSettingOrder
                    Name            = $Group.name
                    Action          = $Script:Actions["$($Group.Properties.action)"]
                    GroupName       = $Group.Properties.groupName       #: Administrators (built -in )
                    NewName         = $Group.Properties.newName         #:
                    Description     = $Group.Properties.description     #:
                    DeleteAllUsers  = if ($Group.Properties.deleteAllUsers -eq '1') { 'Enabled' } elseif ($Group.Properties.deleteAllUsers -eq '0') { 'Disabled' } else { $Group.Properties.deleteAllUsers };
                    DeleteAllGroups = if ($Group.Properties.deleteAllGroups -eq '1') { 'Enabled' } elseif ($Group.Properties.deleteAllGroups -eq '0') { 'Disabled' } else { $Group.Properties.deleteAllGroups };
                    RemoveAccounts  = if ($Group.Properties.removeAccounts -eq '1') { 'Enabled' } elseif ($Group.Properties.removeAccounts -eq '0') { 'Disabled' } else { $Group.Properties.removeAccounts };
                    GroupSid        = $Group.Properties.groupSid        #: S - 1 - 5 - 32 - 544
                }
                $Last = [ordered] @{
                    #Uid = $Group.uid #: {8F435B0A-CD15-464E-85F3-B6A55B9E816A}: {8F435B0A-CD15-464E-85F3-B6A55B9E816A}
                    RunInLoggedOnUserSecurityContext      = if ($Group.userContext -eq '1') { 'Enabled' } elseif ($Group.userContext -eq '0') { 'Disabled' } else { $Group.userContext };
                    RemoveThisItemWhenItIsNoLongerApplied = if ($Group.removePolicy -eq '1') { 'Enabled' } elseif ($Group.removePolicy -eq '0') { 'Disabled' } else { $Group.removePolicy };
                    Filters                               = $Group.Filters         #::
                }
                # Merging GPO with Member
                $GroupObject = $GroupObject + $Member + $Last
                [PSCustomObject] $GroupObject
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Group in $GPO.DataSet.Group) {
            # We're mostly interested in Members
            [Array] $Members = foreach ($Member in $Group.Properties.Members.Member) {
                [ordered] @{
                    MemberName   = $Member.Name
                    MemberAction = $Member.Action
                    MemberSID    = $Member.SID
                }
            }
            # if we have no members we create dummy object to make sure we can use foreach below
            if ($Members.Count -eq 0) {
                $Members = @(
                    [ordered] @{
                        MemberName   = $null
                        MemberAction = $null
                        MemberSID    = $null
                    }
                )
            }
            foreach ($Member in $Members) {
                $CreateGPO = [ordered]@{
                    DisplayName     = $GPO.DisplayName
                    DomainName      = $GPO.DomainName
                    GUID            = $GPO.GUID
                    GpoType         = $GPO.GpoType
                    #GpoCategory = $GPO.GpoCategory #: SecuritySettings
                    #GpoSettings = $GPO.GpoSettings #: SecurityOptions
                    Changed         = [DateTime] $Group.Changed
                    GPOSettingOrder = $Group.GPOSettingOrder
                    Name            = $Group.name
                    Action          = $Script:Actions["$($Group.Properties.action)"]
                    GroupName       = $Group.Properties.groupName       #: Administrators (built -in )
                    NewName         = $Group.Properties.newName         #:
                    Description     = $Group.Properties.description     #:
                    DeleteAllUsers  = if ($Group.Properties.deleteAllUsers -eq '1') { 'Enabled' } elseif ($Group.Properties.deleteAllUsers -eq '0') { 'Disabled' } else { $Group.Properties.deleteAllUsers };
                    DeleteAllGroups = if ($Group.Properties.deleteAllGroups -eq '1') { 'Enabled' } elseif ($Group.Properties.deleteAllGroups -eq '0') { 'Disabled' } else { $Group.Properties.deleteAllGroups };
                    RemoveAccounts  = if ($Group.Properties.removeAccounts -eq '1') { 'Enabled' } elseif ($Group.Properties.removeAccounts -eq '0') { 'Disabled' } else { $Group.Properties.removeAccounts };
                    GroupSid        = $Group.Properties.groupSid        #: S - 1 - 5 - 32 - 544
                }
                $Last = [ordered] @{
                    # Uid = $Group.uid #: {8F435B0A-CD15-464E-85F3-B6A55B9E816A}: {8F435B0A-CD15-464E-85F3-B6A55B9E816A}
                    RunInLoggedOnUserSecurityContext      = if ($Group.userContext -eq '1') { 'Enabled' } elseif ($Group.userContext -eq '0') { 'Disabled' } else { $Group.userContext };
                    RemoveThisItemWhenItIsNoLongerApplied = if ($Group.removePolicy -eq '1') { 'Enabled' } elseif ($Group.removePolicy -eq '0') { 'Disabled' } else { $Group.removePolicy };
                    Filters                               = $Group.Filters         #::
                }
                # Merging GPO with Member
                $CreateGPO = $CreateGPO + $Member + $Last
                $CreateGPO['Linked'] = $GPO.Linked
                $CreateGPO['LinksCount'] = $GPO.LinksCount
                $CreateGPO['Links'] = $GPO.Links
                [PSCustomObject] $CreateGPO
            }
        }
    }
}

function ConvertTo-XMLLocalUser {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        if (-not $GPO.DataSet.User) {
            continue
        }
        [Array] $CreateGPO['Settings'] = foreach ($User in $GPO.DataSet.User) {
            [PSCustomObject] @{
                Changed                       = [DateTime] $User.Changed
                GPOSettingOrder               = $User.GPOSettingOrder
                Action                        = $Script:Actions["$($User.Properties.action)"]
                UserName                      = $User.Properties.userName
                NewName                       = $User.Properties.newName
                FullName                      = $User.Properties.fullName
                Description                   = $User.Properties.description
                Password                      = $User.Properties.cpassword
                MustChangePasswordAtNextLogon = if ($User.Properties.changeLogon -eq '1') { $true } elseif ($User.Properties.changeLogon -eq '0') { $false } else { $User.Properties.changeLogon };
                CannotChangePassword          = if ($User.Properties.noChange -eq '1') { $true } elseif ($User.Properties.noChange -eq '0') { $false } else { $User.Properties.noChange };
                PasswordNeverExpires          = if ($User.Properties.neverExpires -eq '1') { $true } elseif ($User.Properties.neverExpires -eq '0') { $false } else { $User.Properties.neverExpires };
                AccountIsDisabled             = if ($User.Properties.acctDisabled -eq '1') { $true } elseif ($User.Properties.acctDisabled -eq '0') { $false } else { $User.Properties.acctDisabled };
                AccountExpires                = try { [DateTime] $User.Properties.expires } catch { $User.Properties.expires };
                SubAuthority                  = $User.Properties.subAuthority
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($User in $GPO.DataSet.User) {
            $CreateGPO = [ordered]@{
                DisplayName                   = $GPO.DisplayName
                DomainName                    = $GPO.DomainName
                GUID                          = $GPO.GUID
                GpoType                       = $GPO.GpoType
                #GpoCategory = $GPO.GpoCategory #: SecuritySettings
                #GpoSettings = $GPO.GpoSettings #: SecurityOptions
                Changed                       = [DateTime] $User.Changed
                GPOSettingOrder               = $User.GPOSettingOrder
                Action                        = $Script:Actions["$($User.Properties.action)"]
                UserName                      = $User.Properties.userName
                NewName                       = $User.Properties.newName
                FullName                      = $User.Properties.fullName
                Description                   = $User.Properties.description
                Password                      = $User.Properties.cpassword
                MustChangePasswordAtNextLogon = if ($User.Properties.changeLogon -eq '1') { $true } elseif ($User.Properties.changeLogon -eq '0') { $false } else { $User.Properties.changeLogon };
                CannotChangePassword          = if ($User.Properties.noChange -eq '1') { $true } elseif ($User.Properties.noChange -eq '0') { $false } else { $User.Properties.noChange };
                PasswordNeverExpires          = if ($User.Properties.neverExpires -eq '1') { $true } elseif ($User.Properties.neverExpires -eq '0') { $false } else { $User.Properties.neverExpires };
                AccountIsDisabled             = if ($User.Properties.acctDisabled -eq '1') { $true } elseif ($User.Properties.acctDisabled -eq '0') { $false } else { $User.Properties.acctDisabled };
                AccountExpires                = try { [DateTime] $User.Properties.expires } catch { $User.Properties.expires };
                SubAuthority                  = $User.Properties.subAuthority
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLNested {
    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $CreateGPO,
        [System.Xml.XmlElement] $Setting,
        [string[]] $SkipNames,
        [string] $Name
    )

    $Properties = $Setting.PSObject.Properties.Name | Where-Object { $_ -notin $SkipNames }
    $TempName = $Name
    foreach ($Property in $Properties) {
        If ($Property -eq 'Value') {
            if ($Setting.$Property) {
                #$SubProperties = $Setting.$Property.PSObject.Properties.Name
                if ($Setting.$Property.Name) {
                    $Name = $Setting.$Property.Name
                } else {
                    if (-not $Name) {
                        $Name = 'Value'
                    }
                }
                if ($Setting.$Property.Number) {
                    $CreateGPO[$Name] = $Setting.$Property.Number
                } elseif ($Setting.$Property.String) {
                    $CreateGPO[$Name] = $Setting.$Property.String
                } else {
                    $CreateGPO[$Name] = $Setting.$Property

                    #throw
                }
            }
        } else {
            $Name = -join ($Name, $Property)
            $Name = Format-CamelCaseToDisplayName -Text $Name #-RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
            if ($Setting.$Property -is [System.Xml.XmlElement]) {
                #$SubPropeties = $Setting.$Property.PSObject.Properties.Name | Where-Object { $_ -notin $SkipNames }


                ConvertTo-XMLNested -Setting $Setting.$Property -CreateGPO $CreateGPO -Name $Name -SkipNames $SkipNames


            } else {
                $CreateGPO[$Name] = $Setting.$Property
            }
        }
        $Name = $TempName
    }
}
function ConvertTo-XMLPolicies {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Policy in $GPO.DataSet) {
            [PSCustomObject] @{
                PolicyName         = $Policy.Name
                PolicyState        = $Policy.State
                PolicyCategory     = $Policy.Category
                PolicySupported    = $Policy.Supported
                PolicyExplain      = $Policy.Explain
                PolicyText         = $Policy.Text
                PolicyCheckBox     = $Policy.CheckBox
                PolicyDropDownList = $Policy.DropDownList
                PolicyEditText     = $Policy.EditText
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Policy in $GPO.DataSet) {
            $CreateGPO = [ordered]@{
                DisplayName        = $GPO.DisplayName
                DomainName         = $GPO.DomainName
                GUID               = $GPO.GUID
                GpoType            = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                PolicyName         = $Policy.Name
                PolicyState        = $Policy.State
                PolicyCategory     = $Policy.Category
                PolicySupported    = $Policy.Supported
                PolicyExplain      = $Policy.Explain
                PolicyText         = $Policy.Text
                PolicyCheckBox     = $Policy.CheckBox
                PolicyDropDownList = $Policy.DropDownList
                PolicyEditText     = $Policy.EditText
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLPrinter {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = @(
            foreach ($Type in @('SharedPrinter', 'PortPrinter', 'LocalPrinter')) {
                foreach ($Entry in $GPO.DataSet.$Type) {
                    if ($Entry) {
                        ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type $Type -Limited
                    }
                }
            }
            if ($GPO.GpoCategory -eq 'PrinterConnectionSettings') {
                foreach ($Entry in $GPO.DataSet) {
                    ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type 'PrinterConnections' -Limited
                }
            }
        )
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Type in @('SharedPrinter', 'PortPrinter', 'LocalPrinter')) {
            foreach ($Entry in $GPO.DataSet.$Type) {
                if ($Entry) {
                    ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type $Type
                }
            }
        }
        if ($GPO.GpoCategory -eq 'PrinterConnectionSettings') {
            foreach ($Entry in $GPO.DataSet) {
                ConvertTo-XMLPrinterInternal -GPO $GPO -Entry $Entry -Type 'PrinterConnections'
            }
        }
    }
}
function ConvertTo-XMLPrinterInternal {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        $Entry,
        $Type,
        [switch] $Limited
    )
    if ($Limited) {
        $CreateGPO = [ordered]@{
            Changed              = try { [DateTime] $Entry.changed } catch { $Entry.Changed };
            #uid = $Entry.uid
            BypassErrors         = if ($Entry.bypassErrors -eq '1') { $true } else { $false };
            GPOSettingOrder      = $Entry.GPOSettingOrder
            Filter               = $Entry.Filter
            type                 = $Type
            Action               = $null #$Script:Actions["$($Entry.Properties.action)"]
            Comment              = $Entry.Properties.comment
            Path                 = if ($Entry.Properties.path) { $Entry.Properties.Path } elseif ($Entry.Path) { $Entry.Path } else { '' }
            Location             = $Entry.Properties.location

            HostName             = $Entry.Properties.ipAddress     #: 10.42.20.204
            LocalName            = $Entry.Properties.localName     #: CZ02PRT00017
            UseDNS               = if ($Entry.Properties.useDNS -eq '1') { $true } elseif ($Entry.Properties.useDNS -eq '0') { $false } else { $Entry.Properties.useDNS };
            UseIPv6              = if ($Entry.Properties.useIPv6 -eq '1') { $true } elseif ($Entry.Properties.useIPv6 -eq '0') { $false } else { $Entry.Properties.useIPv6 };
            Default              = if ($Entry.Properties.default -eq '1') { $true } elseif ($Entry.Properties.default -eq '0') { $false } else { $Entry.Properties.default };
            SkipLocal            = if ($Entry.Properties.skipLocal -eq '1') { $true } elseif ($Entry.Properties.skipLocal -eq '0') { $false } else { $Entry.Properties.skipLocal };
            DeleteAllShared      = if ($Entry.Properties.deleteAll -eq '1') { $true } elseif ($Entry.Properties.deleteAll -eq '0') { $false } else { $Entry.Properties.deleteAll };
            Persistent           = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent };
            DeleteMaps           = if ($Entry.Properties.deleteMaps -eq '1') { $true } elseif ($Entry.Properties.deleteMaps -eq '0') { $false } else { $Entry.Properties.deleteMaps };
            LPRSettingsQueueName = $Entry.Properties.lprQueue      #:

            Protocol             = $Entry.Properties.protocol      #: PROTOCOL_RAWTCP_TYPE
            PortNumber           = if ($Entry.Properties.portNumber) { $Entry.Properties.portNumber } else { $Entry.Properties.port }
            DoubleSpool          = if ($Entry.Properties.doubleSpool -eq '1') { $true } elseif ($Entry.Properties.doubleSpool -eq '0') { $false } else { $Entry.Properties.doubleSpool };

            SNMPEnabled          = if ($Entry.Properties.snmpEnabled -eq '1') { $true } elseif ($Entry.Properties.snmpEnabled -eq '0') { $false } else { $Entry.Properties.snmpEnabled };
            SNMPCommunityName    = $Entry.Properties.snmpCommunity #: public
            SNMPDeviceIndex      = $Entry.Properties.snmpDevIndex  #: 1
        }
        if ($Entry.Properties.Action) {
            $CreateGPO['Action'] = $Script:Actions["$($Entry.Properties.action)"]
        } else {
            $CreateGPO['Action'] = 'Deploy'
        }
        [PSCustomObject] $CreateGPO
    } else {
        $CreateGPO = [ordered]@{
            DisplayName          = $GPO.DisplayName
            DomainName           = $GPO.DomainName
            GUID                 = $GPO.GUID
            GpoType              = $GPO.GpoType
            GpoCategory          = $GPO.GpoCategory
            GpoSettings          = $GPO.GpoSettings
            Changed              = try { [DateTime] $Entry.changed } catch { $Entry.Changed };
            #uid = $Entry.uid
            BypassErrors         = if ($Entry.bypassErrors -eq '1') { $true } else { $false };
            GPOSettingOrder      = $Entry.GPOSettingOrder
            Filter               = $Entry.Filter
            type                 = $Type
            Action               = $null #$Script:Actions["$($Entry.Properties.action)"]
            Comment              = $Entry.Properties.comment
            Path                 = if ($Entry.Properties.path) { $Entry.Properties.Path } elseif ($Entry.Path) { $Entry.Path } else { '' }
            Location             = $Entry.Properties.location

            HostName             = $Entry.Properties.ipAddress     #: 10.42.20.204
            LocalName            = $Entry.Properties.localName     #: CZ02PRT00017
            UseDNS               = if ($Entry.Properties.useDNS -eq '1') { $true } elseif ($Entry.Properties.useDNS -eq '0') { $false } else { $Entry.Properties.useDNS };
            UseIPv6              = if ($Entry.Properties.useIPv6 -eq '1') { $true } elseif ($Entry.Properties.useIPv6 -eq '0') { $false } else { $Entry.Properties.useIPv6 };
            Default              = if ($Entry.Properties.default -eq '1') { $true } elseif ($Entry.Properties.default -eq '0') { $false } else { $Entry.Properties.default };
            SkipLocal            = if ($Entry.Properties.skipLocal -eq '1') { $true } elseif ($Entry.Properties.skipLocal -eq '0') { $false } else { $Entry.Properties.skipLocal };
            DeleteAllShared      = if ($Entry.Properties.deleteAll -eq '1') { $true } elseif ($Entry.Properties.deleteAll -eq '0') { $false } else { $Entry.Properties.deleteAll };
            Persistent           = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent };
            DeleteMaps           = if ($Entry.Properties.deleteMaps -eq '1') { $true } elseif ($Entry.Properties.deleteMaps -eq '0') { $false } else { $Entry.Properties.deleteMaps };
            LPRSettingsQueueName = $Entry.Properties.lprQueue      #:

            Protocol             = $Entry.Properties.protocol      #: PROTOCOL_RAWTCP_TYPE
            PortNumber           = if ($Entry.Properties.portNumber) { $Entry.Properties.portNumber } else { $Entry.Properties.port }
            DoubleSpool          = if ($Entry.Properties.doubleSpool -eq '1') { $true } elseif ($Entry.Properties.doubleSpool -eq '0') { $false } else { $Entry.Properties.doubleSpool };

            SNMPEnabled          = if ($Entry.Properties.snmpEnabled -eq '1') { $true } elseif ($Entry.Properties.snmpEnabled -eq '0') { $false } else { $Entry.Properties.snmpEnabled };
            SNMPCommunityName    = $Entry.Properties.snmpCommunity #: public
            SNMPDeviceIndex      = $Entry.Properties.snmpDevIndex  #: 1
        }
        if ($Entry.Properties.Action) {
            $CreateGPO['Action'] = $Script:Actions["$($Entry.Properties.action)"]
        } else {
            $CreateGPO['Action'] = 'Deploy'
        }
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    }
}
function ConvertTo-XMLRegistryAutologon {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO
    )
    $CreateGPO = [ordered]@{
        DisplayName                  = $GPO.DisplayName
        DomainName                   = $GPO.DomainName
        GUID                         = $GPO.GUID
        GpoType                      = $GPO.GpoType
        #GpoCategory = $GPOEntry.GpoCategory
        #GpoSettings = $GPOEntry.GpoSettings
        AutoAdminLogon               = $null
        DefaultDomainName            = $null
        DefaultUserName              = $null
        DefaultPassword              = $null
        DateChangedAutoAdminLogon    = $null
        DateChangedDefaultDomainName = $null
        DateChangedDefaultUserName   = $null
        DateChangedDefaultPassword   = $null
    }
    foreach ($Registry in $GPO.DataSet.Registry) {
        if ($Registry.Properties.Key -eq 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon') {
            if ($Registry.Properties.Name -eq 'AutoAdminLogon') {
                $CreateGPO['AutoAdminLogon'] = [bool] $Registry.Properties.value
                $CreateGPO['DateChangedAutoAdminLogon'] = [DateTime] $Registry.changed
            } elseif ($Registry.Properties.Name -eq 'DefaultDomainName') {
                $CreateGPO['DefaultDomainName'] = $Registry.Properties.value
                $CreateGPO['DateChangedDefaultDomainName'] = [DateTime] $Registry.changed
            } elseif ($Registry.Properties.Name -eq 'DefaultUserName') {
                $CreateGPO['DefaultUserName'] = $Registry.Properties.value
                $CreateGPO['DateChangedDefaultUserName'] = [DateTime] $Registry.changed
            } elseif ($Registry.Properties.Name -eq 'DefaultPassword') {
                $CreateGPO['DefaultPassword'] = $Registry.Properties.value
                $CreateGPO['DateChangedDefaultPassword'] = [DateTime] $Registry.changed
            }
        }
    }
    if ($null -ne $CreateGPO['AutoAdminLogon'] -or
        $null -ne $CreateGPO['DefaultDomainName'] -or
        $null -ne $CreateGPO['DefaultUserName'] -or
        $null -ne $CreateGPO['DefaultPassword']
    ) {
        $CreateGPO['Linked'] = $GPOEntry.Linked
        $CreateGPO['LinksCount'] = $GPOEntry.LinksCount
        $CreateGPO['Links'] = $GPOEntry.Links
        [PSCustomObject] $CreateGPO
    }
}
function ConvertTo-XMLRegistryAutologonOnReport {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO
    )
    $CreateGPO = [ordered]@{
        DisplayName                  = $GPO.DisplayName
        DomainName                   = $GPO.DomainName
        GUID                         = $GPO.GUID
        GpoType                      = $GPO.GpoType
        #GpoCategory = $GPOEntry.GpoCategory
        #GpoSettings = $GPOEntry.GpoSettings
        AutoAdminLogon               = $null
        DefaultDomainName            = $null
        DefaultUserName              = $null
        DefaultPassword              = $null
        DateChangedAutoAdminLogon    = $null
        DateChangedDefaultDomainName = $null
        DateChangedDefaultUserName   = $null
        DateChangedDefaultPassword   = $null
    }
    foreach ($Registry in $GPO.Settings) {
        if ($Registry.Key -eq 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon') {
            if ($Registry.Name -eq 'AutoAdminLogon') {
                $CreateGPO['AutoAdminLogon'] = [bool] $Registry.value
                $CreateGPO['DateChangedAutoAdminLogon'] = [DateTime] $Registry.changed
            } elseif ($Registry.Name -eq 'DefaultDomainName') {
                $CreateGPO['DefaultDomainName'] = $Registry.value
                $CreateGPO['DateChangedDefaultDomainName'] = [DateTime] $Registry.changed
            } elseif ($Registry.Name -eq 'DefaultUserName') {
                $CreateGPO['DefaultUserName'] = $Registry.value
                $CreateGPO['DateChangedDefaultUserName'] = [DateTime] $Registry.changed
            } elseif ($Registry.Name -eq 'DefaultPassword') {
                $CreateGPO['DefaultPassword'] = $Registry.value
                $CreateGPO['DateChangedDefaultPassword'] = [DateTime] $Registry.changed
            }
        }
    }
    if ($null -ne $CreateGPO['AutoAdminLogon'] -or
        $null -ne $CreateGPO['DefaultDomainName'] -or
        $null -ne $CreateGPO['DefaultUserName'] -or
        $null -ne $CreateGPO['DefaultPassword']
    ) {
        $CreateGPO['Linked'] = $GPO.Linked        #: True
        $CreateGPO['LinksCount'] = $GPO.LinksCount    #: 1
        $CreateGPO['Links'] = $GPO.Links         #: area1.local
        [PSCustomObject] $CreateGPO
    }
}
function ConvertTo-XMLRegistryInternetExplorerZones {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO
    )
    foreach ($Registry in $GPO.Settings) {
        $Keys = @(
            'Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\'
            'Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\'
        )
        $Found = $false
        foreach ($Key in $Keys) {
            if ($Registry.Key -like "$Key*") {
                $Found = $true
            }
        }
        if ($Found -eq $false) {
            continue
        }
        # https://support.microsoft.com/en-us/help/182569/internet-explorer-security-zones-registry-entries-for-advanced-users
        if ($Registry.Key -like 'Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\*') {
            $CreateGPO = [ordered]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
            }
            $CreateGPO['Disabled'] = $Registry.Disabled
            if ($Registry.Key -like '*EscDomains*') {
                $CreateGPO['Configuration'] = 'Enhanced Security Configuration (ESC)'
            } else {
                $CreateGPO['Configuration'] = 'Domains'
            }
            if ($Registry.Hive -eq 'HKEY_CURRENT_USER') {
                $CreateGPO['Type'] = 'User Policy'
            } elseif ($Registry.Hive -eq 'HKEY_LOCAL_MACHINE') {
                $CreateGPO['Type'] = 'Computer Policy'
            } else {
                $CreateGPO['Type'] = $Registry.Hive
            }
            if ($Registry.Value -eq '00000000') {
                $CreateGPO['Zone'] = 'My Computer (0)'
            } elseif ($Registry.Value -eq '00000001') {
                $CreateGPO['Zone'] = 'Local Intranet Zone (1)'
            } elseif ($Registry.Value -eq '00000002') {
                $CreateGPO['Zone'] = 'Trusted Sites Zone (2)'
            } elseif ($Registry.Value -eq '00000003') {
                $CreateGPO['Zone'] = 'Internet Zone (3)'
            } elseif ($Registry.Value -eq '00000004') {
                $CreateGPO['Zone'] = 'Restricted Sites Zone (4)'
            } else {
                $CreateGPO['Zone'] = $Registry.Value
            }
            [string] $FullKey = foreach ($Key in $Keys) {
                if ($Registry.Key -like "$Key*") {
                    $Registry.Key.Replace($Key, '')
                }
            }
            $DomainSplit = $FullKey.Split('\')
            $Reversed = for ($i = $DomainSplit.Count - 1; $i -ge 0; $i--) {
                $DomainSplit[$i]
            }
            if ($Registry.Name -eq '*') {
                $CreateGPO['DomainZone'] = $Reversed -join '.'
            } else {
                $CreateGPO['DomainZone'] = -join ($Registry.Name, '://', ($Reversed -join '.'))
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLRegistrySettings {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }

        [Array] $CreateGPO['Settings'] = Get-XMLNestedRegistry -GPO $GPO -DataSet $GPO.DataSet
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        Get-XMLNestedRegistry -GPO $GPO -DataSet $GPO.DataSet
    }
}
function ConvertTo-XMLScripts {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Script in $GPO.DataSet) {
            [PSCustomObject] @{
                Type       = $GPO.DataSet.Type
                Command    = $GPO.DataSet.Command
                Parameters = $GPO.DataSet.Parameters
                Order      = $GPO.DataSet.Order
                RunOrder   = $GPO.DataSet.RunOrder
            }
        }
        $CreateGPO['DataCount'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Script in $GPO.DataSet) {
            $CreateGPO = [ordered]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                Type        = $Script.Type
                Command     = $Script.Command
                Parameters  = $Script.Parameters
                Order       = $Script.Order
                RunOrder    = $Script.RunOrder
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLSecurityOptions {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Entry in $GPO.DataSet) {
            $Object = [ordered] @{}
            $Object['KeyName'] = $Entry.KeyName
            $Object['KeyDisplayName'] = $Entry.Display.Name
            $Object['KeyDisplayUnits'] = $Entry.Display.Units
            $Object['KeyDisplayBoolean'] = try { [bool]::Parse($Entry.Display.DisplayBoolean) } catch { $null };
            $Object['KeyDisplayString'] = $Entry.Display.DisplayString
            $Object['SystemAccessPolicyName'] = $Entry.SystemAccessPolicyName
            if ($Entry.SettingString) {
                $Object['KeyValue'] = $Entry.SettingString
            } else {
                $Object['KeyValue'] = $Entry.SettingNumber
            }
            [PSCustomObject] $Object
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Entry in $GPO.DataSet) {
            $CreateGPO = [ordered]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
            }
            $CreateGPO['KeyName'] = $Entry.KeyName
            $CreateGPO['KeyDisplayName'] = $Entry.Display.Name
            $CreateGPO['KeyDisplayUnits'] = $Entry.Display.Units
            $CreateGPO['KeyDisplayBoolean'] = try { [bool]::Parse($Entry.Display.DisplayBoolean) } catch { $null };
            $CreateGPO['KeyDisplayString'] = $Entry.Display.DisplayString
            $CreateGPO['SystemAccessPolicyName'] = $Entry.SystemAccessPolicyName
            if ($Entry.SettingString) {
                $CreateGPO['KeyValue'] = $Entry.SettingString
            } else {
                $CreateGPO['KeyValue'] = $Entry.SettingNumber
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLSoftwareInstallation {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($MsiInstallerr in $GPO.DataSet) {
            [PSCustomObject] @{
                Identifier          = $MsiInstallerr.Identifier          #: { 10495e9e-79c1-4a32-b278-a24cd495437f }
                Name                = $MsiInstallerr.Name                #: Local Administrator Password Solution (2)
                Path                = $MsiInstallerr.Path                #: \\area1.local\SYSVOL\area1.local\Policies\ { 5F5042A0-008F-45E3-8657-79C87BD002E3 }\LAPS.x64.msi
                MajorVersion        = $MsiInstallerr.MajorVersion        #: 6
                MinorVersion        = $MsiInstallerr.MinorVersion        #: 2
                LanguageId          = $MsiInstallerr.LanguageId          #: 1033
                Architecture        = $MsiInstallerr.Architecture        #: 9
                IgnoreLanguage      = if ($MsiInstallerr.IgnoreLanguage -eq 'true') { $true } else { $false }      #: false
                Allowx86Onia64      = if ($MsiInstallerr.Allowx86Onia64 -eq 'true') { $true } else { $false }       #: true
                SupportURL          = $MsiInstallerr.SupportURL          #:
                AutoInstall         = if ($MsiInstallerr.AutoInstall -eq 'true') { $true } else { $false }          #: true
                DisplayInARP        = if ($MsiInstallerr.DisplayInARP -eq 'true') { $true } else { $false }        #: true
                IncludeCOM          = if ($MsiInstallerr.IncludeCOM -eq 'true') { $true } else { $false }          #: true
                SecurityDescriptor  = $MsiInstallerr.SecurityDescriptor  #: SecurityDescriptor
                DeploymentType      = $MsiInstallerr.DeploymentType      #: Assign
                ProductId           = $MsiInstallerr.ProductId           #: { ea8cb806-c109 - 4700 - 96b4-f1f268e5036c }
                ScriptPath          = $MsiInstallerr.ScriptPath          #: \\area1.local\SysVol\area1.local\Policies\ { 5F5042A0-008F-45E3-8657-79C87BD002E3 }\Machine\Applications\ { EAC9B821-FB4D - 457A-806F-E5B528D1E41A }.aas
                DeploymentCount     = $MsiInstallerr.DeploymentCount     #: 0
                InstallationUILevel = $MsiInstallerr.InstallationUILevel #: Maximum
                Upgrades            = if ($MsiInstallerr.Upgrades.Mandatory -eq 'true') { $true } else { $false }            #: Upgrades
                UninstallUnmanaged  = if ($MsiInstallerr.UninstallUnmanaged -eq 'true') { $true } else { $false }   #: false
                LossOfScopeAction   = $MsiInstallerr.LossOfScopeAction   #: Unmanage
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($MsiInstallerr in $GPO.DataSet) {
            $CreateGPO = [ordered]@{
                DisplayName         = $GPO.DisplayName
                DomainName          = $GPO.DomainName
                GUID                = $GPO.GUID
                GpoType             = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                Identifier          = $MsiInstallerr.Identifier          #: { 10495e9e-79c1-4a32-b278-a24cd495437f }
                Name                = $MsiInstallerr.Name                #: Local Administrator Password Solution (2)
                Path                = $MsiInstallerr.Path                #: \\area1.local\SYSVOL\area1.local\Policies\ { 5F5042A0-008F-45E3-8657-79C87BD002E3 }\LAPS.x64.msi
                MajorVersion        = $MsiInstallerr.MajorVersion        #: 6
                MinorVersion        = $MsiInstallerr.MinorVersion        #: 2
                LanguageId          = $MsiInstallerr.LanguageId          #: 1033
                Architecture        = $MsiInstallerr.Architecture        #: 9
                IgnoreLanguage      = if ($MsiInstallerr.IgnoreLanguage -eq 'true') { $true } else { $false }      #: false
                Allowx86Onia64      = if ($MsiInstallerr.Allowx86Onia64 -eq 'true') { $true } else { $false }       #: true
                SupportURL          = $MsiInstallerr.SupportURL          #:
                AutoInstall         = if ($MsiInstallerr.AutoInstall -eq 'true') { $true } else { $false }          #: true
                DisplayInARP        = if ($MsiInstallerr.DisplayInARP -eq 'true') { $true } else { $false }        #: true
                IncludeCOM          = if ($MsiInstallerr.IncludeCOM -eq 'true') { $true } else { $false }          #: true
                SecurityDescriptor  = $MsiInstallerr.SecurityDescriptor  #: SecurityDescriptor
                DeploymentType      = $MsiInstallerr.DeploymentType      #: Assign
                ProductId           = $MsiInstallerr.ProductId           #: { ea8cb806-c109 - 4700 - 96b4-f1f268e5036c }
                ScriptPath          = $MsiInstallerr.ScriptPath          #: \\area1.local\SysVol\area1.local\Policies\ { 5F5042A0-008F-45E3-8657-79C87BD002E3 }\Machine\Applications\ { EAC9B821-FB4D - 457A-806F-E5B528D1E41A }.aas
                DeploymentCount     = $MsiInstallerr.DeploymentCount     #: 0
                InstallationUILevel = $MsiInstallerr.InstallationUILevel #: Maximum
                Upgrades            = if ($MsiInstallerr.Upgrades.Mandatory -eq 'true') { $true } else { $false }            #: Upgrades
                UninstallUnmanaged  = if ($MsiInstallerr.UninstallUnmanaged -eq 'true') { $true } else { $false }   #: false
                LossOfScopeAction   = $MsiInstallerr.LossOfScopeAction   #: Unmanage
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLSystemServices {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($GPOEntry in $GPO.DataSet) {
            [PSCustomObject] @{
                ServiceName                = $GPOEntry.Name
                ServiceStartUpMode         = $GPOEntry.StartUpMode
                SecurityAuditingPresent    = try { [bool]::Parse($GPOEntry.SecurityDescriptor.AuditingPresent.'#text') } catch { $null };
                SecurityPermissionsPresent = try { [bool]::Parse($GPOEntry.SecurityDescriptor.PermissionsPresent.'#text') } catch { $null };
                SecurityDescriptor         = $GPOEntry.SecurityDescriptor
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($GPOEntry in $GPO.DataSet) {
            $CreateGPO = [ordered]@{
                DisplayName                = $GPO.DisplayName
                DomainName                 = $GPO.DomainName
                GUID                       = $GPO.GUID
                GpoType                    = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                ServiceName                = $GPOEntry.Name
                ServiceStartUpMode         = $GPOEntry.StartUpMode
                SecurityAuditingPresent    = try { [bool]::Parse($GPOEntry.SecurityDescriptor.AuditingPresent.'#text') } catch { $null };
                SecurityPermissionsPresent = try { [bool]::Parse($GPOEntry.SecurityDescriptor.PermissionsPresent.'#text') } catch { $null };
                SecurityDescriptor         = $GPOEntry.SecurityDescriptor
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLSystemServicesNT {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Service in $GPO.DataSet.NTService) {
            [PSCustomObject] @{
                GPOSettingOrder                      = $Service.GPOSettingOrder
                #ServiceName = $Service.Name
                ServiceName                          = $Service.Properties.serviceName          #: AppIDSvc: AppIDSvc
                ServiceStartupType                   = $Service.Properties.startupType          #: NOCHANGE: NOCHANGE
                ServiceAction                        = $Service.Properties.serviceAction        #: START: START
                Timeout                              = $Service.Properties.timeout              #: 50: 50
                FirstFailure                         = $Service.Properties.firstFailure         #: REBOOT: REBOOT
                SecondFailure                        = $Service.Properties.secondFailure        #: REBOOT: REBOOT
                ThirdFailure                         = $Service.Properties.thirdFailure         #: REBOOT: REBOOT
                ResetFailCountDelay                  = $Service.Properties.resetFailCountDelay  #: 0: 0
                RestartComputerDelay                 = $Service.Properties.restartComputerDelay #: 60000: 60000
                Filter                               = $Service.Filter
                AccountName                          = $Service.Properties.accountName
                AllowServiceToInteractWithTheDesktop = if ($Service.Properties.interact -eq 1) { 'Yes' } elseif ($Service.Properties.interact -eq 0) { 'No' } else { $null }
                RunThisProgram                       = $Service.Properties.program
                CommandLineParameters                = $Service.Properties.args
                AppendFailCountToEndOfCommandLine    = if ($Service.Properties.append -eq 1) { 'Yes' } elseif ($Service.Properties.append -eq 0) { 'No' } else { $null }
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Service in $GPO.DataSet.NTService) {
            $CreateGPO = [ordered]@{
                DisplayName                          = $GPO.DisplayName
                DomainName                           = $GPO.DomainName
                GUID                                 = $GPO.GUID
                GpoType                              = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                GPOSettingOrder                      = $Service.GPOSettingOrder
                #ServiceName = $Service.Name
                ServiceName                          = $Service.Properties.serviceName          #: AppIDSvc: AppIDSvc
                ServiceStartupType                   = $Service.Properties.startupType          #: NOCHANGE: NOCHANGE
                ServiceAction                        = $Service.Properties.serviceAction        #: START: START
                Timeout                              = $Service.Properties.timeout              #: 50: 50
                FirstFailure                         = $Service.Properties.firstFailure         #: REBOOT: REBOOT
                SecondFailure                        = $Service.Properties.secondFailure        #: REBOOT: REBOOT
                ThirdFailure                         = $Service.Properties.thirdFailure         #: REBOOT: REBOOT
                ResetFailCountDelay                  = $Service.Properties.resetFailCountDelay  #: 0: 0
                RestartComputerDelay                 = $Service.Properties.restartComputerDelay #: 60000: 60000
                Filter                               = $Service.Filter
                AccountName                          = $Service.Properties.accountName
                AllowServiceToInteractWithTheDesktop = if ($Service.Properties.interact -eq 1) { 'Yes' } elseif ($Service.Properties.interact -eq 0) { 'No' } else { $null }
                RunThisProgram                       = $Service.Properties.program
                CommandLineParameters                = $Service.Properties.args
                AppendFailCountToEndOfCommandLine    = if ($Service.Properties.append -eq 1) { 'Yes' } elseif ($Service.Properties.append -eq 0) { 'No' } else { $null }
                <#$
 
                startupType : NOCHANGE
                serviceName : AudioEndpointBuilder
                timeout : 30
                accountName : LocalSystem
                interact : 1
                thirdFailure : RUNCMD
                resetFailCountDelay : 0
                program : fgdfg
                args : dg
                append : 1
 
                Service name AudioEndpointBuilder
                Action No change
                Startup type: No change
                Wait timeout if service is locked: 30 seconds
                Service AccountLog on service as: LocalSystem
                Allow service to interact with the desktop: Yes
 
                First failure: No change
                Second failure: No change
                Subsequent failures: Run a program
                Reset fail count after: 0 days
                Run this program: fgdfg
                Command line parameters: dg
                Append fail count to end of command line: Yes
 
                #>

            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
    }
}
function ConvertTo-XMLTaskScheduler {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Entry in $GPO.DataSet.Drive) {
            [PSCustomObject] @{
                Changed         = [DateTime] $Entry.changed
                #uid = $Entry.uid
                GPOSettingOrder = $Entry.GPOSettingOrder
                Filter          = $Entry.Filter

                Name            = $Entry.Name
                Action          = $Script:Actions["$($Entry.Properties.action)"]
                ThisDrive       = $Entry.Properties.thisDrive
                AllDrives       = $Entry.Properties.allDrives
                UserName        = $Entry.Properties.userName
                Path            = $Entry.Properties.path
                Label           = $Entry.Properties.label
                Persistent      = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent };
                UseLetter       = if ($Entry.Properties.useLetter -eq '1') { $true } elseif ($Entry.Properties.useLetter -eq '0') { $false } else { $Entry.Properties.useLetter };
                Letter          = $Entry.Properties.letter
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Type in @('TaskV2', 'Task', 'ImmediateTaskV2', 'ImmediateTask')) {
            foreach ($Entry in $GPO.DataSet.$Type) {
                $ListActions = foreach ($LoopAction in $Entry.Properties.Task.Actions) {

                    foreach ($InternalAction in $LoopAction.Exec) {
                        $Action = [ordered] @{
                            ActionType       = 'Execute'
                            Command          = $InternalAction.Command         # : cmd
                            Arguments        = $InternalAction.Arguments       # : / c wevtutil qe security / rd:true / f:text / c:1 / q:"*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and (EventID=4727 or EventID=4759 or EventID=4754 or EventID=4731)]]" >group-creation.txt
                            WorkingDirectory = $InternalAction.WorkingDirectory# : % windir % \temp
                            Server           = $Null
                            Subject          = $Null
                            To               = $Null
                            From             = $Null
                            Body             = $Null
                            Attachments      = $Null
                        }
                        $Action
                    }
                    foreach ($InternalAction in $LoopAction.SendEmail) {
                        $Action = [ordered] @{
                            ActionType       = 'SendEmail'
                            Command          = $null
                            Arguments        = $null      # : / c wevtutil qe security / rd:true / f:text / c:1 / q:"*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and (EventID=4727 or EventID=4759 or EventID=4754 or EventID=4731)]]" >group-creation.txt
                            WorkingDirectory = $null # : % windir % \temp
                            Server           = $InternalAction.Server     # : smtp-de
                            Subject          = $InternalAction.Subject    # : AD Group creation
                            To               = $InternalAction.To         # : gm6b@eurofins.de,RalphThomasAussem@eurofins.de,karlthomaseggert@eurofins.de
                            From             = $InternalAction.From       # : %computername%@eurofins.local
                            Body             = $InternalAction.Body       # : A new security group has been created. Check attachment for further details.
                            Attachments      = $InternalAction.Attachments.File -join '; ' # : Attachments
                        }
                        $Action
                    }
                }


                <#
                [DBG]: PS C:\Support\GitHub\GpoZaurr> $Entry.Properties.Task.Triggers.EventTrigger
 
                Enabled Subscription
                ------- ------------
                true <QueryList><Query Id="0" Path="Security"><Select Path="Security">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4727]]</Select></Query></QueryList>
                true <QueryList><Query Id="0" Path="Security"><Select Path="Security">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4731]]</Select></Query></QueryList>
                true <QueryList><Query Id="0" Path="Security"><Select Path="Security">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4754]]</Select></Query></QueryList>
                false <QueryList><Query Id="0" Path="Security"><Select Path="Security">*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4759]]</Select></Query></QueryList>
                #>

                if ($ListActions.Count -eq 0) {
                    $ListActions = @(

                        if ($Entry.Properties.appName) {
                            # This supports Scheduled Task (legacy)
                            $Action = [ordered] @{
                                ActionType       = $Script:Actions["$($Entry.Properties.action)"]
                                Command          = $Entry.Properties.appName
                                Arguments        = $Entry.Properties.args  #
                                WorkingDirectory = $Entry.Properties.startIn  # : % windir % \temp
                                Server           = $null     # : smtp-de
                                Subject          = $null    # : AD Group creation
                                To               = $null
                                From             = $null
                                Body             = $null
                                Attachments      = $null
                            }
                            $Action
                        } else {

                            $Action = [ordered] @{
                                ActionType       = $Script:Actions["$($Entry.Properties.action)"]
                                Command          = $null
                                Arguments        = $null      #
                                WorkingDirectory = $null # : % windir % \temp
                                Server           = $null     # : smtp-de
                                Subject          = $null    # : AD Group creation
                                To               = $null
                                From             = $null
                                Body             = $null
                                Attachments      = $null
                            }
                            $Action
                        }
                    )
                }
                foreach ($Action in $ListActions) {
                    $CreateGPO = [ordered]@{
                        DisplayName     = $GPO.DisplayName
                        DomainName      = $GPO.DomainName
                        GUID            = $GPO.GUID
                        GpoType         = $GPO.GpoType
                        #GpoCategory = $GPOEntry.GpoCategory
                        #GpoSettings = $GPOEntry.GpoSettings
                        Type            = $Type
                        Changed         = [DateTime] $Entry.changed
                        GPOSettingOrder = $Entry.GPOSettingOrder
                        userContext     = ''

                        Name            = $Entry.Name
                        Status          = $Entry.status
                        Action          = $Script:Actions["$($Entry.Properties.action)"]


                        runAs           = $Entry.Properties.runAs     #: NT AUTHORITY\System
                        #logonType = $Entry.Properties.logonType #: InteractiveToken
                        #Task = $Entry.Properties.Task #: Task

                        Comment         = $Entry.Properties.comment
                    }
                    if ($Entry.Properties.startOnlyIfIdle) {
                        # Old legacy task
                        $Middle = [ordered] @{
                            AllowStartOnDemand          = $null        #: true
                            DisallowStartIfOnBatteries  = $Entry.Properties.noStartIfOnBatteries #: false
                            StopIfGoingOnBatteries      = $Entry.Properties.stopIfGoingOnBatteries     #: false
                            AllowHardTerminate          = $null       #: true
                            Enabled                     = $Entry.Properties.enabled               #: true
                            Hidden                      = $null                   #: false
                            MultipleInstancesPolicy     = $null    #: IgnoreNew
                            Priority                    = $null                   #: 7
                            ExecutionTimeLimit          = $null         #: PT1H
                            #IdleSettings = $Entry.Properties.Task.Settings.IdleSettings #: IdleSettings

                            IdleDuration                = $Entry.Properties.deadlineMinutes      # : PT5M
                            IdleWaitTimeout             = $null   # : PT1H
                            IdleStopOnIdleEnd           = $Entry.Properties.stopOnIdleEnd # : false
                            IdleRestartOnIdle           = $Entry.Properties.startOnlyIfIdle # : false

                            RegistrationInfoAuthor      = $null
                            RegistrationInfoDescription = $null
                            deleteWhenDone              = $Entry.Properties.deleteWhenDone

                            <#
                            action : U
                            name : Task Name
                            appName : Run command
                            args : args for command
                            startIn : start me in
                            comment : Oops i did it again
                            enabled : 1
                            deleteWhenDone : 1
                            maxRunTime : 259200000
                            startOnlyIfIdle : 1
                            idleMinutes : 10
                            deadlineMinutes : 60
                            stopOnIdleEnd : 1
                            noStartIfOnBatteries : 1
                            stopIfGoingOnBatteries : 1
                            systemRequired : 0
                            Triggers : Triggers
                            #>

                        }
                    } else {
                        $Middle = [ordered] @{
                            AllowStartOnDemand          = $Entry.Properties.Task.Settings.AllowStartOnDemand         #: true
                            DisallowStartIfOnBatteries  = $Entry.Properties.Task.Settings.DisallowStartIfOnBatteries #: false
                            StopIfGoingOnBatteries      = $Entry.Properties.Task.Settings.StopIfGoingOnBatteries     #: false
                            AllowHardTerminate          = $Entry.Properties.Task.Settings.AllowHardTerminate         #: true
                            Enabled                     = $Entry.Properties.Task.Settings.Enabled                    #: true
                            Hidden                      = $Entry.Properties.Task.Settings.Hidden                     #: false
                            MultipleInstancesPolicy     = $Entry.Properties.Task.Settings.MultipleInstancesPolicy    #: IgnoreNew
                            Priority                    = $Entry.Properties.Task.Settings.Priority                   #: 7
                            ExecutionTimeLimit          = $Entry.Properties.Task.Settings.ExecutionTimeLimit         #: PT1H
                            #IdleSettings = $Entry.Properties.Task.Settings.IdleSettings #: IdleSettings

                            IdleDuration                = $Entry.Properties.Task.Settings.IdleSettings.Duration      # : PT5M
                            IdleWaitTimeout             = $Entry.Properties.Task.Settings.IdleSettings.WaitTimeout   # : PT1H
                            IdleStopOnIdleEnd           = $Entry.Properties.Task.Settings.IdleSettings.StopOnIdleEnd # : false
                            IdleRestartOnIdle           = $Entry.Properties.Task.Settings.IdleSettings.RestartOnIdle # : false

                            RegistrationInfoAuthor      = $Entry.Properties.Task.RegistrationInfo.Author
                            RegistrationInfoDescription = $Entry.Properties.Task.RegistrationInfo.Description

                            deleteWhenDone              = $Entry.Properties.deleteWhenDone
                        }
                    }
                    $End = [ordered] @{
                        id        = $Entry.Properties.Principals.Principal.id        # : Author
                        UserId    = $Entry.Properties.Principals.Principal.UserId    # : NT AUTHORITY\System
                        LogonType = $Entry.Properties.Principals.Principal.LogonType # : InteractiveToken
                        RunLevel  = $Entry.Properties.Principals.Principal.RunLevel  # : HighestAvailable

                        #Persistent = if ($Entry.Properties.persistent -eq '1') { $true } elseif ($Entry.Properties.persistent -eq '0') { $false } else { $Entry.Properties.persistent };
                        #UseLetter = if ($Entry.Properties.useLetter -eq '1') { $true } elseif ($Entry.Properties.useLetter -eq '0') { $false } else { $Entry.Properties.useLetter };
                        #Letter = $Entry.Properties.letter
                    }
                    $CreateGPO = $CreateGPO + $Middle + $End + $Action
                    $Last = [ordered] @{
                        #Uid = $Group.uid #: {8F435B0A-CD15-464E-85F3-B6A55B9E816A}: {8F435B0A-CD15-464E-85F3-B6A55B9E816A}
                        RunInLoggedOnUserSecurityContext      = if ($Entry.userContext -eq '1') { 'Enabled' } elseif ($Entry.userContext -eq '0') { 'Disabled' } else { $Entry.userContext };
                        RemoveThisItemWhenItIsNoLongerApplied = if ($Entry.removePolicy -eq '1') { 'Enabled' } elseif ($Entry.removePolicy -eq '0') { 'Disabled' } else { $Entry.removePolicy };
                        Filters                               = $Group.Filters         #::
                    }
                    $CreateGPO = $CreateGPO + $Last
                    $CreateGPO['Linked'] = $GPO.Linked
                    $CreateGPO['LinksCount'] = $GPO.LinksCount
                    $CreateGPO['Links'] = $GPO.Links
                    [PSCustomObject] $CreateGPO
                }
            }
        }
    }
}
function ConvertTo-XMLUserRightsAssignment {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )

    $UserRightsTranslation = @{
        SeNetworkLogonRight             = 'Access this computer from the network'
        SeMachineAccountPrivilege       = 'Add workstations to domain'
        SeIncreaseQuotaPrivilege        = 'Adjust memory quotas for a process'
        SeInteractiveLogonRight         = 'Allow log on locally'
        SeBackupPrivilege               = 'Back up files and directories'
        SeChangeNotifyPrivilege         = 'Bypass traverse checking Everyone'
        SeSystemTimePrivilege           = 'Change the system time'
        SeCreatePagefilePrivilege       = 'Create a pagefile'
        SeDebugPrivilege                = 'Debug programs'
        SeEnableDelegationPrivilege     = 'Enable computer and user accounts to be trusted for delegation'
        SeRemoteShutdownPrivilege       = 'Force shutdown from a remote system'
        SeAuditPrivilege                = 'Generate security audits'
        SeIncreaseBasePriorityPrivilege = 'Increase scheduling priority'
        SeLoadDriverPrivilege           = 'Load and unload device drivers'
        SeBatchLogonRight               = 'Log on as a batch job'
        SeSecurityPrivilege             = 'Manage auditing and security log'
        SeSystemEnvironmentPrivilege    = 'Modify firmware environment values'
        SeProfileSingleProcessPrivilege = 'Profile single process'
        SeSystemProfilePrivilege        = 'Profile system performance'
        SeUndockPrivilege               = 'Remove computer from docking station'
        SeAssignPrimaryTokenPrivilege   = 'Replace a process level token'
        SeRestorePrivilege              = 'Restore files and directories'
        SeShutdownPrivilege             = 'Shut down the system'
        SeTakeOwnershipPrivilege        = 'Take ownership of files or other objects'
    }

    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Entry in $GPO.DataSet) {
            foreach ($Member in $Entry.Member) {
                [PSCustomObject]@{
                    'UserRightsAssignment'            = $Entry.Name
                    'UserRightsAssignmentDescription' = $UserRightsTranslation[$Entry.Name]
                    'Name'                            = $Member.Name.'#text'
                    'Sid'                             = $Member.SID.'#text'
                }
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Entry in $GPO.DataSet) {
            foreach ($Member in $Entry.Member) {
                $CreateGPO = [ordered]@{
                    DisplayName = $GPO.DisplayName
                    DomainName  = $GPO.DomainName
                    GUID        = $GPO.GUID
                    GpoType     = $GPO.GpoType
                    #GpoCategory = $GPOEntry.GpoCategory
                    #GpoSettings = $GPOEntry.GpoSettings
                }
                $CreateGPO['UserRightsAssignment'] = $Entry.Name
                $CreateGPO['UserRightsAssignmentDescription'] = $UserRightsTranslation[$Entry.Name]
                $CreateGPO['Name'] = $Member.Name.'#text'
                $CreateGPO['Sid'] = $Member.SID.'#text'
                #$CreateGPO['CreatedTime'] = $GPO.CreatedTime # : 06.06.2020 18:03:36
                #$CreateGPO['ModifiedTime'] = $GPO.ModifiedTime # : 17.06.2020 16:08:10
                #$CreateGPO['ReadTime'] = $GPO.ReadTime # : 13.08.2020 10:15:37
                $CreateGPO['Linked'] = $GPO.Linked
                $CreateGPO['LinksCount'] = $GPO.LinksCount
                $CreateGPO['Links'] = $GPO.Links
                [PSCustomObject] $CreateGPO
            }
        }
    }
}
function ConvertTo-XMLWindowsFirewall {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Policy in $GPO.DataSet) {
            [PSCustomObject] @{
                Name    = $Policy.LocalName
                Version = $Policy.PolicyVersion.Value
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Policy in $GPO.DataSet) {
            [PSCustomObject]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
                Name        = $Policy.LocalName
                Version     = $Policy.PolicyVersion.Value
                Linked      = $GPO.Linked
                LinksCount  = $GPO.LinksCount
                Links       = $GPO.Links
            }
        }
    }
}
function ConvertTo-XMLWindowsFirewallConnectionSecurityAuthentiation {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Connection in $GPO.DataSet) {
            [PSCustomObject] @{
                Name               = $Connection.LocalName
                Version            = $Connection.Version
                ConnectionGUID     = $Connection.GUID
                Method             = $Connection.AuthenticationSuites.Method #: MachineCert
                CAName             = $Connection.AuthenticationSuites.CAName #: DC = xyz, DC = evotec, DC = ad, CN = ad-ADCS-CA
                CertAccountMapping = if ($Connection.AuthenticationSuites.CertAccountMapping -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.CertAccountMapping -eq 'false') { $false } else { $Connection.AuthenticationSuites.CertAccountMapping }
                ExcludeCAName      = if ($Connection.AuthenticationSuites.ExcludeCAName -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.ExcludeCAName -eq 'false') { $false } else { $Connection.AuthenticationSuites.ExcludeCAName }
                HealthCert         = if ($Connection.AuthenticationSuites.HealthCert -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.HealthCert -eq 'false') { $false } else { $Connection.AuthenticationSuites.HealthCert }
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Connection in $GPO.DataSet) {
            [PSCustomObject]@{
                DisplayName        = $GPO.DisplayName
                DomainName         = $GPO.DomainName
                GUID               = $GPO.GUID
                GpoType            = $GPO.GpoType
                Name               = $Connection.LocalName
                Version            = $Connection.Version
                ConnectionGUID     = $Connection.GUID
                Method             = $Connection.AuthenticationSuites.Method #: MachineCert
                CAName             = $Connection.AuthenticationSuites.CAName #: DC = xyz, DC = evotec, DC = ad, CN = ad-ADCS-CA
                CertAccountMapping = if ($Connection.AuthenticationSuites.CertAccountMapping -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.CertAccountMapping -eq 'false') { $false } else { $Connection.AuthenticationSuites.CertAccountMapping }
                ExcludeCAName      = if ($Connection.AuthenticationSuites.ExcludeCAName -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.ExcludeCAName -eq 'false') { $false } else { $Connection.AuthenticationSuites.ExcludeCAName }
                HealthCert         = if ($Connection.AuthenticationSuites.HealthCert -eq 'true') { $true } elseif ($Connection.AuthenticationSuites.HealthCert -eq 'false') { $false } else { $Connection.AuthenticationSuites.HealthCert }
                Linked             = $GPO.Linked
                LinksCount         = $GPO.LinksCount
                Links              = $GPO.Links
            }
        }
    }
}
function ConvertTo-XMLWindowsFirewallProfile {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Profile in $GPO.DataSet) {
            [PSCustomObject] @{
                Profile                                     = $Profile.LocalName
                EnableFirewall                              = if ($Profile.EnableFirewall.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                AllowLocalIPsecPolicyMerge                  = if ($Profile.AllowLocalIPsecPolicyMerge.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                AllowLocalPolicyMerge                       = if ($Profile.AllowLocalPolicyMerge.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DefaultInboundAction                        = if ($Profile.DefaultInboundAction.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DefaultOutboundAction                       = if ($Profile.DefaultOutboundAction.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DisableNotifications                        = if ($Profile.DisableNotifications.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DisableUnicastResponsesToMulticastBroadcast = if ($Profile.DisableUnicastResponsesToMulticastBroadcast.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DoNotAllowExceptions                        = if ($Profile.DoNotAllowExceptions.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                LogFilePath                                 = if ($Profile.LogFilePath.Value) { $Profile.LogFilePath.Value } else { 'Not configured' }
                LogDroppedPackets                           = if ($Profile.LogDroppedPackets.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                LogFileSize                                 = if ($Profile.LogFileSize.Value) { $Profile.LogFileSize.Value } else { 'Not configured' }
                LogSuccessfulConnections                    = if ($Profile.LogSuccessfulConnections.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Profile in $GPO.DataSet) {
            [PSCustomObject]@{
                DisplayName                                 = $GPO.DisplayName
                DomainName                                  = $GPO.DomainName
                GUID                                        = $GPO.GUID
                GpoType                                     = $GPO.GpoType
                Profile                                     = $Profile.LocalName
                EnableFirewall                              = if ($Profile.EnableFirewall.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                AllowLocalIPsecPolicyMerge                  = if ($Profile.AllowLocalIPsecPolicyMerge.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                AllowLocalPolicyMerge                       = if ($Profile.AllowLocalPolicyMerge.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DefaultInboundAction                        = if ($Profile.DefaultInboundAction.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DefaultOutboundAction                       = if ($Profile.DefaultOutboundAction.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DisableNotifications                        = if ($Profile.DisableNotifications.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DisableUnicastResponsesToMulticastBroadcast = if ($Profile.DisableUnicastResponsesToMulticastBroadcast.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                DoNotAllowExceptions                        = if ($Profile.DoNotAllowExceptions.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                LogFilePath                                 = if ($Profile.LogFilePath.Value) { $Profile.LogFilePath.Value } else { 'Not configured' }
                LogDroppedPackets                           = if ($Profile.LogDroppedPackets.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                LogFileSize                                 = if ($Profile.LogFileSize.Value) { $Profile.LogFileSize.Value } else { 'Not configured' }
                LogSuccessfulConnections                    = if ($Profile.LogSuccessfulConnections.Value -eq 'true') { 'Yes' } elseif ($Profile.EnableFirewall.Value -eq 'false') { 'No' } else { 'Not configured' }
                Linked                                      = $GPO.Linked
                LinksCount                                  = $GPO.LinksCount
                Links                                       = $GPO.Links
            }
        }
    }
}
function ConvertTo-XMLWindowsFirewallRules {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Rule in $GPO.DataSet) {
            [PSCustomObject] @{
                Version           = $Rule.Version
                Type              = if ($Rule.Dir -eq 'In') { 'Inbound' } elseif ($Rule.Dir -eq 'Out') { 'Outbound' } else { $Rule.Dir }
                Name              = $Rule.Name
                Action            = $Rule.Action
                Enabled           = if ($Rule.Active -eq 'true') { $true } else { $false }
                Profile           = $Rule.Profile
                Svc               = $Rule.Svc
                LocalAddressIPv4  = $Rule.LA4
                LocalAddressIPv6  = $Rule.LA6
                RemoteAddressIPV4 = $Rule.RA4
                RemoteAddressIPV6 = $Rule.RA6
                LocalPort         = $Rule.LPort
                RemotePort        = $Rule.RPort
                Description       = $Rule.Desc
                EmbedCtxt         = $Rule.EmbedCtxt
                Edge              = $Rule.Edge
                IFType            = $Rule.IFType
                Security          = $Rule.Security
                App               = $Rule.App
                Protocol          = $Rule.Protocol
                RMAuth            = $Rule.RMAuth
                RUAuth            = $Rule.RUAuth
                ICMP4             = $Rule.ICMP4
                LocalName         = $Rule.LocalName

            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Rule in $GPO.DataSet) {
            [PSCustomObject]@{
                DisplayName       = $GPO.DisplayName
                DomainName        = $GPO.DomainName
                GUID              = $GPO.GUID
                GpoType           = $GPO.GpoType
                Version           = $Rule.Version
                Type              = if ($Rule.Dir -eq 'In') { 'Inbound' } elseif ($Rule.Dir -eq 'Out') { 'Outbound' } else { $Rule.Dir }
                Name              = $Rule.Name
                Action            = $Rule.Action
                Enabled           = if ($Rule.Active -eq 'true') { $true } else { $false }
                Profile           = $Rule.Profile
                Svc               = $Rule.Svc
                LocalAddressIPv4  = $Rule.LA4
                LocalAddressIPv6  = $Rule.LA6
                RemoteAddressIPV4 = $Rule.RA4
                RemoteAddressIPV6 = $Rule.RA6
                LocalPort         = $Rule.LPort
                RemotePort        = $Rule.RPort
                Description       = $Rule.Desc
                EmbedCtxt         = $Rule.EmbedCtxt
                Edge              = $Rule.Edge
                IFType            = $Rule.IFType
                Security          = $Rule.Security
                App               = $Rule.App
                Protocol          = $Rule.Protocol
                RMAuth            = $Rule.RMAuth
                RUAuth            = $Rule.RUAuth
                ICMP4             = $Rule.ICMP4
                LocalName         = $Rule.LocalName
                Linked            = $GPO.Linked
                LinksCount        = $GPO.LinksCount
                Links             = $GPO.Links
            }
        }
    }
}
<#
Version : 2.30
Action : Allow
Name : @%SystemRoot%\system32\firewallapi.dll,-37303
Dir : In
App : %SystemRoot%\system32\svchost.exe
Svc : dnscache
Profile : Public
RA4 : LocalSubnet
RA6 : LocalSubnet
LPort : 5353
Protocol : 17
Desc : @%SystemRoot%\system32\firewallapi.dll,-37304
Active : true
EmbedCtxt : @%SystemRoot%\system32\firewallapi.dll,-37302
 
 
Version : 2.30
Action : Allow
Name : TEST APP
Dir : In
App : C:\Test\exe.exe
Active : true
 
Version : 2.30
Action : Block
Name : Blo
Dir : Out
App : dfdff
Active : true
 
Version : 2.30
Action : Block
Name : @FirewallAPI.dll,-36012
Dir : Out
App : %SystemRoot%\system32\svchost.exe
Svc : Qwave
Profile : {Private, Public}
RPort : 2177
Protocol : 17
Desc : @FirewallAPI.dll,-36013
Active : true
EmbedCtxt : @FirewallAPI.dll,-36001
#>

function ConvertTo-XMLWindowsFirewallSecurityRules {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $SingleObject
    )
    if ($SingleObject) {
        $CreateGPO = [ordered]@{
            DisplayName = $GPO.DisplayName
            DomainName  = $GPO.DomainName
            GUID        = $GPO.GUID
            GpoType     = $GPO.GpoType
            #GpoCategory = $GPOEntry.GpoCategory
            #GpoSettings = $GPOEntry.GpoSettings
            Count       = 0
            Settings    = $null
        }
        [Array] $CreateGPO['Settings'] = foreach ($Rule in $GPO.DataSet) {
            [PSCustomObject] @{
                Version     = $Rule.Version
                Name        = $Rule.Name
                Action      = $Rule.Action
                Enabled     = if ($Rule.Active -eq 'true') { $true } else { $false }
                Auth1Set    = $Rule.Auth1Set
                Auth2Set    = $Rule.Auth2Set
                Crypto2Set  = $Rule.Crypto2Set
                Description = $Rule.Desc
            }
        }
        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        foreach ($Rule in $GPO.DataSet) {
            [PSCustomObject]@{
                DisplayName = $GPO.DisplayName
                DomainName  = $GPO.DomainName
                GUID        = $GPO.GUID
                GpoType     = $GPO.GpoType
                Version     = $Rule.Version
                Name        = $Rule.Name
                Action      = $Rule.Action
                Enabled     = if ($Rule.Active -eq 'true') { $true } else { $false }
                Auth1Set    = $Rule.Auth1Set
                Auth2Set    = $Rule.Auth2Set
                Crypto2Set  = $Rule.Crypto2Set
                Description = $Rule.Desc
                Linked      = $GPO.Linked
                LinksCount  = $GPO.LinksCount
                Links       = $GPO.Links
            }
        }
    }
}

<#
Version : 2.30
Action : Boundary
Name : TeST Aut
Auth1Set : {E5A5D32A-4BCE-4e4d-B07F-4AB1BA7E5FE3}
Auth2Set : {E5A5D32A-4BCE-4e4d-B07F-4AB1BA7E5FE4}
Crypto2Set : {E5A5D32A-4BCE-4e4d-B07F-4AB1BA7E5FE2}
Desc :
Active : true
 
Version : 2.30
Action : Boundary
Name : CA TEST
Auth1Set : {0E3A2DDC-F31B-42B5-BEAC-890752F9C0BB}
Auth2Set : EmptySet
Crypto2Set : {E5A5D32A-4BCE-4e4d-B07F-4AB1BA7E5FE2}
Desc :
Active : true
#>

function Find-GPOPassword {
    [cmdletBinding()]
    param(
        [string] $Path
    )
    #Convert XML in a String file
    [string]$XMLString = Get-Content -LiteralPath $Path
    #Check if Cpassword Exist in the file
    if ($XMLString.Contains("cpassword")) {
        #Take the Cpassword Value from XML String file
        [string]$Cpassword = [regex]::matches($XMLString, '(cpassword=).+?(?=\")')
        $Cpassword = $Cpassword.split('(\")')[1]
        #Check if Cpassword has a value
        if ($Cpassword.Length -gt 20 -and $Cpassword -notlike '*cpassword*') {
            $Mod = ($Cpassword.length % 4)
            switch ($Mod) {
                '1' { $Cpassword = $Cpassword.Substring(0, $Cpassword.Length - 1) }
                '2' { $Cpassword += ('=' * (4 - $Mod)) }
                '3' { $Cpassword += ('=' * (4 - $Mod)) }
            }
            $Base64Decoded = [Convert]::FromBase64String($Cpassword)
            $AesObject = [System.Security.Cryptography.AesCryptoServiceProvider]::new()
            #Use th AES Key
            [Byte[]] $AesKey = @(0x4e, 0x99, 0x06, 0xe8, 0xfc, 0xb6, 0x6c, 0xc9, 0xfa, 0xf4, 0x93, 0x10, 0x62, 0x0f, 0xfe, 0xe8, 0xf4, 0x96, 0xe8, 0x06, 0xcc, 0x05, 0x79, 0x90, 0x20, 0x9b, 0x09, 0xa4, 0x33, 0xb6, 0x6c, 0x1b)
            $AesIV = New-Object Byte[]($AesObject.IV.Length)
            $AesObject.IV = $AesIV
            $AesObject.Key = $AesKey
            $DecryptorObject = $AesObject.CreateDecryptor()
            [Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
            #Convert Hash variable in a String valute
            $Password = [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
        } else {
            $Password = ''
        }
    }
    $Password
}
function Format-CamelCaseToDisplayName {
    [cmdletBinding()]
    param(
        [string[]] $Text,
        [string] $AddChar
    )
    foreach ($string in $Text) {
        $newString = ''
        $stringChars = $string.GetEnumerator()
        $charIndex = 0
        foreach ($char in $stringChars) {
            # If upper and not first character, add a space
            if ([char]::IsUpper($char) -eq 'True' -and $charIndex -gt 0) {
                $newString = $newString + $AddChar + $char.ToString()
            } elseif ($charIndex -eq 0) {
                # If the first character, make it a capital always
                $newString = $newString + $char.ToString().ToUpper()
            } else {
                $newString = $newString + $char.ToString()
            }
            $charIndex++
        }
        $newString
    }
}

#Format-CamelCaseToDisplayName -Text 'Test1', 'TestingMyAss', 'OtherTest', 'otherTEst'
function Get-ADOrganizationalUnitObject {
    <#
    .SYNOPSIS
    Gets number of objects in a given OU/OUs with ability to find only those being affected by GPOs.
 
    .DESCRIPTION
    Gets number of objects in a given OU/OUs with ability to find only those being affected by GPOs.
 
    .PARAMETER OrganizationalUnit
    One or more organizational units to get the number of objects in.
 
    .PARAMETER Extended
    Adds all objects affected for better understanding
 
    .PARAMETER Summary
    Returns only summary for given OU/OUs
 
    .PARAMETER IncludeAffectedOnly
    Ignores any object types that are not Users or Computers
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER AsHashTable
    Returns results in form of hashtable
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    $OUs = @(
        'OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        'OU=US,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        'OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        'OU=Users,OU=User,OU=SE1,OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
    )
 
    Get-ADOrganizationalUnitObject -OrganizationalUnit $OUs -IncludeAffectedOnly | Format-Table
 
    .EXAMPLE
    $OUs = @(
        'OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        'OU=US,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        'OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        'OU=Users,OU=User,OU=SE1,OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
    )
 
    Get-ADOrganizationalUnitObject -OrganizationalUnit $OUs | Format-Table
 
    .EXAMPLE
    $OUs = @(
        #'OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        #'OU=US,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        'OU=Users,OU=User,OU=SE1,OU=SE,OU=ITR01,DC=ad,DC=evotec,DC=xyz'
        'OU=ITR01,DC=ad,DC=evotec,DC=xyz'
    )
 
    Get-ADOrganizationalUnitObject -OrganizationalUnit $OUs -Summary -IncludeAffectedOnly | Format-List
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [parameter(Mandatory)][Array] $OrganizationalUnit,
        [switch] $Extended,
        [switch] $Summary,
        [switch] $IncludeAffectedOnly,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $AsHashTable,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )

    $CachedOu = [ordered] @{}
    $ListOU = @(
        foreach ($OU in $OrganizationalUnit) {
            if ($OU.DistinguishedName) {
                $OU.DistinguishedName
            } else {
                $OU
            }
        }
    )
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $OUCache = Get-GPOBlockedInheritance -AsHashTable -ExtendedForestInformation $ForestInformation

    if ($Summary) {
        $SummaryData = [ordered] @{
            ObjectsClasses                 = [ordered] @{}
            ObjectsTotalCount              = 0
            ObjectsBlockedInheritanceCount = 0
            ObjectsTotal                   = [ordered] @{}
            ObjectsBlockedInheritance      = [ordered] @{}
            DistinguishedName              = [System.Collections.Generic.List[string]]::new()
        }
    }

    foreach ($OU in $ListOU) {
        $Domain = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $OU
        $ObjectsInOu = Get-ADObject -LDAPFilter "(|(ObjectClass=user)(ObjectClass=contact)(ObjectClass=computer)(ObjectClass=group)(objectClass=inetOrgPerson))" -SearchBase $OU -Server $ForestInformation['QueryServers'][$Domain]['hostname'][0]
        #Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain) / $($TOPOU.DistinguishedName) [$CountTop/$($TopOrganizationalUnits.Count)], found $($ObjectsInOu.Count) objects to process."
        if (-not $CachedOu[$OU]) {
            $CachedOu[$OU] = [ordered] @{
                DistinguishedName                   = $OU
                Domain                              = $Domain
                'ObjectsClasses'                    = [ordered] @{} # only direct, indirect, but not with blocked inheritance
                'ObjectsDirectCount'                = 0
                'ObjectsIndirectCount'              = 0
                'ObjectsTotalCount'                 = 0
                'ObjectsTotalIncludingBlockedCount' = 0
                'ObjectsBlockedInheritanceCount'    = 0
            }
            if ($Extended) {
                $CachedOu[$OU]['ObjectsDirect'] = [ordered] @{}
                $CachedOu[$OU]['ObjectsIndirect'] = [ordered] @{}
                $CachedOu[$OU]['ObjectsTotal'] = [ordered] @{}
                $CachedOu[$OU]['ObjectsTotalIncludingBlocked'] = [ordered] @{}
                $CachedOu[$OU]['ObjectsBlockedInheritance'] = [ordered] @{}
            }
        }
        foreach ($Object in $ObjectsInOu) {
            if ($IncludeAffectedOnly) {
                if ($Object.ObjectClass -notin 'User', 'computer') {
                    continue
                }
            }

            $Place = ConvertFrom-DistinguishedName -ToOrganizationalUnit -DistinguishedName $Object.DistinguishedName
            if (-not $Place) {
                # Write-Verbose -Message "Get-OrganizationalUnitObject - Processing object in container/root $($Object.DistinguishedName)"
            }

            if ($Place -and $OUCache[$Place]) {
                $BlockedInheritance = $OUCache[$Place].BlockedInheritance
            } else {
                $BlockedInheritance = $false
            }

            if ($Summary) {
                $SummaryData['DistinguishedName'].Add($OU)
                $SummaryData['ObjectsClasses'][$Object.ObjectClass] = ''
                if (-not $Place -or $Place -eq $OU) {
                    $SummaryData['ObjectsTotal'][$Object.DistinguishedName] = $Object
                } else {
                    if ($BlockedInheritance) {
                        $SummaryData['ObjectsBlockedInheritance'][$Object.DistinguishedName] = $Object
                    } else {
                        $SummaryData['ObjectsTotal'][$Object.DistinguishedName] = $Object
                    }
                }
            } else {
                # This is standard way of finding OU's
                if (-not $Place -or $Place -eq $OU) {
                    $CachedOu[$OU]['ObjectsDirectCount']++
                    $CachedOu[$OU]['ObjectsTotalCount']++
                    # using hashtable to avoid duplicates
                    $CachedOu[$OU]['ObjectsClasses'][$Object.ObjectClass] = ''
                    # adding all objects to the list, excluding blocked inheritance
                    if ($Extended) {
                        $CachedOu[$OU]['ObjectsTotal'][$Object.DistinguishedName] = $Object
                        $CachedOu[$OU]['ObjectsDirect'][$Object.DistinguishedName] = $Object
                    }
                } else {
                    if ($BlockedInheritance) {
                        # We only check for blocked inheritance if the object is not in the same OU
                        $CachedOu[$OU]['ObjectsBlockedInheritanceCount']++
                        if ($Extended) {
                            $CachedOu[$OU]['ObjectsBlockedInheritance'][$Object.DistinguishedName] = $Object
                        }
                    } else {
                        $CachedOu[$OU]['ObjectsIndirectCount']++
                        $CachedOu[$OU]['ObjectsTotalCount']++

                        # using hashtable to avoid duplicates
                        $CachedOu[$OU]['ObjectsClasses'][$Object.ObjectClass] = ''
                        # adding all objects to the list excluding blocked inheritance
                        if ($Extended) {
                            $CachedOu[$OU]['ObjectsTotal'][$Object.DistinguishedName] = $Object
                            $CachedOu[$OU]['ObjectsIndirect'][$Object.DistinguishedName] = $Object
                        }
                    }
                }
                $CachedOu[$OU]['ObjectsTotalIncludingBlockedCount']++
                if ($Extended) {
                    $CachedOu[$OU]['ObjectsTotalIncludingBlocked'][$Object.DistinguishedName] = $Object
                }
            }
        }

    }
    if ($Summary) {
        foreach ($ObjectDistinguishedName in [string[]] $SummaryData['ObjectsBlockedInheritance'].Keys) {
            if ($SummaryData['ObjectsTotal'][$ObjectDistinguishedName]) {
                $SummaryData['ObjectsBlockedInheritance'].Remove($ObjectDistinguishedName)
            }
        }
        $SummaryData['ObjectsTotalCount'] = $SummaryData['ObjectsTotal'].Count
        $SummaryData['ObjectsBlockedInheritanceCount'] = $SummaryData['ObjectsBlockedInheritance'].Count
        if (-not $Extended) {
            $SummaryData.Remove('ObjectsTotal')
            $SummaryData.Remove('ObjectsBlockedInheritance')
        }
        [PSCustomObject] $SummaryData
    } else {
        if ($AsHashTable) {
            $CachedOu
        } else {
            $CachedOu.Values | ForEach-Object { [PSCustomObject] $_ }
        }
    }
}
function Get-ChoosenDates {
    [CmdletBinding()]
    param(
        [ValidateSet('Everything', 'PastHour', 'CurrentHour', 'PastDay', 'CurrentDay', 'PastMonth', 'CurrentMonth', 'PastQuarter', 'CurrentQuarter', 'Last14Days', 'Last21Days', 'Last30Days' , 'Last7Days', 'Last3Days', 'Last1Days')][string] $DateRange
    )
    # Report Per Hour
    if ($DateRange -eq 'PastHour') {
        $DatesPastHour = Find-DatesPastHour
        if ($DatesPastHour) {
            $DatesPastHour
        }
    }
    if ($DateRange -eq 'CurrentHour') {
        $DatesCurrentHour = Find-DatesCurrentHour
        if ($DatesCurrentHour) {
            $DatesCurrentHour
        }
    }
    # Report Per Day
    if ($DateRange -eq 'PastDay') {
        $DatesDayPrevious = Find-DatesDayPrevious
        if ($DatesDayPrevious) {
            $DatesDayPrevious
        }
    }
    if ($DateRange -eq 'CurrentDay') {
        $DatesDayToday = Find-DatesDayToday
        if ($DatesDayToday) {
            $DatesDayToday
        }
    }
    # Report Per Month
    if ($DateRange -eq 'PastMonth') {
        # Find-DatesMonthPast runs only on 1st of the month unless -Force is used
        $DatesMonthPrevious = Find-DatesMonthPast -Force $true
        if ($DatesMonthPrevious) {
            $DatesMonthPrevious
        }
    }
    if ($DateRange -eq 'CurrentMonth') {
        $DatesMonthCurrent = Find-DatesMonthCurrent
        if ($DatesMonthCurrent) {
            $DatesMonthCurrent
        }
    }
    # Report Per Quarter
    if ($DateRange -eq 'PastQuarter') {
        # Find-DatesMonthPast runs only on 1st of the quarter unless -Force is used
        $DatesQuarterLast = Find-DatesQuarterLast -Force $true
        if ($DatesQuarterLast) {
            $DatesQuarterLast
        }
    }
    if ($DateRange -eq 'CurrentQuarter') {
        $DatesQuarterCurrent = Find-DatesQuarterCurrent
        if ($DatesQuarterCurrent) {
            $DatesQuarterCurrent
        }
    }
    if ($DateRange -eq 'Everything') {
        $DatesEverything = @{
            DateFrom = Get-Date -Year 1900 -Month 1 -Day 1
            DateTo   = Get-Date -Year 2300 -Month 1 -Day 1
        }
        $DatesEverything
    }
    if ($DateRange -eq 'Last1days') {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 1
        if ($DatesCurrentDayMinusDaysX) {
            $DatesCurrentDayMinusDaysX
        }
    }
    if ($DateRange -eq 'Last3days') {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 3
        if ($DatesCurrentDayMinusDaysX) {
            $DatesCurrentDayMinusDaysX
        }
    }
    if ($DateRange -eq 'Last7days') {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 7
        if ($DatesCurrentDayMinusDaysX) {
            $DatesCurrentDayMinusDaysX
        }
    }
    if ($DateRange -eq 'Last14days') {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 14
        if ($DatesCurrentDayMinusDaysX) {
            $DatesCurrentDayMinusDaysX
        }
    }
    if ($DateRange -eq 'Last21days') {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 21
        if ($DatesCurrentDayMinusDaysX) {
            $DatesCurrentDayMinusDaysX
        }
    }
    if ($DateRange -eq 'Last30Days') {
        $DatesCurrentDayMinusDaysX = Find-DatesCurrentDayMinuxDaysX -days 30
        if ($DatesCurrentDayMinusDaysX) {
            $DatesCurrentDayMinusDaysX
        }
    }
}
function Get-GitHubVersion {
    [cmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $Cmdlet,
        [Parameter(Mandatory)][string] $RepositoryOwner,
        [Parameter(Mandatory)][string] $RepositoryName
    )
    $App = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue
    if ($App) {
        [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases" -Verbose:$false)
        $LatestVersion = $GitHubReleases[0]
        if (-not $LatestVersion.Errors) {
            if ($App.Version -eq $LatestVersion.Version) {
                "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)"
            } elseif ($App.Version -lt $LatestVersion.Version) {
                "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?"
            } elseif ($App.Version -gt $LatestVersion.Version) {
                "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!"
            }
        } else {
            "Current: $($App.Version)"
        }
    }
}
function Get-GPOBlockedInheritance {
    [cmdletBinding()]
    param(
        [string] $Filter = '*',

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $AsHashTable,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $OUCache = [ordered] @{}

    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation

    foreach ($Domain in $ForestInformation.Domains) {
        $OrganizationalUnits = Get-ADOrganizationalUnit -Filter $Filter -Properties gpOptions, canonicalName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0] #-SearchScope Subtree
        foreach ($OU in $OrganizationalUnits) {
            $OUCache[$OU.DistinguishedName] = [PSCustomObject] @{
                DistinguishedName  = $OU.DistinguishedName
                BlockedInheritance = if ($OU.gpOptions -eq 1) { $true } else { $false } # blocked inheritance
            }
        }
    }
    if ($AsHashTable) {
        $OUCache
    } else {
        $OUCache.Values
    }
}

function Get-GPOCategories {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [System.Xml.XmlElement[]] $GPOOutput,
        [string] $Splitter,
        [switch] $FullObjects,
        [System.Collections.IDictionary] $CachedCategories
    )
    if (-not $CachedCategories) {
        $CachedCategories = [ordered] @{}
    }
    $LinksInformation = Get-LinksFromXML -GPOOutput $GPOOutput -Splitter $Splitter -FullObjects:$FullObjects
    foreach ($GpoType in @('User', 'Computer')) {
        if ($GPOOutput.$GpoType.ExtensionData.Extension) {
            foreach ($ExtensionType in $GPOOutput.$GpoType.ExtensionData.Extension) {
                # It's possible that one of the ExtensionType records has value null. Weird but happend.
                if ($ExtensionType) {
                    $GPOSettingTypeSplit = ($ExtensionType.type -split ':')
                    try {
                        $KeysToLoop = $ExtensionType | Get-Member -MemberType Properties -ErrorAction Stop | Where-Object { $_.Name -notin 'type', $GPOSettingTypeSplit[0] -and $_.Name -notin @('Blocked') }
                    } catch {
                        Write-Warning "Get-XMLStandard - things went sideways $($_.Exception.Message)"
                        continue
                    }

                    foreach ($GpoSettings in $KeysToLoop.Name) {
                        $Template = [ordered] @{
                            DisplayName = $GPO.DisplayName
                            DomainName  = $GPO.DomainName
                            GUID        = $GPO.Guid
                            GpoType     = $GpoType
                            GpoCategory = $GPOSettingTypeSplit[1]
                            GpoSettings = $GpoSettings
                        }
                        $Template['Linked'] = $LinksInformation.Linked
                        $Template['LinksCount'] = $LinksInformation.LinksCount
                        $Template['Links'] = $LinksInformation.Links
                        $Template['IncludeComments'] = [bool]::Parse($GPOOutput.IncludeComments)
                        $Template['CreatedTime'] = [DateTime] $GPOOutput.CreatedTime
                        $Template['ModifiedTime'] = [DateTime] $GPOOutput.ModifiedTime
                        $Template['ReadTime'] = [DateTime] $GPOOutput.ReadTime
                        $Template['SecurityDescriptor'] = $GPOOutput.SecurityDescriptor
                        $Template['FilterDataAvailable'] = [bool]::Parse($GPOOutput.FilterDataAvailable)
                        $Template['DataSet'] = $ExtensionType.$GpoSettings
                        $ConvertedObject = [PSCustomObject] $Template

                        if (-not $CachedCategories["$($Template.GpoCategory)"]) {
                            $CachedCategories["$($Template.GpoCategory)"] = [ordered] @{}
                        }
                        if (-not $CachedCategories["$($Template.GpoCategory)"]["$($Template.GpoSettings)"]) {
                            $CachedCategories["$($Template.GpoCategory)"]["$($Template.GpoSettings)"] = [System.Collections.Generic.List[PSCustomObject]]::new()
                        }
                        $CachedCategories["$($Template.GpoCategory)"]["$($Template.GpoSettings)"].Add($ConvertedObject)
                        # return GPOCategory
                        $ConvertedObject
                    }
                }
            }
        }
    }
}
function Get-GPOPrivInheritance {
    [cmdletBinding()]
    param(
        [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
        [System.Collections.IDictionary] $CacheReturnedGPOs,
        [System.Collections.IDictionary] $ForestInformation,
        [string] $Domain,
        [switch] $SkipDomainRoot,
        [switch] $SkipDomainControllers
    )
    foreach ($Object in $ADObject) {
        if ($SkipDomainRoot) {
            if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) {
                # other skips Domain Root
                continue
            }
        }
        if ($SkipDomainControllers) {
            if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']) {
                # other skips Domain Controllers
                continue
            }
        }
        $Inheritance = Get-GPInheritance -Target $Object.DistinguishedName
        foreach ($Link in $Inheritance.GpoLinks) {
            [PSCustomObject] @{
                DisplayName       = $Link.DisplayName
                DomainName        = $Domain
                GUID              = $Link.GPOID
                Enabled           = $Link.Enabled
                Enforced          = $Link.Enforced
                Order             = $Link.Order
                Target            = $Object.DistinguishedName
                TargetCanonical   = $Object.CanonicalName
                TargetObjectClass = $Object.objectClass
            }
        }
    }
}
function Get-GPOPrivInheritanceLoop {
    [cmdletBinding()]
    param(
        [Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
        [System.Collections.IDictionary] $CacheReturnedGPOs,
        [System.Collections.IDictionary] $ForestInformation,
        [validateset('Root', 'DomainControllers', 'OrganizationalUnit')][string[]] $Linked,
        [string] $SearchBase,
        [Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,
        [string] $Filter
    )
    if (-not $ADObject) {
        if ($Linked) {
            foreach ($Domain in $ForestInformation.Domains) {
                $Splat = @{
                    #Filter = $Filter
                    Properties = 'distinguishedName', 'gplink', 'CanonicalName'
                    # Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')"
                    Server     = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                }
                if ($Linked -contains 'DomainControllers') {
                    $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']
                    $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
                    $Splat['SearchBase'] = $SearchBase
                    try {
                        $ADObjectGPO = Get-ADObject @Splat
                    } catch {
                        Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                    }
                    Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation
                }
                if ($Linked -contains 'Root') {
                    $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                    $Splat['Filter'] = "objectClass -eq 'domainDNS'"
                    $Splat['SearchBase'] = $SearchBase
                    try {
                        $ADObjectGPO = Get-ADObject @Splat
                    } catch {
                        Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                    }
                    Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation
                }
                if ($Linked -contains 'Site') {
                    # Sites are defined only in primary domain
                    # Sites are not supported by Get-GPInheritance
                }
                if ($Linked -contains 'OrganizationalUnit') {
                    $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                    $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
                    $Splat['SearchBase'] = $SearchBase
                    try {
                        $ADObjectGPO = Get-ADObject @Splat
                    } catch {
                        Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                    }
                    Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -SkipDomainRoot -SkipDomainControllers
                }
            }
        } elseif ($Filter) {
            foreach ($Domain in $ForestInformation.Domains) {
                $Splat = @{
                    Filter     = $Filter
                    Properties = 'distinguishedName', 'gplink', 'CanonicalName'
                    Server     = $ForestInformation['QueryServers'][$Domain]['HostName'][0]

                }
                if ($PSBoundParameters.ContainsKey('SearchBase')) {
                    $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                    $SearchBaseDC = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC
                    if ($SearchBaseDC -ne $DomainDistinguishedName) {
                        # we check if SearchBase is part of domain distinugishname. If it isn't we skip
                        continue
                    }
                    $Splat['SearchBase'] = $SearchBase

                }
                if ($PSBoundParameters.ContainsKey('SearchScope')) {
                    $Splat['SearchScope'] = $SearchScope
                }

                try {
                    $ADObjectGPO = Get-ADObject @Splat
                } catch {
                    Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                }
                Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation
            }
        }
    } else {
        Get-GPOPrivInheritance -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObject -Domain '' -ForestInformation $ForestInformation
    }
}
function Get-GPOPrivLink {
    [cmdletBinding()]
    param(
        [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
        [System.Collections.IDictionary] $CacheReturnedGPOs,
        [System.Collections.IDictionary] $ForestInformation,
        [string] $Domain,
        [switch] $SkipDomainRoot,
        [switch] $SkipDomainControllers,
        [switch] $AsHashTable,
        [switch] $SkipDuplicates
    )
    foreach ($Object in $ADObject) {
        if ($SkipDomainRoot) {
            if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) {
                # other skips Domain Root
                continue
            }
        }
        if ($SkipDomainControllers) {
            if ($Object.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']) {
                # other skips Domain Controllers
                continue
            }
        }
        $OutputGPOs = Get-PrivGPOZaurrLink -Object $Object -Limited:$Limited.IsPresent -GPOCache $GPOCache
        foreach ($OutputGPO in $OutputGPOs) {
            if (-not $SkipDuplicates) {
                $OutputGPO
            } else {
                $UniqueGuid = -join ($OutputGPO.DomainName, $OutputGPO.Guid)
                if (-not $CacheReturnedGPOs[$UniqueGuid]) {
                    $CacheReturnedGPOs[$UniqueGuid] = $OutputGPO
                    $OutputGPO
                }
            }
        }
    }
}
function Get-GPOZaurrLinkInheritance {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER ADObject
    Parameter description
 
    .PARAMETER Filter
    Parameter description
 
    .PARAMETER SearchBase
    Parameter description
 
    .PARAMETER SearchScope
    Parameter description
 
    .PARAMETER Linked
    Parameter description
 
    .PARAMETER Limited
    Parameter description
 
    .PARAMETER SkipDuplicates
    Parameter description
 
    .PARAMETER GPOCache
    Parameter description
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Parameter description
 
    .PARAMETER AsHashTable
    Parameter description
 
    .PARAMETER Summary
    Parameter description
 
    .EXAMPLE
    $Output = Get-GPOZaurrLinkInheritance -Summary
    $Output | Format-Table
 
    $Output[5]
 
    $Output[5].Links | Format-Table
    $Output[5].LinksObjects | Format-Table
 
    .NOTES
    This is based on Get-GPInheritance which isn't ideal and doesn't support sites. Get-GPOZaurrLink is better. Leaving in case I need it later on for private use only.
    #>

    [cmdletbinding(DefaultParameterSetName = 'All')]
    param(
        [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
        # weirdly enough site doesn't really work this way unless you give it 'CN=Configuration,DC=ad,DC=evotec,DC=xyz' as SearchBase
        [parameter(ParameterSetName = 'Filter')][string] $Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')",
        [parameter(ParameterSetName = 'Filter')][string] $SearchBase,
        [parameter(ParameterSetName = 'Filter')][Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,

        [parameter(ParameterSetName = 'Linked', Mandatory)][validateset('Root', 'DomainControllers', 'OrganizationalUnit')][string[]] $Linked,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $Limited,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $SkipDuplicates,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [System.Collections.IDictionary] $GPOCache,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [alias('ForestName')][string] $Forest,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [string[]] $ExcludeDomains,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $AsHashTable,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $Summary
    )
    Begin {
        $CacheReturnedGPOs = [ordered] @{}
        $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        if (-not $GPOCache -and -not $Limited) {
            $GPOCache = @{ }
            # While initially we used $ForestInformation.Domains but the thing is GPOs can be linked to other domains so we need to get them all so we can use cache of it later on even if we're processing just one domain
            # That's why we use $ForestInformation.Forest.Domains instead
            foreach ($Domain in $ForestInformation.Forest.Domains) {
                if ($ForestInformation['QueryServers'][$Domain]) {
                    $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                    Get-GPO -All -DomainName $Domain -Server $QueryServer | ForEach-Object {
                        $GPOCache["$Domain$($_.ID.Guid)"] = $_
                    }
                } else {
                    Write-Warning -Message "Get-GPOZaurrLinkInheritance - Couldn't get query server for $Domain. Skipped."
                }
            }
        }
    }
    Process {
        if (-not $Filter -and -not $Linked) {
            # We choose ALL, except SITE which is not supported gor Get-GPInheritance
            # that's why it's better to use Get-GPOZaurrLink
            #$Linked = 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit'
        }
        $getGPOPrivInheritanceLoopSplat = @{
            Linked            = $Linked
            ForestInformation = $ForestInformation
            CacheReturnedGPOs = $CacheReturnedGPOs
            SearchScope       = $SearchScope
            SearchBase        = $SearchBase
            ADObject          = $ADObject
            Filter            = $Filter
        }
        Remove-EmptyValue -Hashtable $getGPOPrivInheritanceLoopSplat -Recursive

        # we need to use nested functions to support pipeline output and as hashtable and reporting that returns single value
        if ($AsHashTable -or $Summary) {
            $HashTable = [ordered] @{}
            $SummaryHashtable = [ordered] @{}
            $Links = Get-GPOPrivInheritanceLoop @getGPOPrivInheritanceLoopSplat
            foreach ($Link in $Links) {
                $Key = -join ($Link.DomainName, $Link.GUID)
                if (-not $HashTable[$Key]) {
                    $HashTable[$Key] = [System.Collections.Generic.List[PSCustomObject]]::new()
                }
                $HashTable[$Key].Add($Link)
            }
            foreach ($Key in $HashTable.Keys) {
                [Array] $Link = $HashTable[$Key]
                $EnabledLinks = $Link.Enabled.Where( { $_ -eq $true }, 'split')
                if ($EnabledLinks[0].Count -gt 0) {
                    $IsLinked = $true
                } else {
                    $IsLinked = $false
                }
                $SummaryLink = [PSCustomObject] @{
                    DisplayName        = $Link[0].DisplayName
                    DomainName         = $Link[0].DomainName
                    GUID               = $Link[0].GUID
                    Linked             = $IsLinked
                    LinksCount         = $Link.Count
                    LinksEnabledCount  = $EnabledLinks[0].Count
                    LinksDisabledCount = $EnabledLinks[1].Count
                    Links              = $Link.Target
                    LinksObjects       = $Link
                }
                $SummaryHashtable[$Key] = $SummaryLink
            }
            if ($AsHashTable -and $Summary) {
                $SummaryHashtable
            } elseif ($AsHashTable) {
                $HashTable
            } elseif ($Summary) {
                $SummaryHashtable.Values
            }
        } else {
            Get-GPOPrivInheritanceLoop @getGPOPrivInheritanceLoopSplat
        }
    }
    End {

    }
}
function Get-GPOZaurrLinkLoop {
    [cmdletBinding()]
    param(
        [Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
        [System.Collections.IDictionary] $CacheReturnedGPOs,
        [System.Collections.IDictionary] $ForestInformation,
        [validateset('All', 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string[]] $Linked,
        [string] $SearchBase,
        [Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,
        [string] $Filter,
        [switch] $SkipDuplicates,
        [string[]] $Site
    )
    if (-not $ADObject) {
        if ($Site) {
            foreach ($S in $Site) {
                foreach ($Domain in $ForestInformation.Domains) {
                    Write-Verbose "Get-GPOZaurrLink - Getting GPO links for site $Site"
                    # Sites are defined only in primary domain
                    if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) {
                        $Splat = @{
                            #Filter = $Filter
                            Properties = 'distinguishedName', 'gplink', 'CanonicalName'
                            # Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')"
                            Server     = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                        }
                        $Splat['Filter'] = "(objectClass -eq 'site') -and (name -eq '$S')"
                        $Splat['SearchBase'] = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'])
                        try {
                            $ADObjectGPO = Get-ADObject @Splat
                        } catch {
                            Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                        }
                        if ($ADObjectGPO) {
                            Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
                        }
                    }
                }
            }
        } elseif ($SearchBase -or $SearchScope -or $Filter) {
            foreach ($Domain in $ForestInformation.Domains) {
                if (-not $Filter) {
                    $Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')"
                }
                $Splat = @{
                    Filter     = $Filter
                    Properties = 'distinguishedName', 'gplink', 'CanonicalName'
                    Server     = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                }
                if ($PSBoundParameters.ContainsKey('SearchBase')) {
                    $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                    $SearchBaseDC = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC
                    if ($SearchBaseDC -ne $DomainDistinguishedName) {
                        # we check if SearchBase is part of domain distinugishname. If it isn't we skip
                        continue
                    }
                    $Splat['SearchBase'] = $SearchBase
                }
                if ($PSBoundParameters.ContainsKey('SearchScope')) {
                    $Splat['SearchScope'] = $SearchScope
                }
                try {
                    $ADObjectGPO = Get-ADObject @Splat
                } catch {
                    Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                }
                if ($ADObjectGPO) {
                    Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
                }
            }
        } elseif (-not $Filter) {
            # if not linked, we force it to All
            if (-not $Linked) {
                $Linked = 'All'
            }
            foreach ($Domain in $ForestInformation.Domains) {
                Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain"
                $Splat = @{
                    #Filter = $Filter
                    Properties = 'distinguishedName', 'gplink', 'CanonicalName'
                    # Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')"
                    Server     = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                }
                if ($Linked -contains 'Root' -or $Linked -contains 'All') {
                    Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at ROOT level"
                    $Splat['Filter'] = "objectClass -eq 'domainDNS'"
                    $Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                    try {
                        $ADObjectGPO = Get-ADObject @Splat
                    } catch {
                        Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                    }
                    if ($ADObjectGPO) {
                        Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
                    }
                }
                if ($Linked -contains 'Site' -or $Linked -contains 'All') {
                    Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at SITE level"
                    # Sites are defined only in primary domain
                    if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) {
                        $Splat['Filter'] = "(objectClass -eq 'site')"
                        $Splat['SearchBase'] = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'])
                        try {
                            $ADObjectGPO = Get-ADObject @Splat
                        } catch {
                            Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                        }
                        if ($ADObjectGPO) {
                            Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
                        }
                    }
                }
                if ($Linked -contains 'DomainControllers' -or $Linked -contains 'All') {
                    Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at DC level"
                    $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
                    $Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']
                    try {
                        $ADObjectGPO = Get-ADObject @Splat
                    } catch {
                        Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                    }
                    if ($ADObjectGPO) {
                        Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
                    }
                }
                if ($Linked -contains 'OrganizationalUnit' -or $Linked -contains 'All') {
                    Write-Verbose "Get-GPOZaurrLink - Getting GPO links for domain $Domain at OU level"
                    $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
                    $Splat['SearchBase'] = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                    try {
                        $ADObjectGPO = Get-ADObject @Splat
                    } catch {
                        Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                    }
                    if ($ADObjectGPO) {
                        Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -SkipDomainRoot -SkipDomainControllers -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
                    }
                }
            }
        }
        <#
        elseif ($Filter) {
            foreach ($Domain in $ForestInformation.Domains) {
                $Splat = @{
                    Filter = $Filter
                    Properties = 'distinguishedName', 'gplink', 'CanonicalName'
                    Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                }
                if ($PSBoundParameters.ContainsKey('SearchBase')) {
                    $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                    $SearchBaseDC = ConvertFrom-DistinguishedName -DistinguishedName $SearchBase -ToDC
                    if ($SearchBaseDC -ne $DomainDistinguishedName) {
                        # we check if SearchBase is part of domain distinugishname. If it isn't we skip
                        continue
                    }
                    $Splat['SearchBase'] = $SearchBase
                }
                if ($PSBoundParameters.ContainsKey('SearchScope')) {
                    $Splat['SearchScope'] = $SearchScope
                }
                try {
                    $ADObjectGPO = Get-ADObject @Splat
                } catch {
                    Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                }
                if ($ADObjectGPO) {
                    Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObjectGPO -Domain $Domain -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
                }
            }
        }
        #>

    } else {
        Get-GPOPrivLink -CacheReturnedGPOs $CacheReturnedGPOs -ADObject $ADObject -Domain '' -ForestInformation $ForestInformation -AsHashTable:$AsHashTable -SkipDuplicates:$SkipDuplicates
    }
}
function Get-LinksFromXML {
    [cmdletBinding()]
    param(
        [System.Xml.XmlElement[]] $GPOOutput,
        [string] $Splitter,
        [switch] $FullObjects
    )
    $Links = [ordered] @{
        Linked     = $null
        LinksCount = $null
        Links      = $null
    }
    if ($GPOOutput.LinksTo) {
        $Links.Linked = $true
        $Links.LinksCount = ([Array] $GPOOutput.LinksTo).Count
        $Links.Links = foreach ($Link in $GPOOutput.LinksTo) {
            if ($FullObjects) {
                [PSCustomObject] @{
                    Path       = $Link.SOMPath
                    Enabled    = if ($Link.Enabled -eq 'true') { $true } else { $false }
                    NoOverride = if ($Link.NoOverride -eq 'true') { $true } else { $false }
                }
            } else {
                if ($Link.Enabled) {
                    $Link.SOMPath
                }
            }
        }
        if ($Splitter) {
            $Links.Links = $Links.Links -join $Splitter
        }
    } else {
        $Links.Linked = $false
        $Links.LinksCount = 0
        $Links.Links = $null
    }
    [PSCustomObject] $Links
}
function Get-PermissionsAnalysis {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPOPermissions,
        [validateset('WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', 'Default')][string] $Type = 'Default',
        [Parameter(Mandatory)][alias('IncludePermissionType')][Microsoft.GroupPolicy.GPPermissionType] $PermissionType,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [System.Collections.IDictionary] $ADAdministrativeGroups
    )
    if (-not $ADAdministrativeGroups) {
        $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }
    $AdministrativeExists = [ordered] @{
        DisplayName      = $GPOPermissions[0].DisplayName
        DomainName       = $GPOPermissions[0].DomainName
        GUID             = $GPOPermissions[0].GUID
        Skip             = $false
        DomainAdmins     = $false
        EnterpriseAdmins = $false
    }
    #$GPOPermissions = $_
    # Verification Phase
    # When it has GPOSecurityPermissionItem property it means it has permissions, if it doesn't it means we have clean object to process
    if ($GPOPermissions.GPOSecurityPermissionItem) {
        # Permission exists, but may be incomplete
        foreach ($GPOPermission in $GPOPermissions) {
            if ($Type -eq 'Default') {
                # We were looking for specific principal and we got it. nothing to do
                # this is for standard users such as przemyslaw.klys / adam.gonzales
                $AdministrativeExists['Skip'] = $true
                break
            } elseif ($Type -eq 'Administrative') {
                # We are looking for administrative but we need to make sure we got correct administrative
                if ($GPOPermission.Permission -eq $PermissionType) {
                    $AdministrativeGroup = $ADAdministrativeGroups['BySID'][$GPOPermission.PrincipalSid]
                    if ($AdministrativeGroup.SID -like '*-519') {
                        $AdministrativeExists['EnterpriseAdmins'] = $true
                    } elseif ($AdministrativeGroup.SID -like '*-512') {
                        $AdministrativeExists['DomainAdmins'] = $true
                    }
                }
                if ($AdministrativeExists['DomainAdmins'] -and $AdministrativeExists['EnterpriseAdmins']) {
                    $AdministrativeExists['Skip'] = $true
                    break
                }
            } elseif ($Type -eq 'WellKnownAdministrative') {
                # this is for SYSTEM account
                $AdministrativeExists['Skip'] = $true
                break
            } elseif ($Type -eq 'AuthenticatedUsers') {
                # this is for Authenticated Users
                $AdministrativeExists['Skip'] = $true
                break
            }
        }
    }
    [PSCustomObject] $AdministrativeExists
}
function Get-PrivGPOZaurrLink {
    [cmdletBinding()]
    param(
        [Microsoft.ActiveDirectory.Management.ADObject] $Object,
        [switch] $Limited,
        [System.Collections.IDictionary] $GPOCache
    )
    if ($Object.GpLink -and $Object.GpLink.Trim() -ne '') {
        $ObjectsToProcess = $Object.GpLink -split '\]\['
    } elseif ($Object.LinkedGroupPolicyObjects -and $Object.LinkedGroupPolicyObjects.Trim() -ne '') {
        $ObjectsToProcess = $Object.LinkedGroupPolicyObjects -split '\]\['
    } else {
        $ObjectsToProcess = $null
    }
    $ObjectsToProcess | ForEach-Object -Process {
        $Link = $_ -replace 'LDAP://' -replace '\]' -replace '\['
        if ($Link.Length -gt 10) {
            $SplitGPLink = $Link -split ';'
            $DN = $SplitGPLink[0]
            $Option = $SplitGPLink[1]
            if ($Option -eq '0') {
                $Enforced = $false
                $Enabled = $true
            } elseif ($Option -eq '1') {
                $Enabled = $false
                $Enforced = $false
            } elseif ($Option -eq '2') {
                $Enabled = $true
                $Enforced = $true
            } elseif ($Option -eq '3') {
                $Enabled = $false
                $Enforced = $true
            } else {
                if ($Object.GpLink) {
                    Write-Warning "Get-PrivGPOZaurrLink - This should't happen. Please investigate - Option: $Option"
                } else {
                    Write-Warning "Get-PrivGPOZaurrLink - Property GPLink is required to be able to tell if Enabled/Enforced is added. Skipping those settings."
                }
                $Enabled = $null
                $Enforced = $null
            }
            $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDomainCN
            $Output = [ordered] @{
                DistinguishedName = $Object.DistinguishedName
                #Domain = ConvertFrom-DistinguishedName -DistinguishedName $Object.DistinguishedName -ToDomainCN
                CanonicalName     = if ($Object.CanonicalName) { $Object.CanonicalName.TrimEnd('/') } else { $Object.CanonicalName }
                Guid              = [Regex]::Match($DN, '(?={)(.*)(?<=})').Value -replace '{' -replace '}'
                Enforced          = $Enforced
                Enabled           = $Enabled
                ObjectClass       = $Object.ObjectClass
            }
            $Search = -join ($DomainCN, $Output['Guid'])
            if ($GPOCache -and -not $Limited) {
                if ($GPOCache[$Search]) {
                    $Output['DisplayName'] = $GPOCache[$Search].DisplayName
                    $Output['DomainName'] = $GPOCache[$Search].DomainName
                    $Output['Owner'] = $GPOCache[$Search].Owner
                    $Output['GpoStatus'] = $GPOCache[$Search].GpoStatus
                    $Output['Description'] = $GPOCache[$Search].Description
                    $Output['CreationTime'] = $GPOCache[$Search].CreationTime
                    $Output['ModificationTime'] = $GPOCache[$Search].ModificationTime
                    $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDC
                    $Output['GPODistinguishedName'] = $DN
                    # This is object name, usually used for sites
                    $Output['Name'] = $Object.Name
                    [PSCustomObject] $Output
                } else {
                    Write-Warning "Get-PrivGPOZaurrLink - Couldn't find link $Search in a GPO Cache. Lack of permissions for given GPO? Are you running as admin? Skipping."
                }
            } else {
                $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $DN -ToDC
                $Output['GPODistinguishedName'] = $DN
                [PSCustomObject] $Output
            }
        }
    }
}
function Get-PrivPermission {
    [cmdletBinding()]
    param(
        [Microsoft.GroupPolicy.Gpo] $GPO,
        [Object] $SecurityRights,

        [string[]] $Principal,
        [validateset('DistinguishedName', 'Name', 'NetbiosName', 'Sid')][string] $PrincipalType = 'Sid',

        [switch] $SkipWellKnown,
        [switch] $SkipAdministrative,
        [switch] $IncludeOwner,
        [Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType,
        [Microsoft.GroupPolicy.GPPermissionType[]] $ExcludePermissionType,
        [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'All',

        [string[]] $ExcludePrincipal,
        [validateset('DistinguishedName', 'Name', 'Sid')][string] $ExcludePrincipalType = 'Sid',

        [switch] $IncludeGPOObject,
        [System.Collections.IDictionary] $ADAdministrativeGroups,
        [validateSet('AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', 'All')][string[]] $Type = 'All',
        #[System.Collections.IDictionary] $Accounts,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    Begin {
        Write-Verbose "Get-PrivPermission - Processing $($GPO.DisplayName) from $($GPO.DomainName)"
    }
    Process {
        $SecurityRights | ForEach-Object -Process {
            $GPOPermission = $_
            if ($PermitType -ne 'All') {
                if ($PermitType -eq 'Deny') {
                    if ($GPOPermission.Denied -eq $false) {
                        return
                    }
                } else {
                    if ($GPOPermission.Denied -eq $true) {
                        return
                    }
                }
            }
            if ($ExcludePermissionType -contains $GPOPermission.Permission) {
                return
            }
            if ($IncludePermissionType) {
                if ($IncludePermissionType -notcontains $GPOPermission.Permission) {
                    if ($IncludePermissionType -eq 'GpoRead' -and $GPOPermission.Permission -eq 'GpoApply') {
                        # We treat GpoApply as GpoRead as well. This is because when GpoApply is set it becomes GpoRead as well but of course not vice versa
                    } else {
                        return
                    }
                }
            }
            if ($SkipWellKnown.IsPresent -or $Type -contains 'NotWellKnown') {
                if ($GPOPermission.Trustee.SidType -eq 'WellKnownGroup') {
                    return
                }
            }
            if ($SkipAdministrative.IsPresent -or $Type -contains 'NotAdministrative') {
                $IsAdministrative = $ADAdministrativeGroups['BySID'][$GPOPermission.Trustee.Sid.Value]
                if ($IsAdministrative) {
                    return
                }
            }
            if ($Type -contains 'Administrative' -and $Type -notcontains 'All') {
                $IsAdministrative = $ADAdministrativeGroups['BySID'][$GPOPermission.Trustee.Sid.Value]
                if (-not $IsAdministrative) {
                    return
                }
            }
            if ($Type -contains 'NotWellKnownAdministrative' -and $Type -notcontains 'All') {
                # We check for SYSTEM account
                # Maybe we should make it a function and provide more
                if ($GPOPermission.Trustee.Sid -eq 'S-1-5-18') {
                    return
                }
            }
            if ($Type -contains 'WellKnownAdministrative' -and $Type -notcontains 'All') {
                # We check for SYSTEM account
                # Maybe we should make it a function and provide more
                if ($GPOPermission.Trustee.Sid -ne 'S-1-5-18') {
                    return
                }
            }
            if ($Type -contains 'Unknown' -and $Type -notcontains 'All') {
                # May need updates if there's more types
                if ($GPOPermission.Trustee.SidType -ne 'Unknown') {
                    return
                }
            }
            if ($Type -contains 'AuthenticatedUsers' -and $Type -notcontains 'All') {
                if ($GPOPermission.Trustee.Sid -ne 'S-1-5-11') {
                    return
                }
            }
            if ($Type -contains 'DomainComputers' -and $Type -notcontains 'All') {
                $DomainComputersSID = -join ($ExtendedForestInformation['DomainsExtended'][$GPO.DomainName].DomainSID, '-515')
                if ($GPOPermission.Trustee.Sid -ne $DomainComputersSID) {
                    return
                }
            }
            if ($GPOPermission.Trustee.Domain) {
                $UserMerge = -join ($GPOPermission.Trustee.Domain, '\', $GPOPermission.Trustee.Name)
            } else {
                $UserMerge = $null
            }
            if ($Principal) {
                if ($PrincipalType -eq 'Sid') {
                    if ($Principal -notcontains $GPOPermission.Trustee.Sid.Value) {
                        return
                    }
                } elseif ($PrincipalType -eq 'DistinguishedName') {
                    if ($Principal -notcontains $GPOPermission.Trustee.DSPath) {
                        return
                    }
                } elseif ($PrincipalType -eq 'Name') {
                    if ($Principal -notcontains $GPOPermission.Trustee.Name) {
                        return
                    }
                } elseif ($PrincipalType -eq 'NetbiosName') {
                    if ($Principal -notcontains $UserMerge) {
                        return
                    }
                }
            }
            if ($ExcludePrincipal) {
                if ($ExcludePrincipalType -eq 'Sid') {
                    if ($ExcludePrincipal -contains $GPOPermission.Trustee.Sid.Value) {
                        return
                    }
                } elseif ($ExcludePrincipalType -eq 'DistinguishedName') {
                    if ($ExcludePrincipal -contains $GPOPermission.Trustee.DSPath) {
                        return
                    }
                } elseif ($ExcludePrincipalType -eq 'Name') {
                    if ($ExcludePrincipal -contains $GPOPermission.Trustee.Name) {
                        return
                    }
                } elseif ($ExcludePrincipalType -eq 'NetbiosName') {
                    if ($ExcludePrincipal -contains $UserMerge) {
                        return
                    }
                }
            }

            <#
            # Sets permissions name, domain, distinguishedname to proper values
            if ($GPOPermission.Trustee.Name) {
                $DomainPlusName = -join ($GPOPermission.Trustee.Domain, '\', $GPOPermission.Trustee.Name)
                if ($GPOPermission.Trustee.DSPath) {
                    $NetbiosConversion = ConvertFrom-NetbiosName -Identity $DomainPlusName
                    if ($NetbiosConversion.DomainName) {
                        $UserNameDomain = $NetbiosConversion.DomainName
                        $UserName = $NetbiosConversion.Name
                    }
                } else {
                    $UserNameDomain = ''
                    $Username = $DomainPlusName
                }
            } else {
                $DomainPlusName = ''
                $UserNameDomain = ''
                $Username = ''
            }
            #>


            # I don't trust the returned data, some stuff like 'alias' shows up for groups. To unify it with everything else... using my own function
            $PermissionAccount = Get-WinADObject -Identity $GPOPermission.Trustee.Sid.Value -AddType -Cache -Verbose:$false
            if ($PermissionAccount) {
                $UserNameDomain = $PermissionAccount.DomainName
                $UserName = $PermissionAccount.Name
                $SidType = $PermissionAccount.Type
                $ObjectClass = $PermissionAccount.ObjectClass
            } else {
                $ConvertFromSID = ConvertFrom-SID -SID $GPOPermission.Trustee.Sid.Value
                $UserNameDomain = ''
                $Username = $ConvertFromSID.Name
                $SidType = $ConvertFromSID.Type
                if ($SidType -eq 'Unknown') {
                    $ObjectClass = 'unknown'
                } else {
                    $ObjectClass = 'foreignSecurityPrincipal'
                }
            }
            $ReturnObject = [ordered] @{
                DisplayName                = $GPO.DisplayName # : ALL | Enable RDP
                GUID                       = $GPO.ID
                DomainName                 = $GPO.DomainName  # : ad.evotec.xyz
                Enabled                    = $GPO.GpoStatus
                Description                = $GPO.Description
                CreationDate               = $GPO.CreationTime
                ModificationTime           = $GPO.ModificationTime
                PermissionType             = if ($GPOPermission.Denied -eq $true) { 'Deny' } else { 'Allow' }
                Permission                 = $GPOPermission.Permission  # : GpoEditDeleteModifySecurity
                Inherited                  = $GPOPermission.Inherited   # : False
                PrincipalNetBiosName       = $UserMerge
                PrincipalDistinguishedName = $GPOPermission.Trustee.DSPath  #: CN = Domain Admins, CN = Users, DC = ad, DC = evotec, DC = xyz
                PrincipalDomainName        = $UserNameDomain  #: EVOTEC
                PrincipalName              = $UserName    #: Domain Admins
                PrincipalSid               = $GPOPermission.Trustee.Sid.Value     #: S - 1 - 5 - 21 - 853615985 - 2870445339 - 3163598659 - 512
                PrincipalSidType           = $SidType #$GPOPermission.Trustee.SidType #: Group
                PrincipalObjectClass       = $ObjectClass
            }


            if ($IncludeGPOObject) {
                $ReturnObject['GPOObject'] = $GPO
                $ReturnObject['GPOSecurity'] = $SecurityRights
                $ReturnObject['GPOSecurityPermissionItem'] = $GPOPermission
            }
            [PSCustomObject] $ReturnObject
        }
        if ($IncludeOwner) {
            if ($GPO.Owner) {
                # I don't trust the returned data, some stuff like 'alias' shows up for groups. To unify it with everything else... using my own function
                $OwnerAccount = Get-WinADObject -Identity $GPO.Owner -AddType -Cache -Verbose:$false
                if ($OwnerAccount) {
                    $UserNameDomain = $OwnerAccount.DomainName
                    $UserName = $OwnerAccount.Name
                    $SidType = $OwnerAccount.Type
                    $OwnerObjectClass = $OwnerAccount.ObjectClass
                    $SID = $OwnerAccount.ObjectSID
                } else {
                    $ConvertFromSID = ConvertFrom-SID -SID $GPO.Owner
                    $UserNameDomain = ''
                    $Username = $ConvertFromSID.Name
                    $SidType = $ConvertFromSID.Type
                    if ($SidType -eq 'Unknown') {
                        $OwnerObjectClass = 'unknown'
                    } else {
                        $OwnerObjectClass = 'foreignSecurityPrincipal'
                    }
                    $SID = $ConvertFromSID.SID
                }
            } else {
                $UserName = ''
                $UserNameDomain = ''
                $SID = ''
                $SIDType = 'Unknown'
                $DistinguishedName = ''
                $OwnerObjectClass = 'unknown'
            }
            # We have to process it for owners after querying user because $Owners are not as established as standard permissions so we don't know a lot

            if ($Type -contains 'Administrative' -and $Type -notcontains 'All') {
                if ($SID) {
                    $IsAdministrative = $ADAdministrativeGroups['BySID'][$SID]
                    if (-not $IsAdministrative) {
                        return
                    }
                } else {
                    # if there is no SID, it's not administrative
                    return
                }
            }
            if ($Type -contains 'NotWellKnownAdministrative' -and $Type -notcontains 'All') {
                # We check for SYSTEM account
                # Maybe we should make it a function and provide more
                if ($SID -eq 'S-1-5-18') {
                    return
                }
            }
            if ($Type -contains 'WellKnownAdministrative' -and $Type -notcontains 'All') {
                # We check for SYSTEM account
                # Maybe we should make it a function and provide more
                if ($SID -ne 'S-1-5-18') {
                    return
                }
            }
            if ($Type -contains 'Unknown' -and $Type -notcontains 'All') {
                # May need updates if there's more types
                if ($SidType -ne 'Unknown') {
                    return
                }
            }
            if ($Type -contains 'AuthenticatedUsers' -and $Type -notcontains 'All') {
                if ($SID -ne 'S-1-5-11') {
                    return
                }
            }
            if ($Type -contains 'DomainComputers' -and $Type -notcontains 'All') {
                $DomainComputersSID = -join ($ExtendedForestInformation['DomainsExtended'][$GPO.DomainName].DomainSID, '-515')
                if ($SID -ne $DomainComputersSID) {
                    return
                }
            }

            if ($Principal) {
                if ($PrincipalType -eq 'Sid') {
                    if ($Principal -notcontains $SID) {
                        return
                    }
                } elseif ($PrincipalType -eq 'DistinguishedName') {
                    if ($Principal -notcontains $DistinguishedName) {
                        return
                    }
                } elseif ($PrincipalType -eq 'Name') {
                    if ($Principal -notcontains $UserName) {
                        return
                    }
                } elseif ($PrincipalType -eq 'NetbiosName') {
                    if ($Principal -notcontains $GPO.Owner) {
                        return
                    }
                }
            }
            if ($ExcludePrincipal) {
                if ($ExcludePrincipalType -eq 'Sid') {
                    if ($ExcludePrincipal -contains $SID) {
                        return
                    }
                } elseif ($ExcludePrincipalType -eq 'DistinguishedName') {
                    if ($ExcludePrincipal -contains $DistinguishedName) {
                        return
                    }
                } elseif ($ExcludePrincipalType -eq 'Name') {
                    if ($ExcludePrincipal -contains $UserName) {
                        return
                    }
                } elseif ($ExcludePrincipalType -eq 'NetbiosName') {
                    if ($ExcludePrincipal -contains $GPO.Owner) {
                        return
                    }
                }
            }

            $ReturnObject = [ordered] @{
                DisplayName                = $GPO.DisplayName # : ALL | Enable RDP
                GUID                       = $GPO.Id
                DomainName                 = $GPO.DomainName  # : ad.evotec.xyz
                Enabled                    = $GPO.GpoStatus
                Description                = $GPO.Description
                CreationDate               = $GPO.CreationTime
                ModificationTime           = $GPO.ModificationTime
                PermissionType             = 'Allow'
                Permission                 = 'GpoOwner'  # : GpoEditDeleteModifySecurity
                Inherited                  = $false  # : False
                PrincipalNetBiosName       = $GPO.Owner
                PrincipalDistinguishedName = $DistinguishedName  #: CN = Domain Admins, CN = Users, DC = ad, DC = evotec, DC = xyz
                PrincipalDomainName        = $UserNameDomain
                PrincipalName              = $UserName
                PrincipalSid               = $SID     #: S - 1 - 5 - 21 - 853615985 - 2870445339 - 3163598659 - 512
                PrincipalSidType           = $SIDType # #: Group
                PrincipalObjectClass       = $OwnerObjectClass
            }
            if ($IncludeGPOObject) {
                $ReturnObject['GPOObject'] = $GPO
                $ReturnObject['GPOSecurity'] = $SecurityRights
                $ReturnObject['GPOSecurityPermissionItem'] = $null
            }
            [PSCustomObject] $ReturnObject
        }
    }
    End {

    }
}
function Get-WellKnownFolders {
    <#
    .SYNOPSIS
    Gets users and computers well known folders for a forest
 
    .DESCRIPTION
    Gets users and computers well known folders for a forest
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    Get-WellKnownFolders
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )

    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended
    foreach ($Domain in $ForestInformation.Domains) {
        $ForestInformation.DomainsExtended[$Domain].ComputersContainer
        $ForestInformation.DomainsExtended[$Domain].UsersContainer
    }
}
function Get-XMLGPO {
    [cmdletBinding()]
    param(
        [XML] $XMLContent,
        [Microsoft.GroupPolicy.Gpo] $GPO,
        [switch] $PermissionsOnly,
        [switch] $OwnerOnly,
        [System.Collections.IDictionary] $ADAdministrativeGroups,
        [string] $Splitter = [System.Environment]::NewLine,
        [System.Collections.IDictionary] $ExcludeGroupPolicies,
        [string[]] $Type,
        [System.Collections.IDictionary] $LinksSummaryCache
    )

    $DisplayName = $XMLContent.GPO.Name
    $DomainName = $XMLContent.GPO.Identifier.Domain.'#text'

    if ($LinksSummaryCache) {
        $SearchGUID = -join ($XMLContent.GPO.Identifier.Domain.'#text', $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}')
        if ($LinksSummaryCache[$SearchGUID]) {
            $Linked = $LinksSummaryCache[$SearchGUID].Linked
            $LinksEnabledCount = $LinksSummaryCache[$SearchGUID].LinksEnabledCount
            $LinksDisabledCount = $LinksSummaryCache[$SearchGUID].LinksDisabledCount
            $LinksTotalCount = $LinksSummaryCache[$SearchGUID].LinksCount
            $Links = $LinksSummaryCache[$SearchGUID].Links
            $LinksObjects = $LinksSummaryCache[$SearchGUID].LinksObjects
        } else {
            $Linked = $false
            $LinksEnabledCount = 0
            $LinksDisabledCount = 0
            $LinksTotalCount = 0
            $Links = $null
            $LinksObjects = $null
        }
    } else {
        if ($XMLContent.GPO.LinksTo) {
            $LinkSplit = ([Array] $XMLContent.GPO.LinksTo).Where( { $_.Enabled -eq $true }, 'Split')
            [Array] $LinksEnabled = $LinkSplit[0]
            [Array] $LinksDisabled = $LinkSplit[1]
            $LinksEnabledCount = $LinksEnabled.Count
            $LinksDisabledCount = $LinksDisabled.Count
            $LinksTotalCount = ([Array] $XMLContent.GPO.LinksTo).Count
            if ($LinksEnabledCount -eq 0) {
                $Linked = $false
            } else {
                $Linked = $true
            }
            $Links = @(
                $XMLContent.GPO.LinksTo | ForEach-Object -Process {
                    if ($_) {
                        $_.SOMPath
                    }
                }
            ) -join $Splitter
            $LinksObjects = $XMLContent.GPO.LinksTo | ForEach-Object -Process {
                if ($_) {
                    [PSCustomObject] @{
                        CanonicalName = $_.SOMPath
                        Enabled       = $_.Enabled
                        NoOverride    = $_.NoOverride
                    }
                }
            }
        } else {
            $Linked = $false
            $LinksEnabledCount = 0
            $LinksDisabledCount = 0
            $LinksTotalCount = 0
            $Links = $null
            $LinksObjects = $null
        }
    }
    # Find proper values for enabled/disabled user/computer settings
    if ($XMLContent.GPO.Computer.Enabled -eq 'False') {
        $ComputerEnabled = $false
    } elseif ($XMLContent.GPO.Computer.Enabled -eq 'True') {
        $ComputerEnabled = $true
    } else {
        Write-Warning "Get-XMLGPO - Computer enabled not set to true or false [$DisplayName/$DomainName]. Weird."
        $ComputerEnabled = $null
    }
    if ($XMLContent.GPO.User.Enabled -eq 'False') {
        $UserEnabled = $false
    } elseif ($XMLContent.GPO.User.Enabled -eq 'True') {
        $UserEnabled = $true
    } else {
        Write-Warning "Get-XMLGPO - User enabled not set to true or false [$DisplayName/$DomainName] . Weird."
        $UserEnabled = $null
    }
    # Translate Enabled to same as GPO GUI
    if ($UserEnabled -eq $True -and $ComputerEnabled -eq $true) {
        $EnabledBool = $true
        $Enabled = 'Enabled'
    } elseif ($UserEnabled -eq $false -and $ComputerEnabled -eq $false) {
        $EnabledBool = $false
        $Enabled = 'All settings disabled'
    } elseif ($UserEnabled -eq $true -and $ComputerEnabled -eq $false) {
        $EnabledBool = $True
        $Enabled = 'Computer configuration settings disabled'
    } elseif ($UserEnabled -eq $false -and $ComputerEnabled -eq $true) {
        $EnabledBool = $True
        $Enabled = 'User configuration settings disabled'
    }

    # This is kind of old way of doing things, but it's superseded by other way below
    [bool] $ComputerSettingsAvailable = if ($null -eq $XMLContent.GPO.Computer.ExtensionData) { $false } else { $true }
    [bool] $UserSettingsAvailable = if ($null -eq $XMLContent.GPO.User.ExtensionData) { $false } else { $true }

    if ($ComputerSettingsAvailable -eq $false -and $UserSettingsAvailable -eq $false) {
        $NoSettings = $true
    } else {
        $NoSettings = $false
    }

    # $OutputUser = $XMLContent.GPO.User.ExtensionData.Extension | Where-Object { $_.PSObject.Properties.TypeNameOfValue -in 'System.Xml.XmlElement', 'System.Object[]' }
    # $OutputComputer = $XMLContent.GPO.Computer.ExtensionData.Extension | Where-Object { $_.PSObject.Properties.TypeNameOfValue -in 'System.Xml.XmlElement', 'System.Object[]' }


    # This is additional check we do for error check to prevent false-positives for EMPTY on non-english language
    $PreCheckOutputUser = $false
    $PreCheckOutputComputer = $false
    foreach ($Extension in $XMLContent.GPO.User.ExtensionData) {
        if ($Extension.Error) {
            $PreCheckOutputUser = $true
        }
    }
    foreach ($Extension in $XMLContent.GPO.Computer.ExtensionData) {
        if ($Extension.Error) {
            $PreCheckOutputComputer = $true
        }
    }
    if ($PreCheckOutputComputer -eq $true -or $PreCheckOutputUser -eq $true) {
        # in some cases GPResult seems to return an error - this was first noticed by user when using Dutch based system
        # I am not sure if it's possible to fix this error for users, but once that happens checking if GPO is empty fails using the method below
        # therefore we will use the old method of assuming something is empty or not empty in such case
        Write-Warning "Get-XMLGPO - Reading GPO content [$DisplayName/$DomainName] returned an error. This may be because of non-english language. Assesing EMPTY using old method which can report false positives. Be careful please."
        $OutputUser = @()
        $OutputComputer = @()
    } else {
        [Array] $OutputUser = foreach ($ExtensionType in $XMLContent.GPO.User.ExtensionData.Extension) {
            if ($ExtensionType) {
                $GPOSettingTypeSplit = ($ExtensionType.type -split ':')
                try {
                    $KeysToLoop = $ExtensionType | Get-Member -MemberType Properties -ErrorAction Stop | Where-Object { $_.Name -notin 'type', $GPOSettingTypeSplit[0] -and $_.Name -notin @('Blocked') }
                } catch {
                    Write-Warning "Get-XMLGPO - things went sideways [$DisplayName/$DomainName]. Error $($_.Exception.Message)"
                    continue
                }
            }
            $KeysToLoop
        }
        [Array] $OutputComputer = foreach ($ExtensionType in $XMLContent.GPO.Computer.ExtensionData.Extension) {
            if ($ExtensionType) {
                $GPOSettingTypeSplit = ($ExtensionType.type -split ':')
                try {
                    $KeysToLoop = $ExtensionType | Get-Member -MemberType Properties -ErrorAction Stop | Where-Object { $_.Name -notin 'type', $GPOSettingTypeSplit[0] -and $_.Name -notin @('Blocked') }
                } catch {
                    Write-Warning "Get-XMLGPO - things went sideways [$DisplayName/$DomainName]. Error $($_.Exception.Message)"
                    continue
                }
            }
            $KeysToLoop
        }

        [bool] $ComputerSettingsAvailable = if ($OutputComputer.Count -gt 0) { $true } else { $false }
        [bool] $UserSettingsAvailable = if ($OutputUser.Count -gt 0) { $true } else { $false }
    }

    if ($ComputerSettingsAvailable -eq $false -and $UserSettingsAvailable -eq $false) {
        $Empty = $true
    } else {
        $Empty = $false
    }

    $ComputerProblem = $false
    if ($ComputerEnabled -eq $true -and $ComputerSettingsAvailable -eq $true) {
        $ComputerOptimized = $true
    } elseif ($ComputerEnabled -eq $true -and $ComputerSettingsAvailable -eq $false) {
        $ComputerOptimized = $false
    } elseif ($ComputerEnabled -eq $false -and $ComputerSettingsAvailable -eq $false) {
        $ComputerOptimized = $true
    } else {
        # Enabled $false, but ComputerData is there.
        $ComputerOptimized = $false
        $ComputerProblem = $true
    }

    $UserProblem = $false
    if ($UserEnabled -eq $true -and $UserSettingsAvailable -eq $true) {
        $UserOptimized = $true
    } elseif ($UserEnabled -eq $true -and $UserSettingsAvailable -eq $false) {
        $UserOptimized = $false
    } elseif ($UserEnabled -eq $false -and $UserSettingsAvailable -eq $false) {
        $UserOptimized = $true
    } else {
        # Enabled $false, but UserData is there.
        $UserOptimized = $false
        $UserProblem = $true
    }

    if ($UserProblem -or $ComputerProblem) {
        $Problem = $true
    } else {
        $Problem = $false
    }
    if ($UserOptimized -and $ComputerOptimized) {
        $Optimized = $true
    } else {
        $Optimized = $false
    }

    if (-not $PermissionsOnly) {
        if ($ADAdministrativeGroups -and $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text') {
            $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text')"]
            $WellKnown = ConvertFrom-SID -SID $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text' -OnlyWellKnown
            if ($AdministrativeGroup) {
                $OwnerType = 'Administrative'
            } elseif ($WellKnown.Name) {
                $OwnerType = 'WellKnown'
            } else {
                $OwnerType = 'NotAdministrative'
            }
        } elseif ($ADAdministrativeGroups) {
            $OwnerType = 'Unknown'
        } else {
            $OwnerType = 'Unable to asses (local files?)'
        }
    }
    # Mark GPO as excluded
    $Exclude = $false
    if ($ExcludeGroupPolicies) {
        $GUID = $XMLContent.GPO.Identifier.Identifier.'#text'
        $GUIDWithOutBrackets = $GUID.Replace('{', '').Replace('}', '')
        $PolicyWithDomain = -join ($XMLContent.GPO.Identifier.Domain.'#text', $XMLContent.GPO.Name)
        $PolicyWithDomainID = -join ($XMLContent.GPO.Identifier.Domain.'#text', $GUID)
        $PolicyWithDomainIDWithoutBrackets = -join ($XMLContent.GPO.Identifier.Domain.'#text', $GUIDWithOutBrackets)
        if ($ExcludeGroupPolicies[$XMLContent.GPO.Name] -or
            $ExcludeGroupPolicies[$PolicyWithDomain] -or
            $ExcludeGroupPolicies[$PolicyWithDomainID] -or
            $ExcludeGroupPolicies[$GUID] -or
            $ExcludeGroupPolicies[$GUIDWithOutBrackets] -or
            $ExcludeGroupPolicies[$PolicyWithDomainIDWithoutBrackets]
        ) {
            $Exclude = $true
        }
    }
    if ($PermissionsOnly) {
        $GPOOutput = [PsCustomObject] @{
            'DisplayName'          = $XMLContent.GPO.Name
            'DomainName'           = $XMLContent.GPO.Identifier.Domain.'#text'
            'GUID'                 = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}'
            'Enabled'              = $Enabled
            'Name'                 = $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text'
            'Sid'                  = $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text'
            #'SidType' = if (($XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text').Length -le 10) { 'WellKnown' } else { 'Other' }
            'PermissionType'       = 'Allow'
            'Inherited'            = $false
            'Permissions'          = 'Owner'
            'GPODistinguishedName' = $GPO.Path
        }
        $XMLContent.GPO.SecurityDescriptor.Permissions.TrusteePermissions | ForEach-Object -Process {
            if ($_) {
                [PsCustomObject] @{
                    'DisplayName'          = $XMLContent.GPO.Name
                    'DomainName'           = $XMLContent.GPO.Identifier.Domain.'#text'
                    'GUID'                 = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}'
                    'Enabled'              = $Enabled
                    'Name'                 = $_.trustee.name.'#Text'
                    'Sid'                  = $_.trustee.SID.'#Text'
                    #'SidType' = if (($XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text').Length -le 10) { 'WellKnown' } else { 'Other' }
                    'PermissionType'       = $_.type.PermissionType
                    'Inherited'            = if ($_.Inherited -eq 'false') { $false } else { $true }
                    'Permissions'          = $_.Standard.GPOGroupedAccessEnum
                    'GPODistinguishedName' = $GPO.Path
                }
            }
        }
    } elseif ($OwnerOnly) {
        $GPOOutput = [PsCustomObject] @{
            'DisplayName'          = $XMLContent.GPO.Name
            'DomainName'           = $XMLContent.GPO.Identifier.Domain.'#text'
            'GUID'                 = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}'
            'Enabled'              = $Enabled
            'Owner'                = $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text'
            'OwnerSID'             = $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text'
            'OwnerType'            = $OwnerType
            'GPODistinguishedName' = $GPO.Path
        }
    } else {
        $GPOOutput = [PsCustomObject] @{
            'DisplayName'                       = $XMLContent.GPO.Name
            'DomainName'                        = $XMLContent.GPO.Identifier.Domain.'#text'
            'GUID'                              = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}'
            'Days'                              = (New-TimeSpan -Start ([DateTime] $XMLContent.GPO.ModifiedTime) -End (Get-Date)).Days
            'Empty'                             = $Empty
            'Linked'                            = $Linked
            'Enabled'                           = $EnabledBool
            'Optimized'                         = $Optimized
            'Problem'                           = $Problem
            'ApplyPermission'                   = $null
            'Exclude'                           = $Exclude
            'Description'                       = $GPO.Description
            'ComputerPolicies'                  = $XMLContent.GPO.Computer.ExtensionData.Name -join ", "
            'UserPolicies'                      = $XMLContent.GPO.User.ExtensionData.Name -join ", "
            'LinksCount'                        = $LinksTotalCount
            'LinksEnabledCount'                 = $LinksEnabledCount
            'LinksDisabledCount'                = $LinksDisabledCount
            'EnabledDetails'                    = $Enabled
            'ComputerProblem'                   = $ComputerProblem
            'ComputerOptimized'                 = $ComputerOptimized
            'UserProblem'                       = $UserProblem
            'UserOptimized'                     = $UserOptimized
            'ComputerSettingsAvailable'         = $ComputerSettingsAvailable
            'UserSettingsAvailable'             = $UserSettingsAvailable
            #'ComputerSettingsAvailableReal' = $ComputerSettingsAvailableReal
            #'UserSettingsAvailableReal' = $UserSettingsAvailableReal
            'ComputerSettingsTypes'             = $OutputComputer.Name -join ", "
            'UserSettingsTypes'                 = $OutputUser.Name -join ", "
            'ComputerEnabled'                   = $ComputerEnabled
            'UserEnabled'                       = $UserEnabled
            'ComputerSettingsStatus'            = if ($XMLContent.GPO.Computer.VersionDirectory -eq 0 -and $XMLContent.GPO.Computer.VersionSysvol -eq 0) { "NeverModified" } else { "Modified" }
            'ComputerSetttingsVersionIdentical' = if ($XMLContent.GPO.Computer.VersionDirectory -eq $XMLContent.GPO.Computer.VersionSysvol) { $true } else { $false }
            'ComputerSettings'                  = $XMLContent.GPO.Computer.ExtensionData.Extension
            'UserSettingsStatus'                = if ($XMLContent.GPO.User.VersionDirectory -eq 0 -and $XMLContent.GPO.User.VersionSysvol -eq 0) { "NeverModified" } else { "Modified" }
            'UserSettingsVersionIdentical'      = if ($XMLContent.GPO.User.VersionDirectory -eq $XMLContent.GPO.User.VersionSysvol) { $true } else { $false }
            'UserSettings'                      = $XMLContent.GPO.User.ExtensionData.Extension
            'NoSettings'                        = $NoSettings
            'CreationTime'                      = [DateTime] $XMLContent.GPO.CreatedTime
            'ModificationTime'                  = [DateTime] $XMLContent.GPO.ModifiedTime
            'ReadTime'                          = [DateTime] $XMLContent.GPO.ReadTime
            'WMIFilter'                         = $GPO.WmiFilter.name
            'WMIFilterDescription'              = $GPO.WmiFilter.Description
            'GPODistinguishedName'              = $GPO.Path
            'SDDL'                              = if ($Splitter -ne '') { $XMLContent.GPO.SecurityDescriptor.SDDL.'#text' -join $Splitter } else { $XMLContent.GPO.SecurityDescriptor.SDDL.'#text' }
            'Owner'                             = $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text'
            'OwnerSID'                          = $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text'
            'OwnerType'                         = $OwnerType
            'ACL'                               = @(
                [PsCustomObject] @{
                    'Name'           = $XMLContent.GPO.SecurityDescriptor.Owner.Name.'#text'
                    'Sid'            = $XMLContent.GPO.SecurityDescriptor.Owner.SID.'#text'
                    'PermissionType' = 'Allow'
                    'Inherited'      = $false
                    'Permissions'    = 'Owner'
                }
                $XMLContent.GPO.SecurityDescriptor.Permissions.TrusteePermissions | ForEach-Object -Process {
                    if ($_) {
                        [PsCustomObject] @{
                            'Name'           = $_.trustee.name.'#Text'
                            'Sid'            = $_.trustee.SID.'#Text'
                            'PermissionType' = $_.type.PermissionType
                            'Inherited'      = if ($_.Inherited -eq 'false') { $false } else { $true }
                            'Permissions'    = $_.Standard.GPOGroupedAccessEnum
                        }
                    }
                }
            )
            'Auditing'                          = if ($XMLContent.GPO.SecurityDescriptor.AuditingPresent.'#text' -eq 'true') { $true } else { $false }
            'Links'                             = $Links
            'LinksObjects'                      = $LinksObjects
            'GPOObject'                         = $GPO
        }
        if ($GPOOutput.ACL) {
            $GPOOutput.ApplyPermission = $false
            foreach ($Permission in $GPOOutput.ACL) {
                if ($Permission.Permissions -eq 'Apply Group Policy') {
                    $GPOOutput.ApplyPermission = $true
                }
            }
        }

    }
    if ($PermissionsOnly -or $OwnerOnly) {
        $GPOOutput
    } else {
        if (-not $Type -or $Type -contains 'All') {
            $GPOOutput
        } else {
            if ($Type -contains 'Empty') {
                if ($GPOOutput.Empty -eq $true) {
                    $GPOOutput
                }
            }
            if ($Type -contains 'Unlinked') {
                if ($GPOOutput.Linked -eq $false) {
                    $GPOOutput
                }
            }
            if ($Type -contains 'Disabled') {
                if ($GPOOutput.Enabled -eq $false) {
                    $GPOOutput
                }
            }
            if ($Type -contains 'NoApplyPermission') {
                if ($GPOOutput.ApplyPermission -eq $false) {
                    $GPOOutput
                }
            }
        }
    }
}
function Get-XMLNestedRegistry {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [System.Xml.XmlElement[]] $DataSet,
        [string] $Collection,
        [switch] $Limited
    )
    if ($DataSet.Properties) {
        $Registry = $DataSet
        foreach ($Registry in $DataSet) {
            if ($Registry.Properties) {
                if ($Limited) {
                    [PSCustomObject] @{
                        Collection      = $Collection
                        Description     = $Registry.descr
                        Changed         = try { [DateTime] $Registry.changed } catch { $Registry.changed };
                        Disabled        = if ($Registry.disabled -eq '1') { $true } else { $false };
                        GPOSettingOrder = [int] $Registry.GPOSettingOrder
                        Action          = $Script:Actions[$Registry.Properties.action]
                        DisplayDecimal  = if ($Registry.Properties.displayDecimal -eq '1') { $true } else { $false };
                        Default         = if ($Registry.Properties.default -eq '1') { $true } else { $false };
                        Hive            = $Registry.Properties.hive #: HKEY_LOCAL_MACHINE
                        Key             = $Registry.Properties.key  #: SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
                        Name            = $Registry.Properties.name #: AutoAdminLogon
                        Type            = $Registry.Properties.type #: REG_SZ
                        Value           = $Registry.Properties.value #
                        Filters         = $Registry.Filters
                        BypassErrors    = if ($Registry.bypassErrors -eq '1') { $true } else { $false };
                    }
                } else {
                    $CreateGPO = [ordered]@{
                        DisplayName     = $GPO.DisplayName
                        DomainName      = $GPO.DomainName
                        GUID            = $GPO.GUID
                        GpoType         = $GPO.GpoType
                        #GpoCategory = $GPOEntry.GpoCategory
                        #GpoSettings = $GPOEntry.GpoSettings
                        Collection      = $Collection
                        Description     = $Registry.descr
                        Changed         = try { [DateTime] $Registry.changed } catch { $Registry.changed };
                        Disabled        = if ($Registry.disabled -eq '1') { $true } else { $false };
                        GPOSettingOrder = [int] $Registry.GPOSettingOrder
                        Action          = $Script:Actions[$Registry.Properties.action]
                        DisplayDecimal  = if ($Registry.Properties.displayDecimal -eq '1') { $true } else { $false };
                        Default         = if ($Registry.Properties.default -eq '1') { $true } else { $false };
                        Hive            = $Registry.Properties.hive #: HKEY_LOCAL_MACHINE
                        Key             = $Registry.Properties.key  #: SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
                        Name            = $Registry.Properties.name #: AutoAdminLogon
                        Type            = $Registry.Properties.type #: REG_SZ
                        Value           = $Registry.Properties.value #
                        Filters         = $Registry.Filters
                        BypassErrors    = if ($Registry.bypassErrors -eq '1') { $true } else { $false };
                    }
                    $CreateGPO['Linked'] = $GPO.Linked
                    $CreateGPO['LinksCount'] = $GPO.LinksCount
                    $CreateGPO['Links'] = $GPO.Links
                    [PSCustomObject] $CreateGPO
                }
            }
        }
    }
    foreach ($Name in @('Registry', 'Collection')) {
        foreach ($Registry in $DataSet.$Name) {
            if ($Registry.Properties) {
                if ($Limited) {
                    [PSCustomObject] @{
                        Collection      = $Collection
                        Description     = $Registry.descr
                        Changed         = try { [DateTime] $Registry.changed } catch { $Registry.changed };
                        Disabled        = if ($Registry.disabled -eq '1') { $true } else { $false };
                        GPOSettingOrder = [int] $Registry.GPOSettingOrder
                        Action          = $Script:Actions[$Registry.Properties.action]
                        DisplayDecimal  = if ($Registry.Properties.displayDecimal -eq '1') { $true } else { $false };
                        Default         = if ($Registry.Properties.default -eq '1') { $true } else { $false };
                        Hive            = $Registry.Properties.hive #: HKEY_LOCAL_MACHINE
                        Key             = $Registry.Properties.key  #: SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
                        Name            = $Registry.Properties.name #: AutoAdminLogon
                        Type            = $Registry.Properties.type #: REG_SZ
                        Value           = $Registry.Properties.value #
                        Filters         = $Registry.Filters
                        BypassErrors    = if ($Registry.bypassErrors -eq '1') { $true } else { $false };
                    }
                } else {
                    $CreateGPO = [ordered]@{
                        DisplayName     = $GPO.DisplayName
                        DomainName      = $GPO.DomainName
                        GUID            = $GPO.GUID
                        GpoType         = $GPO.GpoType
                        #GpoCategory = $GPOEntry.GpoCategory
                        #GpoSettings = $GPOEntry.GpoSettings
                        Collection      = $Collection
                        Description     = $Registry.descr
                        Changed         = try { [DateTime] $Registry.changed } catch { $Registry.changed };
                        Disabled        = if ($Registry.disabled -eq '1') { $true } else { $false };
                        GPOSettingOrder = [int] $Registry.GPOSettingOrder
                        Action          = $Script:Actions[$Registry.Properties.action]
                        DisplayDecimal  = if ($Registry.Properties.displayDecimal -eq '1') { $true } else { $false }; ;
                        Default         = if ($Registry.Properties.default -eq '1') { $true } else { $false };
                        Hive            = $Registry.Properties.hive #: HKEY_LOCAL_MACHINE
                        Key             = $Registry.Properties.key  #: SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
                        Name            = $Registry.Properties.name #: AutoAdminLogon
                        Type            = $Registry.Properties.type #: REG_SZ
                        Value           = $Registry.Properties.value #
                        Filters         = $Registry.Filters
                        BypassErrors    = if ($Registry.bypassErrors -eq '1') { $true } else { $false };
                    }
                    $CreateGPO['Linked'] = $GPO.Linked
                    $CreateGPO['LinksCount'] = $GPO.LinksCount
                    $CreateGPO['Links'] = $GPO.Links
                    [PSCustomObject] $CreateGPO
                }
            } else {
                if ($Registry.Registry) {
                    #if ($Registry.Name.Count -gt 1) {
                    #Write-Verbose "Registry Name count more than 1"
                    #}
                    $TempCollection = $Collection
                    if ($Collection) {
                        $Collection = "$Collection/$($Registry.name)"
                    } else {
                        $Collection = $Registry.name
                    }
                    Get-XMLNestedRegistry -GPO $GPO -DataSet $Registry.Registry -Collection $Collection
                    $Collection = $TempCollection
                }
                if ($Registry.Collection) {
                    $TempCollection = $Collection
                    #if ($Registry.Collection.Count -gt 1) {
                    # Write-Verbose "Registry collection count more than 1"
                    #}
                    foreach ($MyCollection in $Registry.Collection) {
                        if ($Collection) {
                            #Write-Verbose "Collection1: $Collection - $($Registry.name) - $($MyCollection.name) - $($($MyCollection.name).Count)"
                            $Collection = "$Collection/$($Registry.name)/$($MyCollection.name)"
                            #Write-Verbose "Collection2: $Collection"
                        } else {
                            #Write-Verbose "Collection3: $Collection - $($Registry.name) - $($MyCollection.name)"
                            $Collection = "$($Registry.name)/$($MyCollection.name)"
                            #Write-Verbose "Collection4: $Collection"
                        }

                        Get-XMLNestedRegistry -GPO $GPO -DataSet $MyCollection -Collection $Collection
                        $Collection = $TempCollection
                    }
                }
            }
        }

    }
}
$GPOZaurrAnalysis = [ordered] @{
    Name           = 'Group Policy Content'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Invoke-GPOZaurrContent -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {

    }
    Variables      = @{

    }
    Overview       = {

    }
    Solution       = {
        foreach ($Key in $Script:Reporting['GPOAnalysis']['Data'].Keys) {
            New-HTMLTab -Name $Key {
                New-HTMLTable -DataTable $Script:Reporting['GPOAnalysis']['Data'][$Key] -Filtering -Title $Key
            }
        }
        if ($Script:Reporting['GPOAnalysis']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOAnalysis']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrBlockedInheritance = [ordered] @{
    Name           = 'Group Policy Blocked Inhertiance'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        if ($Script:Reporting['GPOBlockedInheritance']['Exclusions']) {
            Get-GPOZaurrInheritance -IncludeBlockedObjects -IncludeExcludedObjects -OnlyBlockedInheritance -IncludeGroupPoliciesForBlockedObjects -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $Excludeomains -Exclusions $Script:Reporting['GPOBlockedInheritance']['Exclusions']
        } else {
            Get-GPOZaurrInheritance -IncludeBlockedObjects -IncludeExcludedObjects -OnlyBlockedInheritance -IncludeGroupPoliciesForBlockedObjects -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $Excludeomains
        }
    }
    Processing     = {
        foreach ($GPO in $Script:Reporting['GPOBlockedInheritance']['Data']) {
            if (-not $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmlessPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmlessPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'][$GPO.DomainName] = 0
            }
            if ($GPO.Exclude -eq $true) {
                $Script:Reporting['GPOBlockedInheritance']['Variables']['Exclude']++
                $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedExclude'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedExclude'] + $GPO.UsersCount
                $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedExclude'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedExclude'] + $GPO.ComputersCount
            } else {
                $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffected'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffected'] + $GPO.UsersCount
                $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffected'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffected'] + $GPO.ComputersCount
            }
            $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedIncludingExclude'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedIncludingExclude'] + $GPO.UsersCount
            $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedIncludingExclude'] = $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedIncludingExclude'] + $GPO.ComputersCount

            if ($GPO.Exclude -eq $false -and ($GPO.UsersCount -gt 0 -or $GPO.ComputersCount -gt 0)) {
                $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigation']++
                $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'][$GPO.DomainName]++
            }
            if ($GPO.Exclude -eq $false -and ($GPO.UsersCount -eq 0 -and $GPO.ComputersCount -eq 0)) {
                $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmless']++
                $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmlessPerDomain'][$GPO.DomainName]++
            }
            # add gpo from blocked inheritance to create additional table
            #foreach ($GpoBlocked in $Script:Reporting['GPOBlockedInheritance']['Data'].GroupPolicies) {
            #$Script:Reporting['GPOBlockedInheritance']['Variables']['GroupPolicies'].Add($GpoBlocked)
            #}
        }
        if ($Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigation'] -gt 0 -or $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmless'] -gt 0) {
            $Script:Reporting['GPOBlockedInheritance']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOBlockedInheritance']['ActionRequired'] = $false
        }
    }
    Resources      = @(
        'http://www.firewall.cx/microsoft-knowledgebase/windows-2012/1056-windows-2012-group-policy-enforcement.html'
    )
    Variables      = @{
        Total                             = 0
        Exclude                           = 0
        RequiresInvesigation              = 0
        RequiresInvesigationPerDomain     = [ordered] @{}
        DeletionHarmless                  = 0
        DeletionHarmlessPerDomain         = [ordered] @{}
        UsersAffected                     = 0
        UsersAffectedExclude              = 0
        UsersAffectedIncludingExclude     = 0
        ComputersAffected                 = 0
        ComputersAffectedIncludingExclude = 0
        ComputersAffectedExclude          = 0
        GroupPolicies                     = [System.Collections.Generic.List[PSCustomObject]]::new()
    }
    Overview       = {

    }
    Summary        = {
        New-HTMLText -FontSize 10pt -TextBlock {
            "By default, group policy settings that are linked to parent objects are inherited to the child objects in the active directory hierarchy. "
            "By default, Default Domain Policy is linked to the domain and is inherited to all the child objects of the domain hierarchy. "
            "So does any other policies linked to the top level OU's. "
        }
        New-HTMLText -Text "Blocked Inheritance" -FontSize 10pt -FontWeight bold
        New-HTMLText -FontSize 10pt -Text @(
            "As GPOs can be inherited by default, they can also be blocked, if required using the Block Inheritance. "
            "If the Block Inheritance setting is enabled, the inheritance of group policy setting is blocked. "
            "This setting is mostly used when the OU contains users or computers that require different settings than what is applied to the domain level. "
            "Unfortunetly blocking inheritance can have serious security consequences. "
        )
        New-HTMLText -Text @(
            'As it stands currently there are ',
            $Script:Reporting['GPOBlockedInheritance']['Data'].Count,
            ' organisational units with '
            'GPO Inheritance Block'
            ' out of which '
            $Script:Reporting['GPOBlockedInheritance']['Variables']['Exclude'],
            ' are marked as Excluded '
            '(approved by IT). '
        ) -FontSize 10pt -FontWeight normal, bold, normal, bold, normal, bold, normal, bold -LineBreak
        if ($Script:Reporting['GPOBlockedInheritance']['Data'].Count -ne 0) {
            New-HTMLText -Text 'Users & Computers affected by inheritance blocks:' -FontSize 10pt -FontWeight bold
            New-HTMLList -Type Unordered {
                New-HTMLListItem -Text $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffected'], ' users affected due to inheritance blocks' -FontWeight bold, normal
                New-HTMLListItem -Text $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedExclude'], ' users affected, but approved/excluded, due to inheritance blocks' -FontWeight bold, normal
                New-HTMLListItem -Text $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffected'], ' computers affected due to inheritance blocks' -FontWeight bold, normal
                New-HTMLListItem -Text $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedExclude'], ' computers affected, but approved/excluded, due to inheritance blocks' -FontWeight bold, normal
            } -FontSize 10pt

            New-HTMLText -Text 'Following domains require:' -FontSize 10pt -FontWeight bold
            New-HTMLList -Type Unordered {
                foreach ($Domain in $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'].Keys) {
                    New-HTMLListItem -Text "$Domain proposes ", $Script:Reporting['GPOBlockedInheritance']['Variables']['RequiresInvesigationPerDomain'][$Domain], " investigation (computers or users inside)." -FontWeight normal, bold, normal
                    New-HTMLListItem -Text "$Domain proposes ", $Script:Reporting['GPOBlockedInheritance']['Variables']['DeletionHarmlessPerDomain'][$Domain], " removal (mostly harmless due to no computers or users inside)." -FontWeight normal, bold, normal
                }
            } -FontSize 10pt
        }
        New-HTMLText -FontSize 10pt -Text "Please review output in table and follow the steps below table to get Active Directory Group Policies in healthy state."

        if ($Script:Reporting['GPOBlockedInheritance']['Exclusions']) {
            New-HTMLText -LineBreak
            New-HTMLText -Text @(
                "While preparing this report following exclusions were defined. "
                "Please make sure that when you execute your steps to include those exclusions to prevent any issues. "
            ) -FontSize 10pt -FontWeight bold, normal -Color Red, None -LineBreak

            New-HTMLText -Text "Code to use for exclusions: " -FontSize 10pt -FontWeight bold -LineBreak

            $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOBlockedInheritance']['Exclusions']

            if ($Code) {
                New-HTMLCodeBlock -Code $Code -Style powershell
            }
        }
    }
    Solution       = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOBlockedInheritance']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartLegend -Names 'Affected', 'Affected, but excluded' -Color Salmon, PaleGreen
                    New-ChartBarOptions -Type barStacked
                    New-ChartBar -Name 'Users' -Value $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffected'], $Script:Reporting['GPOBlockedInheritance']['Variables']['UsersAffectedExclude']
                    New-ChartBar -Name 'Computers' -Value $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffected'], $Script:Reporting['GPOBlockedInheritance']['Variables']['ComputersAffectedExclude']
                } -Title 'Users & Computers affected due to blocked inheritance' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Organizational Units with Group Policy Blocked Inheritance' {
            New-HTMLTable -DataTable $Script:Reporting['GPOBlockedInheritance']['Data'] -Filtering {
                New-TableEvent -TableID 'TableWithGroupPoliciesBlockedInheritance' -SourceColumnID 8 -TargetColumnID 9
                New-HTMLTableCondition -Name 'Exclude' -Value $true -BackgroundColor DeepSkyBlue -ComparisonType string -Row
                New-TableConditionGroup {
                    New-TableCondition -Name 'BlockedInheritance' -Value $true
                    New-TableCondition -Name 'Exclude' -Value $false
                } -BackgroundColor Salmon -FailBackgroundColor SpringGreen -HighlightHeaders 'BlockedInheritance', 'Exclude'
                New-TableConditionGroup {
                    New-TableCondition -Name 'UsersCount' -Value 0
                    New-TableCondition -Name 'ComputersCount' -Value 0
                } -BackgroundColor Salmon -FailBackgroundColor Amber -HighlightHeaders 'UsersCount', 'ComputersCount'
                New-TableColumnOption -Hidden $true -ColumnIndex 8
            } -PagingOptions 5, 10, 20, 30, 40, 50 -ExcludeProperty GroupPolicies
        }
        New-HTMLSection -Name 'Group Policies affecting objects in Organizational Units with Blocked Inheritance' {
            New-HTMLTable -DataTable $Script:Reporting['GPOBlockedInheritance']['Data'].GroupPolicies -Filtering {
                New-TableCondition -Name 'Enabled' -Value $true -BackgroundColor SpringGreen -FailBackgroundColor Salmon
                New-TableCondition -Name 'Enforced' -Value $true -BackgroundColor Amber -FailBackgroundColor AirForceBlue
                New-TableCondition -Name 'LinkedDirectly' -Value $true -BackgroundColor Amber -FailBackgroundColor AirForceBlue
            } -PagingOptions 5, 10, 20, 30, 40, 50 -DataTableID 'TableWithGroupPoliciesBlockedInheritance'
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix - Organizational Units with Group Policy Blocked Inheritance' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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."
                            }
                            if ($Script:Reporting['GPOBlockedInheritance']['Exclusions']) {
                                New-HTMLWizardStep -Name 'Required exclusions' {
                                    New-HTMLText -Text @(
                                        "While preparing this report following exclusions were defined. "
                                        "Please make sure that when you execute your steps to include those exclusions to prevent any issues. "
                                    )
                                    $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOBlockedInheritance']['Exclusions']

                                    if ($Code) {
                                        New-HTMLCodeBlock -Code $Code -Style powershell
                                    }
                                }
                            }
                            New-HTMLWizardStep -Name 'Prepare report' {
                                New-HTMLText -Text @(
                                    "Depending when this report was run you may want to prepare new report before proceeding removing Group Policy Inheritance Blocks. "
                                    "Please keep in mind that if exclusions for some Organizational OU's were defined you need to pass them to cmdlet below to not remove approved GPO Inheritance Blocks. "
                                    "To generate new report please use:"
                                )
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBlockedGPOInheritanceBefore.html -Verbose -Type GPOBlockedInheritance
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $GPOOutput = Get-GPOZaurrInheritance -IncludeBlockedObjects -IncludeExcludedObjects -OnlyBlockedInheritance
                                    $GPOOutput | 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 OU GPO Inheritance Blocks' {
                                New-HTMLText -Text @(
                                    "Removing inheritance blocks is quite trivial and can be done from GPO GUI. However knowing when to remove is the important part. "
                                    "Please consult other Domain Admins before removing any inheritance blocks, and either approve exclusion or remove blocking inheritance. "
                                )
                            }
                            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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBlockedGPOInheritanceAfter.html -Verbose -Type GPOBlockedInheritance
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOBlockedInheritance']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOBlockedInheritance']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrBrokenLink = [ordered] @{
    Name           = 'Group Policy Broken Links'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Get-GPOZaurrBrokenLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {
        $Script:Reporting['GPOBrokenLink']['Variables']['RequireDeletion'] = $Script:Reporting['GPOBrokenLink']['Data'].Count
        $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'] = @{}
        $Script:Reporting['GPOBrokenLink']['Variables']['Unique'] = @{}
        foreach ($Link In $Script:Reporting['GPOBrokenLink']['Data']) {
            $DomainName = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Link.DistinguishedName
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName]) {
                $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName] = 0
            }
            $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$DomainName]++
            # Lets do unique OU counting
            $Script:Reporting['GPOBrokenLink']['Variables']['Unique'][$Link.CanonicalName] = $Link
        }
        $Script:Reporting['GPOBrokenLink']['Variables']['UniqueObjects'] = $Script:Reporting['GPOBrokenLink']['Variables']['Unique'].Keys

        if ($Script:Reporting['GPOBrokenLink']['Data'].Count -gt 0) {
            $Script:Reporting['GPOBrokenLink']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOBrokenLink']['ActionRequired'] = $false
        }
    }
    Variables      = @{
        RequireDeletion  = 0
        WillFixPerDomain = $null
        UniqueObjects    = $null
        Unique           = $null
    }
    Overview       = {

    }
    Summary        = {
        New-HTMLText -FontSize 10pt -TextBlock {
            "When GPO is deleted correctly, it usually is removed from AD, SYSVOL, and any link to it is also discarded. "
            "Unfortunately, this is true only if the GPO is created and linked within the same domain. "
            "If GPO is linked in another domain, this leaves a broken link hanging on before it was linked. "
            "Additionally, the Remove-GPO cmdlet doesn't handle site link deletions, which causes dead links to be stuck on sites until those are manually deleted. "
            "This means that any GPOs deleted using PowerShell may leave a trail."
        }
        New-HTMLText -Text @(
            'As it stands currently there are ',
            $Script:Reporting['GPOBrokenLink']['Data'].Count,
            ' broken links that need to be deleted over '
            $Script:Reporting['GPOBrokenLink']['Variables']['UniqueObjects'].Count,
            ' unique objects. '
        ) -FontSize 10pt -FontWeight normal, bold, normal, bold, normal -LineBreak
        if ($Script:Reporting['GPOBrokenLink']['Data'].Count -ne 0) {
            New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
            New-HTMLList -Type Unordered {
                foreach ($Domain in $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'].Keys) {
                    New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOBrokenLink']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
                }
            } -FontSize 10pt
        }
    }
    Solution       = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOBrokenLink']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartLegend -Names 'Bad' -Color Salmon
                    New-ChartBar -Name 'Broken Links' -Value $Script:Reporting['GPOBrokenLink']['Data'].Count
                } -Title 'Broken Links' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy Broken Links' {
            New-HTMLTable -DataTable $Script:Reporting['GPOBrokenLink']['Data'] -Filtering {

            } -PagingOptions 10, 20, 30, 40, 50
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to remove Broken Links' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 GPO links. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenLinkBefore.html -Verbose -Type GPOBrokenLink
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $GPOOutput = Get-GPOZaurrBrokenLink -Verbose
                                    $GPOOutput | 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 Broken Links' {
                                New-HTMLText -Text "Following command when executed, runs internally command that lists all broken links. After finding them all it delets them according to given criteria. "
                                New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black

                                New-HTMLCodeBlock -Code {
                                    Repair-GPOZaurrBrokenLink -WhatIf -Verbose
                                }
                                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 changed matches expected data. Once happy with results please follow with command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Repair-GPOZaurrBrokenLink -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed removes only first X number of links. Keep in mind that 5 broken links on a single Organizational Unit are treated as one. Use LimitProcessing parameter to prevent mass delete 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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenLinkAfter.html -Verbose -Type GPOBrokenLink
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOBrokenLink']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOBrokenLink']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrConsistency = [ordered] @{
    Name           = 'GPO Permissions Consistency'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Get-GPOZaurrPermissionConsistency -Type All -VerifyInheritance -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {
        foreach ($GPO in $Script:Reporting['GPOConsistency']['Data']) {
            if ($GPO.ACLConsistent -eq $true) {
                $Script:Reporting['GPOConsistency']['Variables']['Consistent']++
            } else {
                $Script:Reporting['GPOConsistency']['Variables']['Inconsistent']++
            }
            if ($GPO.ACLConsistentInside -eq $true) {
                $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside']++
            } else {
                $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside']++
            }
        }
        if ($Script:Reporting['GPOConsistency']['Variables']['Inconsistent'] -gt 0 -or $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside'] -gt 0 ) {
            $Script:Reporting['GPOConsistency']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOConsistency']['ActionRequired'] = $false
        }
    }
    Variables      = @{
        Consistent         = 0
        Inconsistent       = 0
        ConsistentInside   = 0
        InconsistentInside = 0
    }
    Overview       = {
        New-HTMLPanel {
            New-HTMLText -Text 'Following chart presents ', 'permissions consistency between Active Directory and SYSVOL for Group Policies' -FontSize 10pt -FontWeight normal, bold
            New-HTMLList -Type Unordered {
                New-HTMLListItem -Text 'Top level permissions consistency: ', $Script:Reporting['GPOConsistency']['Variables']['Consistent'] -FontWeight normal, bold
                New-HTMLListItem -Text 'Inherited permissions consistency: ', $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside'] -FontWeight normal, bold
                New-HTMLListItem -Text 'Inconsistent top level permissions: ', $Script:Reporting['GPOConsistency']['Variables']['Inconsistent'] -FontWeight normal, bold
                New-HTMLListItem -Text "Inconsistent inherited permissions: ", $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside'] -FontWeight normal, bold
            } -FontSize 10pt
            New-HTMLText -FontSize 10pt -Text 'Having incosistent permissions on AD in comparison to those on SYSVOL can lead to uncontrolled ability to modify them.'
            New-HTMLChart {
                New-ChartLegend -Names 'Bad', 'Good' -Color PaleGreen, Salmon
                New-ChartBarOptions -Type barStacked
                New-ChartLegend -Name 'Consistent', 'Inconsistent'
                New-ChartBar -Name 'TopLevel' -Value $Script:Reporting['GPOConsistency']['Variables']['Consistent'], $Script:Reporting['GPOConsistency']['Variables']['Inconsistent']
                New-ChartBar -Name 'Inherited' -Value $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside'], $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside']
            } -Title 'Permissions Consistency' -TitleAlignment center
        }
    }
    Summary        = {
        New-HTMLText -FontSize 10pt -TextBlock {
            "When GPO is created, it creates an entry in Active Directory (metadata) and SYSVOL (content). "
            "Two different places mean two different sets of permissions. "
            "The group Policy module is making sure the data in both places is correct. "
            "However, it's not necessarily the case for different reasons, and often permissions go out of sync between AD and SYSVOL. "
            "This test verifies the consistency of policies between AD and SYSVOL in two ways. "
            "It checks top-level permissions for a GPO and then checks if all files within said GPO is inheriting permissions or have different permissions in place."
        }
        New-HTMLText -Text 'Following list presents ', 'permissions consistency between Active Directory and SYSVOL for Group Policies' -FontSize 10pt -FontWeight normal, bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Top level permissions consistency: ', $Script:Reporting['GPOConsistency']['Variables']['Consistent'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Inherited permissions consistency: ', $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Inconsistent top level permissions: ', $Script:Reporting['GPOConsistency']['Variables']['Inconsistent'] -FontWeight normal, bold
            New-HTMLListItem -Text "Inconsistent inherited permissions: ", $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -FontSize 10pt -Text 'Having incosistent permissions on AD in comparison to those on SYSVOL can lead to uncontrolled ability to modify them. Please notice that if ', `
            ' Not available ', 'is visible in the table you should first fix related, more pressing issue, before fixing permissions inconsistency.' -FontWeight normal, bold, normal
    }
    Solution       = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOConsistency']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'Consistent', 'Inconsistent' -Color PaleGreen, Salmon
                    New-ChartBar -Name 'TopLevel' -Value $Script:Reporting['GPOConsistency']['Variables']['Consistent'], $Script:Reporting['GPOConsistency']['Variables']['Inconsistent']
                    New-ChartBar -Name 'Inherited' -Value $Script:Reporting['GPOConsistency']['Variables']['ConsistentInside'], $Script:Reporting['GPOConsistency']['Variables']['InconsistentInside']
                } -Title 'Permissions Consistency' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy Permissions Consistency' {
            New-HTMLTable -DataTable $Script:Reporting['GPOConsistency']['Data'] -Filtering {
                New-HTMLTableCondition -Name 'ACLConsistent' -Value $false -BackgroundColor Salmon -TextTransform capitalize -ComparisonType string
                New-HTMLTableCondition -Name 'ACLConsistentInside' -Value $false -BackgroundColor Salmon -TextTransform capitalize -ComparisonType string
                New-HTMLTableCondition -Name 'ACLConsistent' -Value $true -BackgroundColor PaleGreen -TextTransform capitalize -ComparisonType string
                New-HTMLTableCondition -Name 'ACLConsistentInside' -Value $true -BackgroundColor PaleGreen -TextTransform capitalize -ComparisonType string
                New-HTMLTableCondition -Name 'ACLConsistent' -Value 'Not available' -BackgroundColor Crimson -ComparisonType string
                New-HTMLTableCondition -Name 'ACLConsistentInside' -Value 'Not available' -BackgroundColor Crimson -ComparisonType string
            } -PagingOptions 10, 20, 30, 40, 50
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix - Permissions Consistency' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrPermissionsInconsistentBefore.html -Verbose -Type GPOConsistency
                                }
                                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 {
                                    $GPOOutput = Get-GPOZaurrPermissionConsistency
                                    $GPOOutput | 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 inconsistent permissions' {
                                New-HTMLText -Text "Following command when executed fixes inconsistent permissions."
                                New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black
                                New-HTMLText -Text "Make sure to fill in TargetDomain to match your Domain Admin permission account"

                                New-HTMLCodeBlock -Code {
                                    Repair-GPOZaurrPermissionConsistency -IncludeDomains "TargetDomain" -Verbose -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 deleted matches expected data. Once happy with results please follow with command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Repair-GPOZaurrPermissionConsistency -LimitProcessing 2 -IncludeDomains "TargetDomain"
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed repairs only first X inconsistent permissions. 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-HTMLText -Text "If there's nothing else to be fixed, we can skip to next step step"
                            }
                            New-HTMLWizardStep -Name 'Fix inconsistent downlevel permissions' {
                                New-HTMLText -Text @(
                                    "Unfortunetly this step is manual until automation is developed. "
                                    "If there are inconsistent permissions found inside GPO one has to fix them manually by going into SYSVOL and making sure inheritance is enabled, and that permissions are consistent across all files."
                                    "Please keep in mind that it's possible inconsistent downlevel permissions fix will not be required once the top level fix is applied. "
                                    "Rerun report to find out if you've just fixed top-level permissions. "
                                )
                            }
                            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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrPermissionsInconsistentAfter.html -Verbose -Type GPOConsistency
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOConsistency']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOConsistency']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrDuplicates = [ordered] @{
    Name       = 'Duplicate (CNF) Group Policies'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        Get-GPOZaurrDuplicateObject -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing = {
        $Script:Reporting['GPODuplicates']['Variables']['RequireDeletion'] = $Script:Reporting['GPODuplicates']['Data'].Count
        if ($Script:Reporting['GPODuplicates']['Data'].Count -gt 0) {
            $Script:Reporting['GPODuplicates']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPODuplicates']['ActionRequired'] = $false
        }
    }
    Variables  = @{
        RequireDeletion = 0
    }
    Overview   = {

    }
    Resources  = @(
        'https://social.technet.microsoft.com/wiki/contents/articles/15435.active-directory-duplicate-object-name-resolution.aspx'
        'https://kickthatcomputer.wordpress.com/2014/11/22/seek-and-destroy-duplicate-ad-objects-with-cnf-in-the-name/'
    )
    Summary    = {
        New-HTMLText -FontSize 10pt -TextBlock {
            "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. "
        }
        New-HTMLText -Text 'As it stands currently there are ', $Script:Reporting['GPODuplicates']['Data'].Count, ' CNF (Duplicate) Group Policy objects to be deleted.' -FontSize 10pt -FontWeight normal, bold, normal
    }
    Solution   = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPODuplicates']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartLegend -Names 'Bad' -Color Salmon
                    New-ChartBar -Name 'Duplicate (CNF) object' -Value $Script:Reporting['GPODuplicates']['Data'].Count
                } -Title 'Duplicate (CNF) Objects' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy CNF (Duplicate) Objects' {
            New-HTMLTable -DataTable $Script:Reporting['GPODuplicates']['Data'] -Filtering {

            } -PagingOptions 10, 20, 30, 40, 50
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix - Remove duplicate (CNF) objects' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 duplicate GPO objects. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrDuplicateObjectsBefore.html -Verbose -Type GPODuplicates
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $GPOOutput = Get-GPOZaurrDuplicateObject
                                    $GPOOutput | 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 CNF objects' {
                                New-HTMLText -Text "Following command when executed, runs internally command that lists all duplicate objects."
                                New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black

                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrDuplicateObject -WhatIf -Verbose
                                }
                                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 changed matches expected data. Once happy with results please follow with command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrDuplicateObject -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed removes only first X duplicate objects. Use LimitProcessing parameter to prevent mass delete 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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrDuplicateObjectsAfter.html -Verbose -Type GPODuplicates
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPODuplicates']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPODuplicates']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrFiles = [ordered] @{
    Name           = 'SYSVOL (NetLogon) Files List'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Get-GPOZaurrFiles -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {

    }
    Variables      = @{

    }
    Overview       = {

    }
    Solution       = {
        New-HTMLTable -DataTable $Script:Reporting['GPOFiles']['Data'] -Filtering
        if ($Script:Reporting['GPOFiles']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOFiles']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrGPOUpdates = [ordered] @{
    Name       = 'Group Policies added last 7 days'
    Enabled    = $false
    Action     = $null
    Data       = $null
    Execute    = {
        Get-GPOZaurrUpdates -DateRange Last7Days -DateProperty WhenCreated -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing = {
        foreach ($GPO in $Script:Reporting['GPOUpdates']['Data']) {

            $Script:Reporting['GPOUpdates']['Variables']['GPOTotal']++

            if ($GPO.LinksEnabledCount -eq 0) {
                $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutEnabledLinks']++
            } else {
                $Script:Reporting['GPOUpdates']['Variables']['GPOWithEnabledLinks']++
            }
            if ($GPO.AffectedCount -eq 0) {
                $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutAffectedObjects']++
            }
        }
    }
    Variables  = @{
        GPOTotal                  = 0
        GPOWithoutEnabledLinks    = 0
        GPOWithEnabledLinks       = 0
        GPOWithoutAffectedObjects = 0
    }
    Overview   = {

    }
    Summary    = {
        New-HTMLText -TextBlock {
            "Group Policies are important part of Active Directory. Knowing when those are created and what they affect is important part of admins work."
            "This report shows which GPOs were created in last 7 days and how many objects those are affecting."
        } -FontSize 10pt -LineBreak
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies added in last 7 days: ', $Script:Reporting['GPOUpdates']['Variables']['GPOTotal'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Group Policies without enabled links: ', $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutEnabledLinks'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Group Policies with enabled links: ', $Script:Reporting['GPOUpdates']['Variables']['GPOWithEnabledLinks'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Group Policies without affected objects: ', $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutAffectedObjects'] -FontWeight normal, bold

        } -FontSize 10pt
        New-HTMLText -TextBlock {
            "If you notice any GPO that is not working or against best practices please reach out to your collegues to confirm whether this is as expected."
        } -FontSize 10pt -LineBreak
    }
    Solution   = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOUpdates']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'No enabled links', 'Enabled links' -Color Crimson, MediumOrchid
                    New-ChartBar -Name 'Links enabled' -Value $Script:Reporting['GPOUpdates']['Variables']['GPOWithoutEnabledLinks'], $Script:Reporting['GPOUpdates']['Variables']['GPOWithEnabledLinks']
                } -Title 'Group Policies created last 7 days' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policies added in last 7 days' {
            New-HTMLTable -DataTable $Script:Reporting['GPOUpdates']['Data'] -Filtering {
                #New-HTMLTableCondition -Name 'LinksCount' -Value 0 -BackgroundColor Salmon -ComparisonType number
                #New-HTMLTableCondition -Name 'LinksEnabledCount' -Value 0 -BackgroundColor Salmon -ComparisonType number
                New-HTMLTableCondition -Name 'AffectedCount' -Value 0 -BackgroundColor Salmon -ComparisonType number -FailBackgroundColor Goldenrod
                New-HTMLTableConditionGroup {
                    New-HTMLTableCondition -Name 'LinksCount' -Value 0 -ComparisonType number
                    New-HTMLTableCondition -Name 'LinksEnabledCount' -Value 0 -ComparisonType number
                } -BackgroundColor Salmon -FailBackgroundColor Goldenrod -Logic OR -HighlightHeaders 'LinksCount', 'LinksEnabledCount', 'DisplayName', 'DomainName'
            }
        }
        if ($Script:Reporting['GPOUpdates']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOUpdates']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrLinks = [ordered] @{
    Name           = 'Group Policy Links'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Get-GPOZaurrLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Summary
    }
    Processing     = {

    }
    Variables      = @{

    }
    Overview       = {

    }
    Solution       = {
        New-HTMLTable -DataTable $Script:Reporting['GPOLinks']['Data'] -Filtering
        if ($Script:Reporting['GPOLinks']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOLinks']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrList = [ordered] @{
    Name       = 'Group Policy Summary'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        if ($Script:Reporting['GPOList']['Exclusions']) {
            Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeGroupPolicies $Script:Reporting['GPOList']['Exclusions']
        } else {
            Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
        }
    }
    Processing = {
        # Create Per Domain Variables
        $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'] = @{}
        $Script:Reporting['GPOList']['Variables']['GPOValidPerDomain'] = @{}
        $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'] = @{}
        $Script:Reporting['GPOList']['Variables']['GPOOptimizedPerDomain'] = @{}
        $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'] = @{}
        $Script:Reporting['GPOList']['Variables']['GPONoProblemPerDomain'] = @{}
        $Script:Reporting['GPOList']['Variables']['GPOApplyPermissionYesPerDomain'] = @{}
        $Script:Reporting['GPOList']['Variables']['GPOApplypermissionNoPerDomain'] = @{}
        foreach ($GPO in $Script:Reporting['GPOList']['Data']) {
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOList']['Variables']['GPOValidPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOList']['Variables']['GPOValidPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOList']['Variables']['GPOOptimizedPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOList']['Variables']['GPOOptimizedPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOList']['Variables']['GPONoProblemPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOList']['Variables']['GPONoProblemPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOList']['Variables']['GPOApplyPermissionYesPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOList']['Variables']['GPOApplyPermissionYesPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOList']['Variables']['GPOApplypermissionNoPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOList']['Variables']['GPOApplypermissionNoPerDomain'][$GPO.DomainName] = 0
            }

            if ($GPO.Days -le $Script:Reporting['GPOList']['Variables']['GPOOlderThan']) {
                # Skip GPOS that are younger than 30 days
                $Script:Reporting['GPOList']['Variables']['GPOSkip']++
            }
            if ($GPO.Exclude -eq $true) {
                # Skip GPOS that are excluded
                $Script:Reporting['GPOList']['Variables']['GPOSkipExcluded']++
            }
            if (($GPO.Enabled -eq $false -or $GPO.Empty -eq $true -or $GPO.Linked -eq $false -or $GPO.ApplyPermission -eq $false) -and $GPO.Exclude -eq $true) {
                $Script:Reporting['GPOList']['Variables']['GPONotValidButExcluded']++
                $Script:Reporting['GPOList']['Variables']['GPONotValidButSkippedOrExcluded']++
            } elseif (($GPO.Enabled -eq $false -or $GPO.Empty -eq $true -or $GPO.Linked -eq $false -or $GPO.ApplyPermission -eq $false) -and $GPO.Days -le $Script:Reporting['GPOList']['Variables']['GPOOlderThan']) {
                # Skip GPOS that are younger than 30 days
                $Script:Reporting['GPOList']['Variables']['GPONotValidButSkip']++
                $Script:Reporting['GPOList']['Variables']['GPONotValidButSkippedOrExcluded']++
            }
            if (($GPO.Enabled -eq $false -or $GPO.Empty -eq $true -or $GPO.Linked -eq $false -or $GPO.ApplyPermission -eq $false) -and $GPO.Days) {
                $Script:Reporting['GPOList']['Variables']['GPONotValid']++
                $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOList']['Variables']['GPOValid']++
                $Script:Reporting['GPOList']['Variables']['GPOValidPerDomain'][$GPO.DomainName]++
            }
            if ($GPO.Linked -eq $false -and $GPO.Empty -eq $true) {
                # Not linked, Empty
                $Script:Reporting['GPOList']['Variables']['GPOEmptyAndUnlinked']++
                $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']++
                $Script:Reporting['GPOList']['Variables']['GPONotLinked']++
                $Script:Reporting['GPOList']['Variables']['GPOEmpty']++
            } elseif ($GPO.Linked -eq $true -and $GPO.Empty -eq $true) {
                # Linked, But EMPTY
                $Script:Reporting['GPOList']['Variables']['GPOLinkedButEmpty']++
                $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']++
                $Script:Reporting['GPOList']['Variables']['GPOEmpty']++
                $Script:Reporting['GPOList']['Variables']['GPOLinked']++
            } elseif ($GPO.Linked -eq $false) {
                # Not linked, but not EMPTY
                $Script:Reporting['GPOList']['Variables']['GPONotLinked']++
                $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']++
                $Script:Reporting['GPOList']['Variables']['GPONotEmpty']++
            } elseif ($GPO.Empty -eq $true) {
                # Linked, But EMPTY
                $Script:Reporting['GPOList']['Variables']['GPOEmpty']++
                $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']++
                $Script:Reporting['GPOList']['Variables']['GPOLinked']++
            } else {
                # Linked, not EMPTY
                $Script:Reporting['GPOList']['Variables']['GPOLinked']++
                $Script:Reporting['GPOList']['Variables']['GPONotEmpty']++
            }
            if ($GPO.Enabled -eq $true) {
                $Script:Reporting['GPOList']['Variables']['GPOEnabled']++
            } else {
                $Script:Reporting['GPOList']['Variables']['GPODisabled']++
            }
            if ($GPO.ApplyPermission -eq $true) {
                $Script:Reporting['GPOList']['Variables']['ApplyPermissionYes']++
            } else {
                $Script:Reporting['GPOList']['Variables']['ApplyPermissionNo']++
            }
            if ($GPO.LinksDisabledCount -eq $GPO.LinksCount -and $GPO.LinksCount -gt 0) {
                $Script:Reporting['GPOList']['Variables']['GPOLinkedButLinkDisabled']++
            }
            if ($GPO.ComputerOptimized -eq $true) {
                $Script:Reporting['GPOList']['Variables']['ComputerOptimizedYes']++
            } else {
                $Script:Reporting['GPOList']['Variables']['ComputerOptimizedNo']++
            }
            if ($GPO.ComputerProblem -eq $true) {
                $Script:Reporting['GPOList']['Variables']['ComputerProblemYes']++
            } else {
                $Script:Reporting['GPOList']['Variables']['ComputerProblemNo']++
            }
            if ($GPO.UserOptimized -eq $true) {
                $Script:Reporting['GPOList']['Variables']['UserOptimizedYes']++
            } else {
                $Script:Reporting['GPOList']['Variables']['UserOptimizedNo']++
            }
            if ($GPO.UserProblem -eq $true) {
                $Script:Reporting['GPOList']['Variables']['UserProblemYes']++
            } else {
                $Script:Reporting['GPOList']['Variables']['UserProblemNo']++
            }
            if ($GPO.UserProblem -or $GPO.ComputerProblem) {
                $Script:Reporting['GPOList']['Variables']['GPOWithProblems']++
            }
            if ($GPO.Problem -eq $true) {
                $Script:Reporting['GPOList']['Variables']['GPOProblem']++
                $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOList']['Variables']['GPONoProblem']++
                $Script:Reporting['GPOList']['Variables']['GPONoProblemPerDomain'][$GPO.DomainName]++
            }
            if ($GPO.Optimized -eq $true) {
                $Script:Reporting['GPOList']['Variables']['GPOOptimized']++
                $Script:Reporting['GPOList']['Variables']['GPOOptimizedPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOList']['Variables']['GPONotOptimized']++
                $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'][$GPO.DomainName]++
            }
        }
        $Script:Reporting['GPOList']['Variables']['GPOTotal'] = $Script:Reporting['GPOList']['Data'].Count
        if ($Script:Reporting['GPOList']['Variables']['GPONotValid'] -gt 0 -and $Script:Reporting['GPOList']['Variables']['GPONotValidButSkippedOrExcluded'] -ne $Script:Reporting['GPOList']['Variables']['GPONotValid']) {
            $Script:Reporting['GPOList']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOList']['ActionRequired'] = $false
        }
    }
    Variables  = @{
        GPOOlderThan                    = 30
        GPONotValidPerDomain            = $null
        GPOValidPerDomain               = $null
        GPONotOptimizedPerDomain        = $null
        GPOOptimizedPerDomain           = $null
        GPOProblemPerDomain             = $null
        GPONoProblemPerDomain           = $null
        GPOApplyPermissionYesPerDomain  = $null
        GPOApplyPermissionNoPerDomain   = $null
        GPOWithProblems                 = 0
        ComputerOptimizedYes            = 0
        ComputerOptimizedNo             = 0
        ComputerProblemYes              = 0
        ComputerProblemNo               = 0
        UserOptimizedYes                = 0
        UserOptimizedNo                 = 0
        UserProblemYes                  = 0
        UserProblemNo                   = 0
        GPOOptimized                    = 0
        GPONotOptimized                 = 0
        GPOProblem                      = 0
        GPONoProblem                    = 0
        GPONotLinked                    = 0
        GPOLinked                       = 0
        GPOEmpty                        = 0
        GPONotEmpty                     = 0
        GPOEmptyAndUnlinked             = 0
        GPOEmptyOrUnlinked              = 0
        GPOLinkedButEmpty               = 0
        GPOEnabled                      = 0
        GPODisabled                     = 0
        GPOSkip                         = 0
        GPOSkipExcluded                 = 0
        GPOValid                        = 0
        GPONotValid                     = 0
        GPONotValidButSkip              = 0
        GPONotValidButExcluded          = 0
        GPONotValidButSkippedOrExcluded = 0
        GPOLinkedButLinkDisabled        = 0
        GPOTotal                        = 0
        ApplyPermissionYes              = 0
        ApplyPermissionNo               = 0
    }
    Overview   = {

    }
    Summary    = {
        New-HTMLText -TextBlock {
            "Over time Administrators add more and more group policies, as business requirements change. "
            "Due to neglection or thinking it may serve it's purpose later on a lot of Group Policies often have no value at all. "
            "Either the Group Policy is not linked to anything and just stays unlinked forever, or GPO is linked, but the link (links) are disabled or GPO is totally disabled. "
            "Then there are Group Policies that are targetting certain group or person and that group is removed leaving Group Policy doing nothing. "
            "Additionally sometimes new GPO is created without any settings or the settings are removed over time, but GPO stays in place. "
        } -FontSize 10pt
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies total: ', $Script:Reporting['GPOList']['Variables']['GPOTotal'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies valid: ", $Script:Reporting['GPOList']['Variables']['GPOValid'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies exclusions defined: ", $Script:Reporting['GPOList']['Variables']['GPOSkipExcluded'] -FontWeight normal, bold -Color None, DeepSkyBlue
            New-HTMLListItem -Text "Group Policies ", "NOT", " valid: ", $Script:Reporting['GPOList']['Variables']['GPONotValid'] -FontWeight normal, bold, normal, bold {
                New-HTMLList -Type Unordered {
                    New-HTMLListItem -Text 'Group Policies that are unlinked (are not doing anything currently): ', $Script:Reporting['GPOList']['Variables']['GPONotLinked'] -FontWeight normal, bold
                    New-HTMLListItem -Text "Group Policies that are empty (have no settings): ", $Script:Reporting['GPOList']['Variables']['GPOEmpty'] -FontWeight normal, bold
                    New-HTMLListItem -Text "Group Policies that are linked, but empty: ", $Script:Reporting['GPOList']['Variables']['GPOLinkedButEmpty'] -FontWeight normal, bold
                    New-HTMLListItem -Text "Group Policies that are linked, but link disabled: ", $Script:Reporting['GPOList']['Variables']['GPOLinkedButLinkDisabled'] -FontWeight normal, bold
                    New-HTMLListItem -Text "Group Policies that are disabled (both user/computer sections): ", $Script:Reporting['GPOList']['Variables']['GPODisabled'] -FontWeight normal, bold
                    New-HTMLListItem -Text "Group Policies that have no Apply Permission: ", $Script:Reporting['GPOList']['Variables']['ApplyPermissionNo'] -FontWeight normal, bold
                }
            } -Color Black, Red, Black, Red, Black
            New-HTMLListItem -Text @(
                "Group Policies ", "NOT", " valid, to skip (because of age): ", $Script:Reporting['GPOList']['Variables']['GPONotValidButSkip'], " (modified less than $($Script:Reporting['GPOList']['Variables']['GPOOlderThan']) days ago)"
            ) -FontWeight 'normal', 'bold', 'normal', 'bold', 'normal' -Color 'Black', 'Red', 'Black', 'Red', 'Black'
            New-HTMLListItem -Text @(
                "Group Policies ", "NOT", " valid, to skip (because of exclusions): ", $Script:Reporting['GPOList']['Variables']['GPONotValidButExcluded']
            ) -FontWeight 'normal', 'bold', 'normal', 'bold', 'normal' -Color 'Black', 'Red', 'Black', 'Red', 'Black'
            New-HTMLListItem -Text "Group Policies recently modified: ", $Script:Reporting['GPOList']['Variables']['GPOSkip'], " (modified less than $($Script:Reporting['GPOList']['Variables']['GPOOlderThan']) days ago)" -FontWeight normal, bold
        } -FontSize 10pt

        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOList']['Variables']['GPONotValidPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -Text "Keep in mind that each GPO can match multiple conditions such as being empty and unlinked and disabled at the same time. We're only deleting GPO once." -FontSize 10pt

        New-HTMLText -Text @(
            'All ',
            'empty',
            ' or ',
            'unlinked',
            ' or ',
            'disabled',
            ' Group Policies can be automatically deleted. Please review output in the table and follow steps below table to cleanup Group Policies. ',
            'GPOs that have content, but are disabled require manual intervention. ',
            "If performance is an issue you should consider disabling user or computer sections of GPO when those are not used. "
        ) -FontSize 10pt -FontWeight normal, bold, normal, bold, normal, bold, normal, normal, normal, normal

        New-HTMLText -LineBreak

        New-HTMLText -Text "Additionally, we're reviewing Group Policies that have their section disabled, but contain data." -FontSize 10pt
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies with problems: ', $Script:Reporting['GPOList']['Variables']['GPOWithProblems'] -FontWeight normal, bold {
                New-HTMLList -Type Unordered {
                    New-HTMLListItem -Text 'Group Policies that have content (computer), but are disabled: ', $Script:Reporting['GPOList']['Variables']['ComputerProblemYes'] -FontWeight normal, bold
                    New-HTMLListItem -Text "Group Policies that have content (user), but are disabled: ", $Script:Reporting['GPOList']['Variables']['UserProblemYes'] -FontWeight normal, bold
                }
            }
        } -FontSize 10pt
        New-HTMLText -Text @(
            "Such policies require manual review from whoever owns them. "
            "It could be a mistake tha section was disabled while containing data or that content is no longer needed in which case it should be deleted. "
            "This can't be auto-handled and is INFORMATIONAL only. "
        ) -FontSize 10pt

        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOList']['Variables']['GPOProblemPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt

        New-HTMLText -LineBreak

        New-HTMLText -Text "Moreover, for best performance it's recommended that if there are no settings of certain kind (Computer or User settings) it's best to disable whole section. " -FontSize 10pt
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies with optimization: ' -FontWeight normal, bold {
                New-HTMLList -Type Unordered {
                    New-HTMLListItem -Text 'Group Policies that are optimized (computer) ', $Script:Reporting['GPOList']['Variables']['ComputerOptimizedYes'] -FontWeight normal, bold
                    New-HTMLListItem -Text "Group Policies that are optimized (user): ", $Script:Reporting['GPOList']['Variables']['UserOptimizedYes'] -FontWeight normal, bold
                }
            }
            New-HTMLListItem -Text 'Group Policies without optimization: ' -FontWeight normal, bold {
                New-HTMLList -Type Unordered {
                    New-HTMLListItem -Text 'Group Policies that are not optimized (computer): ', $Script:Reporting['GPOList']['Variables']['ComputerOptimizedNo'] -FontWeight normal, bold
                    New-HTMLListItem -Text "Group Policies that are not optimized (user): ", $Script:Reporting['GPOList']['Variables']['UserOptimizedNo'] -FontWeight normal, bold
                }
            }
        } -FontSize 10pt
        New-HTMLText -Text @(
            "This means "
            $Script:Reporting['GPOList']['Variables']['GPONotOptimized']
            " could be optimized for performance reasons. "
        ) -FontSize 10pt -FontWeight normal, bold, normal

        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOList']['Variables']['GPONotOptimizedPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        # Just in case report is too big and the full file is not attached
        New-HTMLText -FontSize 10pt -Text "To generate up to date report please execute: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Install-Module GPOZaurr -Force', ' or ', ' install module manually.' -Color RoyalBlue, None, None
            New-HTMLListItem -Text 'Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPListBefore.html -Verbose -Type GPOList' -Color RoyalBlue
        } -FontSize 10pt
        New-HTMLText -FontSize 10pt -Text 'Steps above will generate above summary with more details allowing you to get up to date report and steps on how to fix it.'

        if ($Script:Reporting['GPOList']['Exclusions']) {
            New-HTMLText -LineBreak
            New-HTMLText -Text @(
                "While preparing this report following exclusions were defined. "
                "Please make sure that when you execute your steps to include those exclusions to prevent any issues. "
            ) -FontSize 10pt -FontWeight bold, normal -Color Red, None -LineBreak

            New-HTMLText -Text "Code to use for exclusions: " -FontSize 10pt -FontWeight bold -LineBreak

            $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOList']['Exclusions']

            if ($Code) {
                New-HTMLCodeBlock -Code $Code -Style powershell
            }
        }
    }
    Solution   = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOList']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart -Title 'Group Policies Empty & Unlinked' {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Names 'Yes', 'No' -Color SpringGreen, Salmon
                    New-ChartBar -Name 'Linked' -Value $Script:Reporting['GPOList']['Variables']['GPOLinked'], $Script:Reporting['GPOList']['Variables']['GPONotLinked']
                    New-ChartBar -Name 'Not Empty' -Value $Script:Reporting['GPOList']['Variables']['GPONotEmpty'], $Script:Reporting['GPOList']['Variables']['GPOEmpty']
                    New-ChartBar -Name 'Enabled' -Value $Script:Reporting['GPOList']['Variables']['GPOEnabled'], $Script:Reporting['GPOList']['Variables']['GPODisabled']
                    New-ChartBar -Name 'Apply Permission' -Value $Script:Reporting['GPOList']['Variables']['ApplyPermissionYes'], $Script:Reporting['GPOList']['Variables']['ApplyPermissionNo']
                    New-ChartBar -Name 'Valid' -Value $Script:Reporting['GPOList']['Variables']['GPOValid'], $Script:Reporting['GPOList']['Variables']['GPONotValid']
                    New-ChartBar -Name 'Optimized (for speed)' -Value $Script:Reporting['GPOList']['Variables']['GPOOptimized'], $Script:Reporting['GPOList']['Variables']['GPONotOptimized']
                    New-ChartBar -Name 'No problem' -Value $Script:Reporting['GPOList']['Variables']['GPONoProblem'], $Script:Reporting['GPOList']['Variables']['GPOProblem']
                    New-ChartBar -Name 'No problem (computers)' -Value $Script:Reporting['GPOList']['Variables']['ComputerProblemNo'], $Script:Reporting['GPOList']['Variables']['ComputerProblemYes']
                    New-ChartBar -Name 'No problem (users)' -Value $Script:Reporting['GPOList']['Variables']['UserProblemNo'], $Script:Reporting['GPOList']['Variables']['UserProblemYes']
                    New-ChartBar -Name 'Optimized Computers' -Value $Script:Reporting['GPOList']['Variables']['ComputerOptimizedYes'], $Script:Reporting['GPOList']['Variables']['ComputerOptimizedNo']
                    New-ChartBar -Name 'Optimized Users' -Value $Script:Reporting['GPOList']['Variables']['UserOptimizedYes'], $Script:Reporting['GPOList']['Variables']['UserOptimizedNo']
                } -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policies List' {
            New-HTMLContainer {
                New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
                New-HTMLList {
                    New-HTMLListItem -FontWeight bold, normal -Text "Empty", " - means GPO has currently no content. It could be there was content, but it was removed, or that it never had content. "
                    New-HTMLListItem -FontWeight bold, normal -Text "Linked", " - means GPO is linked or unlinked. We need at least one link that is enabled to mark it as linked. If GPO is linked, but all links are disabled, it's not linked. "
                    New-HTMLListItem -FontWeight bold, normal -Text "Enabled", " - means GPO has at least one section enabled. If enabled is set to false that means both sections are disabled, and therefore GPO is not active. "
                    New-HTMLListItem -FontWeight bold, normal -Text "Optimized", " - means GPO section that is not in use is disabled. If section (user or computer) is enabled and there is no content, it's not optimized. "
                    New-HTMLListItem -FontWeight bold, normal -Text "Problem", " - means GPO has one or more section (user or computer) that is disabled, yet there is content in it. "
                    New-HTMLListItem -FontWeight bold, normal -Text "ApplyPermission", " - means GPO has no Apply Permission. This means there's no user/computer/group it's applicable to. "
                } -FontSize 10pt
                New-HTMLTable -DataTable $Script:Reporting['GPOList']['Data'] -Filtering {
                    New-HTMLTableCondition -Name 'Exclude' -Value $true -BackgroundColor DeepSkyBlue -ComparisonType string -Row

                    New-HTMLTableCondition -Name 'Empty' -Value $true -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'Linked' -Value $false -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'Enabled' -Value $false -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'Optimized' -Value $false -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'Problem' -Value $true -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'ApplyPermission' -Value $false -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'ComputerProblem' -Value $true -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'UserProblem' -Value $true -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'ComputerOptimized' -Value $false -BackgroundColor Salmon -ComparisonType string
                    New-HTMLTableCondition -Name 'UserOptimized' -Value $false -BackgroundColor Salmon -TextTransform capitalize -ComparisonType string
                    # reverse
                    New-HTMLTableCondition -Name 'Empty' -Value $false -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'Linked' -Value $true -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'Enabled' -Value $true -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'Optimized' -Value $true -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'Problem' -Value $false -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'ApplyPermission' -Value $true -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'ComputerProblem' -Value $false -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'UserProblem' -Value $false -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'ComputerOptimized' -Value $true -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'UserOptimized' -Value $true -BackgroundColor SpringGreen -TextTransform capitalize -ComparisonType string
                } -PagingOptions 10, 20, 30, 40, 50
            }
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix - Empty & Unlinked & Disabled Group Policies' {
                New-HTMLContainer {
                    New-HTMLSpanStyle -FontSize 10pt {
                        New-HTMLText -Text 'Following steps will guide you how to remove empty or unlinked group policies'
                        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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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."
                            }
                            if ($Script:Reporting['GPOList']['Exclusions']) {
                                New-HTMLWizardStep -Name 'Required exclusions' {
                                    New-HTMLText -Text @(
                                        "While preparing this report following exclusions were defined. "
                                        "Please make sure that when you execute your steps to include those exclusions to prevent any issues. "
                                    )
                                    $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOList']['Exclusions']
                                    if ($Code) {
                                        New-HTMLCodeBlock -Code $Code -Style powershell
                                    }
                                }
                            }
                            New-HTMLWizardStep -Name 'Prepare report' {
                                New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with removal. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrEmptyUnlinked.html -Verbose -Type GPOList
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $GPOOutput = Get-GPOZaurr
                                    $GPOOutput | 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 'Make a backup' {
                                New-HTMLText -TextBlock {
                                    "The process of deleting Group Policies is final. Once GPO is removed - it's gone. "
                                    "To make sure you can recover deleted GPO please make sure to back them up. "
                                }
                                New-HTMLCodeBlock -Code {
                                    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All
                                    $GPOSummary | Format-Table # only if you want to display output of backup
                                }
                                New-HTMLText -TextBlock {
                                    "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. "
                                    "Keep in mind that Remove-GPOZaurr command also has a backup feature built-in. "
                                    "It's possible to skip this backup and use the backup provided as part of Remove-GPOZaurr command. "
                                }
                            }
                            New-HTMLWizardStep -Name 'Excluding Group Policies' {
                                New-HTMLText -Text @(
                                    "Remove-GPOZaurr",
                                    " cmdlet that you will use in next steps is pretty advanced in what it can do. It can remove one or multiple types of problems at the same time. "
                                    "That means you can pick just EMPTY, just UNLINKED or just DISABLED but also a mix of them if you want. "
                                    "It also provides a way to exclude some GPOs from being removed even though they match condition. "
                                    "You would do so using following approach "
                                ) -FontSize 10pt -FontWeight bold, normal
                                New-HTMLCodeBlock -Code {
                                    $Exclusions = {
                                        Skip-GroupPolicy -Name 'TEST | Drive Mapping'
                                        Skip-GroupPolicy -Name 'Default Domain Policy'
                                        Skip-GroupPolicy -Name 'Default Domain Controllers Policy' -DomaiName 'JustOneDomain'
                                        '{D39BF08A-87BF-4662-BFA0-E56240EBD5A2}'
                                        'COMPUTERS | Enable Sets'
                                    }
                                    Remove-GPOZaurr -RequireDays 30 -Type Empty, Unlinked, Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor' -WhatIf -ExcludeGroupPolicies $Exclusions
                                }
                                New-HTMLText -Text @(
                                    "Code above when executed will scan YourDomainYouHavePermissionsFor, find all empty, unlinked, disabled group policies, backup any GPO just before it's to be deleted to `$Env:UserProfile\Desktop\GPO. "
                                    "If it's not able to make a backup it will terminate. Additionally it will skip all 3 group policies that have been shown above. "
                                    "You can one or multiple group policies to be skipped. "
                                    "Now go ahead and find what's there"
                                )
                            }
                            New-HTMLWizardStep -Name 'Remove GPOs (Manual)' {
                                New-HTMLText -Text @(
                                    "Please condider deleting GPOs manually if the amount of GPOs to delete is small enough. "
                                    "Deleting 1-5-30 GPOs manually on domain of 4000 GPOs will be much faster than doing it in controlled manner with automated steps mentioned on next steps. "
                                    "What can take 30 minutes manually, can take 8 hours using automated script, because of amount of checks required by the script over and over to delete a single GPO. "
                                ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black
                            }
                            New-HTMLWizardStep -Name 'Remove GPOs that are EMPTY' {
                                New-HTMLText -Text @(
                                    "Following command when executed removes every ",
                                    "EMPTY"
                                    " Group Policy. Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental removal.",
                                    "Make sure to use BackupPath which will make sure that for each GPO that is about to be deleted a backup is made to folder on a desktop."
                                    "You can skip parameters related to backup if you did backup all GPOs prior to running remove command. "
                                ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Empty -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Empty -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 deleted matches expected data. "
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Empty -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Empty -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed deletes only first X empty GPOs. Use LimitProcessing parameter to prevent mass delete 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."
                                    "Please make sure to check if backup is made as well before going all in."
                                }
                                New-HTMLText -Text "If there's nothing else to be deleted, we can skip to next step step"
                            }
                            New-HTMLWizardStep -Name 'Remove GPOs that are UNLINKED' {
                                New-HTMLText -Text @(
                                    "Following command when executed removes every ",
                                    "NOT LINKED"
                                    " Group Policy. Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental removal.",
                                    "Make sure to use BackupPath which will make sure that for each GPO that is about to be deleted a backup is made to folder on a desktop."
                                    "You can skip parameters related to backup if you did backup all GPOs prior to running remove command. "
                                ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 deleted matches expected data. "
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed deletes only first X unlinked GPOs. Use LimitProcessing parameter to prevent mass delete 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."
                                    "Please make sure to check if backup is made as well before going all in."
                                }
                                New-HTMLText -Text "If there's nothing else to be deleted, we can skip to next step step"
                            }
                            New-HTMLWizardStep -Name 'Remove GPOs that are DISABLED' {
                                New-HTMLText -Text @(
                                    "Following command when executed removes every ",
                                    "DISABLED"
                                    " Group Policy. Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental removal.",
                                    "Make sure to use BackupPath which will make sure that for each GPO that is about to be deleted a backup is made to folder on a desktop."
                                    "You can skip parameters related to backup if you did backup all GPOs prior to running remove command. "
                                ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black
                                New-HTMLText -TextBlock {
                                    ""
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 deleted matches expected data. "
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type Disabled -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed deletes only first X disabled GPOs. Use LimitProcessing parameter to prevent mass delete 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. "
                                    "Please make sure to check if backup is made as well before going all in."
                                }
                                New-HTMLText -Text "If there's nothing else to be deleted, we can skip to next step step."
                            }
                            New-HTMLWizardStep -Name 'Remove GPOs that do not APPLY' {
                                New-HTMLText -Text @(
                                    "Following command when executed removes every ",
                                    "NoApplyPermission"
                                    " Group Policy. Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental removal.",
                                    "Make sure to use BackupPath which will make sure that for each GPO that is about to be deleted a backup is made to folder on a desktop."
                                    "You can skip parameters related to backup if you did backup all GPOs prior to running remove command. "
                                ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black
                                New-HTMLText -TextBlock {
                                    ""
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type NoApplyPermission -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type NoApplyPermission -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 deleted matches expected data. "
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type NoApplyPermission -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurr -RequireDays 30 -Type NoApplyPermission -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed deletes only first X NoApplyPermission GPOs. Use LimitProcessing parameter to prevent mass delete 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. "
                                    "Please make sure to check if backup is made as well before going all in."
                                }
                                New-HTMLText -Text "If there's nothing else to be deleted, we can skip to next step step."
                            }
                            New-HTMLWizardStep -Name 'Optimize GPOs (optional)' {
                                New-HTMLText -Text @(
                                    "Following command when executed disables user or computer section when there's no content for given type. ",
                                    "This makes sure that when GPO is processed for application it's empty section is ignored. Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental disabling of sections."
                                ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Optimize-GPOZaurr -All -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Optimize-GPOZaurr -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 optimized matches expected data. "
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Optimize-GPOZaurr -All -LimitProcessing 2 -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Optimize-GPOZaurr -All -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed optimizes only first X not optimized GPOs. Use LimitProcessing parameter to prevent mass changes 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. "
                                    "Please make sure to check if backup is made as well before going all in."
                                }
                                New-HTMLText -Text "If there's nothing else to be optimized, we can skip to next step step."
                            }
                            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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrEmptyUnlinkedAfter.html -Verbose -Type GPOList
                                }
                                New-HTMLText -Text "If there are no more problems to solve, GPOs to optimize in the report you're done! Enjoy rest of the day!" -Color BlueDiamond
                            }
                        } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center -EnableAllAnchors
                    }
                }
            }
            <#
            if ($Script:Reporting['GPOList']['Exclusions']) {
                New-HTMLSection -Invisible {
                    New-HTMLSection -Name 'Group Policies Exclusions' {
                        New-HTMLTable -DataTable $Script:Reporting['GPOList']['Exclusions'] -Filtering {
 
                        }
                    }
                    New-HTMLSection -Name 'Group Policies Exclusions Code' {
                        New-HTMLContainer {
                            New-HTMLText -Text 'Please make sure to use following exclusions when executing removal' -FontSize 10pt
                            New-HTMLCodeBlock -Code $Script:Reporting['GPOList']['Exclusions']
                        }
                    }
                }
            }
            #>

        }
        if ($Script:Reporting['GPOList']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOList']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrNetLogonOwners = [ordered] @{
    Name           = 'NetLogon Owners'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Get-GPOZaurrNetLogon -OwnerOnly -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {
        foreach ($File in $Script:Reporting['NetLogonOwners']['Data']) {
            # if ($File.FileSystemRights -eq 'Owner') {
            # Process Owner part of the report
            $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwners']++
            if ($File.OwnerType -eq 'WellKnownAdministrative') {
                $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrative']++
            } elseif ($File.OwnerType -eq 'Administrative') {
                $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrative']++
            } else {
                $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersNotAdministrative']++
            }
            if ($File.OwnerSid -eq 'S-1-5-32-544') {
                $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrators']++
            } elseif ($File.OwnerType -in 'WellKnownAdministrative', 'Administrative') {
                $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrativeNotAdministrators']++
                $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix']++
            } else {
                $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix']++
            }
            #$Script:Reporting['NetLogonPermissions']['Variables']['Owner'].Add($File)
            #} else {
            # Process all other part of the report
            # $Script:Reporting['NetLogonPermissions']['Variables']['NonOwner'].Add($File)

            # if ($File.Status -eq 'Review permission required') {
            # $Script:Reporting['NetLogonPermissions']['Variables']['PermissionReviewRequired']++
            # } elseif ($File.Status -eq 'Removal permission required') {
            # $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequired']++
            # } elseif ($File.Status -eq 'Not assesed') {
            # $Script:Reporting['NetLogonPermissions']['Variables']['PermissionNotAssesed']++
            # } else {
            # $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOK']++
            # }
            # }
        }
        if ($Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix'] -gt 0) {
            $Script:Reporting['NetLogonOwners']['ActionRequired'] = $true
        } else {
            $Script:Reporting['NetLogonOwners']['ActionRequired'] = $false
        }
        # if (-not $Script:Reporting['NetLogonPermissions']['ActionRequired']) {
        # # if owners require fixing, we don't need to check those as we get this anyways
        # # if owners don't require fixing we check permissions anyways
        # if ($Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequired'] -gt 0 -or $Script:Reporting['NetLogonPermissions']['Variables']['PermissionReviewRequired'] -gt 0) {
        # $Script:Reporting['NetLogonPermissions']['ActionRequired'] = $true
        # } else {
        # $Script:Reporting['NetLogonPermissions']['ActionRequired'] = $false
        # }
        # }
    }
    Variables      = @{
        NetLogonOwners                                = 0
        NetLogonOwnersAdministrators                  = 0
        NetLogonOwnersNotAdministrative               = 0
        NetLogonOwnersAdministrative                  = 0
        NetLogonOwnersAdministrativeNotAdministrators = 0
        NetLogonOwnersToFix                           = 0
        #Owner = [System.Collections.Generic.List[PSCustomObject]]::new()
        #NonOwner = [System.Collections.Generic.List[PSCustomObject]]::new()

        # PermissionReviewRequired = 0
        # PermissionRemovalRequired = 0
        # PermissionOK = 0
        # PermissionNotAssesed = 0
    }
    Overview       = {
        # New-HTMLPanel {
        # New-HTMLText -Text 'Following chart presents ', 'NetLogon Summary' -FontSize 10pt -FontWeight normal, bold
        # New-HTMLList -Type Unordered {
        # New-HTMLListItem -Text 'NetLogon Files in Total: ', $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwners'] -FontWeight normal, bold
        # New-HTMLListItem -Text 'NetLogon BUILTIN\Administrators as Owner: ', $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwnersAdministrators'] -FontWeight normal, bold
        # New-HTMLListItem -Text "NetLogon Owners requiring change: ", $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwnersToFix'] -FontWeight normal, bold {
        # New-HTMLList -Type Unordered {
        # New-HTMLListItem -Text 'Not Administrative: ', $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwnersNotAdministrative'] -FontWeight normal, bold
        # New-HTMLListItem -Text 'Administrative, but not BUILTIN\Administrators: ', $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwnersAdministrativeNotAdministrators'] -FontWeight normal, bold
        # }
        # }
        # } -FontSize 10pt
        # #New-HTMLText -FontSize 10pt -Text 'Those problems must be resolved before doing other clenaup activities.'
        # New-HTMLChart {
        # New-ChartPie -Name 'Correct Owners' -Value $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwnersAdministrators'] -Color LightGreen
        # New-ChartPie -Name 'Incorrect Owners' -Value $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwnersToFix'] -Color Crimson
        # } -Title 'NetLogon Owners' -TitleAlignment center
        # }
        # New-HTMLPanel {

        # }
    }
    Summary        = {
        New-HTMLText -TextBlock {
            "NetLogon is crucial part of Active Directory. Files stored there are available on each and every computer or server in the company. "
            "Keeping those files clean and secure is very important task. "
            "It's important that NetLogon file owners are set to BUILTIN\Administrators (SID: S-1-5-32-544). "
            "Owners have full control over the file object. Current owner of the file may be an Administrator but it doesn't guarentee that he/she will be in the future. "
            "That's why as a best-practice it's recommended to change any non-administrative owners to BUILTIN\Administrators, and even Administrative accounts should be replaced with it. "
        } -FontSize 10pt
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'NetLogon Files in Total: ', $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwners'] -FontWeight normal, bold
            New-HTMLListItem -Text 'NetLogon BUILTIN\Administrators as Owner: ', $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrators'] -FontWeight normal, bold
            New-HTMLListItem -Text "NetLogon Owners requiring change: ", $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix'] -FontWeight normal, bold {
                New-HTMLList -Type Unordered {
                    New-HTMLListItem -Text 'Not Administrative: ', $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersNotAdministrative'] -FontWeight normal, bold
                    New-HTMLListItem -Text 'Administrative, but not BUILTIN\Administrators: ', $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrativeNotAdministrators'] -FontWeight normal, bold
                }
            }
        } -FontSize 10pt
        New-HTMLText -Text "Follow the steps below table to get NetLogon Owners into compliant state." -FontSize 10pt
    }
    Solution       = {
        #New-HTMLTab -Name 'NetLogon Owners' {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['NetLogonOwners']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartPie -Name 'Correct Owners' -Value $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersAdministrators'] -Color SpringGreen
                    New-ChartPie -Name 'Incorrect Owners' -Value $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix'] -Color Salmon
                } -Title 'NetLogon Owners' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'NetLogon File Owners' {
            New-HTMLTable -DataTable $Script:Reporting['NetLogonOwners']['Data'] -Filtering {
                New-HTMLTableCondition -Name 'OwnerSid' -Value "S-1-5-32-544" -BackgroundColor LightGreen -ComparisonType string
                New-HTMLTableCondition -Name 'OwnerSid' -Value "S-1-5-32-544" -BackgroundColor Salmon -ComparisonType string -Operator ne
                New-HTMLTableCondition -Name 'OwnerType' -Value "WellKnownAdministrative" -BackgroundColor LightGreen -ComparisonType string -Operator eq
                New-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor LightGreen -ComparisonType string -Operator eq
                New-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor Salmon -ComparisonType string -Operator ne
            }
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix NetLogon Owners ' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 with removal. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrNetLogonBefore.html -Verbose -Type NetLogonOwners
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $NetLogonOutput = Get-GPOZaurrNetLogon -OwnerOnly -Verbose
                                    $NetLogonOutput | 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 'Set non-compliant file owners to BUILTIN\Administrators' {
                                New-HTMLText -Text "Following command when executed runs internally command that lists all file owners and if it doesn't match changes it BUILTIN\Administrators. It doesn't change compliant owners."
                                New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black

                                New-HTMLCodeBlock -Code {
                                    Repair-GPOZaurrNetLogonOwner -Verbose -WhatIf
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Repair-GPOZaurrNetLogonOwner -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data. "
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start replacement of owners process): " -LineBreak -FontWeight bold
                                New-HTMLText -TextBlock {
                                    "This command when executed sets new owner only on first X non-compliant NetLogon files. Use LimitProcessing parameter to prevent mass change 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-HTMLCodeBlock -Code {
                                    Repair-GPOZaurrNetLogonOwner -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Repair-GPOZaurrNetLogonOwner -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                            }
                            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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrNetLogonAfter.html -Verbose -Type NetLogonOwners
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['NetLogonOwners']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['NetLogonOwners']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
        #}
        #New-HTMLTab -Name 'NetLogon Permissions' {
        # New-HTMLSection -Invisible {
        # New-HTMLPanel {
        # #& $Script:GPOConfiguration['NetLogonPermissions']['Summary']
        # }
        # New-HTMLPanel {
        # #New-HTMLChart {
        # # New-ChartPie -Name 'Correct Owners' -Value $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwnersAdministrators'] -Color LightGreen
        # # New-ChartPie -Name 'Incorrect Owners' -Value $Script:Reporting['NetLogonPermissions']['Variables']['NetLogonOwnersToFix'] -Color Crimson
        # #} -Title 'NetLogon Owners' -TitleAlignment center
        # }
        # }
        # # New-HTMLSection -Name 'NetLogon Files List' {
        # # New-HTMLTable -DataTable $Script:Reporting['NetLogonPermissions']['Variables']['Owner'] -Filtering {
        # # New-HTMLTableCondition -Name 'PrincipalSid' -Value "S-1-5-32-544" -BackgroundColor LightGreen -ComparisonType string
        # # New-HTMLTableCondition -Name 'PrincipalSid' -Value "S-1-5-32-544" -BackgroundColor Salmon -ComparisonType string -Operator ne
        # # New-HTMLTableCondition -Name 'PrincipalType' -Value "WellKnownAdministrative" -BackgroundColor LightGreen -ComparisonType string -Operator eq
        # # }
        # # }
        # New-HTMLSection -Name 'NetLogon Files List' {
        # New-HTMLTable -DataTable $Script:Reporting['NetLogonPermissions']['Variables']['NonOwner'] -Filtering {
        # New-HTMLTableCondition -Name 'PrincipalType' -Value "Unknown" -BackgroundColor Salmon -ComparisonType string -Operator eq -Row
        # New-HTMLTableCondition -Name 'PrincipalType' -Value "WellKnownAdministrative" -BackgroundColor LightGreen -ComparisonType string -Operator eq -Row
        # New-HTMLTableCondition -Name 'Status' -Value "Review permission required" -BackgroundColor PaleGoldenrod -ComparisonType string -Operator eq -Row
        # New-HTMLTableCondition -Name 'Status' -Value "Removal permission required" -BackgroundColor Salmon -ComparisonType string -Operator eq -Row
        # New-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor LightGreen -ComparisonType string -Operator eq
        # }
        # }
        # if ($Script:Reporting['NetLogonPermissions']['WarningsAndErrors']) {
        # New-HTMLSection -Name 'Warnings & Errors to Review' {
        # New-HTMLTable -DataTable $Script:Reporting['NetLogonPermissions']['WarningsAndErrors'] -Filtering {
        # New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
        # New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
        # }
        # }
        # }
        # }
    }
}
$GPOZaurrNetLogonPermissions = [ordered] @{
    Name           = 'NetLogon Permissions'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Get-GPOZaurrNetLogon -SkipOwner -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {
        $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'] = @{}
        $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'] = @{}
        $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'] = @{}
        $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'] = @{}

        foreach ($File in $Script:Reporting['NetLogonPermissions']['Data']) {
            if (-not $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'][$File.DomainName]) {
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'][$File.DomainName] = 0
            }
            if (-not $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'][$File.DomainName]) {
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'][$File.DomainName] = 0
            }
            if (-not $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'][$File.DomainName]) {
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'][$File.DomainName] = 0
            }
            if (-not $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'][$File.DomainName]) {
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'][$File.DomainName] = 0
            }

            if ($File.Status -eq 'Review permission required') {
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionReviewRequired']++
                if ($File.FileSystemRights -like '*Modify*') {
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReview']++
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'][$File.DomainName]++
                } elseif ($File.FileSystemRights -like '*Write*') {
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReview']++
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'][$File.DomainName]++
                } elseif ($File.FileSystemRights -like '*FullControl*') {
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReview']++
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'][$File.DomainName]++
                } else {
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOtherReview']++
                }
            } elseif ($File.Status -eq 'Removal permission required') {
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequired']++
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'][$File.DomainName]++
                if ($File.PrincipalObjectClass -in 'user', 'computer') {
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredBecauseObject']++
                } else {
                    $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredBecauseUnknown']++
                }
            } elseif ($File.Status -eq 'Not assesed') {
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionNotAssesed']++
            } else {
                $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOK']++
            }
        }
        if ($Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequired'] -gt 0 -or $Script:Reporting['NetLogonPermissions']['Variables']['PermissionReviewRequired'] -gt 0) {
            $Script:Reporting['NetLogonPermissions']['ActionRequired'] = $true
        } else {
            $Script:Reporting['NetLogonPermissions']['ActionRequired'] = $false
        }
    }
    Variables      = @{
        PermissionReviewRequired                = 0
        PermissionRemovalRequired               = 0
        PermissionOK                            = 0
        PermissionNotAssesed                    = 0

        PermissionWriteReview                   = 0
        PermissionFullControlReview             = 0
        PermissionModifyReview                  = 0
        PermissionOtherReview                   = 0
        PermissionRemovalRequiredBecauseObject  = 0
        PermissionRemovalRequiredBecauseUnknown = 0


        PermissionWriteReviewPerDomain          = $null
        PermissionFullControlReviewPerDomain    = $null
        PermissionModifyReviewPerDomain         = $null
        PermissionRemovalRequiredPerDomain      = $null
    }
    Overview       = {

    }
    Summary        = {
        New-HTMLText -TextBlock {
            "NetLogon is crucial part of Active Directory. Files stored there are available on each and every computer or server in the company. "
            "Keeping those files clean and secure is very important task. "
            "Each file stored on NETLOGON has it's own permissions. "
            "It's important that crucial permissions such as FullControl, Modify or Write permissions are only applied to proper, trusted groups of users. "
            "Additionally permissions for FullControl, Modify or Write should not be granted to direct users or computers. Only groups are allowed! "
            ""
        } -FontSize 10pt
        New-HTMLText -Text 'Assesment overall: ' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Permissions that look ok: ' {
                New-HTMLList -Type Unordered {
                    New-HTMLListItem -Text 'Assesed and as expected ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOK'] -FontWeight normal, bold
                    New-HTMLListItem -Text 'Not assesed, but not critical (read/execute only) ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionNotAssesed'] -FontWeight normal, bold
                }
            }
            New-HTMLListItem -Text 'Permissions requiring review:' {
                New-HTMLList -Type Unordered {
                    New-HTMLListItem -Text 'Full control permissions ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReview'] -FontWeight normal, bold
                    New-HTMLListItem -Text 'Modify permissions ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReview'] -FontWeight normal, bold
                    New-HTMLListItem -Text 'Write permissions ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReview'] -FontWeight normal, bold
                }
            }
            New-HTMLListItem -Text 'Permissions requiring removal: ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequired'] -FontWeight normal, bold {
                New-HTMLList -Type Unordered {
                    New-HTMLListItem -Text 'Because of object type (user/computer) ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredBecauseObject'] -FontWeight normal, bold
                    New-HTMLListItem -Text 'Because of unknown permissions ', $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredBecauseUnknown'] -FontWeight normal, bold
                }
            }
        } -FontSize 10pt -LineBreak
        New-HTMLText -Text 'Assesment split per domain (will require permissions to fix): ' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires review of ", $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReviewPerDomain'][$Domain], " full control" -FontWeight normal, bold, normal
                New-HTMLListItem -Text "$Domain requires review of ", $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReviewPerDomain'][$Domain], " modify permission" -FontWeight normal, bold, normal
                New-HTMLListItem -Text "$Domain requires review of ", $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReviewPerDomain'][$Domain], " write permission" -FontWeight normal, bold, normal
                New-HTMLListItem -Text "$Domain requires removal of ", $Script:Reporting['NetLogonPermissions']['Variables']['PermissionRemovalRequiredPerDomain'][$Domain], " permissions" -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -Text "Please review output in table and follow the steps below table to get NetLogon permissions in order." -FontSize 10pt
    }
    Solution       = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['NetLogonPermissions']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartPie -Name 'Full Control requiring review' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionFullControlReview'] -Color Crimson
                    New-ChartPie -Name 'Modify requiring review' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionModifyReview'] -Color Plum
                    New-ChartPie -Name 'Write requiring review' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionWriteReview'] -Color LightCoral
                    New-ChartPie -Name 'Permissions OK' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionOK'] -Color LightGreen
                    New-ChartPie -Name 'Permissions ReadOnly/Execute' -Value $Script:Reporting['NetLogonPermissions']['Variables']['PermissionNotAssesed'] -Color Aqua
                } -Title 'NetLogon Permissions' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'NetLogon Files List' {
            New-HTMLTable -DataTable $Script:Reporting['NetLogonPermissions']['Data'] -Filtering {
                New-HTMLTableCondition -Name 'PrincipalType' -Value "Unknown" -BackgroundColor Salmon -ComparisonType string -Operator eq -Row
                New-HTMLTableCondition -Name 'PrincipalType' -Value "WellKnownAdministrative" -BackgroundColor LightGreen -ComparisonType string -Operator eq -Row
                New-HTMLTableCondition -Name 'Status' -Value "Review permission required" -BackgroundColor PaleGoldenrod -ComparisonType string -Operator eq
                New-HTMLTableCondition -Name 'Status' -Value "Removal permission required" -BackgroundColor Salmon -ComparisonType string -Operator eq -Row
                New-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor LightGreen -ComparisonType string -Operator eq
            }
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix NetLogon Permissions ' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 with removal. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrNetLogonBefore.html -Verbose -Type NetLogonPermissions
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $NetLogonOutput = Get-GPOZaurrNetLogon -SkipOwner -Verbose
                                    $NetLogonOutput | 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 'Remove permissions manually for non-compliant users/groups' {
                                New-HTMLText -Text @(
                                    "In case of NETLOGON permissions it's impossible to tell what in a given moment for given domain should be automatically removed except for the very obvious ",
                                    "unknown ", 'permissions. Domain Admins have to make their assesment on and remove permissions from users or groups that '
                                    "they think do not belong. "
                                ) -FontWeight normal, bold, normal
                            }
                            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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrNetLogonAfter.html -Verbose -Type NetLogonPermissions
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['NetLogonPermissions']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['NetLogonPermissions']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrOrganizationalUnit = [ordered] @{
    Name           = 'Group Policy Organizational Units'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        if ($Script:Reporting['GPOOrganizationalUnit']['Exclusions']) {
            Get-GPOZaurrOrganizationalUnit -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeOrganizationalUnit $Script:Reporting['GPOOrganizationalUnit']['Exclusions']
        } else {
            Get-GPOZaurrOrganizationalUnit -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
        }
    }
    Processing     = {
        # Create Per Domain Variables
        $Script:Reporting['GPOOrganizationalUnit']['Variables']['RequiresDiffFixPerDomain'] = @{}
        $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'] = @{}
        foreach ($OU in $Script:Reporting['GPOOrganizationalUnit']['Data']) {
            $Script:Reporting['GPOOrganizationalUnit']['Variables']['TotalOU']++
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName]) {
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName] = 0
            }
            if ($OU.Status -contains 'Unlink GPO' -and $OU.Status -contains 'Delete OU') {
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPOEmpty']++
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFix']++
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName]++
            } elseif ($OU.Status -contains 'Unlink GPO') {
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPO']++
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFix']++
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName]++
            } elseif ($OU.Status -contains 'Delete OU') {
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['DeleteOU']++
                #$Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFix']++
                #$Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$OU.DomainName]++
            } elseif ($OU.Status -contains 'Excluded' -or $OU.Status -contains 'Excluded, Default OU') {
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['Excluded']++
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['ExcludedOU'].Add($OU.Organizationalunit)
            } else {
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['Legitimate']++
            }
        }
        if ($Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFix'] -gt 0) {
            $Script:Reporting['GPOOrganizationalUnit']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOOrganizationalUnit']['ActionRequired'] = $false
        }
    }
    Variables      = @{
        TotalOU          = 0
        UnlinkGPO        = 0
        UnlinkGPOEmpty   = 0
        DeleteOU         = 0
        Legitimate       = 0
        Excluded         = 0
        ExcludedOU       = [System.Collections.Generic.List[string]]::new()
        WillFix          = 0
        WillFixPerDomain = $null
    }
    Overview       = {

    }
    Summary        = {
        New-HTMLText -FontSize 10pt -Text @(
            "In most Active Directories there are a lot of Organizational Units that have different use cases to store different type of objects. "
            "As Active Directories change over time you can often find Organizational Units with linked GPOs and no objects inside. "
            "In some cases thats's expected, but in some cases it's totally unnessecary, and for very large AD can be a problem. "
            "Additionally only User and Computer objects can have GPO applied to them, so having GPO applied to a any other object type won't really work. "
        )
        New-HTMLText -FontSize 10pt -Text "Following can happen: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Organizational Units that can have Group Policies unlinked (objects exists): ', $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPO'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Organizational Units that can have Group Policies unlinked (no applicable objects): ', $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPOEmpty'] -FontWeight normal, bold
            New-HTMLListItem -Text "Organizational Units that can be deleted (no objects/no gpos) - ", "optional", ": ", $Script:Reporting['GPOOrganizationalUnit']['Variables']['DeleteOU'] -FontWeight normal, bold, normal, bold -Color None, red, None, None
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOOrganizationalUnit']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt

        if ($Script:Reporting['GPOOrganizationalUnit']['Variables']['ExcludedOU'].Count -gt 0) {
            New-HTMLText -Text @(
                'There are ',
                $Script:Reporting['GPOOrganizationalUnit']['Variables']['ExcludedOU'].Count,
                " Organizational Units that are excluded.",
                " Please make sure to exclude those when executing unlinking/removal procedures. "
            ) -FontSize 10pt -FontWeight normal, bold, normal, bold -Color None, Red, None, Red
            <#
            New-HTMLList -Type Unordered {
                foreach ($OU in $Script:Reporting['GPOOrganizationalUnit']['Variables']['ExcludedOU']) {
                    New-HTMLListItem -Text $OU -FontWeight normal, bold, normal
                }
            } -FontSize 10pt
            #>

        }

        New-HTMLText -Text @(
            "Please make sure that you really want to unlink GPO or delete Organizational Unit before executing changes. Sometimes it's completly valid to keep one or the other. "
            "Unlinking GPO from OU that has no Computer or User objects is fairly safe exercise. Removing OU requires a bit more dive in, and should only be executed if you know what you're doing. "
        ) -FontWeight normal, bold -Color None, Red -FontSize 10pt

        if ($Script:Reporting['GPOOrganizationalUnit']['Exclusions']) {
            New-HTMLText -LineBreak
            New-HTMLText -Text @(
                "While preparing this report following exclusions were defined. "
                "Please make sure that when you execute your steps to include those exclusions to prevent any issues. "
            ) -FontSize 10pt -FontWeight bold, normal -Color Red, None -LineBreak

            New-HTMLText -Text "Code to use for exclusions: " -FontSize 10pt -FontWeight bold -LineBreak

            $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOOrganizationalUnit']['Exclusions']

            if ($Code) {
                New-HTMLCodeBlock -Code $Code -Style powershell
            }
        }
    }
    Solution       = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOOrganizationalUnit']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type bar -Distributed
                    New-ChartAxisY -LabelMaxWidth 200 -LabelAlign left -Show
                    New-ChartBar -Name "Unlink GPO ($($Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPO']))" -Value $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPO']
                    New-ChartBar -Name "Unlink GPO Delete OU ($($Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPOEmpty']))" -Value $Script:Reporting['GPOOrganizationalUnit']['Variables']['UnlinkGPOEmpty']
                    New-ChartBar -Name "Delete OU ($($Script:Reporting['GPOOrganizationalUnit']['Variables']['DeleteOU']))" -Value $Script:Reporting['GPOOrganizationalUnit']['Variables']['DeleteOU']
                } -Title 'Organizational Units' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy Organizational Units' {
            New-HTMLTable -DataTable $Script:Reporting['GPOOrganizationalUnit']['Data'] -Filtering {
                New-TableHeader -ResponsiveOperations none -Names 'GPONames', 'Objects'
                New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Unlink GPO, Delete OU' -BackgroundColor Salmon -Row
                New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Unlink GPO' -BackgroundColor YellowOrange -Row
                New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Delete OU' -BackgroundColor Red -Row
                New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'OK' -BackgroundColor LightGreen -Row
                New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Excluded' -BackgroundColor DeepSkyBlue -Row
                New-HTMLTableCondition -Name 'Status' -ComparisonType string -Value 'Excluded, Default OU' -BackgroundColor DeepSkyBlue -Row
            } -PagingOptions 10, 20, 30, 40, 50 -ExcludeProperty GPO
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix Group Organizational Units' {
                New-HTMLContainer {
                    New-HTMLSpanStyle -FontSize 10pt {
                        #New-HTMLText -Text 'Following steps will guide you how to fix group policy owners'
                        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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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."
                            }
                            if ($Script:Reporting['GPOOrganizationalUnit']['Exclusions']) {
                                New-HTMLWizardStep -Name 'Required exclusions' {
                                    New-HTMLText -Text @(
                                        "While preparing this report following exclusions were defined. "
                                        "Please make sure that when you execute your steps to include those exclusions to prevent any issues. "
                                    )
                                    $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOOrganizationalUnit']['Exclusions']

                                    if ($Code) {
                                        New-HTMLCodeBlock -Code $Code -Style powershell
                                    }
                                }
                            }
                            New-HTMLWizardStep -Name 'Prepare report' {
                                New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with unlinking unused Group Policies. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOOrganizationalUnitBefore.html -Verbose -Type GPOOrganizationalUnit
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $OwnersGPO = Get-GPOZaurrOrganizationalUnit -Verbose
                                    $OwnersGPO | 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 'Unlink unused Group Policies' {
                                New-HTMLText -Text @(
                                    "Following command when executed runs cleanup procedure that unlinks all Group Policies from Organizational Units that have no user or computer objects. "
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental unlinking."
                                    'When run it will remove any GPO links from Organizational Units that have no objects applicable for GPOs.'
                                ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrLinkEmptyOU -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrLinkEmptyOU -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 removed matches expected data. "
                                    "Keep in mind that there is no backup for this, and if link is removed you would need to relink it yourself."
                                    "Once you remove it, it's gone. "
                                } -LineBreak
                                New-HTMLText -Text 'Once happy with results please follow with command (this will start removal process): ' -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrLinkEmptyOU -WhatIf -LimitProcessing 2 -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrLinkEmptyOU -WhatIf -LimitProcessing 2 -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete 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. "
                                } -LineBreak
                                New-HTMLText -TextBlock {
                                    "It's possible to exclude certain OU's from having GPO's unlinked using follwing method: "
                                } -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    $Exclude = @(
                                        "OU=Groups,OU=Production,DC=ad,DC=evotec,DC=pl"
                                        "OU=Test \, OU,OU=ITR02,DC=ad,DC=evotec,DC=xyz"
                                    )
                                    Remove-GPOZaurrLinkEmptyOU -Verbose -LimitProcessing 3 -WhatIf -ExcludeOrganizationalUnit $Exclude
                                }
                            }
                            New-HTMLWizardStep -Name 'Delete unused Organizational Units' {
                                New-HTMLText -Text @(
                                    "Following automation is not yet implemented. Requires more testing as potentially it could do more damage than help."
                                )
                            }
                            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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOOrganizationalUnitAfter.html -Verbose -Type GPOOrganizationalUnit
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOOrganizationalUnit']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOOrganizationalUnit']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                } -PagingOptions 10, 20, 30, 40, 50
            }
        }
    }
}
$GPOZaurrOrphans = [ordered] @{
    Name           = 'Broken Group Policies'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Get-GPOZaurrBroken -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {
        $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'] = @{}
        $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'] = @{}
        $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'] = @{}
        foreach ($GPO in $Script:Reporting['GPOBroken']['Data']) {
            if (-not $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName] = 0
            }
            if ($GPO.Status -eq 'Not available in AD') {
                $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD']++
                $Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++
                $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
            } elseif ($GPO.Status -eq 'Not available on SYSVOL') {
                $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol']++
                $Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++
                $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
            } elseif ($GPO.Status -eq 'Permissions issue') {
                $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue']++
                $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]++
            } elseif ($GPO.Status -eq 'ObjectClass issue') {
                $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue']++
                $Script:Reporting['GPOBroken']['Variables']['ToBeDeleted']++
                $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssuePerDomain'][$GPO.DomainName]++
                $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
            }
        }
        if ($Script:Reporting['GPOBroken']['Variables']['ToBeDeleted'] -gt 0) {
            $Script:Reporting['GPOBroken']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOBroken']['ActionRequired'] = $false
        }
    }
    Variables      = @{
        NotAvailableInAD                      = 0
        NotAvailableOnSysvol                  = 0
        NotAvailablePermissionIssue           = 0
        NotAvailablePermissionIssuePerDomain  = $null
        ToBeDeleted                           = 0
        ToBeDeletedPerDomain                  = $null
        NotAvailableObjectClassIssue          = 0
        NotAvailableObjectClassIssuePerDomain = $null
    }
    Overview       = {
        <#
        New-HTMLPanel {
            New-HTMLText -TextBlock {
                "Group Policies are stored in two places - Active Directory (metadata) and SYSVOL (content)."
                "Since those are managed in different ways, replicated in different ways it's possible because of different issues they get out of sync."
            } -LineBreak
            New-HTMLText -Text "For example:"
            New-HTMLList -Type Unordered {
                New-HTMLListItem -Text 'USN Rollback in AD could cause group policies to reappar in Active Directory, yet SYSVOL data would be unavailable'
                New-HTMLListItem -Text 'Group Policy deletion failing to delete GPO content'
                New-HTMLListItem -Text 'DFSR replication failing between DCs'
            }
            New-HTMLText -Text 'Following chart presents ', 'Broken / Orphaned Group Policies' -FontSize 10pt -FontWeight normal, bold
            New-HTMLList -Type Unordered {
                New-HTMLListItem -Text 'Group Policies on SYSVOL, but no details in AD: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'] -FontWeight normal, bold
                New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold
                New-HTMLListItem -Text "Group Policies which couldn't be assed due to permissions issue: ", $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue'] -FontWeight normal, bold
            } -FontSize 10pt
            New-HTMLText -FontSize 10pt -Text 'Those problems must be resolved before doing other clenaup activities.'
            New-HTMLChart {
                New-ChartBarOptions -Type barStacked
                New-ChartLegend -Name 'Not in AD', 'Not on SYSVOL', 'Permissions Issue' -Color Crimson, LightCoral, IndianRed
                New-ChartBar -Name 'Orphans' -Value $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue']
            } -Title 'Broken / Orphaned Group Policies' -TitleAlignment center
        }
        #>

    }
    Summary        = {
        New-HTMLText -TextBlock {
            "Group Policies are stored in two places - Active Directory (metadata) and SYSVOL (content)."
            "Since those are managed in different ways, replicated in different ways it's possible because of different issues they get out of sync."
        } -FontSize 10pt -LineBreak
        New-HTMLText -Text "For example:" -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'USN Rollback in AD could cause already deleted Group Policies to reapper in Active Directory, yet SYSVOL data would be unavailable'
            New-HTMLListItem -Text 'Group Policy deletion failing to delete GPO content'
            New-HTMLListItem -Text 'Permission issue preventing deletion of GPO content'
            New-HTMLListItem -Text 'Failing DFSR replication between DCs'
        } -FontSize 10pt
        New-HTMLText -Text 'Following problems were detected:' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies on SYSVOL, but no details in AD: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Group Policies which exists, but have wrong ObjectClass: ', $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which couldn't be assessed due to permissions issue: ", $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOBroken']['Variables']['ToBeDeletedPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -Text "Please review output in table and follow the steps below table to get Active Directory Group Policies in healthy state." -FontSize 10pt
    }
    Solution       = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOBroken']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'Not in AD', 'Not on SYSVOL', 'ObjectClass Issue', 'Permissions Issue' -Color Crimson, LightCoral, MediumOrchid, IndianRed
                    New-ChartBar -Name 'Broken' -Value $Script:Reporting['GPOBroken']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOBroken']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOBroken']['Variables']['NotAvailableObjectClassIssue'], $Script:Reporting['GPOBroken']['Variables']['NotAvailablePermissionIssue']
                } -Title 'Broken / Orphaned Group Policies' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Health State of Group Policies' {
            New-HTMLTable -DataTable $Script:Reporting['GPOBroken']['Data'] -Filtering {
                New-HTMLTableCondition -Name 'Status' -Value "Not available in AD" -BackgroundColor Salmon -ComparisonType string
                New-HTMLTableCondition -Name 'Status' -Value "Not available on SYSVOL" -BackgroundColor LightCoral -ComparisonType string
                New-HTMLTableCondition -Name 'Status' -Value "ObjectClass issue" -BackgroundColor MediumOrchid -ComparisonType string
                New-HTMLTableCondition -Name 'Status' -Value "Permissions issue" -BackgroundColor MediumVioletRed -ComparisonType string -Color White
            } -PagingOptions 10, 20, 30, 40, 50
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix - Not available on SYSVOL / Active Directory / ObjectClass issue' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 with removal. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenGpoBefore.html -Verbose -Type GPOBroken
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $GPOOutput = Get-GPOZaurrBroken -Verbose
                                    $GPOOutput | 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 'Make a backup (optional)' {
                                New-HTMLText -TextBlock {
                                    "The process fixing broken GPOs will delete AD or SYSVOL content depending on type of a problem. "
                                    "While it's always useful to have a backup, this backup won't actually backup those broken group policies"
                                    " for a simple reason that those are not backupable. You can't back up GPO if there is no SYSVOL content"
                                    " and you can't backup GPO if there's only SYSVOL content. "
                                    "However, since the script does make changes to GPOs it's advised to have a backup anyways! "
                                }
                                New-HTMLCodeBlock -Code {
                                    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All
                                    $GPOSummary | Format-Table # only if you want to display output of backup
                                }
                                New-HTMLText -TextBlock {
                                    "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. "
                                }
                            }
                            New-HTMLWizardStep -Name 'Fix GPOs not available in AD' {
                                New-HTMLText -Text @(
                                    "Following command when executed runs cleanup procedure that removes all broken GPOs on SYSVOL side. ",
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf ",
                                    "parameter as shown below to prevent accidental removal. ",
                                    'When run it will remove any GPO remains from SYSVOL, that should not be there, as AD metadata is already gone.'
                                    "Please notice I'm using SYSVOL as a type, because the removal will happen on SYSVOL. "
                                ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type SYSVOL -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type SYSVOL -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose
                                }
                                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 deleted matches expected data. "
                                    "Keep in mind that what backup command does is simply copy SYSVOL content to given place. "
                                    "Since there is no GPO metadata in AD there's no real restore process for this step. "
                                    "It's there to make sure if someone kept some data in there and wants to get access to it, he/she can. "
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start deletion process): " -LineBreak -FontWeight bold

                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type SYSVOL -LimitProcessing 2 -BackupPath $Env:UserProfile\Desktop\GPOSYSVOLBackup -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type SYSVOL -LimitProcessing 2 -BackupPath $Env:UserProfile\Desktop\GPOSYSVOLBackup -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete 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. "
                                    "If there's nothing else to be deleted on SYSVOL side, we can skip to next step step. "
                                }
                            }
                            New-HTMLWizardStep -Name 'Fix GPOs not available on SYSVOL' {
                                New-HTMLText -Text @(
                                    "Following command when executed runs cleanup procedure that removes all broken GPOs on Active Directory side."
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental removal."
                                    'When run it will remove any GPO remains from AD, that should not be there, as SYSVOL content is already gone.'
                                    "Please notice I'm using AD as a type, because the removal will happen on AD side. "
                                ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type AD -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type AD -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose
                                }
                                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 deleted matches expected data. "
                                    "Keep in mind that there is no backup for this. "
                                    "Since there is no SYSVOL data, and only AD object is there there's no real restore process for this step. "
                                    "Once you delete it, it's gone. "
                                } -LineBreak
                                New-HTMLText -Text 'Once happy with results please follow with command (this will start deletion process): ' -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type AD -LimitProcessing 2 -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type AD -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete 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. "
                                    "If there's nothing else to be deleted on AD side, we can skip to next step step. "
                                }
                            }
                            New-HTMLWizardStep -Name 'Fix GPOs of wrong ObjectClass' {
                                New-HTMLText -Text @(
                                    "Following command when executed runs cleanup procedure that removes all GPOs which have ObjectClass of Container, rather than required groupPolicyContainer. "
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental removal."
                                    'When run it will remove GPO metadata from AD, and any files/folders from SYSVOL.'
                                ) -FontWeight normal, normal, bold, normal -Color Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type ObjectClass -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type ObjectClass -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose
                                }
                                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 deleted matches expected data. "
                                    "Keep in mind that there is no backup for this as backup process doesn't see GPOs that are of wrong ObjectClass. "
                                    "Once you delete it, it's gone. "
                                } -LineBreak
                                New-HTMLText -Text 'Once happy with results please follow with command (this will start deletion process): ' -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type ObjectClass -LimitProcessing 2 -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrBroken -Type ObjectClass -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor' -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed deletes only first X broken GPOs. Use LimitProcessing parameter to prevent mass delete 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. "
                                    "If there's nothing else to be deleted, we can skip to next step step. "
                                }
                            }
                            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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrBrokenGpoAfter.html -Verbose -Type GPOBroken
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOBroken']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOBroken']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrOwners = [ordered] @{
    Name           = 'Group Policy Owners'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        if ($Script:Reporting['GPOOwners']['Exclusions']) {
            Get-GPOZaurrOwner -IncludeSysvol -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ApprovedOwner $Script:Reporting['GPOOwners']['Exclusions']
        } else {
            Get-GPOZaurrOwner -IncludeSysvol -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
        }
    }
    Processing     = {
        # Create Per Domain Variables
        $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'] = @{}
        $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'] = @{}
        foreach ($GPO in $Script:Reporting['GPOOwners']['Data']) {
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName] = 0
            }
            # Checks
            if ($GPO.Status -contains 'Consistent') {
                $Script:Reporting['GPOOwners']['Variables']['IsConsistent']++
            } elseif ($GPO.Status -contains 'Inconsistent') {
                $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent']++
            }
            if ($GPO.Status -contains 'Administrative') {
                $Script:Reporting['GPOOwners']['Variables']['IsAdministrative']++
            } elseif ($GPO.Status -contains 'Approved') {
                $Script:Reporting['GPOOwners']['Variables']['IsApproved']++
            } else {
                $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative']++
            }

            if ($GPO.SysvolExists -eq $false) {
                $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFix']++
                $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$GPO.DomainName]++
            } else {
                if ($GPO.Status -contains 'Inconsistent') {
                    $Script:Reporting['GPOOwners']['Variables']['WillFix']++
                    $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName]++
                } elseif ($GPO.Status -contains 'NotAdministrative' -and $GPO.Status -notcontains 'Approved') {
                    $Script:Reporting['GPOOwners']['Variables']['WillFix']++
                    $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName]++
                } else {
                    $Script:Reporting['GPOOwners']['Variables']['WillNotTouch']++
                }
            }
            <#
            if (($GPO.IsOwnerAdministrative -eq $false -or $GPO.IsOwnerConsistent -eq $false) -and $GPO.Status -and $GPO.SysvolExists -eq $true) {
                $Script:Reporting['GPOOwners']['Variables']['WillFix']++
                $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$GPO.DomainName]++
            } elseif ($GPO.SysvolExists -eq $false) {
                $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFix']++
                $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOOwners']['Variables']['WillNotTouch']++
            }
            #>

        }
        if ($Script:Reporting['GPOOwners']['Variables']['WillFix'] -gt 0) {
            $Script:Reporting['GPOOwners']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOOwners']['ActionRequired'] = $false
        }
    }
    Variables      = @{
        IsAdministrative         = 0
        IsApproved               = 0
        IsNotAdministrative      = 0
        IsConsistent             = 0
        IsNotConsistent          = 0
        WillFix                  = 0
        RequiresDiffFix          = 0
        WillNotTouch             = 0
        RequiresDiffFixPerDomain = $null
        WillFixPerDomain         = $null
    }
    Overview       = {
        <#
        New-HTMLPanel {
            New-HTMLText -Text 'Following chart presents Group Policy owners and whether they are administrative and consistent. By design an owner of Group Policy should be Domain Admins or Enterprise Admins group only to prevent malicious takeover. ', `
                "It's also important that owner in Active Directory matches owner on SYSVOL (file system)." -FontSize 10pt
            New-HTMLList -Type Unordered {
                New-HTMLListItem -Text 'Administrative Owners: ', $Script:Reporting['GPOOwners']['Variables']['IsAdministrative'] -FontWeight normal, bold
                New-HTMLListItem -Text 'Non-Administrative Owners: ', $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative'] -FontWeight normal, bold
                New-HTMLListItem -Text "Owners consistent in AD and SYSVOL: ", $Script:Reporting['GPOOwners']['Variables']['IsConsistent'] -FontWeight normal, bold
                New-HTMLListItem -Text "Owners not-consistent in AD and SYSVOL: ", $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent'] -FontWeight normal, bold
            } -FontSize 10pt
            New-HTMLChart {
                New-ChartBarOptions -Type barStacked
                New-ChartLegend -Name 'Yes', 'No' -Color PaleGreen, Orchid
                New-ChartBar -Name 'Is administrative' -Value $Script:Reporting['GPOOwners']['Variables']['IsAdministrative'], $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative']
                New-ChartBar -Name 'Is consistent' -Value $Script:Reporting['GPOOwners']['Variables']['IsConsistent'], $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent']
            } -Title 'Group Policy Owners' -TitleAlignment center
        }
        #>

    }
    Summary        = {
        New-HTMLText -FontSize 10pt -Text @(
            "By default, GPO creation is usually maintained by Domain Admins or Enterprise Admins. "
            "When GPO is created by Domain Admins or Enterprise Admins group members, the GPO Owner is set to Domain Admins. "
            "When GPO is created by a member of Group Policy Creator Owners or other group has delegated rights to create a GPO, "
            "the owner of said GPO is not Domain Admins group but is assigned to the relevant user. "
            "GPO Owners should be Domain Admins or Enterprise Admins to prevent abuse. "
            "If that isn't so, it means the owner can fully control GPO and potentially change its settings in an uncontrolled way. "
            "While at the moment of creation of new GPO, it's not a problem, in the long term, it's possible such a person may no longer be admin, yet keep their rights over GPO. "
            "As your aware, Group Policies are stored in 2 places. In Active Directory (metadata) and SYSVOL (settings). "
            "This means that there are 2 places where GPO Owners exists. "
            "This also means that for multiple reasons, AD and SYSVOL can be out of sync when it comes to their permissions, which can lead to uncontrolled ability to modify them. "
            "Ownership in Active Directory and Ownership of SYSVOL for said GPO is required to be the same."
        )
        New-HTMLText -Text "Here's a short summary of ", "Group Policy Owners", ": " -FontSize 10pt -FontWeight normal, bold, normal
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Administrative Owners: ', $Script:Reporting['GPOOwners']['Variables']['IsAdministrative'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Non-Administrative, but approved Owners (for example AGPM): ', $Script:Reporting['GPOOwners']['Variables']['IsApproved'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Non-Administrative Owners: ', $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative'] -FontWeight normal, bold
            New-HTMLListItem -Text "Owners consistent in AD and SYSVOL: ", $Script:Reporting['GPOOwners']['Variables']['IsConsistent'] -FontWeight normal, bold
            New-HTMLListItem -Text "Owners not-consistent in AD and SYSVOL: ", $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -FontSize 10pt -Text "Following will need to happen: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies requiring owner change: ', $Script:Reporting['GPOOwners']['Variables']['WillFix'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which can't be fixed (no SYSVOL?): ", $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFix'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies unaffected: ", $Script:Reporting['GPOOwners']['Variables']['WillNotTouch'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOOwners']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require fixing using, ', 'different methods:' -FontSize 10pt -FontWeight bold, bold -Color Black, RedRobin -TextDecoration none, underline
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOOwners']['Variables']['RequiresDiffFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt

        if ($Script:Reporting['GPOOwners']['Exclusions']) {
            New-HTMLText -LineBreak
            New-HTMLText -Text @(
                "While preparing this report following exclusions were defined. "
                "Please make sure that when you execute your steps to include those exclusions to prevent any issues. "
            ) -FontSize 10pt -FontWeight bold, normal -Color Red, None -LineBreak

            New-HTMLText -Text "Code to use for exclusions: " -FontSize 10pt -FontWeight bold -LineBreak

            $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOOwners']['Exclusions']

            if ($Code) {
                New-HTMLCodeBlock -Code $Code -Style powershell
            }
        }
    }
    Solution       = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOOwners']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'Yes', 'No', 'Approved' -Color LightGreen, Salmon, DeepSkyBlue
                    New-ChartBar -Name 'Is administrative' -Value $Script:Reporting['GPOOwners']['Variables']['IsAdministrative'], $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative'], $Script:Reporting['GPOOwners']['Variables']['IsApproved']
                    New-ChartBar -Name 'Is consistent' -Value $Script:Reporting['GPOOwners']['Variables']['IsConsistent'], $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent']
                } -Title 'Group Policy Owners' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy Owners' {
            New-HTMLTable -DataTable $Script:Reporting['GPOOwners']['Data'] -Filtering {
                #New-HTMLTableCondition -Name 'IsOwnerConsistent' -Value $false -BackgroundColor Salmon -ComparisonType string -Row
                #New-HTMLTableCondition -Name 'IsOwnerAdministrative' -Value $false -BackgroundColor Salmon -ComparisonType string -Row

                New-HTMLTableCondition -Name 'Status' -Value 'Administrative, Consistent' -BackgroundColor LightGreen -ComparisonType string -Row
                New-HTMLTableCondition -Name 'Status' -Value 'NotAdministrative, Consistent, Approved' -BackgroundColor DeepSkyBlue -ComparisonType string -Row

                New-HTMLTableCondition -Name 'Status' -Value 'Administrative, Inconsistent' -BackgroundColor Salmon -ComparisonType string -Row
                New-HTMLTableCondition -Name 'Status' -Value 'NotAdministrative, Inconsistent' -BackgroundColor Salmon -ComparisonType string -Row
                #New-HTMLTableCondition -Name 'Status' -Value 'Administrative, Inconsistent, Approved' -BackgroundColor Salmon -ComparisonType string -Row
                # New-HTMLTableCondition -Name 'Status' -Value 'NotAdministrative, Inconsistent, Approved' -BackgroundColor Salmon -ComparisonType string -Row

            } -PagingOptions 10, 20, 30, 40, 50
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix Group Policy Owners' {
                New-HTMLContainer {
                    New-HTMLSpanStyle -FontSize 10pt {
                        #New-HTMLText -Text 'Following steps will guide you how to fix group policy owners'
                        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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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."
                            }
                            if ($Script:Reporting['GPOOwners']['Exclusions']) {
                                New-HTMLWizardStep -Name 'Required exclusions' {
                                    New-HTMLText -Text @(
                                        "While preparing this report following exclusions were defined. "
                                        "Please make sure that when you execute your steps to include those exclusions to prevent any issues. "
                                    )
                                    $Code = New-GPOZaurrExclusions -ExclusionsArray $Script:Reporting['GPOOwners']['Exclusions']

                                    if ($Code) {
                                        New-HTMLCodeBlock -Code $Code -Style powershell
                                    }
                                }
                            }
                            New-HTMLWizardStep -Name 'Prepare report' {
                                New-HTMLText -Text "Depending when this report was run you may want to prepare new report before proceeding with fixing Group Policy Owners. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOOwnersBefore.html -Verbose -Type GPOOwners
                                }
                                New-HTMLText -TextBlock {
                                    "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 {
                                    $OwnersGPO = Get-GPOZaurrOwner -IncludeSysvol -Verbose
                                    $OwnersGPO | 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 'Make a backup (optional)' {
                                New-HTMLText -TextBlock {
                                    "The process of fixing GPO Owner does NOT touch GPO content. It simply changes owners on AD and SYSVOL at the same time. "
                                    "However, it's always good to have a backup before executing changes that may impact Active Directory. "
                                }
                                New-HTMLCodeBlock -Code {
                                    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All
                                    $GPOSummary | Format-Table # only if you want to display output of backup
                                }
                                New-HTMLText -TextBlock {
                                    "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. "
                                }
                            }
                            New-HTMLWizardStep -Name 'Set GPO Owners to Administrative (Domain Admins)' {
                                New-HTMLText -Text "Following command will find any GPO which doesn't have proper GPO Owner (be it due to inconsistency or not being Domain Admin) and will enforce new GPO Owner. "
                                New-HTMLText -Text "Make sure when running it for the first time to run it with ", "WhatIf", " parameter as shown below to prevent accidental removal." -FontWeight normal, bold, normal -Color Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Set-GPOZaurrOwner -Type All -Verbose -WhatIf
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Set-GPOZaurrOwner -Type All -Verbose -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data."
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Set-GPOZaurrOwner -Type All -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Set-GPOZaurrOwner -Type All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed sets new owner only on first X non-compliant GPO Owners for AD/SYSVOL. Use LimitProcessing parameter to prevent mass change 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. "
                                } -LineBreak
                                New-HTMLText -TextBlock {
                                    "It's possible to define certain owners as being approved (for example with domain that have AGPM). "
                                    "Make sure to verify if excluded/approved owners were provided in Required Exclusions tab, or add your own when nessecary. "
                                    "You can approve owners with following code: "
                                } -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    $Approved = @(
                                        'EVOTEC\przemyslaw.klys'
                                        'EVOTEC\green.b'
                                    )
                                    Set-GPOZaurrOwner -Type All -Verbose -LimitProcessing 2 -ApprovedOwner $Approved
                                }
                                New-HTMLText -TextBlock {
                                    "Please keep in mind that ApprovedOwner is only applicable to Non-Administrative permissions to provide a way to approve special use cases. "
                                    "It won't do anything for inconsistent, unknown permissions as those are still treated as wrong. "
                                }
                            }
                            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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOOwnersAfter.html -Verbose -Type GPOOwners
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOOwners']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOOwners']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                } -PagingOptions 10, 20, 30, 40, 50
            }
        }
    }
}
$GPOZaurrPassword = [ordered] @{
    Name       = 'Group Policy Passwords'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        Get-GPOZaurrPassword -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing = {

    }
    Variables  = @{

    }
    Overview   = {

    }
    Solution   = {
        New-HTMLTable -DataTable $Script:Reporting['GPOPassword']['Data'] -Filtering

        if ($Script:Reporting['GPOPassword']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOPassword']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrPermissionsAdministrative = [ordered] @{
    Name       = 'Group Policy Administrative Permissions'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        $Object = [ordered] @{
            Permissions = Get-GPOZaurrPermission -Type Administrative -IncludePermissionType GpoEditDeleteModifySecurity -ReturnSecurityWhenNoData -IncludeGPOObject -ReturnSingleObject -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
        }
        $Object['PermissionsPerRow'] = $Object['Permissions'] | ForEach-Object { $_ }
        $Object['PermissionsAnalysis'] = [System.Collections.Generic.List[PSCustomObject]]::new()
        $Object
    }
    Processing = {
        # Create Per Domain Variables
        $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'] = @{}
        $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouchPerDomain'] = @{}

        foreach ($GPO in $Script:Reporting['GPOPermissionsAdministrative']['Data'].Permissions) {
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'][$GPO[0].DomainName]) {
                $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'][$GPO[0].DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouchPerDomain'][$GPO[0].DomainName]) {
                $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouchPerDomain'][$GPO[0].DomainName] = 0
            }
            # Checks

            $Analysis = Get-PermissionsAnalysis -GPOPermissions $GPO -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Type Administrative -PermissionType GpoEditDeleteModifySecurity
            if ($Analysis.Skip) {
                $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouch']++
                $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouchPerDomain'][$GPO[0].DomainName]++
            } else {
                $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFix']++
                $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'][$GPO[0].DomainName]++
            }
            # lets create table and add it there
            $Script:Reporting['GPOPermissionsAdministrative']['Data'].'PermissionsAnalysis'.Add($Analysis)
        }
        if ($Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFix'] -gt 0) {
            $Script:Reporting['GPOPermissionsAdministrative']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOPermissionsAdministrative']['ActionRequired'] = $false
        }
    }
    Variables  = @{
        WillFix               = 0
        WillNotTouch          = 0
        WillFixPerDomain      = $null
        WillNotTouchPerDomain = $null
    }
    Overview   = {

    }
    Summary    = {
        New-HTMLText -FontSize 10pt -TextBlock {
            "When GPO is created by default it gets Domain Admins and Enterprise Admins with Edit/Delete/Modify Security permissions. "
            "For some reason, some Administrators remove those permissions or modify them when they shouldn't touch those at all. "
            "Since having Edit/Delete/Modify Security permissions doesn't affect GPOApply permissions there's no reason to remove Domain Admins or Enterprise Admins from permissions, or limit their rights. "
        } -LineBreak
        New-HTMLText -FontSize 10pt -Text "Assesment results: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies requiring adding Domain Admins or Enterprise Admins: ', $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFix'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouch'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -Text @(
            "That means we need to fix permissions on: "
            $($Script:Reporting['GPOPermissionsAdministrative']['Variables'].WillFix)
            " out of "
            $($Script:Reporting['GPOPermissionsAdministrative']['Data'].Permissions).Count
            " Group Policies. "
        ) -FontSize 10pt -FontWeight bold, bold, normal, bold, normal -Color Black, FreeSpeechRed, Black, Black -LineBreak -TextDecoration none, underline, underline, underline, none
    }
    Solution   = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOPermissionsAdministrative']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'Yes', 'No' -Color SpringGreen, Salmon
                    New-ChartBar -Name 'Administrative Users Present' -Value $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillNotTouch'], $Script:Reporting['GPOPermissionsAdministrative']['Variables']['WillFix']
                    #New-ChartBar -Name 'Accessible Group Policies' -Value $Script:Reporting['GPOPermissionsAdministrative']['Variables']['Read'], $Script:Reporting['GPOPermissionsAdministrative']['Variables']['CouldNotRead']
                } -Title 'Group Policy Permissions' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy Administrative Users Permissions Summary' {
            New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsAdministrative']['Data'].PermissionsPerRow -Filtering {
                New-HTMLTableCondition -Name 'Permission' -Value '' -BackgroundColor Salmon -ComparisonType string -Row
            } -PagingOptions 7, 15, 30, 45, 60
        }
        New-HTMLSection -Name 'Group Policy Administrative Users Analysis' {
            New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsAdministrative']['Data'].PermissionsAnalysis -Filtering {
                # New-HTMLTableCondition -Name 'Permission' -Value '' -BackgroundColor Salmon -ComparisonType string -Row
            } -PagingOptions 7, 15, 30, 45, 60
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix Group Policy Administrative Users' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 with fixing Group Policy Authenticated Users. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsAdministrativeBefore.html -Verbose -Type GPOPermissionsAdministrative
                                }
                                New-HTMLText -TextBlock {
                                    "When executed it will take a while to generate all data and provide you with new report depending on size of environment. "
                                    "GPOs with problems will be those not having any value for Permission/PermissionType columns. "
                                    "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 {
                                    $AdministrativeUsers = Get-GPOZaurrPermission -Type Administrative -IncludePermissionType GpoEditDeleteModifySecurity -ReturnSecurityWhenNoData
                                    $AdministrativeUsers | 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 'Make a backup (optional)' {
                                New-HTMLText -TextBlock {
                                    "The process of fixing GPO Permissions does NOT touch GPO content. It simply adds permissionss on AD and SYSVOL at the same time for given GPO. "
                                    "However, it's always good to have a backup before executing changes that may impact Active Directory. "
                                }
                                New-HTMLCodeBlock -Code {
                                    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All
                                    $GPOSummary | Format-Table # only if you want to display output of backup
                                }
                                New-HTMLText -TextBlock {
                                    "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. "
                                }
                            }

                            New-HTMLWizardStep -Name 'Add Administrative Groups proper permissions GPO' {
                                New-HTMLText -Text @(
                                    "Following command will find any GPO which doesn't have Domain Admins and Enterprise Admins added with GpoEditDeleteModifySecurity and will add it as GpoEditDeleteModifySecurity. ",
                                    "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ",
                                    "It ensures that Domain Admins and Enterprise Admins can manage GPO. ",
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental adding of permissions."
                                ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data."
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed adds Enterprise Admins or/and Domain Admins (GpoEditDeleteModifySecurity permission) only on first X non-compliant Group Policies. "
                                    "Use LimitProcessing parameter to prevent mass change 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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsAdministrativeAfter.html -Verbose -Type GPOPermissionsAdministrative
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOPermissionsAdministrative']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsAdministrative']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                } -PagingOptions 10, 20, 30, 40, 50
            }
        }
    }
}
$GPOZaurrPermissionsAnalysis = [ordered] @{
    Name       = 'Group Policy Permissions Analysis'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        $Object = [ordered] @{
            Permissions = Get-GPOZaurrPermission -ReturnSecurityWhenNoData -IncludeGPOObject -ReturnSingleObject -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
        }
        $Object['PermissionsPerRow'] = $Object['Permissions'] | ForEach-Object { $_ }
        $Object['PermissionsAnalysis'] = Get-GPOZaurrPermissionAnalysis -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Permissions $Object.Permissions
        $Object['PermissionsIssues'] = Get-GPOZaurrPermissionIssue -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
        $Object
    }
    Processing = {
        # Create Per Domain Variables
        $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'] = @{}
        $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrativePerDomain'] = @{}

        $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'] = @{}
        $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAuthenticatedUsersPerDomain'] = @{}

        $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'] = @{}
        $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystemPerDomain'] = @{}

        $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'] = @{}
        $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknownPerDomain'] = @{}

        $Script:Reporting['GPOPermissions']['Variables']['WillNotFixPerDomain'] = @{}
        $Script:Reporting['GPOPermissions']['Variables']['WillFixPerDomain'] = @{}

        $Script:Reporting['GPOPermissions']['Variables']['ReadPerDomain'] = @{}
        $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'] = @{}
        $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'] = @{}

        foreach ($GPO in $Script:Reporting['GPOPermissions']['Data'].PermissionsIssues) {
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['ReadPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['ReadPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'][$GPO.DomainName] = 0
            }
            if ($GPO.PermissionIssue) {
                $Script:Reporting['GPOPermissions']['Variables']['CouldNotRead']++
                $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOPermissions']['Variables']['Read']++
                $Script:Reporting['GPOPermissions']['Variables']['ReadPerDomain'][$GPO.DomainName]++
            }
            $Script:Reporting['GPOPermissions']['Variables']['Total']++
            $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'][$GPO.DomainName]++
        }
        foreach ($GPO in $Script:Reporting['GPOPermissions']['Data'].PermissionsAnalysis) {
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillNotFixPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotFixPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrativePerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrativePerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystemPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystemPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknownPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknownPerDomain'][$GPO.DomainName] = 0
            }

            # Checks
            if ($GPO.Administrative -eq $true) {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrative']++
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrativePerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrative']++
                $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'][$GPO.DomainName]++
            }

            if ($GPO.AuthenticatedUsers -eq $true) {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAuthenticatedUsers']++
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAuthenticatedUsersPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsers']++
                $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'][$GPO.DomainName]++
            }

            if ($GPO.System -eq $true) {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystem']++
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystemPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixSystem']++
                $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'][$GPO.DomainName]++
            }

            if ($GPO.Unknown -eq $false) {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknown']++
                $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknownPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknown']++
                $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'][$GPO.DomainName]++
            }

            if ($GPO.Status -eq $false) {
                $Script:Reporting['GPOPermissions']['Variables']['WillFix']++
                $Script:Reporting['GPOPermissions']['Variables']['WillFixPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOPermissions']['Variables']['WillNotFix']++
                $Script:Reporting['GPOPermissions']['Variables']['WillNotFixPerDomain'][$GPO.DomainName]++
            }
        }
        if ($Script:Reporting['GPOPermissions']['Variables']['WillFix'] -gt 0 -or $Script:Reporting['GPOPermissions']['Variables']['CouldNotRead'] -gt 0) {
            $Script:Reporting['GPOPermissions']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOPermissions']['ActionRequired'] = $false
        }
    }
    Variables  = @{
        # Issues / Couldnt read report
        Read                                    = 0
        ReadPerDomain                           = $null
        CouldNotRead                            = 0
        CouldNotReadPerDomain                   = $null
        TotalPerDomain                          = $null
        Total                                   = 0
        # Permissions analysis
        WillFix                                 = 0
        WillNotFix                              = 0
        WillFixAdministrative                   = 0
        WillNotTouchAdministrative              = 0
        WillFixUnknown                          = 0
        WillNotTouchUnknown                     = 0
        WillNotTouchSystem                      = 0
        WillFixSystem                           = 0
        WillNotTouchAuthenticatedUsers          = 0
        WillFixAuthenticatedUsers               = 0
        WillNotTouchAuthenticatedUsersPerDomain = $null
        WillFixAuthenticatedUsersPerDomain      = $null
        WillNotTouchSystemPerDomain             = $null
        WillFixSystemPerDomain                  = $null
        WillFixAdministrativePerDomain          = $null
        WillNotTouchAdministrativePerDomain     = $null
        WillNotFixPerDomain                     = $null
        WillFixPerDomain                        = $null
        WillFixUnknownPerDomain                 = $null
        WillNotTouchUnknownPerDomain            = $null
    }
    Summary    = {
        New-HTMLText -FontSize 10pt -Text "When GPO is created it gets a handful of standard permissions. Those are:"
        New-HTMLList {
            New-HTMLListItem -Text "NT AUTHORITY\Authenticated Users with GpoApply permissions"
            New-HTMLListItem -Text "Domain Admins and Enterprise Admins with Edit/Delete/Modify permissions"
            New-HTMLListItem -Text "SYSTEM account with Edit/Delete/Modify permissions"
        } -FontSize 10pt
        New-HTMLText -FontSize 10pt -Text "But then IT people change those permissions to their own needs. While most changes make sense and are required to be able to target proper groups of people, some changes are not required or even bad. "

        New-HTMLText -Text "First problem relates to NT AUTHORITY\Authenticated Users" -FontSize 10pt -FontWeight bold -TextDecoration underline -Alignment center

        New-HTMLText -FontSize 10pt -TextBlock {
            "When GPO is created one of the permissions that are required for proper functioning of Group Policies is NT AUTHORITY\Authenticated Users. "
            "Some Administrators don't follow best practices and trying to remove GpoApply permission, remove also GpoRead permission from a GPO which can have consequences. "
            "On June 14th, 2016 Microsoft released [HotFix](https://support.microsoft.com/en-gb/help/3159398/ms16-072-description-of-the-security-update-for-group-policy-june-14-2) that requires Authenticated Users to be present on all Group Policies to function properly. "
            "MS16-072 changes the security context with which user group policies are retrieved. "
            "This by-design behavior change protects customers’ computers from a security vulnerability. "
        }
        New-HTMLList {
            New-HTMLListItem -Text "Before MS16-072 is installed, user group policies were retrieved by using the user’s security context. "
            New-HTMLListItem -Text "After MS16-072 is installed, user group policies are retrieved by using the computer's security context."
        } -FontSize 10pt

        New-HTMLText -FontSize 10pt -Text @(
            "Unfortunetly it's not as simple as it sounds. While checking for permissions mostly works fine, it's possible a Group Policy has totally removed account permissions from being able to asses any of it. ",
            "The account we're using "
            "$($Env:USERDOMAIN)\$($Env:USERNAME.ToUpper())",
            " may simply not have enough permisions to properly asses permissions for a GPO. "
            "Therefore we're using dual assesment for this situation, where first assesment is checking for GPO visibility or lack of it, and second assesment is checking for direct permissions assignement. "
            "We just were able to detect the problem, but hopefully higher level account (Domain Admin) should be able to provide full assesment. "
        ) -FontWeight normal, normal, bold, normal -Color None, None, BlueDiamond, none, none

        New-HTMLText -FontSize 10pt -Text "First assesment results: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text "Group Policies couldn't read at all: ", $Script:Reporting['GPOPermissions']['Variables']['CouldNotRead'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies with permissions allowing read: ", $Script:Reporting['GPOPermissions']['Variables']['Read'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'].Keys) {
                New-HTMLListItem -Text @(
                    "$Domain requires ",
                    $Script:Reporting['GPOPermissions']['Variables']['CouldNotReadPerDomain'][$Domain],
                    " changes out of ",
                    $Script:Reporting['GPOPermissions']['Variables']['TotalPerDomain'][$Domain],
                    "."
                ) -FontWeight normal, bold, normal
            }
        } -FontSize 10pt

        New-HTMLText -FontSize 10pt -Text "Second Assesment results " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies requiring Authenticated Users with GpoRead permission: ', $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsers'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAuthenticatedUsers'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsersPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -Text "Second problem relates to Domain Admins and Enterprise Admins" -FontSize 10pt -FontWeight bold -TextDecoration underline -Alignment center
        New-HTMLText -FontSize 10pt -TextBlock {
            "When GPO is created by default it gets Domain Admins and Enterprise Admins with Edit/Delete/Modify Security permissions. "
            "For some reason, some Administrators remove those permissions or modify them when they shouldn't touch those at all. "
            "Since having Edit/Delete/Modify Security permissions doesn't affect GPOApply permissions there's no reason to remove Domain Admins or Enterprise Admins from permissions, or limit their rights. "
            "Domain Admins and Enterprise Admins have to have either GPOEditModify permissions or at the very least GPOCustom. "
            "When GPOCustom is set it usually means there's a mix of Allow and Deny permission in place (for example deny GPOApply). "
            "In such case we're assuming you know what you're doing. However it's always possible to review those permissions, as those are marked in the table for review. "
        } -LineBreak

        New-HTMLText -FontSize 10pt -Text "Assesment results " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies requiring Administrative permission fix: ', $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrative'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrative'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrativePerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt

        New-HTMLText -Text "Third problem relates to SYSTEM account" -FontSize 10pt -FontWeight bold -TextDecoration underline -Alignment center
        New-HTMLText -FontSize 10pt -TextBlock {
            "When GPO is created by default it gets SYSTEM account with Edit/Delete/Modify Security permissions. "
            "For some reason, some Administrators remove those permissions or modify them when they shouldn't touch those at all. "
            "Since having Edit/Delete/Modify Security permissions doesn't affect GPOApply permissions there's no reason to remove SYSTEM from permissions, or limit their rights. "
        } -LineBreak

        New-HTMLText -FontSize 10pt -Text "Assesment results " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies requiring SYSTEM permission fix: ', $Script:Reporting['GPOPermissions']['Variables']['WillFixSystem'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystem'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['WillFixSystemPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt

        New-HTMLText -Text "Fourth problem relates to UNKNOWN SID" -FontSize 10pt -FontWeight bold -TextDecoration underline -Alignment center
        New-HTMLText -FontSize 10pt -TextBlock {
            "Sometimes groups or users are deleted in Active Directory and unfortunetly their permissions are not cleaned automatically. "
            "Those are left in-place and stay there forever until removed. "
        } -LineBreak

        New-HTMLText -FontSize 10pt -Text "Assesment results " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies requiring Unknown permission removal: ', $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknown'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknown'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknownPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt -LineBreak
        # Just in case report is too big and the full file is not attached
        New-HTMLText -FontSize 10pt -Text "To generate up to date report please execute: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Install-Module GPOZaurr -Force', ' or ', ' install module manually.' -Color RoyalBlue, None, None
            New-HTMLListItem -Text 'Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsBefore.html -Verbose -Type GPOPermissions' -Color RoyalBlue
        } -FontSize 10pt
        New-HTMLText -FontSize 10pt -Text 'Steps above will generate above summary with more details allowing you to get up to date report and steps on how to fix it.'
    }
    Solution   = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOPermissions']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'Yes', 'No' -Color SpringGreen, Salmon
                    New-ChartBar -Name 'Visible Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['Read'], $Script:Reporting['GPOPermissions']['Variables']['CouldNotRead']
                    New-ChartBar -Name 'Overall Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotFix'], $Script:Reporting['GPOPermissions']['Variables']['WillFix']
                    New-ChartBar -Name 'Administrative Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrative'], $Script:Reporting['GPOPermissions']['Variables']['WillFixAdministrative']
                    New-ChartBar -Name 'Authenticated Users Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchAdministrative'], $Script:Reporting['GPOPermissions']['Variables']['WillFixAuthenticatedUsers']
                    New-ChartBar -Name 'System Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchSystem'], $Script:Reporting['GPOPermissions']['Variables']['WillFixSystem']
                    New-ChartBar -Name 'Unknown Permissions' -Value $Script:Reporting['GPOPermissions']['Variables']['WillNotTouchUnknown'], $Script:Reporting['GPOPermissions']['Variables']['WillFixUnknown']
                } -Title 'Group Policy Permissions' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy Visibility Analysis' {
            New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['Data'].PermissionsIssues -Filtering {
                New-HTMLTableCondition -Name 'PermissionIssue' -Value $true -BackgroundColor Salmon -ComparisonType string -Row
            } -PagingOptions 7, 15, 30, 45, 60 -DefaultSortColumn PermissionIssue -DefaultSortOrder Descending
        }
        New-HTMLSection -Name 'Group Policy Permissions Analysis' {
            New-HTMLContainer {
                New-HTMLText -Text 'Explanation to table columns:' -FontSize 10pt
                New-HTMLList {
                    New-HTMLListItem -FontWeight bold, normal -Text "Status", " - means GPO has at least one problem with permissions. "
                    New-HTMLListItem -FontWeight bold, normal -Text "Administrative", " - means GPO has problem with either Domain Admins or Enterprise Admins not having proper permissions. "
                    New-HTMLListItem -FontWeight bold, normal -Text "AuthenticatedUsers", " - means GPO has Authenticated Users missing either as GPOApply or GPORead. "
                    New-HTMLListItem -FontWeight bold, normal -Text "System", " - means GPO has SYSTEM permission missing or lacking proper permissions. "
                    New-HTMLListItem -FontWeight bold, normal -Text "DomainAdmins", " - means GPO has Domain Admins missing or having wrong permissions. "
                    New-HTMLListItem -FontWeight bold, normal -Text "EnterpriseAdmins", " - means GPO has Enterprise Admins missing or having wrong permissions. "
                } -FontSize 10pt
                New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['Data'].PermissionsAnalysis -Filtering {
                    New-HTMLTableCondition -Name 'Status' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'Administrative' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'AuthenticatedUsers' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'System' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'Unknown' -Value 'False' -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'DomainAdmins' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string
                    New-HTMLTableCondition -Name 'EnterpriseAdmins' -Value 'True' -BackgroundColor SpringGreen -ComparisonType string

                    # Mark as warning
                    New-HTMLTableCondition -Name 'DomainAdminsPermission' -Value 'GpoCustom' -BackgroundColor Moccasin -ComparisonType string
                    New-HTMLTableCondition -Name 'EnterpriseAdminsPermission' -Value 'GpoCustom' -BackgroundColor Moccasin -ComparisonType string
                    # Reverse
                    New-HTMLTableCondition -Name 'Status' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne
                    New-HTMLTableCondition -Name 'Administrative' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne
                    New-HTMLTableCondition -Name 'AuthenticatedUsers' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne
                    New-HTMLTableCondition -Name 'System' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne
                    New-HTMLTableCondition -Name 'Unknown' -Value 'False' -BackgroundColor Salmon -ComparisonType string -Operator ne
                    New-HTMLTableCondition -Name 'DomainAdmins' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne
                    New-HTMLTableCondition -Name 'EnterpriseAdmins' -Value 'True' -BackgroundColor Salmon -ComparisonType string -Operator ne

                    New-TableEvent -TableID 'GPOPermissionsAll' -SourceColumnName 'GUID' -TargetColumnID 1 # TargetColumnID 1 eq GUID on the other table
                } -PagingOptions 7, 15, 30, 45, 60
            }
        }
        New-HTMLSection -Name 'All Permissions' {
            New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['Data'].PermissionsPerRow -Filtering {
                New-HTMLTableHeader -Names 'PrincipalNetBiosName', 'PrincipalDistinguishedName', 'PrincipalDomainName', 'PrincipalName', 'PrincipalSid', 'PrincipalSidType' -Title 'Account Information'
                New-HTMLTableCondition -Name 'Permission' -Value 'GpoEditDeleteModifySecurity' -BackgroundColor HotPink -ComparisonType string -Operator eq
                New-HTMLTableCondition -Name 'Permission' -Value 'GpoCustom' -BackgroundColor Moccasin -ComparisonType string
                New-HTMLTableCondition -Name 'Permission' -Value 'GpoApply' -BackgroundColor Orange -ComparisonType string
                New-HTMLTableCondition -Name 'Permission' -Value 'GpoRead' -BackgroundColor MediumSpringGreen -ComparisonType string -Operator eq
                New-HTMLTableCondition -Name 'PrincipalSidType' -Value 'Unknown' -BackgroundColor Salmon -ComparisonType string -Operator eq
            } -PagingOptions 7, 15, 30, 45, 60 -DataTableID 'GPOPermissionsAll'
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix Group Policy Administrative Users' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 with fixing Group Policy Authenticated Users. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsBefore.html -Verbose -Type GPOPermissions
                                }
                                New-HTMLText -TextBlock {
                                    "When executed it will take a while to generate all data and provide you with new report depending on size of environment. "
                                    "GPOs with problems will be those not having any value for Permission/PermissionType columns. "
                                    "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 {
                                    # This gets all permissions
                                    $AllPermissions = Get-GPOZaurrPermission
                                    $AllPermissions | Format-Table

                                    # this analyses permissions
                                    $PermissionsAnalysis = Get-GPOZaurrPermissionAnalysis
                                    $PermissionsAnalysis | 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 'Make a backup (optional)' {
                                New-HTMLText -TextBlock {
                                    "The process of fixing GPO Permissions does NOT touch GPO content. It simply adds permissionss on AD and SYSVOL at the same time for given GPO. "
                                    "However, it's always good to have a backup before executing changes that may impact Active Directory. "
                                }
                                New-HTMLCodeBlock -Code {
                                    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All
                                    $GPOSummary | Format-Table # only if you want to display output of backup
                                }
                                New-HTMLText -TextBlock {
                                    "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. "
                                }
                            }
                            New-HTMLWizardStep -Name 'Add Authenticated Users permissions' {
                                New-HTMLText -Text @(
                                    "Following command will find any GPO which doesn't have Authenticated User as GpoRead or GpoApply and will add it as GpoRead. ",
                                    "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ",
                                    "It ensures that COMPUTERS can read GPO properly to be able to Apply it. ",
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental adding of permissions."
                                ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data."
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed adds Authenticated Users (GpoRead permission) only on first X non-compliant Group Policies. "
                                    "Use LimitProcessing parameter to prevent mass change 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 'Add Administrative Groups permissions' {
                                New-HTMLText -Text @(
                                    "Following command will find any GPO which doesn't have Domain Admins and Enterprise Admins added with GpoEditDeleteModifySecurity and will add it as GpoEditDeleteModifySecurity. ",
                                    "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ",
                                    "It ensures that Domain Admins and Enterprise Admins can manage GPO. ",
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental adding of permissions."
                                ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data."
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed adds Enterprise Admins or/and Domain Admins (GpoEditDeleteModifySecurity permission) only on first X non-compliant Group Policies. "
                                    "Use LimitProcessing parameter to prevent mass change 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 'Add SYSTEM permissions' {
                                New-HTMLText -Text @(
                                    "Following command will find any GPO which doesn't have SYSTEM account added with GpoEditDeleteModifySecurity and will add it as GpoEditDeleteModifySecurity. ",
                                    "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ",
                                    "It ensures that SYSTEM can manage GPO. ",
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental adding of permissions."
                                ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data."
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed adds SYSTEM account (GpoEditDeleteModifySecurity permission) only on first X non-compliant Group Policies. "
                                    "Use LimitProcessing parameter to prevent mass change 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 'Remove UNKNOWN permissions' {
                                New-HTMLText -Text @(
                                    "Following command will find any GPO which has an unknown SID and will remove it. ",
                                    "This change doesn't change any other permissions. ",
                                    "It ensures that GPOs have no unknown permissions present. ",
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental adding of permissions."
                                ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrPermission -Verbose -Type Unknown -WhatIf
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrPermission -Verbose -Type Unknown -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data."
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrPermission -Verbose -Type Unknown -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrPermission -Verbose -Type Unknown -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed removes only first X unknwon permissions from Group Policies. "
                                    "Use LimitProcessing parameter to prevent mass change 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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsAfter.html -Verbose -Type GPOPermissions
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOPermissions']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrPermissionsRead = [ordered] @{
    Name       = 'Group Policy Authenticated Users Permissions'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        [ordered] @{
            Permissions = Get-GPOZaurrPermission -Type AuthenticatedUsers -ReturnSecurityWhenNoData -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
            Issues      = Get-GPOZaurrPermissionIssue -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
        }
    }
    Processing = {
        # This is a workaround - we need to use it since we have 0 permissions

        # Create Per Domain Variables
        $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'] = @{}
        $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouchPerDomain'] = @{}
        $Script:Reporting['GPOPermissionsRead']['Variables']['ReadPerDomain'] = @{}
        $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'] = @{}
        $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'] = @{}

        foreach ($GPO in $Script:Reporting['GPOPermissionsRead']['Data'].Issues) {
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['ReadPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissionsRead']['Variables']['ReadPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'][$GPO.DomainName] = 0
            }
            if ($GPO.PermissionIssue) {
                $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead']++
                $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOPermissionsRead']['Variables']['Read']++
                $Script:Reporting['GPOPermissionsRead']['Variables']['ReadPerDomain'][$GPO.DomainName]++
            }
            $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'][$GPO.DomainName]++
        }
        foreach ($GPO in $Script:Reporting['GPOPermissionsRead']['Data'].Permissions) {
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouchPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouchPerDomain'][$GPO.DomainName] = 0
            }
            # Checks
            if ($GPO.Permission -in 'GpoApply', 'GpoRead') {
                $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouch']++
                $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouchPerDomain'][$GPO.DomainName]++
            } else {
                $Script:Reporting['GPOPermissionsRead']['Variables']['WillFix']++
                $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'][$GPO.DomainName]++
            }
        }
        if ($Script:Reporting['GPOPermissionsRead']['Variables']['WillFix'] -gt 0 -or $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead'] -gt 0) {
            $Script:Reporting['GPOPermissionsRead']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOPermissionsRead']['ActionRequired'] = $false
        }
        # Summary from 2 reports
        $Script:Reporting['GPOPermissionsRead']['Variables']['TotalToFix'] = $Script:Reporting['GPOPermissionsRead']['Variables']['WillFix'] + $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead']
    }
    Variables  = @{
        WillFix               = 0
        WillNotTouch          = 0
        WillFixPerDomain      = $null
        WillNotTouchPerDomain = $null
        CouldNotRead          = 0
        CouldNotReadPerDomain = $null
        Read                  = 0
        ReadPerDomain         = $null
        TotalToFix            = 0
        TotalPerDomain        = $null
    }
    Overview   = {

    }
    Summary    = {
        New-HTMLText -FontSize 10pt -TextBlock {
            "When GPO is created one of the permissions that are required for proper functioning of Group Policies is NT AUTHORITY\Authenticated Users. "
            "Some Administrators don't follow best practices and trying to remove GpoApply permission, remove also GpoRead permission from a GPO which can have consequences. "
        } -LineBreak
        New-HTMLText -Text "On June 14th, 2016 Microsoft released [HotFix](https://support.microsoft.com/en-gb/help/3159398/ms16-072-description-of-the-security-update-for-group-policy-june-14-2) that requires Authenticated Users to be present on all Group Policies to function properly: " -FontSize 10pt
        New-HTMLText -TextBlock {
            "MS16-072 changes the security context with which user group policies are retrieved. "
            "This by-design behavior change protects customers’ computers from a security vulnerability. "
            "Before MS16-072 is installed, user group policies were retrieved by using the user’s security context. "
            "After MS16-072 is installed, user group policies are retrieved by using the computer's security context."
        } -FontStyle italic -FontSize 10pt -FontWeight bold -LineBreak
        New-HTMLText -FontSize 10pt -Text @(
            "There are two parts to this assesment. Reading all Group Policies Permissions that account ",
            "$($Env:USERDOMAIN)\$($Env:USERNAME.ToUpper())",
            " has permissions to read and provide detailed assesment about permissions. ",
            "Second assesment checks for permissions that this account is not able to read at all, and therefore it has no visibility about permissions set on it. "
            "We just were able to detect the problem, but hopefully higher level account (Domain Admin) should be able to provide full assesment. "
        ) -FontWeight normal, bold, normal
        New-HTMLText -FontSize 10pt -Text "First assesment results: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies requiring Authenticated Users with GpoRead permission: ', $Script:Reporting['GPOPermissionsRead']['Variables']['WillFix'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which don't require changes: ", $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouch'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissionsRead']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -FontSize 10pt -Text "Secondary assesment results: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text "Group Policies couldn't read at all: ", $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies with permissions allowing read: ", $Script:Reporting['GPOPermissionsRead']['Variables']['Read'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'With split per domain (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'].Keys) {
                New-HTMLListItem -Text @(
                    "$Domain requires ",
                    $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotReadPerDomain'][$Domain],
                    " changes out of ",
                    $Script:Reporting['GPOPermissionsRead']['Variables']['TotalPerDomain'][$Domain],
                    "."
                ) -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -Text @(
            "That means we need to fix permissions on: "
            $($Script:Reporting['GPOPermissionsRead']['Variables']['TotalToFix'])
            " out of "
            $($Script:Reporting['GPOPermissionsRead']['Data'].Issues).Count
            " Group Policies. "
        ) -FontSize 10pt -FontWeight bold, bold, normal, bold, normal -Color Black, FreeSpeechRed, Black, Black -LineBreak -TextDecoration none, underline, underline, underline, none
    }
    Solution   = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOPermissionsRead']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'Yes', 'No' -Color SpringGreen, Salmon
                    New-ChartBar -Name 'Authenticated Users Available' -Value $Script:Reporting['GPOPermissionsRead']['Variables']['WillNotTouch'], $Script:Reporting['GPOPermissionsRead']['Variables']['WillFix']
                    New-ChartBar -Name 'Accessible Group Policies' -Value $Script:Reporting['GPOPermissionsRead']['Variables']['Read'], $Script:Reporting['GPOPermissionsRead']['Variables']['CouldNotRead']
                } -Title 'Group Policy Permissions' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy Authenticated Users Analysis' {
            New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRead']['Data'].Permissions -Filtering {
                New-HTMLTableCondition -Name 'Permission' -Value '' -BackgroundColor Salmon -ComparisonType string -Row
            } -PagingOptions 7, 15, 30, 45, 60
        }
        New-HTMLSection -Name 'Group Policy Issues Assesment' {
            New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRead']['Data'].Issues -Filtering {
                New-HTMLTableCondition -Name 'PermissionIssue' -Value $true -BackgroundColor Salmon -ComparisonType string -Row
            } -PagingOptions 7, 15, 30, 45, 60 -DefaultSortColumn PermissionIssue -DefaultSortOrder Descending
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix Group Policy Authenticated Users' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 with fixing Group Policy Authenticated Users. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsReadBefore.html -Verbose -Type GPOPermissionsRead
                                }
                                New-HTMLText -TextBlock {
                                    "When executed it will take a while to generate all data and provide you with new report depending on size of environment. "
                                    "GPOs with problems will be those not having any value for Permission/PermissionType columns. "
                                    "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 {
                                    $AuthenticatedUsers = Get-GPOZaurrPermission -Type AuthenticatedUsers -ReturnSecurityWhenNoData
                                    $AuthenticatedUsers | 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 'Make a backup (optional)' {
                                New-HTMLText -TextBlock {
                                    "The process of fixing GPO Permissions does NOT touch GPO content. It simply adds permissionss on AD and SYSVOL at the same time for given GPO. "
                                    "However, it's always good to have a backup before executing changes that may impact Active Directory. "
                                }
                                New-HTMLCodeBlock -Code {
                                    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All
                                    $GPOSummary | Format-Table # only if you want to display output of backup
                                }
                                New-HTMLText -TextBlock {
                                    "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. "
                                }
                            }

                            New-HTMLWizardStep -Name 'Add Authenticated Users ability to read all GPO' {
                                New-HTMLText -Text @(
                                    "Following command will find any GPO which doesn't have Authenticated User as GpoRead or GpoApply and will add it as GpoRead. ",
                                    "This change doesn't change GpoApply permission, therefore it won't change to whom the GPO applies to. ",
                                    "It ensures that COMPUTERS can read GPO properly to be able to Apply it. ",
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental adding of permissions."
                                ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -WhatIf -Verbose
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -WhatIf -Verbose -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data."
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -Verbose -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -All -Verbose -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed adds Authenticated Users (GpoRead permission) only on first X non-compliant Group Policies. "
                                    "Use LimitProcessing parameter to prevent mass change 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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsReadAfter.html -Verbose -Type GPOPermissionsRead
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOPermissionsRead']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRead']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                } -PagingOptions 10, 20, 30, 40, 50
            }
        }
    }
}
$GPOZaurrPermissionsRoot = [ordered] @{
    Name       = 'Group Policies Root Permissions'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        Get-GPOZaurrPermissionRoot -SkipNames -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing = {

    }
    Variables  = @{

    }
    Overview   = {

    }
    Solution   = {
        New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRoot']['Data'] -Filtering
        if ($Script:Reporting['GPOPermissionsRoot']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsRoot']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
$GPOZaurrPermissionsUnknown = [ordered] @{
    Name       = 'Group Policy Unknown Permissions'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        Get-GPOZaurrPermission -Type Unknown -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing = {
        # Create Per Domain Variables
        $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'] = @{}
        foreach ($GPO in $Script:Reporting['GPOPermissionsUnknown']['Data']) {
            # Create Per Domain Variables
            if (-not $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'][$GPO[0].DomainName]) {
                $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'][$GPO[0].DomainName] = 0
            }
            # Checks
            $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFix']++
            $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'][$GPO[0].DomainName]++
        }
        if ($Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFix'] -gt 0) {
            $Script:Reporting['GPOPermissionsUnknown']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOPermissionsUnknown']['ActionRequired'] = $false
        }
    }
    Variables  = @{
        WillFix          = 0
        WillFixPerDomain = $null
    }
    Overview   = {

    }
    Summary    = {
        New-HTMLText -FontSize 10pt -TextBlock {
            "Group Policies contain multiple permissions for different level of access. "
            "Be it adminstrative permissions, read permissions or apply permissions. "
            "Over time some users or groups get deleted for different reasons and such permission in Group Policies leave a trace in form of Unknown SID. "
            "Unknown SIDs can also be remains of Active Directory Trusts, that have been deleted or are otherwise unavailable. "
            "Following assesment detects all unknown permissions and provides them for review & deletion. "
        } -LineBreak
        New-HTMLText -FontSize 10pt -Text "Assesment results: " -FontWeight bold
        New-HTMLList -Type Unordered {
            New-HTMLListItem -Text 'Group Policies requiring removal of unknown SIDs: ', $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFix'] -FontWeight normal, bold
        } -FontSize 10pt
        New-HTMLText -Text 'Following domains require actions (permissions required):' -FontSize 10pt -FontWeight bold
        New-HTMLList -Type Unordered {
            foreach ($Domain in $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFixPerDomain'][$Domain], " changes." -FontWeight normal, bold, normal
            }
        } -FontSize 10pt
        New-HTMLText -Text @(
            "That means we need to remove "
            $($Script:Reporting['GPOPermissionsUnknown']['Variables'].WillFix)
            " unknown permissions from Group Policies. "
        ) -FontSize 10pt -FontWeight normal, bold, normal -Color Black, FreeSpeechRed, Black -LineBreak -TextDecoration none, underline, none
    }
    Solution   = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOPermissionsUnknown']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'Yes' -Color Salmon
                    New-ChartBar -Name 'Unknown Permissions Present' -Value $Script:Reporting['GPOPermissionsUnknown']['Variables']['WillFix']
                } -Title 'Group Policy Permissions' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Group Policy Unknown Permissions Analysis' {
            New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsUnknown']['Data'] -Filtering {
                New-HTMLTableCondition -Name 'Permission' -Value '' -BackgroundColor Salmon -ComparisonType string -Row
            } -PagingOptions 7, 15, 30, 45, 60
        }
        if ($Script:Reporting['Settings']['HideSteps'] -eq $false) {
            New-HTMLSection -Name 'Steps to fix Group Policy Unknown Permissions' {
                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 GPOZaurr -Force
                                    Import-Module GPOZaurr -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 with removing unknown permissions. To generate new report please use:"
                                New-HTMLCodeBlock -Code {
                                    Invoke-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsUnknownBefore.html -Verbose -Type GPOPermissionsUnknown
                                }
                                New-HTMLText -TextBlock {
                                    "When executed it will take a while to generate all data and provide you with new report depending on size of environment. "
                                    "The table only shows GPO and their unknown permissions. "
                                    "It doesn't show permissions that are not subject of this investigation. "
                                    "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 {
                                    $UnknownPermissions = Get-GPOZaurrPermission -Type Unknown
                                    $UnknownPermissions | 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 'Make a backup (optional)' {
                                New-HTMLText -TextBlock {
                                    "The process of fixing GPO Permissions does NOT touch GPO content. It simply removes permissionss on AD and SYSVOL at the same time for given GPO. "
                                    "However, it's always good to have a backup before executing changes that may impact Active Directory. "
                                }
                                New-HTMLCodeBlock -Code {
                                    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All
                                    $GPOSummary | Format-Table # only if you want to display output of backup
                                }
                                New-HTMLText -TextBlock {
                                    "Above command when executed will make a backup to Desktop, create GPO folder and within it it will put all those GPOs. "
                                }
                            }
                            New-HTMLWizardStep -Name 'Remove Unknown Permissions' {
                                New-HTMLText -Text @(
                                    "Following command will find any GPO which has an unknown SID and will remove it. ",
                                    "This change doesn't change any other permissions. ",
                                    "It ensures that GPOs have no unknown permissions present. ",
                                    "Make sure when running it for the first time to run it with ",
                                    "WhatIf",
                                    " parameter as shown below to prevent accidental adding of permissions."
                                ) -FontWeight normal, normal, normal, normal, bold, normal -Color Black, Black, Black, Black, Red, Black
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrPermission -Verbose -Type Unknown -WhatIf
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrPermission -Verbose -Type Unknown -WhatIf -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                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 changed matches expected data."
                                } -LineBreak
                                New-HTMLText -Text "Once happy with results please follow with command (this will start fixing process): " -LineBreak -FontWeight bold
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrPermission -Verbose -Type Unknown -LimitProcessing 2
                                }
                                New-HTMLText -TextBlock {
                                    "Alternatively for multi-domain scenario, if you have limited Domain Admin credentials to a single domain please use following command: "
                                }
                                New-HTMLCodeBlock -Code {
                                    Remove-GPOZaurrPermission -Verbose -Type Unknown -LimitProcessing 2 -IncludeDomains 'YourDomainYouHavePermissionsFor'
                                }
                                New-HTMLText -TextBlock {
                                    "This command when executed removes only first X unknwon permissions from Group Policies. "
                                    "Use LimitProcessing parameter to prevent mass change 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-GPOZaurr -FilePath $Env:UserProfile\Desktop\GPOZaurrGPOPermissionsUnknownAfter.html -Verbose -Type GPOPermissionsUnknown
                                }
                                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
                    }
                }
            }
        }
        if ($Script:Reporting['GPOPermissionsUnknown']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOPermissionsUnknown']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                } -PagingOptions 10, 20, 30, 40, 50
            }
        }
    }
}
$GPOZaurrSysVolLegacyFiles = [ordered] @{
    Name       = 'SYSVOL Legacy ADM Files'
    Enabled    = $false
    Action     = $null
    Data       = $null
    Execute    = {
        Get-GPOZaurrLegacyFiles -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing = {

    }
    Variables  = @{

    }
    Overview   = {

    }
    Solution   = {
        New-HTMLTable -DataTable $Script:Reporting['SysVolLegacyFiles']['Data'] -Filtering
        if ($Script:Reporting['SysVolLegacyFiles']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['SysVolLegacyFiles']['WarningsAndErrors'] -Filtering {
                    New-HTMLTableCondition -Name 'Type' -Value 'Warning' -BackgroundColor SandyBrown -ComparisonType string -Row
                    New-HTMLTableCondition -Name 'Type' -Value 'Error' -BackgroundColor Salmon -ComparisonType string -Row
                }
            }
        }
    }
}
function New-GPOZaurrExclusions {
    [cmdletBinding()]
    param(
        [alias('ExcludeGroupPolicies', 'ExclusionsCode', 'ExclusionsArray')][Parameter(Position = 1)][object] $Exclusions
    )

    if ($Exclusions) {
        if ($Exclusions -is [scriptblock]) {
            #$Script:Reporting[$T]['Exclusions'] = $Exclusions
            #$Script:Reporting[$T]['ExclusionsCode'] = $Exclusions
            [string] $Code = @(
                "`$Exclusions = {"
                " " + $Exclusions.ToString()
                "}"
            )
            $Code
        }
        if ($Exclusions -is [Array]) {
            #$Script:Reporting[$T]['Exclusions'] = $Exclusions
            #$ExclusionsArray = $Exclusions
            [string] $Code = @(
                '$Exclusions = @('
                [System.Environment]::NewLine
                foreach ($Exclusion in $Exclusions) {
                    " `"$Exclusion`"" + [System.Environment]::NewLine
                }
                [System.Environment]::NewLine
                ')'
            )
            $Code
        }
    }
}
function New-GPOZaurrReportConsole {
    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $Results,
        [string] $ComputerName
    )
    Begin {
        $GPODeny = @{
            Color       = 'Yellow', 'Red', 'Yellow', 'Red'
            StartSpaces = 6
        }
        $GPOSuccess = @{
            Color       = 'Yellow', 'Green', 'Yellow', 'Green'
            StartSpaces = 6
        }
        $WriteSummary = @{
            Color       = 'Yellow', 'Blue'
            StartSpaces = 3
        }
        $ComputerWhereApplied = ($Results.ComputerResults.GroupPolicies | Sort-Object -Property DomainName, Name).Where( { $_.Status -eq 'Applied' }, 'split')
        $UserWhereApplied = ($Results.UserResults.GroupPolicies | Sort-Object -Property Name).Where( { $_.Status -eq 'Applied' }, 'split')
    }
    Process {
        if ($Results.ComputerResults) {
            Write-Color -Text 'Computer Settings' -Color Yellow -LinesBefore 1
            Write-Color -Text '[>] Last time Group Policy was applied: ', $Results.ComputerResults.Summary.ReadTime @WriteSummary
            Write-Color -Text '[>] Computer Name: ', $Results.ComputerResults.Summary.ComputerName @WriteSummary
            Write-Color -Text '[>] Domain Name: ', $Results.ComputerResults.Summary.DomainName @WriteSummary
            Write-Color -Text '[>] Organizational Unit: ', $Results.ComputerResults.Summary.OrganizationalUnit @WriteSummary
            Write-Color -Text '[>] Site: ', $Results.ComputerResults.Summary.Site @WriteSummary
            Write-Color -Text '[>] GPO Types: ', ($Results.ComputerResults.Summary.GPOTypes -replace [System.Environment]::NewLine, ', ') @WriteSummary
            Write-Color -Text '[>] Slow link: ', ($Results.ComputerResults.Summary.SlowLink) @WriteSummary

            Write-Color -Text 'Applied Group Policy Objects' -StartSpaces 3 -LinesBefore 1
            foreach ($GPO in $ComputerWhereApplied[0]) {
                Write-Color -Text '[+] [', $GPO.DomainName, '] ', $GPO.Name @GPOSuccess
            }

            Write-Color -Text 'Denied Group Policy Objects' -StartSpaces 3
            foreach ($GPO in $ComputerWhereApplied[1]) {
                Write-Color -Text '[-] [', $GPO.DomainName, '] ', $GPO.Name @GPODeny
            }
        } else {
            Write-Color -Text 'Computer Settings' -Color Yellow -LinesBefore 1
            Write-Color -Text '[>] Last time Group Policy was applied: ', 'Unable to get? No administrative permission?' @WriteSummary
            Write-Color -Text '[>] Computer Name: ', $ComputerName @WriteSummary
        }

        Write-Color -Text 'User Settings' -Color Yellow -LinesBefore 1
        Write-Color -Text '[>] Last time Group Policy was applied: ', $Results.UserResults.Summary.ReadTime @WriteSummary
        Write-Color -Text '[>] Computer Name: ', $Results.UserResults.Summary.ComputerName @WriteSummary
        Write-Color -Text '[>] Domain Name: ', $Results.UserResults.Summary.DomainName @WriteSummary
        Write-Color -Text '[>] Organizational Unit: ', $Results.UserResults.Summary.OrganizationalUnit @WriteSummary
        Write-Color -Text '[>] Site: ', $Results.UserResults.Summary.Site @WriteSummary
        Write-Color -Text '[>] GPO Types: ', ($Results.UserResults.Summary.GPOTypes -replace [System.Environment]::NewLine, ', ') @WriteSummary
        Write-Color -Text '[>] Slow link: ', ($Results.UserResults.Summary.SlowLink) @WriteSummary

        Write-Color -Text 'Applied Group Policy Objects' -StartSpaces 3
        foreach ($GPO in $UserWhereApplied[0] ) {
            Write-Color -Text '[+] [', $GPO.DomainName, '] ', $GPO.Name @GPOSuccess
        }

        Write-Color -Text 'Denied Group Policy Objects' -StartSpaces 3
        foreach ($GPO in $UserWhereApplied[1]) {
            Write-Color -Text '[-] [', $GPO.DomainName, '] ', $GPO.Name @GPODeny
        }
    }
}
function New-GPOZaurrReportHTML {
    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $Support,
        [string] $Path,
        [switch] $Online,
        [switch] $Open
    )
    $PSDefaultParameterValues = @{
        "New-HTMLTable:WarningAction" = 'SilentlyContinue'
    }
    if (-not $Path) {
        $Path = [io.path]::GetTempFileName().Replace('.tmp', ".html")
    }
    $ComputerName = $($Support.ResultantSetPolicy.LoggingComputer)
    #$UserName = $($Support.ResultantSetPolicy.UserName)
    #$LoggingMode = $($Support.ResultantSetPolicy.LoggingMode)
    New-HTML -TitleText "Group Policy Report - $ComputerName" {
        #New-HTMLTabOptions -SlimTabs -Transition -LinearGradient -SelectorColor Akaroa
        New-HTMLTableOption -DataStore JavaScript -BoolAsString
        New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
        New-HTMLPanelStyle -BorderRadius 0px
        New-HTMLTabOptions -SlimTabs `
            -BorderBottomStyleActive solid -BorderBottomColorActive LightSkyBlue -BackgroundColorActive none `
            -TextColorActive Black -Align left -BorderRadius 0px -RemoveShadow -TextColor Grey -TextTransform capitalize
        New-HTMLTab -Name 'Information' {
            New-HTMLSection {
                #New-HTMLTable -DataTable $Support.ResultantSetPolicy -HideFooter -Transpose
                New-HTMLSection -HeaderText 'General Information' {
                    New-HTMLTable -DataTable $Support.ComputerInformation.Time -Filtering -Transpose {
                        New-TableHeader -Names 'Name', 'Value' -Title 'Time Information'
                    }
                    New-HTMLTable -DataTable $Support.ComputerInformation.BIOS -Filtering -Transpose {
                        New-TableHeader -Names 'Name', 'Value' -Title 'BIOS Information'
                    }
                }
                New-HTMLContainer {
                    New-HTMLSection -HeaderText 'CPU Information' {
                        New-HTMLTable -DataTable $Support.ComputerInformation.CPU -Filtering
                    }
                    New-HTMLSection -HeaderText 'RAM Information' {
                        New-HTMLTable -DataTable $Support.ComputerInformation.RAM -Filtering
                    }
                }
            }
            New-HTMLSection -HeaderText 'Operating System Information' {
                New-HTMLTable -DataTable $Support.ComputerInformation.OperatingSystem -Filtering
                New-HTMLTable -DataTable $Support.ComputerInformation.System -Filtering
            }
            New-HTMLSection -HeaderText 'Disk Information' {
                New-HTMLTable -DataTable $Support.ComputerInformation.Disk -Filtering
                New-HTMLTable -DataTable $Support.ComputerInformation.DiskLogical -Filtering
            }
            New-HTMLSection -HeaderText 'Services Information' {
                New-HTMLTable -DataTable $Support.ComputerInformation.Services -Filtering
            }
        }
        foreach ($Key in $Support.Keys) {
            if ($Key -in 'ResultantSetPolicy', 'ComputerInformation') {
                continue
            }
            New-HTMLTab -Name $Key {
                New-HTMLTab -Name 'Summary' {
                    New-HTMLSection -Invisible {
                        New-HTMLSection -HeaderText 'Summary' {
                            New-HTMLTable -DataTable $Support.$Key.Summary -Filtering -PagingOptions @(7, 14 )
                            New-HTMLTable -DataTable $Support.$Key.SummaryDetails -Filtering -PagingOptions @(7, 14) -Transpose
                        }
                        New-HTMLSection -HeaderText 'Part of Security Groups' {
                            New-HTMLTable -DataTable $Support.$Key.SecurityGroups -Filtering -PagingOptions @(7, 14)
                        }
                    }
                    <#
                    New-HTMLSection -HeaderText 'Summary Downloads' {
                        New-HTMLTable -DataTable $Support.$Key.SummaryDownload -HideFooter
                    }
                    #>

                    #New-HTMLSection -HeaderText 'Resultant Set Policy' {
                    # New-HTMLTable -DataTable $Support.$Key.ResultantSetPolicy -HideFooter
                    #}
                }
                New-HTMLTab -Name 'Group Policies' {
                    New-HTMLSection -Invisible {
                        <#
                        New-HTMLSection -HeaderText 'Processing Time' {
                            New-HTMLTable -DataTable $Support.$Key.ProcessingTime -Filtering
                        }
                        #>

                        New-HTMLSection -HeaderText 'ExtensionStatus' {
                            New-HTMLPanel {
                                New-HTMLTable -DataTable $Support.$Key.ExtensionStatus -Filtering
                            }
                            New-HTMLPanel {
                                New-HTMLChart -Title 'Extension TimeLine' -TitleAlignment center {
                                    foreach ($Extension in $Support.$Key.ExtensionStatus) {
                                        New-ChartTimeLine -DateFrom ([DateTime] $Extension.BeginTime) -DateTo ([DateTime] $Extension.EndTime) -Name $Extension.Name
                                    }
                                }
                            }
                        }
                    }
                    New-HTMLSection -HeaderText 'Group Policies' {
                        New-HTMLTable -DataTable $Support.$Key.GroupPolicies -Filtering {
                            # Global color applied
                            New-TableCondition -Name 'Status' -Value 'Applied' -BackgroundColor BrightGreen -Row
                            New-TableCondition -Name 'Status' -Value 'Denied' -BackgroundColor Salmon -Row

                            # One by one colors
                            New-TableCondition -Name 'IsValid' -Value $true -BackgroundColor BrightGreen
                            New-TableCondition -Name 'IsValid' -Value $false -BackgroundColor Salmon

                            New-TableCondition -Name 'FilterAllowed' -Value $true -BackgroundColor BrightGreen
                            New-TableCondition -Name 'FilterAllowed' -Value $false -BackgroundColor Salmon

                            New-TableCondition -Name 'AccessAllowed' -Value $true -BackgroundColor BrightGreen
                            New-TableCondition -Name 'AccessAllowed' -Value $false -BackgroundColor Salmon
                        }
                    }
                    New-HTMLSection -HeaderText 'Group Policies Links' {
                        New-HTMLTable -DataTable $Support.$Key.GroupPoliciesLinks -Filtering
                    }
                    <#
                    New-HTMLSection -HeaderText 'Group Policies Applied' {
                        New-HTMLTable -DataTable $Support.$Key.GroupPoliciesApplied -Filtering
                    }
                    New-HTMLSection -HeaderText 'Group Policies Denied' {
                        New-HTMLTable -DataTable $Support.$Key.GroupPoliciesDenied -Filtering
                    }
                    #>

                }
                New-HTMLTab -Name 'Extension Data' {
                    New-HTMLSection -HeaderText 'Extension Data' {
                        New-HTMLTable -DataTable $Support.$Key.ExtensionData -Filtering
                    }
                }
                New-HTMLTab -Name 'Scope of Management' {
                    New-HTMLSection -HeaderText 'Scope of Management' {
                        New-HTMLTable -DataTable $Support.$Key.ScopeOfManagement -Filtering
                    }
                }
                <#
                New-HTMLTab -Name 'Events By ID' {
                    foreach ($ID in $Support.$Key.EventsByID.Keys) {
                        New-HTMLSection -HeaderText "Event ID $ID" {
                            New-HTMLTable -DataTable $Support.$Key.EventsByID[$ID] -Filtering -AllProperties
                        }
                    }
                }
                New-HTMLTab -Name 'Events' {
                    New-HTMLSection -HeaderText 'Events' {
                        New-HTMLTable -DataTable $Support.$Key.Events -Filtering -AllProperties
                    }
                }
                #>

            }
        }
        if ($Support.ComputerResults.Results) {
            New-HTMLTab -Name 'Details' {
                foreach ($Detail in $Support.ComputerResults.Results.Keys) {
                    $ShortDetails = $Support.ComputerResults.Results[$Detail]
                    New-HTMLTab -Name $Detail {
                        New-HTMLTab -Name 'Test' {
                            New-HTMLSection -HeaderText 'Summary Downloads' {
                                New-HTMLTable -DataTable $ShortDetails.SummaryDownload -HideFooter
                            }
                            New-HTMLSection -HeaderText 'Processing Time' {
                                New-HTMLTable -DataTable $ShortDetails.ProcessingTime -Filtering
                            }
                            New-HTMLSection -HeaderText 'Group Policies Applied' {
                                New-HTMLTable -DataTable $ShortDetails.GroupPoliciesApplied -Filtering
                            }
                            New-HTMLSection -HeaderText 'Group Policies Denied' {
                                New-HTMLTable -DataTable $ShortDetails.GroupPoliciesDenied -Filtering
                            }
                        }
                        New-HTMLTab -Name 'Events By ID' {
                            foreach ($ID in $ShortDetails.EventsByID.Keys) {
                                New-HTMLSection -HeaderText "Event ID $ID" {
                                    New-HTMLTable -DataTable $ShortDetails.EventsByID[$ID] -Filtering -AllProperties
                                }
                            }
                        }
                        New-HTMLTab -Name 'Events' {
                            New-HTMLSection -HeaderText 'Events' {
                                New-HTMLTable -DataTable $ShortDetails.Events -Filtering -AllProperties
                            }
                        }
                    }
                }
            }
        }
    } -Online:$Online.IsPresent -Open:$Open.IsPresent -FilePath $Path
}
function Remove-PrivPermission {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [string] $Principal,
        [validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'DistinguishedName',
        [PSCustomObject] $GPOPermission,
        [alias('PermissionType')][Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType
    )
    if ($GPOPermission.PrincipalName) {
        $Text = "Removing SID: $($GPOPermission.PrincipalSid), Name: $($GPOPermission.PrincipalDomainName)\$($GPOPermission.PrincipalName), SidType: $($GPOPermission.PrincipalSidType) from domain $($GPOPermission.DomainName)"
    } else {
        $Text = "Removing SID: $($GPOPermission.PrincipalSid), Name: EMPTY, SidType: $($GPOPermission.PrincipalSidType) from domain $($GPOPermission.DomainName)"
    }
    if ($PrincipalType -eq 'DistinguishedName') {
        if ($GPOPermission.DistinguishedName -eq $Principal -and $GPOPermission.Permission -eq $IncludePermissionType) {
            if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, $Text)) {
                try {
                    Write-Verbose "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType for $($Principal) / $($GPOPermission.PrincipalName) / Type: $($GPOPermission.PermissionType)"
                    $GPOPermission.GPOSecurity.Remove($GPOPermission.GPOSecurityPermissionItem)
                    $GPOPermission.GPOObject.SetSecurityInfo($GPOPermission.GPOSecurity)
                } catch {
                    Write-Warning "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType failed for $($Principal) with error: $($_.Exception.Message)"
                }
            }
        }
    } elseif ($PrincipalType -eq 'Sid') {
        if ($GPOPermission.PrincipalSid -eq $Principal -and $GPOPermission.Permission -eq $IncludePermissionType) {
            if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, $Text)) {
                try {
                    Write-Verbose "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType for $($Principal) / $($GPOPermission.PrincipalName) / Type: $($GPOPermission.PermissionType)"
                    $GPOPermission.GPOSecurity.Remove($GPOPermission.GPOSecurityPermissionItem)
                    $GPOPermission.GPOObject.SetSecurityInfo($GPOPermission.GPOSecurity)
                } catch {
                    if ($_.Exception.Message -like '*The request is not supported. (Exception from HRESULT: 0x80070032)*') {
                        Write-Warning "Remove-GPOZaurrPermission - Bummer! The request is not supported, but lets fix it differently."
                        # This is basically atomic approach. We will totally remove any permissions for that user on ACL level to get rid of this situation.
                        # This situation should only happen if DENY is on FULL Control
                        $ACL = Get-ADACL -ADObject $GPOPermission.GPOObject.Path -Bundle #-Verbose:$VerbosePreference
                        if ($ACL) {
                            Remove-ADACL -ACL $ACL -Principal $Principal -AccessControlType Deny -Verbose:$VerbosePreference
                        }
                        # I've noticed that in situation where Edit settings, delete, modify security is set and then set to Deny we need to fix it once more
                        $ACL = Get-ADACL -ADObject $GPOPermission.GPOObject.Path -Bundle
                        if ($ACL) {
                            Remove-ADACL -ACL $ACL -Principal $Principal -AccessControlType Allow -Verbose:$VerbosePreference
                        }
                    } else {
                        Write-Warning "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType failed for $($Principal) with error: $($_.Exception.Message)"
                    }
                }
            }
        }
    } elseif ($PrincipalType -eq 'Name') {
        if ($GPOPermission.PrincipalName -eq $Principal -and $GPOPermission.Permission -eq $IncludePermissionType) {
            if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, $Text)) {
                try {
                    Write-Verbose "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType for $($Principal) / $($GPOPermission.PrincipalName) / Type: $($GPOPermission.PermissionType)"
                    $GPOPermission.GPOSecurity.Remove($GPOPermission.GPOSecurityPermissionItem)
                    $GPOPermission.GPOObject.SetSecurityInfo($GPOPermission.GPOSecurity)
                } catch {
                    Write-Warning "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType failed for $($Principal) with error: $($_.Exception.Message)"
                }
            }
        }
    }
}

function Reset-GPOZaurrStatus {
    param(

    )
    #if (-not $Script:GPOConfigurationClean) {
    # $Script:GPOConfigurationClean = Copy-Dictionary -Dictionary $Script:GPOConfiguration
    #} else {
    # $Script:GPOConfiguration = Copy-Dictionary -Dictionary $Script:GPOConfigurationClean
    #}
    if (-not $Script:DefaultTypes) {
        $Script:DefaultTypes = foreach ($T in $Script:GPOConfiguration.Keys) {
            if ($Script:GPOConfiguration[$T].Enabled) {
                $T
            }
        }
    } else {
        foreach ($T in $Script:GPOConfiguration.Keys) {
            $Script:GPOConfiguration[$T]['Enabled'] = $false
        }
        foreach ($T in $Script:DefaultTypes) {
            $Script:GPOConfiguration[$T]['Enabled'] = $true
        }
    }
}
$Script:Actions = @{
    C = 'Create'
    D = 'Delete'
    U = 'Update'
    R = 'Replace'
}
$Script:GPOConfiguration = [ordered] @{
    GPOBroken                    = $GPOZaurrOrphans
    GPOBrokenLink                = $GPOZaurrBrokenLink
    GPOOwners                    = $GPOZaurrOwners
    GPOConsistency               = $GPOZaurrConsistency
    GPODuplicates                = $GPOZaurrDuplicates
    GPOOrganizationalUnit        = $GPOZaurrOrganizationalUnit
    GPOList                      = $GPOZaurrList
    GPOLinks                     = $GPOZaurrLinks
    GPOPassword                  = $GPOZaurrPassword
    GPOPermissions               = $GPOZaurrPermissionsAnalysis
    GPOPermissionsAdministrative = $GPOZaurrPermissionsAdministrative
    GPOPermissionsRead           = $GPOZaurrPermissionsRead
    GPOPermissionsRoot           = $GPOZaurrPermissionsRoot
    GPOPermissionsUnknown        = $GPOZaurrPermissionsUnknown
    GPOFiles                     = $GPOZaurrFiles
    GPOBlockedInheritance        = $GPOZaurrBlockedInheritance
    GPOAnalysis                  = $GPOZaurrAnalysis
    GPOUpdates                   = $GPOZaurrGPOUpdates
    NetLogonOwners               = $GPOZaurrNetLogonOwners
    NetLogonPermissions          = $GPOZaurrNetLogonPermissions
    SysVolLegacyFiles            = $GPOZaurrSysVolLegacyFiles
}
$Script:GPODitionary = [ordered] @{
    AccountPolicies                                 = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'Account'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Account Policies'
        Code       = {
            ConvertTo-XMLAccountPolicy -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLAccountPolicy -GPO $GPO -SingleObject
        }
    }
    Audit                                           = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'Audit'
            }
            @{
                Category = 'AuditSettings'
                Settings = 'AuditSetting'
            }
        )
        GPOPath    = @(
            'Policies -> Windows Settings -> Security Settings -> Advanced Audit Policy Configuration -> Audit Policies'
            'Policies -> Windows Settings -> Security Settings -> Local Policies -> Audit Policy'
        )
        Code       = {
            ConvertTo-XMLAudit -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLAudit -GPO $GPO -SingleObject
        }
    }
    Autologon                                       = [ordered] @{
        # We want to process this based on other report called RegistrySettings
        # This is because registry settings can be stored in Collections or nested within other registry settings
        # The original function ConvertTo-XMLRegistryAutologon was processing it in limited ordered and potentially would skip some entries.
        ByReports  = @(
            @{
                Report = 'RegistrySettings'
            }
        )
        GPOPath    = 'Preferences -> Windows Settings -> Registry'
        CodeReport = {
            ConvertTo-XMLRegistryAutologonOnReport -GPO $GPO
        }
    }
    AutoPlay                                        = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/AutoPlay Policies'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/AutoPlay Policies*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/AutoPlay Policies*' -SingleObject
        }
    }
    Biometrics                                      = @{
        Types   = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath = 'Policies -> Administrative Templates -> Windows Components/Biometrics'
        Code    = {
            #ConvertTo-XMLBitlocker -GPO $GPO
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Biometrics*'
        }
    }
    Bitlocker                                       = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/BitLocker Drive Encryption'
        Code       = {
            #ConvertTo-XMLBitlocker -GPO $GPO
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/BitLocker Drive Encryption*'
        }
        CodeSingle = {
            #ConvertTo-XMLBitlocker -GPO $GPO
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/BitLocker Drive Encryption*' -SingleObject
        }
    }
    ControlPanel                                    = [ordered]@{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Controol Panel'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel' -SingleObject
        }
    }
    ControlPanelAddRemove                           = [ordered]@{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Control Panel/Add or Remove Programs'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Add or Remove Programs'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Add or Remove Programs' -SingleObject
        }
    }
    ControlPanelDisplay                             = [ordered]@{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Control Panel/Display'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Display'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Display' -SingleObject
        }
    }
    ControlPanelPersonalization                     = [ordered]@{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Control Panel/Personalization'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Personalization'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Personalization' -SingleObject
        }
    }
    ControlPanelPrinters                            = [ordered]@{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Control Panel/Printers'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Printers'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Printers' -SingleObject
        }
    }
    ControlPanelPrograms                            = [ordered]@{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Control Panel/Programs'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Programs'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Programs' -SingleObject
        }
    }
    ControlPanelRegional                            = [ordered]@{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Control Panel/Regional and Language Options'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Regional and Language Options'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Control Panel/Regional and Language Options' -SingleObject
        }
    }
    CredentialsDelegation                           = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> System/Credentials Delegation'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Credentials Delegation*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Credentials Delegation*' -SingleObject
        }
    }
    CustomInternationalSettings                     = [ordered]@{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Custom International Settings'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Custom International Settings*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Custom International Settings*' -SingleObject
        }
    }
    Desktop                                         = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Desktop'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Desktop*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Desktop*' -SingleObject
        }
    }
    DnsClient                                       = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Network/DNS Client'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Network/DNS Client*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Network/DNS Client*' -SingleObject
        }
    }
    DriveMapping                                    = [ordered] @{
        Types      = @(
            @{
                Category = 'DriveMapSettings'
                Settings = 'DriveMapSettings'
            }
        )
        GPOPath    = 'Preferences -> Windows Settings -> Drive Maps'
        Code       = {
            ConvertTo-XMLDriveMapSettings -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLDriveMapSettings -GPO $GPO -SingleObject
        }
    }
    EventLog                                        = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'EventLog'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Event Log'
        Code       = {
            ConvertTo-XMLEventLog -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLEventLog -GPO $GPO
        }
    }
    EventForwarding                                 = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Event Forwarding'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Event Forwarding*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Event Forwarding*' -SingleObject
        }
    }
    EventLogService                                 = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Event Log Service'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Event Log Service*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Event Log Service*' -SingleObject
        }
    }
    FileExplorer                                    = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/File Explorer'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/File Explorer*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/File Explorer*' -SingleObject
        }
    }
    FolderRedirection                               = @{
        Types      = @(
            @{
                Category = 'FolderRedirectionSettings'
                Settings = 'Folder'
            }
        )
        GPOPath    = 'Windows Settings -> Folder Redirection'
        Code       = {
            ConvertTo-XMLFolderRedirection -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLFolderRedirection -GPO $GPO -SingleObject
        }
    }
    FolderRedirectionPolicy                         = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> System/Folder Redirection'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Folder Redirection'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Folder Redirection' -SingleObject
        }
    }
    FSLogix                                         = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> FSLogix'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'FSLogix'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'FSLogix' -SingleObject
        }
    }
    GoogleChrome                                    = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = @(
            'Policies -> Administrative Templates -> Google Chrome'
            'Policies -> Administrative Templates -> Google/Google Chrome'
            'Policies -> Administrative Templates -> Google Chrome - Default Settings (users can override)'
        )
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Google Chrome', 'Google/Google Chrome', 'Google Chrome - Default Settings (users can override)'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Google Chrome', 'Google/Google Chrome', 'Google Chrome - Default Settings (users can override)' -SingleObject
        }
    }
    GroupPolicy                                     = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> System/Group Policy'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Group Policy*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Group Policy*' -SingleObject
        }
    }
    InternetCommunicationManagement                 = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> System/Internet Communication Management'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Internet Communication Management*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Internet Communication Management*' -SingleObject
        }
    }
    InternetExplorer                                = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Internet Explorer'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Internet Explorer*', 'Composants Windows/Celle Internet Explorer'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Internet Explorer*', 'Composants Windows/Celle Internet Explorer' -SingleObject
        }
    }
    InternetExplorerZones                           = [ordered] @{
        ByReports  = @(
            @{
                Report = 'RegistrySettings'
            }
        )
        GPOPath    = 'Preferences -> Windows Settings -> Registry'
        CodeReport = {
            ConvertTo-XMLRegistryInternetExplorerZones -GPO $GPO
        }
    }
    KDC                                             = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> System/KDC'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/KDC'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/KDC' -SingleObject
        }
    }
    LAPS                                            = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> LAPS'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'LAPS'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'LAPS' -SingleObject
        }
    }
    Lithnet                                         = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Lithnet/Password Protection for Active Directory'
        Code       = {
            #ConvertTo-XMLLithnetFilter -GPO $GPO
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Lithnet/Password Protection for Active Directory*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Lithnet/Password Protection for Active Directory*' -SingleObject
        }
    }
    LocalUsers                                      = [ordered] @{
        Types      = @(
            @{
                Category = 'LugsSettings'
                Settings = 'LocalUsersAndGroups'
            }
        )
        GPOPath    = 'Preferences -> Control Panel Settings -> Local Users and Groups'
        Code       = {
            ConvertTo-XMLLocalUser -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLLocalUser -GPO $GPO -SingleObject
        }
    }
    LocalGroups                                     = [ordered] @{
        Types      = @(
            @{
                Category = 'LugsSettings'
                Settings = 'LocalUsersAndGroups'
            }
        )
        GPOPath    = 'Preferences -> Control Panel Settings -> Local Users and Groups'
        Code       = {
            ConvertTo-XMLLocalGroups -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLLocalGroups -GPO $GPO -SingleObject
        }
    }
    Logon                                           = @{
        Types      = @(
            @{ Category = 'RegistrySettings'; Settings = 'Policy' }
        )
        GPOPath    = 'Policies -> Administrative Templates -> System/Logon'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Logon*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Logon*' -SingleObject
        }
    }
    MicrosoftOutlook2002                            = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Microsoft Outlook 2002'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2002*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2002*' -SingleObject
        }
    }
    MicrosoftEdge                                   = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = @(
            'Policies -> Administrative Templates -> Microsoft Edge'
            'Policies -> Administrative Templates -> Windows Components/Edge UI'
            'Policies -> Administrative Templates -> Windows Components/Microsoft Edge'
        )
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Edge*', 'Windows Components/Microsoft Edge', 'Windows Components/Edge UI'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Edge*', 'Windows Components/Microsoft Edge', 'Windows Components/Edge UI' -SingleObject
        }
    }
    MicrosoftOutlook2003                            = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = @(
            'Policies -> Administrative Templates -> Microsoft Office Outlook 2003'
            'Policies -> Administrative Templates -> Outlook 2003 RPC Encryption'
        )
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Office Outlook 2003*', 'Outlook 2003 RPC Encryption'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Office Outlook 2003*', 'Outlook 2003 RPC Encryption' -SingleObject
        }
    }
    MicrosoftOutlook2010                            = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Microsoft Outlook 2010'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2010*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2010*' -SingleObject
        }
    }
    MicrosoftOutlook2013                            = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Microsoft Outlook 2013'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2013*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2013*' -SingleObject
        }
    }
    MicrosoftOutlook2016                            = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Microsoft Outlook 2016'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2016*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Microsoft Outlook 2016*' -SingleObject
        }
    }
    MicrosoftManagementConsole                      = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Microsoft Management Console'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Management Console*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Management Console*' -SingleObject
        }
    }
    NetMeeting                                      = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/NetMeeting'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/NetMeeting*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/NetMeeting*' -SingleObject
        }
    }
    MSSLegacy                                       = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> MSS (Legacy)'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'MSS (Legacy)'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'MSS (Legacy)' -SingleObject
        }
    }
    MSSecurityGuide                                 = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> MS Security Guide'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'MS Security Guide'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'MS Security Guide' -SingleObject
        }
    }
    OneDrive                                        = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/OneDrive'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/OneDrive*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/OneDrive*' -SingleObject
        }
    }
    Policies                                        = @{
        Comment    = "This isn't really translated"
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates'
        Code       = {
            ConvertTo-XMLPolicies -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLPolicies -GPO $GPO -SingleObject
        }
    }
    Printers                                        = @{
        Types      = @(
            @{
                Category = 'PrintersSettings'
                Settings = 'Printers'
            }
            @{
                Category = 'PrinterConnectionSettings'
                Settings = 'PrinterConnection'
            }
        )
        GPOPath    = 'Preferences -> Control Panel Settings -> Printers'
        Code       = {
            ConvertTo-XMLPrinter -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLPrinter -GPO $GPO -SingleObject
        }
    }
    PrintersPolicies                                = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = @(
            'Policies -> Administrative Templates -> Printers'
            'Policies -> Administrative Templates -> Control Panel/Printers'
        )
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Printers*', 'Control Panel/Printers*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Printers*', 'Control Panel/Printers*' -SingleObject
        }
    }
    PublicKeyPoliciesCertificates                   = [ordered] @{
        Types      = @(
            @{
                Category = 'PublicKeySettings'
                Settings = 'RootCertificate'
            }
            @{
                Category = 'PublicKeySettings'
                Settings = 'IntermediateCACertificate'
            }
            @{
                Category = 'PublicKeySettings'
                Settings = 'TrustedPeopleCertificate'
            }
            @{
                Category = 'PublicKeySettings'
                Settings = 'UntrustedCertificate'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies'
        Code       = {
            ConvertTo-XMLCertificates -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLCertificates -GPO $GPO -SingleObject
        }
    }
    <#
    PublicKeyPoliciesAll = [ordered] @{
        Types = @(
            @{
                Category = 'PublicKeySettings'
                Settings = 'AutoEnrollmentSettings'
            }
            @{
                Category = 'PublicKeySettings'
                Settings = 'EFSSettings'
            }
            @{
                Category = 'PublicKeySettings'
                Settings = 'RootCertificateSettings'
            }
        )
        GPOPath = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies'
        Code = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject
        }
    }
    #>

    PublicKeyPoliciesAutoEnrollment                 = [ordered] @{
        Types      = @(
            @{
                Category = 'PublicKeySettings'
                Settings = 'AutoEnrollmentSettings'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies'
        Code       = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject
        }
    }
    PublicKeyPoliciesEFS                            = [ordered] @{
        Types      = @(
            @{
                Category = 'PublicKeySettings'
                Settings = 'EFSSettings'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies'
        Code       = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject
        }
    }
    PublicKeyPoliciesRootCA                         = [ordered] @{
        Types      = @(
            @{
                Category = 'PublicKeySettings'
                Settings = 'RootCertificateSettings'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Public Key Policies'
        Code       = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject
        }
    }
    PublicKeyPoliciesEnrollmentPolicy               = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = @(
            'Policies -> Windows Settings -> Security Settings -> Public Key Policies'
        )
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Internet Communication Management*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Internet Communication Management*' -SingleObject
        }
    }
    RegistrySetting                                 = [ordered] @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'RegistrySetting'
            }
        )
        GPOPath    = "Mixed - missing ADMX?"
        Code       = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLGenericPublicKey -GPO $GPO -SingleObject
        }
    }
    RegistrySettings                                = [ordered] @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'RegistrySettings'
            }
        )
        GPOPath    = 'Preferences -> Windows Settings -> Registry'
        Code       = {
            ConvertTo-XMLRegistrySettings -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLRegistrySettings -GPO $GPO -SingleObject
        }
    }
    OnlineAssistance                                = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Online Assistance'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Online Assistance*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Online Assistance*' -SingleObject
        }
    }
    RemoteAssistance                                = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> System/Remote Assistance'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Remote Assistance*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Remote Assistance*' -SingleObject
        }
    }
    RemoteDesktopServices                           = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Remote Desktop Services'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Remote Desktop Services*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Remote Desktop Services*' -SingleObject
        }
    }
    RSSFeeds                                        = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/RSS Feeds'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/RSS Feeds*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/RSS Feeds*' -SingleObject
        }
    }
    Scripts                                         = [ordered] @{
        Types      = @(
            @{
                Category = 'Scripts'
                Settings = 'Script'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Scripts'
        Code       = {
            ConvertTo-XMLScripts -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLScripts -GPO $GPO -SingleObject
        }
    }
    SecurityOptions                                 = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'SecurityOptions'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Local Policies -> Security Options'
        Code       = {
            ConvertTo-XMLSecurityOptions -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLSecurityOptions -GPO $GPO -SingleObject
        }
    }
    SoftwareInstallation                            = [ordered] @{
        Types      = @(
            @{
                Category = 'SoftwareInstallationSettings'
                Settings = 'MsiApplication'
            }
        )
        GPOPath    = 'Policies -> Software Settings -> Software Installation'
        Code       = {
            ConvertTo-XMLSoftwareInstallation -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLSoftwareInstallation -GPO $GPO -SingleObject
        }
    }
    SystemServices                                  = [ordered] @{
        Types       = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'SystemServices'
            }
        )
        Description = ''
        GPOPath     = 'Policies -> Windows Settings -> Security Settings -> System Services'
        Code        = {
            ConvertTo-XMLSystemServices -GPO $GPO
        }
        CodeSingle  = {
            ConvertTo-XMLSystemServices -GPO $GPO -SingleObject
        }
    }
    SystemServicesNT                                = [ordered] @{
        Types       = @(
            @{
                Category = 'ServiceSettings'
                Settings = 'NTServices'
            }
        )
        Description = ''
        GPOPath     = 'Preferences -> Control Pannel Settings -> Services'
        Code        = {
            ConvertTo-XMLSystemServicesNT -GPO $GPO
        }
        CodeSingle  = {
            ConvertTo-XMLSystemServicesNT -GPO $GPO -SingleObject
        }
    }
    <#
    SystemServicesNT1 = [ordered] @{
        Types = @(
            @{
                Category = 'ServiceSettings'
                Settings = 'NTServices'
            }
        )
        Description = ''
        GPOPath = 'Preferences -> Control Pannel Settings -> Services'
        Code = {
            ConvertTo-XMLSecuritySettings -GPO $GPO -Category 'NTService'
        }
        CodeSingle = {
            ConvertTo-XMLSecuritySettings -GPO $GPO -SingleObject
        }
    }
    #>

    TaskScheduler                                   = [ordered] @{
        Types       = @(
            @{
                Category = 'ScheduledTasksSettings'
                Settings = 'ScheduledTasks'
            }
        )
        Description = ''
        GPOPath     = 'Preferences -> Control Pannel Settings -> Scheduled Tasks'
        Code        = {
            ConvertTo-XMLTaskScheduler -GPO $GPO
        }
        CodeSingle  = {
            ConvertTo-XMLTaskScheduler -GPO $GPO -SingleObject
        }
    }
    TaskSchedulerPolicies                           = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Task Scheduler'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Task Scheduler*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Task Scheduler*' -SingleObject
        }
    }
    <#
    TaskScheduler1 = [ordered] @{
        Types = @(
            @{
                Category = 'ScheduledTasksSettings'
                Settings = 'ScheduledTasks'
            }
        )
        Description = ''
        GPOPath = ''
        Code = {
            ConvertTo-XMLSecuritySettings -GPO $GPO -Category 'TaskV2', 'Task', 'ImmediateTaskV2', 'ImmediateTask'
        }
        CodeSingle = {
            ConvertTo-XMLTaskScheduler -GPO $GPO -SingleObject
        }
    }
    #>

    UserRightsAssignment                            = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'UserRightsAssignment'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Local Policies -> User Rights Assignment'
        Code       = {
            ConvertTo-XMLUserRightsAssignment -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLUserRightsAssignment -GPO $GPO -SingleObject
        }
    }
    WindowsDefender                                 = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Windows Defender'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Defender*' -SingleObject
        }
    }
    WindowsDefenderExploitGuard                     = @{
        # this needs improvements because of DropDownList
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Microsoft Defender Antivirus/Microsoft Defender Exploit Guard*' -SingleObject
        }
    }
    # WindowsFirewall = @{
    # # Contains only one setting showing version
    # Types = @(
    # @{
    # Category = 'WindowsFirewallSettings'
    # Settings = 'GlobalSettings'
    # }
    # )
    # GPOPath = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security'
    # Code = {
    # ConvertTo-XMLWindowsFirewall -GPO $GPO
    # }
    # CodeSingle = {
    # ConvertTo-XMLWindowsFirewall -GPO $GPO -SingleObject
    # }
    # }
    WindowsFirewallConnectionSecurityRules          = @{
        Types      = @(
            @{
                Category = 'WindowsFirewallSettings'
                Settings = 'ConnectionSecurityRules'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security'
        Code       = {
            ConvertTo-XMLWindowsFirewallSecurityRules -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLWindowsFirewallSecurityRules -GPO $GPO -SingleObject
        }
    }
    WindowsFirewallConnectionSecurityAuthentication = @{
        Types      = @(
            @{
                Category = 'WindowsFirewallSettings'
                Settings = 'Phase1AuthenticationSets'
            }
            @{
                Category = 'WindowsFirewallSettings'
                Settings = 'Phase2AuthenticationSets'
            }
            #@{
            # Category = 'WindowsFirewallSettings'
            # Settings = 'DefaultPhase1CryptoSet'
            #}
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security'
        Code       = {
            ConvertTo-XMLWindowsFirewallConnectionSecurityAuthentiation -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLWindowsFirewallConnectionSecurityAuthentiation -GPO $GPO -SingleObject
        }
    }
    WindowsFirewallProfiles                         = @{
        Types      = @(
            @{
                Category = 'WindowsFirewallSettings'
                Settings = 'DomainProfile'
            }
            @{
                Category = 'WindowsFirewallSettings'
                Settings = 'PublicProfile'
            }
            @{
                Category = 'WindowsFirewallSettings'
                Settings = 'PrivateProfile'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security'
        Code       = {
            ConvertTo-XMLWindowsFirewallProfile -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLWindowsFirewallProfile -GPO $GPO -SingleObject
        }
    }
    WindowsFirewallRules                            = @{
        Types      = @(
            @{
                Category = 'WindowsFirewallSettings'
                Settings = 'InboundFirewallRules'
            }
            @{
                Category = 'WindowsFirewallSettings'
                Settings = 'OutboundFirewallRules'
            }
        )
        GPOPath    = 'Policies -> Windows Settings -> Security Settings -> Windows Firewall with Advanced Security -> '
        Code       = {
            ConvertTo-XMLWindowsFirewallRules -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLWindowsFirewallRules -GPO $GPO -SingleObject
        }
    }
    WindowsHelloForBusiness                         = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Windows Hello For Business'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Hello For Business*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Hello For Business*' -SingleObject
        }
    }
    WindowsInstaller                                = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Windows Installer'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Installer*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Installer*' -SingleObject
        }
    }
    WindowsLogon                                    = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Windows Logon Options'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Logon Options*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Logon Options*' -SingleObject
        }
    }
    WindowsMediaPlayer                              = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Windows Media Player'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Media Player*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Media Player*' -SingleObject
        }
    }
    WindowsMessenger                                = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Windows Messenger'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Messenger*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Messenger*' -SingleObject
        }
    }
    WindowsPowerShell                               = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Windows PowerShell'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows PowerShell*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows PowerShell*' -SingleObject
        }
    }
    WindowsRemoteManagement                         = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> Windows Components/Windows Remote Management (WinRM)'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Remote Management (WinRM)*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Remote Management (WinRM)*' -SingleObject
        }
    }
    WindowsTimeService                              = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = 'Policies -> Administrative Templates -> System/Windows Time Service'
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Windows Time Service*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'System/Windows Time Service*' -SingleObject
        }
    }
    WindowsUpdate                                   = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        GPOPath    = @(
            'Policies -> Administrative Templates -> Windows Components/Windows Update'
            #'Policies -> Administrative Templates -> Windows Components/Delivery Optimization'
        )
        Code       = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Update*', 'Windows Components/Delivery Optimization*'
        }
        CodeSingle = {
            ConvertTo-XMLGenericPolicy -GPO $GPO -Category 'Windows Components/Windows Update*', 'Windows Components/Delivery Optimization*' -SingleObject
        }
    }
}
function Test-SysVolFolders {
    [cmdletBinding()]
    param(
        [Array] $GPOs,
        [string] $Server,
        [string] $Domain,
        [System.Collections.IDictionary] $PoliciesAD,
        [string] $PoliciesSearchBase
    )
    $Differences = @{ }
    $SysvolHash = @{ }
    $GPOGUIDS = ConvertFrom-DistinguishedName -DistinguishedName $GPOs.DistinguishedName
    $SysVolPath = "\\$($Server)\SYSVOL\$Domain\Policies"
    Write-Verbose "Get-GPOZaurrBroken - Processing SYSVOL from \\$($Server)\SYSVOL\$Domain\Policies"
    try {
        $SYSVOL = Get-ChildItem -Path "\\$($Server)\SYSVOL\$Domain\Policies" -Exclude 'PolicyDefinitions' -ErrorAction Stop -Verbose:$false
    } catch {
        $Sysvol = $Null
    }
    foreach ($_ in $SYSVOL) {
        $GUID = $_.Name
        $SysvolHash[$GUID] = $_
    }
    $Files = $SYSVOL.Name
    if ($Files) {
        $Comparing = Compare-Object -ReferenceObject $GPOGUIDS -DifferenceObject $Files -IncludeEqual
        foreach ($_ in $Comparing) {
            if ($_.InputObject -eq 'PolicyDefinitions') {
                # we skip policy definitions
                continue
            }
            $ADStatus = $PoliciesAD[$_.InputObject]
            if ($_.SideIndicator -eq '==') {
                #$Found = 'Exists'
                $Found = $ADStatus
            } elseif ($_.SideIndicator -eq '<=') {
                $Found = 'Not available on SYSVOL'
            } elseif ($_.SideIndicator -eq '=>') {
                if ($PoliciesAD[$_.InputObject]) {
                    $Found = $PoliciesAD[$_.InputObject]
                } else {
                    $Found = 'Not available in AD'
                }
            } else {
                # This shouldn't happen at all
                $Found = 'Orphaned GPO'
            }

            $Differences[$_.InputObject] = $Found
        }
    }
    $GPOSummary = @(
        $Count = 0
        foreach ($GPO in $GPOS) {
            $Count++
            $GPOGuid = ConvertFrom-DistinguishedName -DistinguishedName $GPO.DistinguishedName
            if ($GPO.DisplayName) {
                $GPODisplayName = $GPO.DisplayName
                $GPOName = $GPO.Name
                Write-Verbose "Get-GPOZaurrBroken - Processing [$($Domain)]($Count/$($GPOS.Count)) $($GPO.DisplayName)"
            } else {
                $GPOName = $GPOGuid
                $GPODisplayName = $GPOGuid
                Write-Verbose "Get-GPOZaurrBroken - Processing [$($Domain)]($Count/$($GPOS.Count)) $($GPOGuid)"
            }
            if ($null -ne $SysvolHash[$GPOGuid].FullName) {
                $FullPath = $SysvolHash[$GPOGuid].FullName
                $ErrorMessage = ''
            } else {
                $FullPath = -join ($SysVolPath, "\$($GPOGuid)")
                $ErrorMessage = 'Not found on SYSVOL'
            }
            if ($null -eq $Differences[$GPOGuid]) {
                $SysVolStatus = 'Unknown issue'
            } else {
                $SysVolStatus = $Differences[$GPOGuid]
            }
            [PSCustomObject] @{
                DisplayName       = $GPODisplayName
                Status            = $SysVolStatus
                DomainName        = $Domain
                SysvolServer      = $Server
                ObjectClass       = $GPO.ObjectClass
                Id                = $GPOName
                Path              = $FullPath
                DistinguishedName = -join ("CN=", $GPOGuid, ",", $PoliciesSearchBase)
                Description       = $GPO.Description
                CreationTime      = $GPO.Created
                ModificationTime  = $GPO.Modified
                Error             = $ErrorMessage
            }
        }
        # Now we need to list thru Sysvol files and fine those that do not exists as GPO and create dummy GPO objects to show orphaned gpos
        Write-Verbose "Get-GPOZaurrBroken - Processing SYSVOL differences"
        foreach ($_ in $Differences.Keys) {
            if ($Differences[$_] -in 'Not available in AD') {
                $FullPath = $SysvolHash[$_].FullName
                [PSCustomObject] @{
                    DisplayName       = $SysvolHash[$_].BaseName
                    Status            = $Differences[$_]
                    DomainName        = $Domain
                    SysvolServer      = $Server
                    ObjectClass       = ''
                    Id                = $_
                    Path              = $FullPath
                    DistinguishedName = -join ("CN=", $_, ",", $PoliciesSearchBase)
                    Description       = $null
                    CreationTime      = $SysvolHash[$_].CreationTime
                    ModificationTime  = $SysvolHash[$_].LastWriteTime
                    Error             = $ErrorMessage
                }
            }
        }
    )
    $GPOSummary | Sort-Object -Property DisplayName
}
function Add-GPOPermission {
    [cmdletBinding()]
    param(
        [validateset('WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', 'Default')][string] $Type = 'Default',
        [Microsoft.GroupPolicy.GPPermissionType] $IncludePermissionType,
        [alias('Trustee')][string] $Principal,
        [alias('TrusteeType')][validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'DistinguishedName',
        [validateSet('Allow', 'Deny')][string] $PermitType = 'Allow'
    )
    if ($Type -eq 'Default') {
        @{
            Action                = 'Add'
            Type                  = 'Default'
            Principal             = $Principal
            IncludePermissionType = $IncludePermissionType
            PrincipalType         = $PrincipalType
            PermitType            = $PermitType
        }
    } elseif ($Type -eq 'AuthenticatedUsers') {
        @{
            Action                = 'Add'
            Type                  = 'AuthenticatedUsers'
            IncludePermissionType = $IncludePermissionType
            PermitType            = $PermitType
        }
    } elseif ($Type -eq 'Administrative') {
        @{
            Action                = 'Add'
            Type                  = 'Administrative'
            IncludePermissionType = $IncludePermissionType
            PermitType            = $PermitType
        }
    } elseif ($Type -eq 'WellKnownAdministrative') {
        @{
            Action                = 'Add'
            Type                  = 'WellKnownAdministrative'
            IncludePermissionType = $IncludePermissionType
            PermitType            = $PermitType
        }
    }
}
function Add-GPOZaurrPermission {
    [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'GPOName')]
    param(
        [Parameter(ParameterSetName = 'GPOName', Mandatory)][string] $GPOName,
        [Parameter(ParameterSetName = 'GPOGUID', Mandatory)][alias('GUID', 'GPOID')][string] $GPOGuid,
        [Parameter(ParameterSetName = 'All', Mandatory)][switch] $All,

        [Parameter(ParameterSetName = 'ADObject', Mandatory)]
        [alias('OrganizationalUnit', 'DistinguishedName')][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,

        [validateset('WellKnownAdministrative', 'Administrative', 'AuthenticatedUsers', 'Default')][string] $Type = 'Default',

        [alias('Trustee')][string] $Principal,
        [alias('TrusteeType')][validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'DistinguishedName',

        [Parameter(Mandatory)][alias('IncludePermissionType')][Microsoft.GroupPolicy.GPPermissionType] $PermissionType,
        [switch] $Inheritable,

        [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'All',

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [System.Collections.IDictionary] $ADAdministrativeGroups,
        [int] $LimitProcessing = [int32]::MaxValue
    )
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended
    if (-not $ADAdministrativeGroups) {
        $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }
    if ($GPOName) {
        $Splat = @{
            GPOName = $GPOName
        }
    } elseif ($GPOGUID) {
        $Splat = @{
            GPOGUID = $GPOGUID
        }
    } else {
        $Splat = @{}
    }
    $Splat['IncludeGPOObject'] = $true
    $Splat['Forest'] = $Forest
    $Splat['IncludeDomains'] = $IncludeDomains
    if ($Type -ne 'Default') {
        $Splat['Type'] = $Type
    }
    $Splat['PermitType'] = $PermitType
    $Splat['Principal'] = $Principal
    if ($PrincipalType) {
        $Splat.PrincipalType = $PrincipalType
    }
    $Splat['ExcludeDomains'] = $ExcludeDomains
    $Splat['ExtendedForestInformation'] = $ExtendedForestInformation
    $Splat['IncludePermissionType'] = $PermissionType
    $Splat['SkipWellKnown'] = $SkipWellKnown.IsPresent
    $Splat['SkipAdministrative'] = $SkipAdministrative.IsPresent

    $CountFixed = 0

    Do {
        # This should always return results. When no data is found it should return basic information that will allow us to add credentials.
        Get-GPOZaurrPermission @Splat -ReturnSecurityWhenNoData -ReturnSingleObject | ForEach-Object {
            $GPOPermissions = $_
            $PermissionsAnalysis = Get-PermissionsAnalysis -GPOPermissions $GPOPermissions -ADAdministrativeGroups $ADAdministrativeGroups -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Type $Type -PermissionType $PermissionType
            <#
            # Prepare data to clean
            $Skip = $false
            $AdministrativeExists = @{
                DomainAdmins = $false
                EnterpriseAdmins = $false
            }
            $GPOPermissions = $_
            # Verification Phase
            # When it has GPOSecurityPermissionItem property it means it has permissions, if it doesn't it means we have clean object to process
            if ($GPOPermissions.GPOSecurityPermissionItem) {
                # Permission exists, but may be incomplete
                foreach ($GPOPermission in $GPOPermissions) {
                    if ($Type -eq 'Default') {
                        # We were looking for specific principal and we got it. nothing to do
                        # this is for standard users such as przemyslaw.klys / adam.gonzales
                        $Skip = $true
                        break
                    } elseif ($Type -eq 'Administrative') {
                        # We are looking for administrative but we need to make sure we got correct administrative
                        if ($GPOPermission.Permission -eq $PermissionType) {
                            $AdministrativeGroup = $ADAdministrativeGroups['BySID'][$GPOPermission.PrincipalSid]
                            if ($AdministrativeGroup.SID -like '*-519') {
                                $AdministrativeExists['EnterpriseAdmins'] = $true
                            } elseif ($AdministrativeGroup.SID -like '*-512') {
                                $AdministrativeExists['DomainAdmins'] = $true
                            }
                        }
                        if ($AdministrativeExists['DomainAdmins'] -and $AdministrativeExists['EnterpriseAdmins']) {
                            $Skip = $true
                            break
                        }
                    } elseif ($Type -eq 'WellKnownAdministrative') {
                        # this is for SYSTEM account
                        $Skip = $true
                        break
                    } elseif ($Type -eq 'AuthenticatedUsers') {
                        # this is for Authenticated Users
                        $Skip = $true
                        break
                    }
                }
            }
            #>

            if (-not $PermissionsAnalysis.'Skip') {
                if (-not $GPOPermissions) {
                    # This is bad - things went wrong
                    Write-Warning "Add-GPOZaurrPermission - Couldn't get permissions for GPO. Things aren't what they should be. Skipping!"
                } else {
                    $GPO = $GPOPermissions[0]
                    if ($GPOPermissions.GPOSecurityPermissionItem) {
                        # We asked, we got response, now we need to check if maybe we're missing one of the two administrative groups
                        if ($Type -eq 'Administrative') {
                            # this is a case where something was returned. Be it Domain Admins or Enterprise Admins or both. But we still need to check because it may have been Domain Admins from other domain or just one of the two required groups
                            if ($PermissionsAnalysis.'DomainAdmins' -eq $false) {
                                $Principal = $ADAdministrativeGroups[$GPO.DomainName]['DomainAdmins']
                                Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)"
                                $CountFixed++
                                if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) {
                                    try {
                                        $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent)
                                        $GPO.GPOSecurity.Add($AddPermission)
                                        $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity)
                                    } catch {
                                        Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)"
                                    }
                                }
                            }
                            if ($PermissionsAnalysis.'EnterpriseAdmins' -eq $false) {
                                $Principal = $ADAdministrativeGroups[$ForestInformation.Forest.RootDomain]['EnterpriseAdmins']
                                Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)"
                                $CountFixed++
                                if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) {
                                    try {
                                        $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent)
                                        $GPO.GPOSecurity.Add($AddPermission)
                                        $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity)
                                    } catch {
                                        Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)"
                                    }
                                }
                            }
                        } elseif ($Type -eq 'Default') {
                            # This shouldn't really happen, as if we got response, and it didn't exists it wouldn't be here
                            Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType skipped for $($Principal). This shouldn't even happen!"
                        }
                    } else {
                        # We got no response. That means we either asked incorrectly or we need to fix permission. Trying to do so
                        if ($Type -eq 'Default') {
                            Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)"
                            $CountFixed++
                            if ($PrincipalType -eq 'DistinguishedName') {
                                $ADIdentity = Get-WinADObject -Identity $Principal
                                if ($ADIdentity) {
                                    Write-Verbose "Add-GPOZaurrPermission - Need to convert DN $Principal to SID $($ADIdentity.ObjectSID) to $($GPO.DisplayName) at $($GPO.DomainName)"
                                    $Principal = $ADIdentity.ObjectSID
                                }
                            }
                            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) {
                                try {
                                    Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal)"
                                    $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent)
                                    $GPO.GPOSecurity.Add($AddPermission)
                                    $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity)
                                } catch {
                                    Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)"
                                }
                            }
                        } elseif ($Type -eq 'Administrative') {
                            # this is a case where both Domain Admins/Enterprise Admins were missing
                            $Principal = $ADAdministrativeGroups[$GPO.DomainName]['DomainAdmins']
                            Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)"
                            $CountFixed++
                            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) {
                                try {
                                    $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent)
                                    $GPO.GPOSecurity.Add($AddPermission)
                                    $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity)
                                } catch {
                                    Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)"
                                }
                            }
                            $Principal = $ADAdministrativeGroups[$ForestInformation.Forest.RootDomain]['EnterpriseAdmins']
                            Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)"
                            $CountFixed++
                            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) {
                                try {
                                    $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent)
                                    $GPO.GPOSecurity.Add($AddPermission)
                                    $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity)
                                } catch {
                                    Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) with error: $($_.Exception.Message)"
                                }
                            }
                        } elseif ($Type -eq 'WellKnownAdministrative') {
                            $Principal = 'S-1-5-18'
                            Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)"
                            $CountFixed++
                            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal (SYSTEM) / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) {
                                try {
                                    $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent)
                                    $GPO.GPOSecurity.Add($AddPermission)
                                    $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity)
                                } catch {
                                    Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) (SYSTEM) with error: $($_.Exception.Message)"
                                }
                            }
                        } elseif ($Type -eq 'AuthenticatedUsers') {
                            $Principal = 'S-1-5-11'
                            Write-Verbose "Add-GPOZaurrPermission - Adding permission $PermissionType for $($Principal) to $($GPO.DisplayName) at $($GPO.DomainName)"
                            $CountFixed++
                            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Adding $Principal (Authenticated Users) / $PermissionType to $($GPO.DisplayName) at $($GPO.DomainName)")) {
                                try {
                                    $AddPermission = [Microsoft.GroupPolicy.GPPermission]::new($Principal, $PermissionType, $Inheritable.IsPresent)
                                    $GPO.GPOSecurity.Add($AddPermission)
                                    $GPO.GPOObject.SetSecurityInfo($GPO.GPOSecurity)
                                } catch {
                                    Write-Warning "Add-GPOZaurrPermission - Adding permission $PermissionType failed for $($Principal) (Authenticated Users) with error: $($_.Exception.Message)"
                                }
                            }
                        }
                    }
                }
            }
            if ($CountFixed -ge $LimitProcessing) {
                # We want to exit foreach-object, but ForEach-Object doesn't really allow that
                # that's why there is Do/While which will make sure that breaks doesn't break what it's not supposed to
                break
            }
        }
    } while ($false)
}
function Backup-GPOZaurr {
    <#
    .SYNOPSIS
    Provides Backup functionality to Group Policies
 
    .DESCRIPTION
    Provides Backup functionality to Group Policies
 
    .PARAMETER LimitProcessing
    Limits amount of GPOs that are backed up
 
    .PARAMETER Type
    Provides a way to backup only Empty or Unlinked GPOs. The default is All.
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER BackupPath
    Path where to keep the backup
 
    .PARAMETER BackupDated
    Whether cmdlet should created Dated folders for executed backup or not. Keep in mind it's not nessecary and two backups made to same folder have their dates properly tagged
 
    .EXAMPLE
    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All
    $GPOSummary | Format-Table # only if you want to display output of backup
 
    .EXAMPLE
    $GPOSummary = Backup-GPOZaurr -BackupPath "$Env:UserProfile\Desktop\GPO" -Verbose -Type All -BackupDated
    $GPOSummary | Format-Table # only if you want to display output of backup
 
    .NOTES
    General notes
    #>

    [cmdletBinding(SupportsShouldProcess)]
    param(
        [int] $LimitProcessing,
        [validateset('Empty', 'Unlinked', 'Disabled', 'All')][string[]] $Type = 'All',
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string] $BackupPath,
        [switch] $BackupDated
    )
    Begin {
        if ($BackupDated) {
            $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))"
        } else {
            $BackupFinalPath = $BackupPath
        }
        Write-Verbose "Backup-GPOZaurr - Backing up to $BackupFinalPath"
        $null = New-Item -ItemType Directory -Path $BackupFinalPath -Force
        $Count = 0
    }
    Process {
        Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Type $Type | ForEach-Object {
            $GPO = $_
            Write-Verbose "Backup-GPOZaurr - Backing up GPO $($GPO.DisplayName) from $($GPO.DomainName)"
            $Count++
            try {
                $BackupInfo = Backup-GPO -Guid $GPO.GUID -Domain $GPO.DomainName -Path $BackupFinalPath -ErrorAction Stop
                $BackupInfo
            } catch {
                Write-Warning "Backup-GPOZaurr - Backing up GPO $($GPO.DisplayName) from $($GPO.DomainName) failed: $($_.Exception.Message)"
            }
            if ($LimitProcessing -eq $Count) {
                break
            }
        }
    }
    End {

    }
}
function Clear-GPOZaurrSysvolDFSR {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [int] $LimitProcessing = [int32]::MaxValue
    )
    # Based on https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/manually-clearing-the-conflictanddeleted-folder-in-dfsr/ba-p/395711
    $StatusCodes = @{
        '0' = 'Success' # MONITOR_STATUS_SUCCESS
        '1' = 'Generic database error' #MONITOR_STATUS_GENERIC_DB_ERROR
        '2' = 'ID record not found' # MONITOR_STATUS_IDRECORD_NOT_FOUND
        '3' = 'Volume not found' # MONITOR_STATUS_VOLUME_NOT_FOUND
        '4' = 'Access denied' #MONITOR_STATUS_ACCESS_DENIED
        '5' = 'Generic error' #MONITOR_STATUS_GENERIC_ERROR
    }

    #WMIC.EXE /namespace:\\root\microsoftdfs path dfsrreplicatedfolderconfig get replicatedfolderguid, replicatedfoldername
    #WMIC.EXE /namespace:\\root\microsoftdfs path dfsrreplicatedfolderinfo where "replicatedfolderguid='<RF GUID>'" call cleanupconflictdirectory
    #WMIC.EXE /namespace:\\root\microsoftdfs path dfsrreplicatedfolderinfo where "replicatedfolderguid='70bebd41-d5ae-4524-b7df-4eadb89e511e'" call cleanupconflictdirectory

    # https://docs.microsoft.com/en-us/previous-versions/windows/desktop/dfsr/dfsrreplicatedfolderinfo
    $getGPOZaurrSysvolDFSRSplat = @{
        Forest                    = $Forest
        IncludeDomains            = $IncludeDomains
        ExcludeDomains            = $ExcludeDomains
        ExtendedForestInformation = $ExtendedForestInformation
        ExcludeDomainControllers  = $ExcludeDomainControllers
        IncludeDomainControllers  = $IncludeDomainControllers
        SkipRODC                  = $SkipRODC
    }
    Get-GPOZaurrSysvolDFSR @getGPOZaurrSysvolDFSRSplat | Select-Object -First $LimitProcessing | ForEach-Object {
        $Executed = Invoke-CimMethod -InputObject $_.DFSR -MethodName 'cleanupconflictdirectory' -CimSession $_.ComputerName
        if ($Executed) {
            [PSCustomObject] @{
                Status       = $StatusCodes["$($Executed.ReturnValue)"]
                ComputerName = $Executed.PSComputerName
            }
        }
    }
}
function ConvertFrom-CSExtension {
    [cmdletBinding()]
    param(
        [string[]] $CSE,
        [switch] $Limited
    )
    $GUIDs = @{
        # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpreg/f0dba6b8-704f-45d5-999f-1a0a694a6df9
        '{35378EAC-683F-11D2-A89A-00C04FBBCFA2}' = 'Client-side extension GUID (CSE GUID)'
        '{0F6B957E-509E-11D1-A7CC-0000F87571E3}' = 'Tool Extension GUID (User Policy Settings)'
        '{D02B1F73-3407-48AE-BA88-E8213C6761F1}' = 'Tool Extension GUID (User Policy Settings)'
        '{0F6B957D-509E-11D1-A7CC-0000F87571E3}' = 'Tool Extension GUID (Computer Policy Settings)'
        '{D02B1F72-3407-48AE-BA88-E8213C6761F1}' = 'Tool Extension GUID (Computer Policy Settings)'
        '{0ACDD40C-75AC-47ab-BAA0-BF6DE7E7FE63}' = 'Wireless Group Policy'
        '{0E28E245-9368-4853-AD84-6DA3BA35BB75}' = 'Group Policy Environment'
        '{16be69fa-4209-4250-88cb-716cf41954e0}' = 'Central Access Policy Configuration'
        '{17D89FEC-5C44-4972-B12D-241CAEF74509}' = 'Group Policy Local Users and Groups'
        '{1A6364EB-776B-4120-ADE1-B63A406A76B5}' = 'Group Policy Device Settings'
        '{25537BA6-77A8-11D2-9B6C-0000F8080861}' = 'Folder Redirection'
        '{2A8FDC61-2347-4C87-92F6-B05EB91A201A}' = 'MitigationOptions'
        '{346193F5-F2FD-4DBD-860C-B88843475FD3}' = 'ConfigMgr User State Management Extension.'
        '{3610eda5-77ef-11d2-8dc5-00c04fa31a66}' = 'Microsoft Disk Quota'
        '{3A0DBA37-F8B2-4356-83DE-3E90BD5C261F}' = 'Group Policy Network Options'
        '{426031c0-0b47-4852-b0ca-ac3d37bfcb39}' = 'QoS Packet Scheduler'
        '{42B5FAAE-6536-11d2-AE5A-0000F87571E3}' = 'Scripts'
        '{4bcd6cde-777b-48b6-9804-43568e23545d}' = 'Remote Desktop USB Redirection'
        '{4CFB60C1-FAA6-47f1-89AA-0B18730C9FD3}' = 'Internet Explorer Zonemapping'
        '{4D2F9B6F-1E52-4711-A382-6A8B1A003DE6}' = 'RADCProcessGroupPolicyEx'
        '{4d968b55-cac2-4ff5-983f-0a54603781a3}' = 'Work Folders'
        '{5794DAFD-BE60-433f-88A2-1A31939AC01F}' = 'Group Policy Drive Maps'
        '{6232C319-91AC-4931-9385-E70C2B099F0E}' = 'Group Policy Folders'
        '{6A4C88C6-C502-4f74-8F60-2CB23EDC24E2}' = 'Group Policy Network Shares'
        '{7150F9BF-48AD-4da4-A49C-29EF4A8369BA}' = 'Group Policy Files'
        '{728EE579-943C-4519-9EF7-AB56765798ED}' = 'Group Policy Data Sources'
        '{74EE6C03-5363-4554-B161-627540339CAB}' = 'Group Policy Ini Files'
        '{7933F41E-56F8-41d6-A31C-4148A711EE93}' = 'Windows Search Group Policy Extension'
        '{7B849a69-220F-451E-B3FE-2CB811AF94AE}' = 'Internet Explorer User Accelerators'
        '{827D319E-6EAC-11D2-A4EA-00C04F79F83A}' = 'Security'
        '{8A28E2C5-8D06-49A4-A08C-632DAA493E17}' = 'Deployed Printer Connections'
        '{91FBB303-0CD5-4055-BF42-E512A681B325}' = 'Group Policy Services'
        '{A3F3E39B-5D83-4940-B954-28315B82F0A8}' = 'Group Policy Folder Options'
        '{AADCED64-746C-4633-A97C-D61349046527}' = 'Group Policy Scheduled Tasks'
        '{B087BE9D-ED37-454f-AF9C-04291E351182}' = 'Group Policy Registry'
        '{B587E2B1-4D59-4e7e-AED9-22B9DF11D053}' = '802.3 Group Policy'
        '{BA649533-0AAC-4E04-B9BC-4DBAE0325B12}' = 'Windows To Go Startup Options'
        '{BC75B1ED-5833-4858-9BB8-CBF0B166DF9D}' = 'Group Policy Printers'
        '{C34B2751-1CF4-44F5-9262-C3FC39666591}' = 'Windows To Go Hibernate Options'
        '{C418DD9D-0D14-4efb-8FBF-CFE535C8FAC7}' = 'Group Policy Shortcuts'
        '{C631DF4C-088F-4156-B058-4375F0853CD8}' = 'Microsoft Offline Files'
        '{c6dc5466-785a-11d2-84d0-00c04fb169f7}' = 'Software Installation'
        '{cdeafc3d-948d-49dd-ab12-e578ba4af7aa}' = 'TCPIP'
        '{CF7639F3-ABA2-41DB-97F2-81E2C5DBFC5D}' = 'Internet Explorer Machine Accelerators'
        '{e437bc1c-aa7d-11d2-a382-00c04f991e27}' = 'IP Security'
        '{E47248BA-94CC-49c4-BBB5-9EB7F05183D0}' = 'Group Policy Internet Settings'
        '{E4F48E54-F38D-4884-BFB9-D4D2E5729C18}' = 'Group Policy Start Menu Settings'
        '{E5094040-C46C-4115-B030-04FB2E545B00}' = 'Group Policy Regional Options'
        '{E62688F0-25FD-4c90-BFF5-F508B9D2E31F}' = 'Group Policy Power Options'
        '{F312195E-3D9D-447A-A3F5-08DFFA24735E}' = 'ProcessVirtualizationBasedSecurityGroupPolicy'
        '{f3ccc681-b74c-4060-9f26-cd84525dca2a}' = 'Audit Policy Configuration'
        '{F9C77450-3A41-477E-9310-9ACD617BD9E3}' = 'Group Policy Applications'
        '{FB2CA36D-0B40-4307-821B-A13B252DE56C}' = 'Enterprise QoS'
        '{fbf687e6-f063-4d9f-9f4f-fd9a26acdd5f}' = 'CP'
        '{FC491EF1-C4AA-4CE1-B329-414B101DB823}' = 'ProcessConfigCIPolicyGroupPolicy'
        '{169EBF44-942F-4C43-87CE-13C93996EBBE}' = 'UEV Policy'
        '{2BFCC077-22D2-48DE-BDE1-2F618D9B476D}' = 'AppV Policy'
        '{4B7C3B0F-E993-4E06-A241-3FBE06943684}' = 'Per-process Mitigation Options'
        '{7909AD9E-09EE-4247-BAB9-7029D5F0A278}' = 'MDM Policy'
        '{CFF649BD-601D-4361-AD3D-0FC365DB4DB7}' = 'Delivery Optimization GP extension'
        '{D76B9641-3288-4f75-942D-087DE603E3EA}' = 'AdmPwd'
        '{9650FDBC-053A-4715-AD14-FC2DC65E8330}' = 'Unknown'
        '{B1BE8D72-6EAC-11D2-A4EA-00C04F79F83A}' = 'EFS Recovery'
        '{A2E30F80-D7DE-11d2-BBDE-00C04F86AE3B}' = 'Internet Explorer Maintenance Policy Processing'
        '{FC715823-C5FB-11D1-9EEF-00A0C90347FF}' = 'Internet Explorer Maintenance Extension Protocol' # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gpie/f566a58a-4114-4981-b1e2-30b9d1a3c0e6
    }
    foreach ($C in $CSE) {
        if (-not $Limited) {
            if ($GUIDs[$C]) {
                [PSCustomObject] @{ Name = $C; Description = $GUIDs[$C] }
            } else {
                [PSCustomObject] @{ Name = $C; Description = $C }
            }
        } else {
            if ($GUIDs[$C]) {
                $GUIDs[$C]
            } else {
                $CSE
            }
        }
    }
}
function Find-CSExtension {
    [cmdletBinding()]
    param(
        [string[]] $CSE,
        [string] $ComputerName
    )
    #List Group Policy Client Side Extensions, CSEs, from Windows 10
    $Keys = Get-PSRegistry -RegistryPath "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\GPExtensions" -ComputerName $ComputerName
    foreach ($Key in $Keys.PSSubKeys) {
        $RegistryKey = Get-PSRegistry -RegistryPath "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\GPExtensions\$Key" -ComputerName $ComputerName
        if ($CSE) {
            foreach ($C in $CSE) {
                if ($RegistryKey.DefaultKey -eq $Key) {
                    [PSCustomObject] @{ Name = $Key; Description = $RegistryKey.DefaultKey }
                }
            }
        } else {
            [PSCustomObject] @{ CSE = $Key; Description = $RegistryKey.DefaultKey }
        }
    }
}
function Get-GPOZaurr {
    <#
    .SYNOPSIS
    Gets information about all Group Policies. Similar to what Get-GPO provides by default.
 
    .DESCRIPTION
    Gets information about all Group Policies. Similar to what Get-GPO provides by default.
 
    .PARAMETER ExcludeGroupPolicies
    Marks the GPO as excluded from the list.
 
    .PARAMETER GPOName
    Provide a GPOName to get information about a specific GPO.
 
    .PARAMETER GPOGuid
    Provide a GPOGuid to get information about a specific GPO.
 
    .PARAMETER Type
    Choose a specific type of GPO. Options are: 'Empty', 'Unlinked', 'Disabled', 'NoApplyPermission', 'All'. Default is All.
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER GPOPath
    Define GPOPath where the XML files are located to be analyzed instead of asking Active Directory
 
    .PARAMETER PermissionsOnly
    Only show permissions, by default all information is shown
 
    .PARAMETER OwnerOnly
    only show owner information, by default all information is shown
 
    .PARAMETER Limited
    Provide limited output without analyzing XML data
 
    .PARAMETER ADAdministrativeGroups
    Ability to provide ADAdministrativeGroups from different function to speed up processing
 
    .EXAMPLE
    $GPOs = Get-GPOZaurr
    $GPOs | Format-Table DisplayName, Owner, OwnerSID, OwnerType
 
    .EXAMPLE
    $GPO = Get-GPOZaurr -GPOName 'ALL | Allow use of biometrics'
    $GPO | Format-List *
 
    .EXAMPLE
    $GPOS = Get-GPOZaurr -ExcludeGroupPolicies {
        Skip-GroupPolicy -Name 'de14_usr_std'
        Skip-GroupPolicy -Name 'de14_usr_std' -DomaiName 'ad.evotec.xyz'
        Skip-GroupPolicy -Name 'All | Trusted Websites' #-DomaiName 'ad.evotec.xyz'
        '{D39BF08A-87BF-4662-BFA0-E56240EBD5A2}'
        'COMPUTERS | Enable Sets'
    }
    $GPOS | Format-Table -AutoSize *
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [scriptblock] $ExcludeGroupPolicies,
        [string] $GPOName,
        [alias('GUID', 'GPOID')][string] $GPOGuid,

        [validateset('Empty', 'Unlinked', 'Disabled', 'NoApplyPermission', 'All')][string[]] $Type,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string[]] $GPOPath,

        [switch] $PermissionsOnly,
        [switch] $OwnerOnly,
        [switch] $Limited,
        [System.Collections.IDictionary] $ADAdministrativeGroups
    )
    Begin {
        if (-not $ADAdministrativeGroups) {
            Write-Verbose "Get-GPOZaurr - Getting ADAdministrativeGroups"
            $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        }
        if (-not $GPOPath) {
            $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        }
        $ExcludeGPO = [ordered] @{}
        if ($ExcludeGroupPolicies) {
            $ExExecution = Invoke-Command -ScriptBlock $ExcludeGroupPolicies
            foreach ($GroupPolicy in $ExExecution) {
                if ($GroupPolicy -is [string]) {
                    $ExcludeGPO[$GroupPolicy] = $true
                } elseif ($GroupPolicy.Name -and $GroupPolicy.DomainName) {
                    $PolicyName = -join ($GroupPolicy.DomainName, $GroupPolicy.Name)
                    $ExcludeGPO[$PolicyName] = $true
                } elseif ($GroupPolicy.Name) {
                    $ExcludeGPO[$GroupPolicy.Name] = $true
                } else {
                    Write-Warning "Get-GPOZaurr - Exclusion takes only Group Policy Name as string, or as hashtable with domain name @{ Name = 'Group Policy Name'; DomainName = 'Domain' }."
                    continue
                }
            }
        }
        if ($OwnerOnly -or $PermissionsOnly -and $Type) {
            Write-Warning "Get-GPOZaurr - Using PermissionOnly or OwnerOnly with Type is not supported. "
        }
        if (-not $GPOPath) {
            # This is needed, because Get-GPOReport doesn't deliver full scope of links, just some of it. It doesn't cover OUs with blocked inheritance, sites or crosslinked
            $LinksSummaryCache = Get-GPOZaurrLink -AsHashTable -Summary -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        }
    }
    Process {
        if (-not $GPOPath) {
            foreach ($Domain in $ForestInformation.Domains) {
                $QueryServer = $ForestInformation.QueryServers[$Domain]['HostName'][0]
                $Count = 0
                if ($GPOName) {
                    $getGPOSplat = @{
                        Name        = $GPOName
                        Domain      = $Domain
                        Server      = $QueryServer
                        ErrorAction = 'SilentlyContinue'
                    }
                } elseif ($GPOGuid) {
                    $getGPOSplat = @{
                        Guid        = $GPOGuid
                        Domain      = $Domain
                        Server      = $QueryServer
                        ErrorAction = 'SilentlyContinue'
                    }
                } else {
                    $getGPOSplat = @{
                        All         = $true
                        Server      = $QueryServer
                        Domain      = $Domain
                        ErrorAction = 'SilentlyContinue'
                    }
                }
                $GroupPolicies = Get-GPO @getGPOSplat
                foreach ($GPO in $GroupPolicies) {
                    $Count++
                    Write-Verbose "Get-GPOZaurr - Processing [$($GPO.DomainName)]($Count/$($GroupPolicies.Count)) $($_.DisplayName)"
                    if (-not $Limited) {
                        try {
                            $XMLContent = Get-GPOReport -ID $GPO.ID -ReportType XML -Server $ForestInformation.QueryServers[$Domain].HostName[0] -Domain $Domain -ErrorAction Stop
                        } catch {
                            Write-Warning "Get-GPOZaurr - Failed to get [$($GPO.DomainName)]($Count/$($GroupPolicies.Count)) $($GPO.DisplayName) GPOReport: $($_.Exception.Message). Skipping."
                            continue
                        }
                        Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -GPO $GPO -PermissionsOnly:$PermissionsOnly.IsPresent -ADAdministrativeGroups $ADAdministrativeGroups -ExcludeGroupPolicies $ExcludeGPO -Type $Type -LinksSummaryCache $LinksSummaryCache
                    } else {
                        $GPO
                    }
                }
            }
        } else {
            foreach ($Path in $GPOPath) {
                Write-Verbose "Get-GPOZaurr - Getting GPO content from XML files"
                Get-ChildItem -LiteralPath $Path -Recurse -Filter *.xml -ErrorAction SilentlyContinue | ForEach-Object {
                    if ($_.Name -ne 'GPOList.xml') {
                        $XMLContent = [XML]::new()
                        $XMLContent.Load($_.FullName)
                        Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -PermissionsOnly:$PermissionsOnly.IsPresent -ExcludeGroupPolicies $ExcludeGPO -Type $Type
                    }
                }
                Write-Verbose "Get-GPOZaurr - Finished GPO content from XML files"
            }
        }
    }
    End {

    }
}
function Get-GPOZaurrAD {
    [cmdletbinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(ParameterSetName = 'GPOName')]
        [string] $GPOName,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [alias('GUID', 'GPOID')][string] $GPOGuid,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,

        [DateTime] $DateFrom,
        [DateTime] $DateTo,
        [ValidateSet('PastHour', 'CurrentHour', 'PastDay', 'CurrentDay', 'PastMonth', 'CurrentMonth', 'PastQuarter', 'CurrentQuarter', 'Last14Days', 'Last21Days', 'Last30Days', 'Last7Days', 'Last3Days', 'Last1Days')][string] $DateRange,
        [ValidateSet('WhenCreated', 'WhenChanged')][string[]] $DateProperty = 'WhenCreated',
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    Begin {
        $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }
    Process {
        foreach ($Domain in $ForestInformation.Domains) {
            if ($PSCmdlet.ParameterSetName -eq 'GPOGUID') {
                if ($GPOGuid) {
                    if ($GPOGUID -notlike '*{*') {
                        $GUID = -join ("{", $GPOGUID, '}')
                    } else {
                        $GUID = $GPOGUID
                    }
                    $Splat = @{
                        Filter = "(objectClass -eq 'groupPolicyContainer') -and (Name -eq '$GUID')"
                        Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                    }
                } else {
                    Write-Warning "Get-GPOZaurrAD - GPOGUID parameter is empty. Provide name and try again."
                    continue
                }
            } elseif ($PSCmdlet.ParameterSetName -eq 'GPOName') {
                if ($GPOName) {
                    $Splat = @{
                        Filter = "(objectClass -eq 'groupPolicyContainer') -and (DisplayName -eq '$GPOName')"
                        Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                    }
                } else {
                    Write-Warning "Get-GPOZaurrAD - GPOName parameter is empty. Provide name and try again."
                    continue
                }
            } else {
                $Splat = @{
                    Filter = "(objectClass -eq 'groupPolicyContainer')"
                    Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                }
            }
            # allows to only get GPOs from a specific date range
            if ($PSBoundParameters.ContainsKey('DateRange')) {
                $Dates = Get-ChoosenDates -DateRange $DateRange
                $DateFrom = $($Dates.DateFrom)
                $DateTo = $($Dates.DateTo)

                if ($DateProperty -contains 'WhenChanged' -and $DateProperty -contains 'WhenCreated') {
                    $Splat['Filter'] = -join ($Splat['Filter'], ' -and ((WhenChanged -ge $DateFrom -and WhenChanged -le $DateTo) -or (WhenCreated -ge $DateFrom -and WhenCreated -le $DateTo))')
                } elseif ($DateProperty -eq 'WhenChanged' -or $DateProperty -eq 'WhenCreated') {
                    $Property = $DateProperty[0]
                    $Splat['Filter'] = -join ($Splat['Filter'], ' -and ($Property -ge $DateFrom -and $Property -le $DateTo)')
                } else {
                    Write-Warning -Message "Get-GPOZaurrAD - DateProperty parameter is empty. Provide name and try again."
                    continue
                }
            } elseif ($PSBoundParameters.ContainsKey('DateFrom') -and $PSBoundParameters.ContainsKey('DateTo')) {
                # already set $DateFrom,DateTo
                #$Splat['Filter'] = -join ($Splat['Filter'], '-and ($DateProperty -ge $DateFrom -and $DateProperty -le $DateTo)')
                if ($DateProperty -contains 'WhenChanged' -and $DateProperty -contains 'WhenCreated') {
                    $Splat['Filter'] = -join ($Splat['Filter'], ' -and ((WhenChanged -ge $DateFrom -and WhenChanged -le $DateTo) -or (WhenCreated -ge $DateFrom -and WhenCreated -le $DateTo))')
                } elseif ($DateProperty -eq 'WhenChanged' -or $DateProperty -eq 'WhenCreated') {
                    $Property = $DateProperty[0]
                    $Splat['Filter'] = -join ($Splat['Filter'], ' -and ($Property -ge $DateFrom -and $Property -le $DateTo)')
                } else {
                    Write-Warning -Message "Get-GPOZaurrAD - DateProperty parameter is empty. Provide name and try again."
                    continue
                }
            } else {
                # not needed
            }

            Write-Verbose -Message "Get-GPOZaurrAD - Searching domain $Domain with filter $($Splat['Filter'])"
            Get-ADObject @Splat -Properties DisplayName, Name, Created, Modified, ntSecurityDescriptor, gPCFileSysPath, gPCFunctionalityVersion, gPCWQLFilter, gPCMachineExtensionNames, Description, CanonicalName, DistinguishedName | ForEach-Object -Process {
                $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToDomainCN
                $GUID = $_.Name -replace '{' -replace '}'
                if (($GUID).Length -ne 36) {
                    Write-Warning "Get-GPOZaurrAD - GPO GUID ($($($GUID.Replace("`n",' ')))) is incorrect. Skipping $($_.DisplayName) / Domain: $($DomainCN)"
                } else {
                    $Output = [ordered]@{ }
                    $Output['DisplayName'] = $_.DisplayName
                    $Output['DomainName'] = $DomainCN
                    $Output['Description'] = $_.Description
                    $Output['GUID'] = $GUID
                    $Output['Path'] = $_.gPCFileSysPath
                    #$Output['FunctionalityVersion'] = $_.gPCFunctionalityVersion
                    $Output['Created'] = $_.Created
                    $Output['Modified'] = $_.Modified
                    $Output['Owner'] = $_.ntSecurityDescriptor.Owner
                    $Output['GPOCanonicalName'] = $_.CanonicalName
                    $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToDC
                    $Output['GPODistinguishedName'] = $_.DistinguishedName
                    [PSCustomObject] $Output
                }
            }
        }
    }
    End {

    }
}
function Get-GPOZaurrBackupInformation {
    [cmdletBinding()]
    param(
        [string[]] $BackupFolder
    )
    Begin {

    }
    Process {
        foreach ($Folder in $BackupFolder) {
            if ($Folder) {
                if ((Test-Path -LiteralPath "$Folder\manifest.xml")) {
                    [xml] $Xml = Get-Content -LiteralPath "$Folder\manifest.xml"
                    $Xml.Backups.BackupInst | ForEach-Object -Process {
                        [PSCustomObject] @{
                            DisplayName      = $_.GPODisplayName.'#cdata-section'
                            DomainName       = $_.GPODomain.'#cdata-section'
                            Guid             = $_.GPOGUid.'#cdata-section' -replace '{' -replace '}'
                            DomainGuid       = $_.GPODomainGuid.'#cdata-section' -replace '{' -replace '}'
                            DomainController = $_.GPODomainController.'#cdata-section'
                            BackupTime       = [DateTime]::Parse($_.BackupTime.'#cdata-section')
                            ID               = $_.ID.'#cdata-section' -replace '{' -replace '}'
                            Comment          = $_.Comment.'#cdata-section'
                        }
                    }
                } else {
                    Write-Warning "Get-GPOZaurrBackupInformation - No backup information available"
                }
            }
        }
    }
    End {

    }
}
function Get-GPOZaurrBroken {
    <#
    .SYNOPSIS
    Detects broken or otherwise damaged Group Policies
 
    .DESCRIPTION
    Detects broken or otherwise damaged Group Policies providing insight whether GPO exists in both AD and SYSVOL.
    It provides few statuses:
    - Permissions issue - means account couldn't read GPO due to permissions
    - ObjectClass issue - means that ObjectClass is of type Container, rather than expected groupPolicyContainer
    - Not available on SYSVOL - means SYSVOL data is missing, yet AD metadata is available
    - Not available in AD - means AD metadata is missing, yet SYSVOL data is available
    - Exists - means AD metadata and SYSVOL data are available
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER ExcludeDomainControllers
    Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER IncludeDomainControllers
    Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER SkipRODC
    Skip Read-Only Domain Controllers. By default all domain controllers are included.
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER VerifyDomainControllers
    Forces cmdlet to check GPO Existance on Domain Controllers rather then per domain
 
    .EXAMPLE
    Get-GPOZaurrBroken -Verbose | Format-Table
 
    .NOTES
    General notes
    #>

    [alias('Get-GPOZaurrSysvol')]
    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $VerifyDomainControllers
    )
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended
    foreach ($Domain in $ForestInformation.Domains) {
        $TimeLog = Start-TimeLog
        Write-Verbose "Get-GPOZaurrBroken - Starting process for $Domain"
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer
        $PoliciesAD = @{}
        if ($SystemsContainer) {
            $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer)
            $PoliciesInAD = Get-ADObject -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties Name, gPCFileSysPath, DisplayName, DistinguishedName, Description, Created, Modified, ObjectClass, ObjectGUID
            foreach ($Policy in $PoliciesInAD) {
                $GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName
                if ($Policy.ObjectClass -eq 'Container') {
                    # This usually means GPO deletion process somehow failed and while object itself stayed it isn't groupPolicyContainer anymore
                    $PoliciesAD[$GUIDFromDN] = 'ObjectClass issue'
                } else {
                    $GUID = $Policy.Name
                    if ($GUID -and $GUIDFromDN) {
                        $PoliciesAD[$GUIDFromDN] = 'Exists'
                    } else {
                        $PoliciesAD[$GUIDFromDN] = 'Permissions issue'
                    }
                }
            }
        } else {
            Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Skipping"
        }
        if ($PoliciesInAD.Count -ge 2) {
            if (-not $VerifyDomainControllers) {
                Test-SysVolFolders -GPOs $PoliciesInAD -Server $Domain -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase
            } else {
                foreach ($Server in $ForestInformation['DomainDomainControllers']["$Domain"]) {
                    Write-Verbose "Get-GPOZaurrBroken - Processing $Domain \ $($Server.HostName.Trim())"
                    Test-SysVolFolders -GPOs $PoliciesInAD -Server $Server.Hostname -Domain $Domain -PoliciesAD $PoliciesAD -PoliciesSearchBase $PoliciesSearchBase
                }
            }
        } else {
            Write-Warning "Get-GPOZaurrBroken - GPO count for $Domain is less then 2. This is not expected for fully functioning domain. Skipping processing SYSVOL folder."
        }
        $TimeEnd = Stop-TimeLog -Time $TimeLog -Option OneLiner
        Write-Verbose "Get-GPOZaurrBroken - Finishing process for $Domain (Time to process: $TimeEnd)"
    }
}
function Get-GPOZaurrBrokenLink {
    <#
    .SYNOPSIS
    Finds any GPO link that doesn't have a matching GPO (already removed GPO).
 
    .DESCRIPTION
    Finds any GPO link that doesn't have a matching GPO (already removed GPO).
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    Get-GPOZaurrBrokenLink -Verbose | Format-Table -AutoSize *
 
    .EXAMPLE
    Get-GPOZaurrBrokenLink -Verbose -IncludeDomains ad.evotec.pl | Format-Table -AutoSize *
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $ErrorFound = $false
    $PoliciesAD = @{}
    # We need to request all GPOS from Forest. Requesting just for any domain won't be enough
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -Extended # -Extended
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer
        if ($SystemsContainer) {
            $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer)
            try {
                $PoliciesInAD = Get-ADObject -ErrorAction Stop -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties Name, gPCFileSysPath, DisplayName, DistinguishedName, Description, Created, Modified, ObjectClass, ObjectGUID
            } catch {
                Write-Warning "Get-GPOZaurrBrokenLink - An error occured while searching $PoliciesSearchBase. Error $($_.Exception.Message). Please resolve this before continuing."
                $ErrorFound = $true
                break
            }
            foreach ($Policy in $PoliciesInAD) {
                $GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName
                # $Key = "$($Domain)$($GuidFromDN)"
                $Key = $Policy.DistinguishedName
                if ($Policy.ObjectClass -eq 'Container') {
                    # This usually means GPO deletion process somehow failed and while object itself stayed it isn't groupPolicyContainer anymore
                    $PoliciesAD[$Key] = 'ObjectClass issue'
                } else {
                    $GUID = $Policy.Name
                    if ($GUID -and $GUIDFromDN) {
                        $PoliciesAD[$Key] = 'Exists'
                    } else {
                        $PoliciesAD[$Key] = 'Permissions issue'
                    }
                }
            }
        } else {
            Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Skipping"
        }
    }
    if ($ErrorFound) {
        return
    }
    # In case of links we can request here whatever user requested.
    # This will search for broken links in domain user requested
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -Extended -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $Links = Get-GPOZaurrLinkLoop -Linked 'All' -ForestInformation $ForestInformation
    foreach ($Link in $Links) {
        if (-not $PoliciesAD[$Link.GPODistinguishedName]) {
            $Link
        }
    }
}
function Get-GPOZaurrDictionary {
    [cmdletBinding()]
    param(
        [string] $Splitter = [System.Environment]::NewLine
    )
    foreach ($Policy in $Script:GPODitionary.Keys) {

        if ($Script:GPODitionary[$Policy].ByReports) {
            [Array] $Type = foreach ($T in  $Script:GPODitionary[$Policy].ByReports ) {
                $T.Report
            }

        } else {
            [Array]$Type = foreach ($T in $Script:GPODitionary[$Policy].Types) {
                ( -join ($T.Category, '/', $T.Settings))
            }
        }

        [PSCustomObject] @{
            Name  = $Policy
            Types = $Type -join $Splitter
            Path  = $Script:GPODitionary[$Policy].GPOPath -join $Splitter
            #Details = $Script:GPODitionary[$Policy]
        }
    }
}
function Get-GPOZaurrDuplicateObject {
    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )

    $getWinADDuplicateObjectSplat = @{
        Forest                        = $Forest
        IncludeDomains                = $IncludeDomains
        ExcludeDomains                = $ExcludeDomains
        ExtendedForestInformation     = $ExtendedForestInformation
        PartialMatchDistinguishedName = "*,CN=Policies,CN=System,DC=*"
        Extended                      = $true
    }

    $DuplicateObjects = Get-WinADDuplicateObject @getWinADDuplicateObjectSplat
    $DuplicateObjects
}
function Get-GPOZaurrFiles {
    [cmdletbinding()]
    param(
        [ValidateSet('All', 'Netlogon', 'Sysvol')][string[]] $Type = 'All',
        [ValidateSet('None', 'MACTripleDES', 'MD5', 'RIPEMD160', 'SHA1', 'SHA256', 'SHA384', 'SHA512')][string] $HashAlgorithm = 'None',
        [switch] $Signature,
        [switch] $AsHashTable,
        [switch] $Extended,
        [switch] $ExtendedMetaData,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $GPOCache = @{}
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $GPOList = Get-GPOZaurrAD -ExtendedForestInformation $ForestInformation
    foreach ($GPO in $GPOList) {
        if (-not $GPOCache[$GPO.DomainName]) {
            $GPOCache[$GPO.DomainName] = @{}
        }
        $GPOCache[$($GPO.DomainName)][($GPO.GUID)] = $GPO
    }
    foreach ($Domain in $ForestInformation.Domains) {
        $Path = @(
            if ($Type -contains 'All') {
                "\\$Domain\SYSVOL\$Domain"
            }
            if ($Type -contains 'Sysvol') {
                "\\$Domain\SYSVOL\$Domain\policies"
            }
            if ($Type -contains 'NetLogon') {
                "\\$Domain\NETLOGON"
            }
        )
        # Order does matter
        $Folders = [ordered] @{
            "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" = @{
                Name = 'SYSVOL PolicyDefinitions'
            }
            "\\$Domain\SYSVOL\$Domain\policies"                   = @{
                Name = 'SYSVOL Policies'
            }
            "\\$Domain\SYSVOL\$Domain\scripts"                    = @{
                Name = 'NETLOGON Scripts'
            }
            "\\$Domain\SYSVOL\$Domain\StarterGPOs"                = @{
                Name = 'SYSVOL GPO Starters'
            }
            "\\$Domain\NETLOGON"                                  = @{
                Name = 'NETLOGON Scripts'
            }
            "\\$Domain\SYSVOL\$Domain\DfsrPrivate"                = @{
                Name = 'DfsrPrivate'
            }
            "\\$Domain\SYSVOL\$Domain"                            = @{
                Name = 'SYSVOL Root'
            }
        }
        Get-ChildItem -Path $Path -ErrorAction SilentlyContinue -Recurse -ErrorVariable err -File -Force | ForEach-Object {
            # Lets reset values just to be sure those are empty
            $GPO = $null
            $BelongsToGPO = $false
            $GPODisplayName = $null
            $SuggestedAction = $null
            $SuggestedActionComment = $null
            $FileType = foreach ($Key in $Folders.Keys) {
                if ($_.FullName -like "$Key*") {
                    $Folders[$Key]
                    break
                }
            }
            if ($FileType.Name -eq 'SYSVOL Policies') {
                $FoundGUID = $_.FullName -match '[\da-zA-Z]{8}-([\da-zA-Z]{4}-){3}[\da-zA-Z]{12}'
                if ($FoundGUID) {
                    $GPO = $GPOCache[$Domain][$matches[0]]
                    if ($GPO) {
                        $BelongsToGPO = $true
                        $GPODisplayName = $GPO.DisplayName
                    }
                }
                $Correct = @(
                    [System.IO.Path]::Combine($GPO.Path, 'GPT.INI')
                    [System.IO.Path]::Combine($GPO.Path, 'GPO.cmt')
                    [System.IO.Path]::Combine($GPO.Path, 'Group Policy', 'GPE.ini')
                    foreach ($TypeM in @('Machine', 'User')) {
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Registry.pol')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'comment.cmtx')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Registry\Registry.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Printers\Printers.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\ScheduledTasks\ScheduledTasks.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Services\Services.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Groups\Groups.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\RegionalOptions\RegionalOptions.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\FolderOptions\FolderOptions.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Drives\Drives.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\InternetSettings\InternetSettings.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Folders\Folders.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\PowerOptions\PowerOptions.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Shortcuts\Shortcuts.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\Files\Files.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\EnvironmentVariables\EnvironmentVariables.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\NetworkOptions\NetworkOptions.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\DataSources\DataSources.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\NetworkShares\NetworkShares.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Preferences\StartMenuTaskbar\StartMenuTaskbar.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Applications\Microsoft\TBLayout.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Applications\Microsoft\DefaultApps.xml')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Applications\ADE.CFG')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Scripts\scripts.ini')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Scripts\psscripts.ini')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Documents & Settings\fdeploy.ini')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Documents & Settings\fdeploy1.ini')
                        [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Documents & Settings\fdeploy2.ini')
                        if ($_.Extension -eq '.aas') {
                            [System.IO.Path]::Combine($GPO.Path, $TypeM, 'Applications', $_.Name)
                        }
                    }
                    [System.IO.Path]::Combine($GPO.Path, 'Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf')
                    [System.IO.Path]::Combine($GPO.Path, 'Machine\Microsoft\Windows NT\Audit\audit.csv')
                )
                if ($GPO) {
                    if ($_.FullName -in $Correct) {
                        $SuggestedAction = 'Skip assesment'
                        $SuggestedActionComment = 'Correctly placed in SYSVOL'
                    } elseif ($_.FullName -like '*_NTFRS_*') {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Most likely replication error'
                    } elseif ($_.Extension -eq '.adm') {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Most likely legacy ADM files'
                    } elseif ($_.Name -eq 'Thumbs.db') {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Most likely database files to store image thumbnails on Windows systems.'
                    }
                    if (-not $SuggestedAction) {
                        $FullPathAdmFiles = [System.IO.Path]::Combine($GPO.Path, 'Adm\admfiles.ini')
                        if ($_.FullName -eq $FullPathAdmFiles) {
                            $SuggestedAction = 'Consider deleting'
                            $SuggestedActionComment = 'Most likely legacy ADM files settings file'
                        }
                    }
                    if (-not $SuggestedAction) {
                        foreach ($Ext in @('*old*', '*bak*', '*bck', '.new')) {
                            if ($_.Extension -like $Ext) {
                                $SuggestedAction = 'Consider deleting'
                                $SuggestedActionComment = 'Most likely backup files'
                                break
                            }
                        }
                    }
                    if (-not $SuggestedAction) {
                        <#
                        $IEAK = @(
                            'microsoft\IEAK\install.ins'
                            'MICROSOFT\IEAK\BRANDING\cs\connect.ras'
                            'microsoft\IEAK\BRANDING\cs\connect.set'
                            'microsoft\IEAK\BRANDING\cs\cs.dat'
                            'microsoft\IEAK\BRANDING\ADM\inetcorp.iem'
                            'microsoft\IEAK\BRANDING\ADM\inetcorp.inf'
                            'microsoft\IEAK\install.ins'
                            'microsoft\IEAK\BRANDING\favs\Outlook.ico'
                            'microsoft\IEAK\BRANDING\favs\Bio.ico'
                            'MICROSOFT\IEAK\BRANDING\favs\$fi380.ico'
                            'microsoft\IEAK\BRANDING\PROGRAMS\programs.inf'
                            'MICROSOFT\IEAK\BRANDING\RATINGS\ratings.inf'
                            'MICROSOFT\IEAK\BRANDING\RATINGS\ratrsop.inf'
                            'microsoft\IEAK\BRANDING\ZONES\seczones.inf'
                            'microsoft\IEAK\BRANDING\ZONES\seczrsop.inf'
                            'microsoft\IEAK\BRANDING\ZONES\seczrsop.inf'
                        )
                        #>

                        if ($_.FullName -like '*microsoft\IEAK*') {
                            # https://docs.microsoft.com/en-us/internet-explorer/ie11-deploy-guide/missing-internet-explorer-maintenance-settings-for-ie11#:~:text=The%20Internet%20Explorer%20Maintenance%20(IEM,Internet%20Explorer%2010%20or%20newer.
                            $SuggestedAction = 'GPO requires cleanup'
                            $SuggestedActionComment = 'Internet Explorer Maintenance (IEM) is deprecated for IE 11'
                        }
                    }
                } else {
                    <#
                    $FullPathAdmFiles = [System.IO.Path]::Combine($GPO.Path, 'Adm\admfiles.ini')
                    if ($_.FullName -in $Correct) {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Most likely orphaned SYSVOL GPO'
                    } elseif ($_.Extension -eq '.adm') {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Most likely orphaned SYSVOL GPO (legacy ADM files)'
                    } elseif ($_.FullName -eq $FullPathAdmFiles) {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Most likely orphaned SYSVOL GPO (legacy ADM files)'
                    }
                    #>

                    $SuggestedAction = 'Consider deleting'
                    $SuggestedActionComment = 'Most likely orphaned SYSVOL GPO'
                }
            } elseif ($FileType.Name -eq 'NETLOGON Scripts') {
                foreach ($Ext in @('*old*', '*bak*', '*bck', '.new')) {
                    if ($_.Extension -like $Ext) {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Most likely backup files'
                        break
                    }
                }
                if (-not $SuggestedAction) {
                    # We didn't find it in earlier check, lets go deeper
                    if ($_.Extension.Length -gt 6 -and $_.Extension -notin @('.config', '.sites', '.ipsec')) {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Extension longer then 5 chars'
                    } elseif ($_.Extension -eq '') {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'No extension'
                    }
                }
                if (-not $SuggestedAction) {
                    foreach ($Name in @('*old*', '*bak*', '*bck*', '*Copy', '*backup*')) {
                        if ($_.BaseName -like $Name) {
                            $SuggestedAction = 'Consider deleting'
                            $SuggestedActionComment = "FileName contains backup related names ($Name)"
                            break
                        }
                    }
                }
                if (-not $SuggestedAction) {
                    if ($_.Name -eq 'Thumbs.db') {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'Most likely database files to store image thumbnails on Windows systems.'
                    }
                }
                if (-not $SuggestedAction) {
                    foreach ($FullName in @('*backup*', '*Delete*', '*Obsoleet*', '*Obsolete*', '*Archive*')) {
                        if ($_.FullName -like $FullName) {
                            $SuggestedAction = 'Consider deleting'
                            $SuggestedActionComment = "Fullname contains backup related names ($FullName)"
                            break
                        }
                    }
                }
                if (-not $SuggestedAction) {
                    # We replace all letters leaving only numbers
                    # We want to find if there is a date possibly
                    $StrippedNumbers = $_.Name -replace "[^0-9]" , ''
                    if ($StrippedNumbers.Length -gt 5) {
                        $SuggestedAction = 'Consider deleting'
                        $SuggestedActionComment = 'FileName contains over 5 numbers (date?)'
                    }
                }
            } elseif ($FileType.Name -eq 'SYSVOL PolicyDefinitions') {
                if ($_.Extension -in @('.admx', '.adml')) {
                    $SuggestedAction = 'Skip assesment'
                    $SuggestedActionComment = 'Most likely ADMX templates'
                }
            } elseif ($FileType.Name -eq 'SYSVOL GPO Starters') {
                $FoundGUID = $_.FullName -match '[\da-zA-Z]{8}-([\da-zA-Z]{4}-){3}[\da-zA-Z]{12}'
                if ($FoundGUID) {
                    $GUID = $matches[0]
                    $TemporaryStarterPath = "\\$Domain\SYSVOL\$Domain\StarterGPOs\{$GUID}"
                    $Correct = @(
                        [System.IO.Path]::Combine($TemporaryStarterPath, 'StarterGPO.tmplx')
                        [System.IO.Path]::Combine($TemporaryStarterPath, 'en-US', 'StarterGPO.tmpll')
                        foreach ($TypeM in @('Machine', 'User')) {
                            [System.IO.Path]::Combine($TemporaryStarterPath, $TypeM, 'Registry.pol')
                            [System.IO.Path]::Combine($TemporaryStarterPath, $TypeM, 'comment.cmtx')
                        }
                    )
                    if ($_.FullName -in $Correct) {
                        $SuggestedAction = 'Skip assesment'
                        $SuggestedActionComment = 'Correctly placed in SYSVOL'
                    }
                }
            } else {

            }
            if (-not $SuggestedAction) {
                $SuggestedAction = 'Requires verification'
                $SuggestedActionComment = 'Not able to auto asses'
            }
            if (-not $ExtendedMetaData) {
                $MetaData = [ordered] @{
                    LocationType           = $FileType.Name
                    FullName               = $_.FullName
                    #Name = $_.Name
                    Extension              = $_.Extension
                    SuggestedAction        = $SuggestedAction
                    SuggestedActionComment = $SuggestedActionComment
                    BelongsToGPO           = $BelongsToGPO
                    GPODisplayName         = $GPODisplayName
                    Attributes             = $_.Attributes
                    CreationTime           = $_.CreationTime
                    LastAccessTime         = $_.LastAccessTime
                    LastWriteTime          = $_.LastWriteTime
                }
            } else {
                $MetaData = Get-FileMetaData -File $_ -AsHashTable
                $MetaData['SuggestedAction'] = $SuggestedAction
                $MetaData['SuggestedActionComment'] = $SuggestedActionComment
                $MetaData['BelongsToGPO'] = $BelongsToGPO
                $MetaData['GPODisplayName'] = $GPODisplayName
            }
            if ($Signature) {
                try {
                    $DigitalSignature = Get-AuthenticodeSignature -FilePath $_.Fullname -ErrorAction Stop
                } catch {
                    Write-Warning "Get-GPOZaurrFiles - Error when reading signature: $($_.Exception.Message)"
                }
                if ($DigitalSignature) {
                    $MetaData['SignatureStatus'] = $DigitalSignature.Status
                    $MetaData['IsOSBinary'] = $DigitalSignature.IsOSBinary
                    $MetaData['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject
                    if ($Extended) {
                        $MetaData['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer
                        $MetaData['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber
                        $MetaData['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore
                        $MetaData['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter
                        $MetaData['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint
                    }
                } else {
                    $MetaData['SignatureStatus'] = 'Not available'
                    $MetaData['IsOSBinary'] = $null
                    $MetaData['SignatureCertificateSubject'] = $null
                    if ($Extended) {
                        $MetaData['SignatureCertificateIssuer'] = $null
                        $MetaData['SignatureCertificateSerialNumber'] = $null
                        $MetaData['SignatureCertificateNotBefore'] = $null
                        $MetaData['SignatureCertificateNotAfter'] = $null
                        $MetaData['SignatureCertificateThumbprint'] = $null
                    }
                }
            }
            if ($HashAlgorithm -ne 'None') {
                $MetaData['ChecksumSHA256'] = (Get-FileHash -LiteralPath $_.FullName -Algorithm $HashAlgorithm).Hash
            }
            if ($AsHashTable) {
                $MetaData
            } else {
                [PSCustomObject] $MetaData
            }
        }
        foreach ($e in $err) {
            Write-Warning "Get-GPOZaurrFiles - $($e.Exception.Message) ($($e.CategoryInfo.Reason))"
        }
    }
}
function Get-GPOZaurrFilesPolicyDefinition {
    [alias('Get-GPOZaurrFilesPolicyDefinitions')]
    [cmdletbinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [switch] $Signature
    )
    $Output = [ordered] @{
        FilesToDelete = [System.Collections.Generic.List[Object]]::new()
    }
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $FilesCache = @{}
    foreach ($Domain in $ForestInformation.Domains) {
        $Output[$Domain] = [ordered] @{}
        $FilesCache[$Domain] = [ordered] @{}
        $Directories = Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" -Directory -ErrorAction SilentlyContinue -ErrorVariable err
        [Array] $Languages = foreach ($Directory in $Directories) {
            if ($Directory.BaseName.Length -eq 5) {
                $Directory.BaseName
            }
        }
        $Files = Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" -ErrorAction SilentlyContinue -ErrorVariable +err -File #| Select-Object Name, FullName, CreationTime, LastWriteTime, Attributes
        foreach ($File in $Files) {
            $FilesCache[$Domain][$($File.BaseName)] = [ordered] @{
                Name           = $File.BaseName
                FullName       = $File.FullName
                IsReadOnly     = $File.IsReadOnly
                CreationTime   = $File.CreationTime
                LastAccessTime = $File.LastAccessTime
                LastWriteTime  = $File.LastWriteTime
                IsConsistent   = $false
            }
            foreach ($Language in $Languages) {
                $FilesCache[$Domain][$($File.BaseName)][$Language] = $false
            }
            if ($Signature) {
                $DigitalSignature = Get-AuthenticodeSignature -FilePath $File.FullName
                $FilesCache[$Domain][$($File.BaseName)]['SignatureStatus'] = $DigitalSignature.Status
                $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject
                $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer
                $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber
                $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore
                $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter
                $FilesCache[$Domain][$($File.BaseName)]['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint
                $FilesCache[$Domain][$($File.BaseName)]['IsOSBinary'] = $DigitalSignature.IsOSBinary
            }
        }
        foreach ($Directory in $Directories) {
            $FilesLanguage = Get-ChildItem -Path $Directory.FullName -ErrorAction SilentlyContinue -ErrorVariable +err
            foreach ($FileLanguage in $FilesLanguage) {
                if ($FileLanguage.Extension -eq '.adml') {
                    if ($FilesCache[$Domain][$FileLanguage.BaseName]) {
                        $FilesCache[$Domain][$FileLanguage.BaseName][$Directory.Name] = $true
                    } else {
                        #Write-Warning "Get-GPOZaurrFilesPolicyDefinition - File $($FileLanguage.FullName) doesn't have a match."
                        $Output.FilesToDelete.Add(
                            [PSCustomobject] @{
                                Name           = $FileLanguage.BaseName
                                FullName       = $FileLanguage.FullName
                                IsReadOnly     = $FileLanguage.IsReadOnly
                                CreationTime   = $FileLanguage.CreationTime
                                LastAccessTime = $FileLanguage.LastAccessTime
                                LastWriteTime  = $FileLanguage.LastWriteTime
                            }
                        )
                    }
                } else {

                }
            }
        }

        foreach ($e in $err) {
            Write-Warning "Get-GPOZaurrLegacy - $($e.Exception.Message) ($($e.CategoryInfo.Reason))"
        }
        $ExcludeProperty = @(
            'Name', 'FullName', 'IsReadOnly', 'CreationTime', 'LastAccessTime', 'LastWriteTime', 'IsConsistent'
            'SignatureCertificateSubject', 'SignatureCertificateIssuer', 'SignatureCertificateSerialNumber', 'SignatureCertificateNotBefore'
            'SignatureCertificateNotAfter', 'SignatureCertificateThumbprint', 'SignatureStatus', 'IsOSBinary'
        )
        $Properties = Select-Properties -Objects $FilesCache[$Domain][0] -ExcludeProperty $ExcludeProperty
        $Output[$Domain] = foreach ($File in $FilesCache[$Domain].Keys) {
            $Values = foreach ($Property in $Properties) {
                $FilesCache[$Domain][$File][$Property]
            }
            if ($Values -notcontains $false) {
                $FilesCache[$Domain][$File]['IsConsistent'] = $true
            }
            [PSCustomObject] $FilesCache[$Domain][$File]
        }
    }
    $Output
}
function Get-GPOZaurrFolders {
    [cmdletBinding()]
    param(
        [ValidateSet('All', 'Netlogon', 'Sysvol')][string[]] $Type = 'All',
        [ValidateSet('All', 'NTFRS', 'Empty')][string] $FolderType = 'All',
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $AsHashTable
    )
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $Path = @(
            if ($Type -contains 'All') {
                "\\$Domain\SYSVOL\$Domain"
            }
            if ($Type -contains 'Sysvol') {
                "\\$Domain\SYSVOL\$Domain\policies"
            }
            if ($Type -contains 'NetLogon') {
                "\\$Domain\NETLOGON"
            }
        )
        # Order does matter
        $Folders = [ordered] @{
            "\\$Domain\SYSVOL\$Domain\policies\PolicyDefinitions" = @{
                Name = 'SYSVOL PolicyDefinitions'
            }
            "\\$Domain\SYSVOL\$Domain\policies"                   = @{
                Name = 'SYSVOL Policies'
            }
            "\\$Domain\SYSVOL\$Domain\scripts"                    = @{
                Name = 'NETLOGON Scripts'
            }
            "\\$Domain\SYSVOL\$Domain\StarterGPOs"                = @{
                Name = 'SYSVOL GPO Starters'
            }
            "\\$Domain\NETLOGON"                                  = @{
                Name = 'NETLOGON Scripts'
            }
            "\\$Domain\SYSVOL\$Domain\DfsrPrivate"                = @{
                Name = 'DfsrPrivate'
            }
            "\\$Domain\SYSVOL\$Domain"                            = @{
                Name = 'SYSVOL Root'
            }
        }
        $Exclusions = @{
            DfsrPrivate                = @{
                ConflictAndDeleted = $true
                Deleted            = $true
                Installing         = $true
            }
            'SYSVOL Policies'          = @{
                User    = $true
                Machine = $true
            }
            'NETLOGON Scripts'         = @{

            }
            'SYSVOL Root'              = @{

            }
            'SYSVOL GPO Starters'      = @{

            }
            'SYSVOL PolicyDefinitions' = @{

            }
        }

        Get-ChildItem -Path $Path -ErrorAction SilentlyContinue -Recurse -ErrorVariable +err -Force -Directory | ForEach-Object {
            $FileType = foreach ($Key in $Folders.Keys) {
                if ($_.FullName -like "$Key*") {
                    $Folders[$Key]
                    break
                }
            }
            $RootFolder = $Folders["$($_.FullName)"]
            if ($RootFolder) {
                $IsRootFolder = $true
            } else {
                $IsRootFolder = $false
            }

            $IsExcluded = $Exclusions["$($FileType.Name)"]["$($_.Name)"] -is [bool]
            if ($IsRootFolder -and $IsExcluded -eq $false) {
                $IsExcluded = $true
            }

            $FullFolder = Test-Path -Path "$($_.FullName)\*"
            $BrokenReplicationRoot = $_.Name -like '*_NTFRS_*'
            $BrokenReplicationChild = $_.FullName -like '*_NTFRS_*' -and $_.Name -notlike '*_NTFRS_*'
            $BrokenReplication = $_.FullName -like '*_NTFRS_*'

            $Object = [ordered] @{
                FolderType               = $FileType.Name
                FullName                 = $_.FullName
                IsEmptyFolder            = -not $FullFolder
                IsBrokenReplication      = $BrokenReplication
                IsBrokenReplicationRoot  = $BrokenReplicationRoot
                IsBrokenReplicationChild = $BrokenReplicationChild
                IsRootFolder             = $IsRootFolder
                IsExcluded               = $IsExcluded
                Name                     = $_.Name
                Root                     = $_.Root
                Parent                   = $_.Parent
                CreationTime             = $_.CreationTime
                LastWriteTime            = $_.LastWriteTime
                Attributes               = $_.Attributes
                DomainName               = $Domain
            }
            if (-not $Object.IsExcluded) {
                if ($FolderType -eq 'Empty' -and $Object.IsEmptyFolder -eq $true) {
                    if ($AsHashTable) {
                        $Object
                    } else {
                        [PSCustomObject] $Object
                    }
                } elseif ($FolderType -eq 'NTFRS' -and $Object.IsBrokenReplicationRoot -eq $true) {
                    if ($AsHashTable) {
                        $Object
                    } else {
                        [PSCustomObject] $Object
                    }
                } elseif ($FolderType -eq 'All') {
                    if ($AsHashTable) {
                        $Object
                    } else {
                        [PSCustomObject] $Object
                    }
                }
            }
        }
    }
    foreach ($e in $err) {
        Write-Warning "Get-GPOZaurrFolders - $($e.Exception.Message) ($($e.CategoryInfo.Reason))"
    }
}
function Get-GPOZaurrInheritance {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER IncludeBlockedObjects
    Include OU's with blocked inheritance. Default disabled
 
    .PARAMETER OnlyBlockedInheritance
    Show only OU's with blocked inheritance
 
    .PARAMETER IncludeExcludedObjects
    Show excluded objets. Default disabled
 
    .PARAMETER Exclusions
    Provide exclusions for OU's approved by IT. You can provide OU by canonical name or distinguishedName
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    $Objects = Get-GPOZaurrInheritance -IncludeBlockedObjects -IncludeExcludedObjects -OnlyBlockedInheritance -Exclusions $ExcludedOU
    $Objects | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [switch] $IncludeBlockedObjects,
        [switch] $OnlyBlockedInheritance,
        [switch] $IncludeExcludedObjects,
        [switch] $IncludeGroupPoliciesForBlockedObjects,
        [string[]] $Exclusions,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    Begin {
        $ExclusionsCache = @{}
        $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        foreach ($Exclusion in $Exclusions) {
            $ExclusionsCache[$Exclusion] = $true
        }
    }
    Process {
        foreach ($Domain in $ForestInformation.Domains) {
            $OrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties gpOptions, canonicalName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0]
            foreach ($OU in $OrganizationalUnits) {
                $InheritanceInformation = [Ordered] @{
                    CanonicalName      = $OU.canonicalName
                    BlockedInheritance = if ($OU.gpOptions -eq 1) { $true } else { $false }
                    Exclude            = $false
                    DomainName         = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $OU.DistinguishedName
                }
                if ($InheritanceInformation.BlockedInheritance -and $IncludeGroupPoliciesForBlockedObjects.IsPresent) {
                    $GPInheritance = Get-GPInheritance -Target $OU.distinguishedName
                    $ActiveGroupPolicies = foreach ($GPO in $GPInheritance.InheritedGpoLinks) {
                        [PSCustomObject] @{
                            OrganizationalUnit   = $OU.canonicalName
                            DisplayName          = $GPO.DisplayName
                            DomainName           = $GPO.GpoDomainName
                            LinkedDirectly       = if ($OU.DistinguishedName -eq $GPO.Target) { $true } else { $false }
                            GPOID                = $GPO.GPOID
                            Enabled              = $GPO.Enabled
                            Enforced             = $GPO.Enforced
                            Order                = $GPO.Order
                            LinkedTo             = $GPO.Target
                            OrganizationalUnitDN = $OU.DistinguishedName
                        }
                    }
                } else {
                    $ActiveGroupPolicies = $null
                }
                if ($Exclusions) {
                    if ($ExclusionsCache[$OU.canonicalName]) {
                        $InheritanceInformation['Exclude'] = $true
                    } elseif ($ExclusionsCache[$OU.DistinguishedName]) {
                        $InheritanceInformation['Exclude'] = $true
                    }
                }
                if (-not $IncludeExcludedObjects -and $InheritanceInformation['Exclude']) {
                    continue
                }
                if (-not $IncludeBlockedObjects) {
                    if ($OnlyBlockedInheritance) {
                        if ($InheritanceInformation.BlockedInheritance -eq $true) {
                            [PSCustomObject] $InheritanceInformation
                        }
                    } else {
                        [PSCustomObject] $InheritanceInformation
                    }
                } else {
                    if ($InheritanceInformation) {
                        if ($InheritanceInformation.BlockedInheritance -eq $true) {
                            $InheritanceInformation['UsersCount'] = $null
                            $InheritanceInformation['ComputersCount'] = $null
                            [Array] $InheritanceInformation['Users'] = (Get-ADUser -SearchBase $OU.DistinguishedName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0] -Filter *).SamAccountName
                            [Array] $InheritanceInformation['Computers'] = (Get-ADComputer -SearchBase $OU.DistinguishedName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0] -Filter *).SamAccountName
                            $InheritanceInformation['UsersCount'] = $InheritanceInformation['Users'].Count
                            $InheritanceInformation['ComputersCount'] = $InheritanceInformation['Computers'].Count
                        } else {
                            $InheritanceInformation['UsersCount'] = $null
                            $InheritanceInformation['ComputersCount'] = $null
                            $InheritanceInformation['Users'] = $null
                            $InheritanceInformation['Computers'] = $null
                        }
                    }
                    $InheritanceInformation['DistinguishedName'] = $OU.DistinguishedName
                    $InheritanceInformation['GroupPolicies'] = $ActiveGroupPolicies
                    if ($OnlyBlockedInheritance) {
                        if ($InheritanceInformation.BlockedInheritance -eq $true) {
                            [PSCustomObject] $InheritanceInformation
                        }
                    } else {
                        [PSCustomObject] $InheritanceInformation
                    }
                }
            }
        }
    }
}
function Get-GPOZaurrLegacyFiles {
    [cmdletbinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        Get-ChildItem -Path "\\$Domain\SYSVOL\$Domain\policies" -ErrorAction SilentlyContinue -Recurse -Include '*.adm', 'admfiles.ini' -ErrorVariable err -Force | ForEach-Object {
            [PSCustomObject] @{
                Name          = $_.Name
                FullName      = $_.FullName
                CreationTime  = $_.CreationTime
                LastWriteTime = $_.LastWriteTime
                Attributes    = $_.Attributes
                DomainName    = $Domain
                DirectoryName = $_.DirectoryName
            }
        }
        foreach ($e in $err) {
            Write-Warning "Get-GPOZaurrLegacyFiles - $($e.Exception.Message) ($($e.CategoryInfo.Reason))"
        }
    }
}
function Get-GPOZaurrLink {
    [cmdletbinding(DefaultParameterSetName = 'Linked')]
    param(
        [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,
        # site doesn't really work this way unless you give it 'CN=Configuration,DC=ad,DC=evotec,DC=xyz' as SearchBase
        [parameter(ParameterSetName = 'Filter')][string] $Filter, # "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')"
        [parameter(ParameterSetName = 'Filter')][string] $SearchBase,
        [parameter(ParameterSetName = 'Filter')][Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,

        [parameter(ParameterSetName = 'Linked')][validateset('All', 'Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string[]] $Linked,

        [parameter(ParameterSetName = 'Site')][string[]] $Site,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $Limited,

        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $SkipDuplicates,

        [parameter(ParameterSetName = 'Site')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [System.Collections.IDictionary] $GPOCache,

        [parameter(ParameterSetName = 'Site')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [alias('ForestName')][string] $Forest,

        [parameter(ParameterSetName = 'Site')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [string[]] $ExcludeDomains,

        [parameter(ParameterSetName = 'Site')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,

        [parameter(ParameterSetName = 'Site')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [parameter(ParameterSetName = 'Site')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $AsHashTable,

        [parameter(ParameterSetName = 'Site')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $Summary
    )
    Begin {
        $CacheReturnedGPOs = [ordered] @{}
        $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        if (-not $GPOCache -and -not $Limited) {
            $GPOCache = @{ }
            # While initially we used $ForestInformation.Domains but the thing is GPOs can be linked to other domains so we need to get them all so we can use cache of it later on even if we're processing just one domain
            # That's why we use $ForestInformation.Forest.Domains instead
            foreach ($Domain in $ForestInformation.Forest.Domains) {
                Write-Verbose "Get-GPOZaurrLink - Building GPO cache for domain $Domain"
                if ($ForestInformation['QueryServers'][$Domain]) {
                    $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                    Get-GPO -All -DomainName $Domain -Server $QueryServer | ForEach-Object {
                        $GPOCache["$Domain$($_.ID.Guid)"] = $_
                    }
                } else {
                    Write-Warning -Message "Get-GPOZaurrLink - Couldn't get query server for $Domain. Skipped."
                }
            }
        }
    }
    Process {
        $getGPOLoopSplat = @{
            Linked            = $Linked
            ForestInformation = $ForestInformation
            SearchScope       = $SearchScope
            SearchBase        = $SearchBase
            ADObject          = $ADObject
            Filter            = $Filter
            SkipDuplicates    = $SkipDuplicates
            Site              = $Site
        }
        Remove-EmptyValue -Hashtable $getGPOLoopSplat -Recursive
        $getGPOLoopSplat['CacheReturnedGPOs'] = $CacheReturnedGPOs
        if ($AsHashTable -or $Summary) {
            $HashTable = [ordered] @{}
            $SummaryHashtable = [ordered] @{}
            $Links = Get-GPOZaurrLinkLoop @getGPOLoopSplat
            foreach ($Link in $Links) {
                $Key = -join ($Link.DomainName, $Link.GUID)
                if (-not $HashTable[$Key]) {
                    $HashTable[$Key] = [System.Collections.Generic.List[PSCustomObject]]::new()
                }
                $HashTable[$Key].Add($Link)
            }
            foreach ($Key in $HashTable.Keys) {
                [Array] $Link = $HashTable[$Key]
                $EnabledLinks = $Link.Enabled.Where( { $_ -eq $true }, 'split')

                $LinkedRoot = $false
                $LinkedRootPlaces = [System.Collections.Generic.List[string]]::new()
                $LinkedSite = $false
                $LinkedSitePlaces = [System.Collections.Generic.List[string]]::new()
                $LinkedOU = $false
                $LinkedCrossDomain = $false
                $LinkedCrossDomainPlaces = [System.Collections.Generic.List[string]]::new()
                foreach ($InternalLink in $Link) {
                    if ($InternalLink.ObjectClass -eq 'domainDNS') {
                        $LinkedRoot = $true
                        $LinkedRootPlaces.Add($InternalLink.DistinguishedName)
                    } elseif ($InternalLink.ObjectClass -eq 'site') {
                        $LinkedSite = $true
                        $LinkedSitePlaces.Add($InternalLink.Name)
                    } else {
                        $LinkedOU = $true
                    }
                    $CN = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $InternalLink.distinguishedName
                    $GPOCN = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $InternalLink.GPODomainDistinguishedName
                    if ($CN -ne $GPOCN) {
                        $LinkedCrossDomain = $true
                        $LinkedCrossDomainPlaces.Add($InternalLink.DistinguishedName)
                    }
                }
                if ($EnabledLinks[0].Count -gt 0) {
                    $IsLinked = $true
                } else {
                    $IsLinked = $false
                }
                $SummaryLink = [PSCustomObject] @{
                    DisplayName             = $Link[0].DisplayName
                    DomainName              = $Link[0].DomainName
                    GUID                    = $Link[0].GUID
                    Linked                  = $IsLinked
                    LinksCount              = $Link.Count
                    LinksEnabledCount       = $EnabledLinks[0].Count
                    LinksDisabledCount      = $EnabledLinks[1].Count
                    LinkedCrossDomain       = $LinkedCrossDomain
                    LinkedRoot              = $LinkedRoot
                    LinkedSite              = $LinkedSite
                    LinkedOU                = $LinkedOU
                    LinkedSitePlaces        = $LinkedSitePlaces
                    LinkedRootPlaces        = $LinkedRootPlaces
                    LinkedCrossDomainPlaces = $LinkedCrossDomainPlaces
                    Links                   = $Link.DistinguishedName
                    LinksObjects            = $Link
                }
                $SummaryHashtable[$Key] = $SummaryLink
            }
            if ($AsHashTable -and $Summary) {
                $SummaryHashtable
            } elseif ($AsHashTable) {
                $HashTable
            } elseif ($Summary) {
                $SummaryHashtable.Values
            }
        } else {
            Get-GPOZaurrLinkLoop @getGPOLoopSplat
        }
    }
    End {

    }
}
function Get-GPOZaurrLinkSummary {
    [cmdletBinding()]
    param(
        [ValidateSet('All', 'MultipleLinks', 'OneLink', 'LinksSummary')][string[]] $Report = 'All',
        [switch] $UnlimitedProperties,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $HighestCount = 0 # to keep number of depth
    $CacheSummaryLinks = [ordered] @{} # cache

    # Get all links
    $Links = Get-GPOZaurrLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Linked Root, DomainControllers, OrganizationalUnit
    foreach ($Link in $Links) {
        if (-not $CacheSummaryLinks["$($Link.DomainName)$($Link.Guid)"]) {
            $CacheSummaryLinks["$($Link.DomainName)$($Link.Guid)"] = [System.Collections.Generic.List[System.Object]]::new()
        }
        $CacheSummaryLinks["$($Link.DomainName)$($Link.Guid)"].Add($Link)
    }

    $ReturnObject = [ordered] @{
        MultipleLinks = [System.Collections.Generic.List[System.Object]]::new()
        OneLink       = [System.Collections.Generic.List[System.Object]]::new()
        LinksSummary  = [System.Collections.Generic.List[System.Object]]::new()
    }

    foreach ($Key in $CacheSummaryLinks.Keys) {
        $GPOs = $CacheSummaryLinks[$Key]

        [Array] $LinkingSummary = foreach ($GPO in $GPOs) {
            $SplitttedOU = ($GPO.DistinguishedName -split ',')
            [Array] $Clean = foreach ($_ in $SplitttedOU) {
                if ($_ -notlike 'DC=*') { $_ -replace 'OU=' }
            }
            if ($Clean.Count -gt $HighestCount) {
                $HighestCount = $Clean.Count
            }
            if ($Clean) {
                $Test = [ordered] @{
                    DisplayName = $GPO.DisplayName
                    Guid        = $GPO.Guid
                    DomainName  = $GPO.DomainName
                    Level0      = ConvertFrom-DistinguishedName -DistinguishedName $GPO.DistinguishedName -ToDomainCN
                }
                for ($i = 1; $i -le 10; $i++) {
                    $Test["Level$i"] = $Clean[ - $i]
                }
                [PSCustomobject] $Test
            } else {
                $Test = [ordered] @{
                    DisplayName = $GPO.DisplayName
                    Guid        = $GPO.Guid
                    DomainName  = $GPO.DomainName
                    Level0      = $GPO.CanonicalName
                }
                for ($i = 1; $i -le 10; $i++) {
                    $Test["Level$i"] = $null
                }
                [PSCustomobject] $Test
            }
        }
        if ($Report -contains 'MultipleLinks' -or $Report -contains 'All') {
            foreach ($Link in $LinkingSummary) {
                $ReturnObject.MultipleLinks.Add($Link)
            }
            #continue
        }
        if ($Report -eq 'OneLink' -or $Report -contains 'All') {
            $List = [ordered] @{
                DisplayName = $GPOs[0].DisplayName
                Guid        = $GPOs[0].Guid
                DomainName  = $GPOs[0].DomainName
                LinksCount  = $GPOs.Count
            }
            for ($i = 0; $i -le 10; $i++) {
                $List["Level$i"] = ($LinkingSummary."Level$i" | Select-Object -Unique).Count
                $List["Level$($i)List"] = ($LinkingSummary."Level$i" | Select-Object -Unique)
            }
            $List.LinksDistinguishedName = $GPOs.DistinguishedName          # = Computers, OU = ITR02, DC = ad, DC = evotec, DC = xyz
            $List.LinksCanonicalName = $GPOs.CanonicalName

            $List.Owner = $GPOs[0].Owner                      #: EVOTEC\Domain Admins
            $List.GpoStatus = $GPOs[0].GpoStatus                  #: AllSettingsEnabled
            $List.Description = $GPOs[0].Description                #:
            $List.CreationTime = $GPOs[0].CreationTime               #: 16.12.2019 21:25:32
            $List.ModificationTime = $GPOs[0].ModificationTime           #: 30.05.2020 19:12:58
            $List.GPODomainDistinguishedName = $GPOs[0].GPODomainDistinguishedName #: DC = ad, DC = evotec, DC = xyz
            $List.GPODistinguishedName = $GPOs[0].GPODistinguishedName       #: cn = { AA782787 - 002B-4B8C-886F-05873F2DC0CA }, cn = policies, cn = system, DC = ad, DC = evotec, DC = xy

            $ReturnObject.OneLink.Add( [PSCustomObject] $List)
        }
        if ($Report -eq 'LinksSummary' -or $Report -contains 'All') {
            $Output = [PSCustomObject] @{
                DisplayName                = $GPOs[0].DisplayName                #: COMPUTERS | LAPS
                Guid                       = $GPOs[0].Guid                       #: AA782787 - 002B-4B8C-886F-05873F2DC0CA
                DomainName                 = $GPOs[0].DomainName                 #: ad.evotec.xyz
                LinksCount                 = $GPOs.Count
                LinksDistinguishedName     = $GPOs.DistinguishedName          # = Computers, OU = ITR02, DC = ad, DC = evotec, DC = xyz
                LinksCanonicalName         = $GPOs.CanonicalName              #: ad.evotec.xyz / ITR02 / Computers
                Owner                      = $GPOs[0].Owner                      #: EVOTEC\Domain Admins
                GpoStatus                  = $GPOs[0].GpoStatus                  #: AllSettingsEnabled
                Description                = $GPOs[0].Description                #:
                CreationTime               = $GPOs[0].CreationTime               #: 16.12.2019 21:25:32
                ModificationTime           = $GPOs[0].ModificationTime           #: 30.05.2020 19:12:58
                GPODomainDistinguishedName = $GPOs[0].GPODomainDistinguishedName #: DC = ad, DC = evotec, DC = xyz
                GPODistinguishedName       = $GPOs[0].GPODistinguishedName       #: cn = { AA782787 - 002B-4B8C-886F-05873F2DC0CA }, cn = policies, cn = system, DC = ad, DC = evotec, DC = xy
            }
            $ReturnObject.LinksSummary.Add($Output)
        }
    }
    # Processing output
    if (-not $UnlimitedProperties) {
        if ($Report -contains 'MultipleLinks' -or $Report -contains 'All') {
            $Properties = @(
                'DisplayName'
                'DomainName'
                'GUID'
                for ($i = 0; $i -le $HighestCount; $i++) {
                    "Level$i"
                }
                #'Owner'
                #'GpoStatus'
                #'Description'
                #'CreationTime'
                #'ModificationTime'
                #'GPODomainDistinguishedName'
                #'GPODistinguishedName'
            )
            $ReturnObject.MultipleLinks = $ReturnObject.MultipleLinks | Select-Object -Property $Properties
        }
        if ($Report -contains 'OneLink' -or $Report -contains 'All') {
            $Properties = @(
                'DisplayName'
                'DomainName'
                'GUID'
                for ($i = 0; $i -le $HighestCount; $i++) {
                    "Level$i"
                    "Level$($i)List"
                }
                'LinksDistinguishedName'
                'LinksCanonicalName'
                'Owner'
                'GpoStatus'
                'Description'
                'CreationTime'
                'ModificationTime'
                'GPODomainDistinguishedName'
                'GPODistinguishedName'
            )
            $ReturnObject.OneLink = $ReturnObject.OneLink | Select-Object -Property $Properties
        }
        #if ($Report -contains 'LinksSummary' -or $Report -contains 'All') {
        # Not needed because there's no dynamic properties, but if there would be we need to uncomment and fix it
        #}
    }
    if ($Report.Count -eq 1 -and $Report -notcontains 'All') {
        $ReturnObject["$Report"]
    } else {
        $ReturnObject
    }
}
function Get-GPOZaurrNetLogon {
    [cmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [parameter(ParameterSetName = 'OwnerOnly')][switch] $OwnerOnly,
        [parameter(ParameterSetName = 'SkipOwner')][switch] $SkipOwner,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $FilesAll = foreach ($Domain in $ForestInformation.Domains) {
        $Path = -join ("\\", $Domain, '\Netlogon')
        $PathOnSysvol = -join ("\\", $Domain, "\SYSVOL\", $Domain, "\Scripts")
        [Array] $Files = Get-ChildItem -LiteralPath $Path -Recurse -Force -ErrorVariable Err -ErrorAction SilentlyContinue
        foreach ($e in $err) {
            Write-Warning "Get-GPOZaurrNetLogon - Listing file failed with error $($e.Exception.Message) ($($e.CategoryInfo.Reason))"
        }
        $Count = 0
        foreach ($File in $Files) {
            $Count++
            Write-Verbose "GPOZaurrNetLogon - Processing [$($Domain)]($Count/$($Files.Count)) $($File.FullName)"
            try {
                $ACL = Get-Acl -Path $File.FullName -ErrorAction Stop
            } catch {
                Write-Warning "Get-GPOZaurrNetLogon - ACL reading failed for $($File.FullName) with error $($_.Exception.Message) ($($_.CategoryInfo.Reason))"
            }
            if ($ACL.Owner) {
                $IdentityOwner = Convert-Identity -Identity $ACL.Owner -Verbose:$false
                $IdentityOwnerAdvanced = Get-WinADObject -Identity $ACL.Owner -Cache -Verbose:$false
            } else {
                $IdentityOwner = [PSCustomObject] @{ Name = ''; SID = ''; Type = 'Unknown' }
                $IdentityOwnerAdvanced = [PSCustomObject] @{ ObjectClass = '' }
            }
            if (-not $OwnerOnly) {
                if (-not $SkipOwner) {
                    if ($IdentityOwner.SID -eq "S-1-5-32-544") {
                        $Status = 'OK'
                    } else {
                        $Status = 'Replace owner required'
                    }
                    [PSCustomObject] @{
                        FullName             = $File.FullName
                        Status               = $Status
                        DomainName           = $Domain
                        Extension            = $File.Extension
                        CreationTime         = $File.CreationTime
                        LastAccessTime       = $File.LastAccessTime
                        LastWriteTime        = $File.LastWriteTime
                        Attributes           = $File.Attributes
                        AccessControlType    = 'Allow' # : Allow
                        Principal            = $IdentityOwner.Name         # : BUILTIN\Administrators
                        PrincipalSid         = $IdentityOwner.SID
                        PrincipalType        = $IdentityOwner.Type
                        PrincipalObjectClass = $IdentityOwnerAdvanced.ObjectClass
                        FileSystemRights     = 'Owner'  # : FullControl
                        IsInherited          = $false
                        FullNameOnSysVol     = $File.FullName.Replace($Path, $PathOnSysvol)
                    }
                }
                $FilePermission = Get-FilePermissions -Path $File.FullName -ACLS $ACL -Verbose:$false
                foreach ($Perm in $FilePermission) {
                    $Identity = Convert-Identity -Identity $Perm.Principal -Verbose:$false
                    $AdvancedIdentity = Get-WinADObject -Identity $Perm.Principal -Cache -Verbose:$false
                    $Status = 'Not assesed'
                    if ($Perm.FileSystemRights -eq [System.Security.AccessControl.FileSystemRights]::FullControl) {
                        if ($Identity.Type -eq 'WellKnownAdministrative') {
                            $Status = 'OK'
                        } else {
                            if ($AdvancedIdentity.ObjectClass -in 'user', 'computer') {
                                $Status = 'Removal permission required'
                            } else {
                                $Status = 'Review permission required'
                            }
                        }
                    } elseif ($Perm.FileSystemRights -like "*Modify*") {
                        if ($AdvancedIdentity.ObjectClass -in 'user', 'computer') {
                            $Status = 'Removal permission required'
                        } else {
                            $Status = 'Review permission required'
                        }
                    } elseif ($Perm.FileSystemRights -like "*Write*") {
                        if ($AdvancedIdentity.ObjectClass -in 'user', 'computer') {
                            $Status = 'Removal permission required'
                        } else {
                            $Status = 'Review permission required'
                        }
                    }
                    if ($Identity.Type -eq 'Unknown') {
                        $Status = 'Removal permission required'
                    }
                    [PSCustomObject] @{
                        FullName             = $File.FullName
                        Status               = $Status
                        DomainName           = $Domain
                        Extension            = $File.Extension
                        CreationTime         = $File.CreationTime
                        LastAccessTime       = $File.LastAccessTime
                        LastWriteTime        = $File.LastWriteTime
                        Attributes           = $File.Attributes
                        AccessControlType    = $Perm.AccessControlType # : Allow
                        Principal            = $Identity.Name         # : BUILTIN\Administrators
                        PrincipalSid         = $Identity.SID
                        PrincipalType        = $Identity.Type
                        PrincipalObjectClass = $AdvancedIdentity.ObjectClass
                        FileSystemRights     = $Perm.FileSystemRights  # : FullControl
                        IsInherited          = $Perm.IsInherited       # : True
                        FullNameOnSysVol     = $File.FullName.Replace($Path, $PathOnSysvol)
                    }

                }
            } else {
                if ($IdentityOwner.SID -eq "S-1-5-32-544") {
                    $Status = 'OK'
                } else {
                    $Status = 'Replace owner required'
                }
                [PSCustomObject] @{
                    FullName         = $File.FullName
                    Status           = $Status
                    DomainName       = $Domain
                    Extension        = $File.Extension
                    CreationTime     = $File.CreationTime
                    LastAccessTime   = $File.LastAccessTime
                    LastWriteTime    = $File.LastWriteTime
                    Attributes       = $File.Attributes
                    Owner            = $IdentityOwner.Name
                    OwnerSid         = $IdentityOwner.SID
                    OwnerType        = $IdentityOwner.Type
                    FullNameOnSysVol = $File.FullName.Replace($Path, $PathOnSysvol)
                }
            }
        }
    }
    $FilesAll
}

function Get-GPOZaurrOrganizationalUnit {
    [CmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [ValidateSet('OK', 'Unlink', 'Delete')][string[]] $Option,
        [alias('ExcludeOU', 'Exclusions')][string[]] $ExcludeOrganizationalUnit
    )
    $CachedOu = [ordered] @{}
    $CachedGPO = [ordered] @{}

    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $DefaultFolders = Get-WellKnownFolders -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $GroupPolicies = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($GPO in $GroupPolicies) {
        $CachedGPO[$GPO.GPODistinguishedName] = $GPO
    }
    foreach ($Domain in $ForestInformation.Domains) {
        Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain)"
        $CountTop = 0
        $TopOrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties LinkedGroupPolicyObjects, DistinguishedName, ntSecurityDescriptor -Server $ForestInformation['QueryServers'][$Domain]['hostname'][0] -SearchScope OneLevel
        foreach ($TopOU in $TopOrganizationalUnits) {
            $CountTop++
            Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain) / $($TOPOU.DistinguishedName) [$CountTop/$($TopOrganizationalUnits.Count)]"
            # cache top ou
            if ($TopOU.LinkedGroupPolicyObjects) {
                $LinkedGPOs = $CachedGPO[$TopOU.LinkedGroupPolicyObjects]
            } else {
                $LinkedGPOs = $null
            }
            $CachedOu[$TopOU.DistinguishedName] = [ordered]@{
                'LinkedGroupPolicyObjects' = $TopOU.LinkedGroupPolicyObjects
                'LinkedGroupPolicy'        = $LinkedGPOs
                'Objects'                  = [ordered] @{}
                'ObjectsClasses'           = [ordered] @{}
                'ObjectsCountDirect'       = 0
                'ObjectsCountIndirect'     = 0
                'ObjectsCountTotal'        = 0
                'Level'                    = 'Top'
                'RootLevel'                = $TopOU.Name
                'Domain'                   = $Domain
            }

            # cache children OUs
            $OUs = Get-ADOrganizationalUnit -SearchScope Subtree -SearchBase $TopOU.DistinguishedName -Server $ForestInformation['QueryServers'][$Domain]['hostname'][0] -Properties LinkedGroupPolicyObjects, DistinguishedName -Filter *
            Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain) / $($TOPOU.DistinguishedName) [$CountTop/$($TopOrganizationalUnits.Count)], found $($OUs.Count) OU's to process."
            foreach ($OU in $OUs) {
                if (-not $CachedOu[$OU.DistinguishedName]) {
                    if ($OU.LinkedGroupPolicyObjects) {
                        $LinkedGPOs = $CachedGPO[$OU.LinkedGroupPolicyObjects]
                    } else {
                        $LinkedGPOs = $null
                    }
                    $CachedOu[$OU.DistinguishedName] = [ordered]@{
                        'LinkedGroupPolicyObjects' = $OU.LinkedGroupPolicyObjects
                        'LinkedGroupPolicy'        = $LinkedGPOs
                        'Objects'                  = [ordered] @{}
                        'ObjectsClasses'           = [ordered] @{}
                        'ObjectsCountDirect'       = 0
                        'ObjectsCountIndirect'     = 0
                        'ObjectsCountTotal'        = 0
                        'Level'                    = 'Child'
                        'RootLevel'                = $TopOU.Name
                        'Domain'                   = $Domain
                    }
                }
            }
            # Find all objects in those OUs
            $ObjectsInOu = Get-ADObject -LDAPFilter "(|(ObjectClass=user)(ObjectClass=contact)(ObjectClass=computer)(ObjectClass=group)(objectClass=inetOrgPerson))" -SearchBase $TopOU.distinguishedName -Server $ForestInformation['QueryServers'][$Domain]['hostname'][0]
            Write-Verbose "Get-GPOZaurrOrganizationalUnit - Processing $($Domain) / $($TOPOU.DistinguishedName) [$CountTop/$($TopOrganizationalUnits.Count)], found $($ObjectsInOu.Count) objects to process."
            foreach ($Object in $ObjectsInOu) {
                $Place = ConvertFrom-DistinguishedName -ToOrganizationalUnit -DistinguishedName $Object.DistinguishedName
                $AllOUs = ConvertFrom-DistinguishedName -ToMultipleOrganizationalUnit -IncludeParent -DistinguishedName $Place
                foreach ($OU in $AllOUs) {
                    if ($OU -eq $Place) {
                        $CachedOu[$OU]['Objects'][$Object.DistinguishedName] = $Object
                        $CachedOu[$OU]['ObjectsClasses'][$Object.ObjectClass] = ''
                        $CachedOu[$OU]['ObjectsCountDirect']++
                    } else {
                        $CachedOu[$OU]['ObjectsClasses'][$Object.ObjectClass] = ''
                        $CachedOu[$OU]['ObjectsCountIndirect']++
                    }
                    $CachedOu[$OU]['ObjectsCountTotal']++
                }
            }
        }
    }
    foreach ($OU in $CachedOu.Keys) {
        $ObjectClasses = [string[]] $CachedOu[$OU]['ObjectsClasses'].Keys

        if ($CachedOu[$OU]['ObjectsCountTotal'] -eq 0 -and $CachedOu[$OU]['LinkedGroupPolicyObjects'].Count -gt 0) {
            $Status = "Unlink GPO", 'Delete OU'
        } elseif ($CachedOu[$OU]['ObjectsCountTotal'] -eq 0 -and $CachedOu[$OU]['LinkedGroupPolicyObjects'].Count -eq 0) {
            $Status = 'Delete OU'
        } elseif ($CachedOU[$Ou]['ObjectsCountTotal'] -gt 0 -and $CachedOu[$OU]['LinkedGroupPolicyObjects'].Count -gt 0 -and $ObjectClasses -notcontains 'User' -and $ObjectClasses -notcontains 'Computer' ) {
            $Status = "Unlink GPO"
        } else {
            $Status = 'OK'
        }

        if ($Option) {
            $Found = $false
            if ($Option -contains 'Ok' -and $Status -contains 'OK') {
                $Found = $true
            } elseif ($Option -contains 'Unlink' -and $Status -contains 'Unlink GPO') {
                $Found = $true
            } elseif ($Option -contains 'Delete' -and $Status -contains 'Delete OU') {
                $Found = $true
            }
            if ($ExcludeOrganizationalUnit) {
                foreach ($ExcludedOU in $ExcludeOrganizationalUnit) {
                    if ($OU -like $ExcludedOU) {
                        $Found = $false
                        break
                    }
                }
            }
            foreach ($Exclude in $DefaultFolders) {
                if ($OU -eq "$Exclude") {
                    $Found = $false
                    break
                }
            }
            if (-not $Found) {
                continue
            }
        } else {
            if ($ExcludeOrganizationalUnit) {
                foreach ($ExcludedOU in $ExcludeOrganizationalUnit) {
                    if ($OU -like $ExcludedOU) {
                        $Status = 'Excluded'
                        break
                    }
                }
            }
            foreach ($Exclude in $DefaultFolders) {
                if ($OU -eq "$Exclude") {
                    $Status = 'Excluded, Default OU'
                    break
                }
            }
        }

        [PSCustomObject] @{
            Organizationalunit  = $OU
            Level               = $CachedOu[$OU]['Level']
            RootLevel           = $CachedOu[$OU]['RootLevel']
            DomainName          = $CachedOu[$OU]['Domain']
            Status              = $Status
            GPOCount            = $CachedOu[$OU]['LinkedGroupPolicyObjects'].Count
            ObjectCountDirect   = $CachedOu[$OU]['ObjectsCountDirect']
            ObjectCountIndirect = $CachedOu[$OU]['ObjectsCountIndirect']
            ObjectCountTotal    = $CachedOu[$OU]['ObjectsCountTotal']
            ObjectClasses       = $ObjectClasses
            GPONames            = $CachedOu[$OU]['LinkedGroupPolicy'].DisplayName
            Objects             = $CachedOu[$OU]['Objects'].Values.Name
            GPO                 = $CachedOu[$OU]['LinkedGroupPolicy']
        }
    }
}
function Get-GPOZaurrOwner {
    <#
    .SYNOPSIS
    Gets owners of GPOs from Active Directory and SYSVOL
 
    .DESCRIPTION
    Gets owners of GPOs from Active Directory and SYSVOL
 
    .PARAMETER GPOName
    Name of GPO. By default all GPOs are returned
 
    .PARAMETER GPOGuid
    GUID of GPO. By default all GPOs are returned
 
    .PARAMETER IncludeSysvol
    Includes Owner from SYSVOL as well
 
    .PARAMETER SkipBroken
    Doesn't display GPOs that have no SYSVOL content (orphaned GPOs)
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER ADAdministrativeGroups
    Ability to provide AD Administrative Groups from another command to speed up processing
 
    .PARAMETER ApprovedOwner
    Ability to provide different owner (non administrative that still is approved for use)
 
    .EXAMPLE
    Get-GPOZaurrOwner -Verbose -IncludeSysvol
 
    .EXAMPLE
    Get-GPOZaurrOwner -Verbose -IncludeSysvol -SkipBroken
 
    .NOTES
    General notes
    #>

    [cmdletbinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(ParameterSetName = 'GPOName')][string] $GPOName,
        [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid,

        [switch] $IncludeSysvol,
        [switch] $SkipBroken,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [System.Collections.IDictionary] $ADAdministrativeGroups,

        [alias('Exclusion', 'Exclusions')][string[]] $ApprovedOwner
    )
    Begin {
        $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        if (-not $ADAdministrativeGroups) {
            $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        }
        $Count = 0
    }
    Process {
        $getGPOZaurrADSplat = @{
            Forest                    = $Forest
            IncludeDomains            = $IncludeDomains
            ExcludeDomains            = $ExcludeDomains
            ExtendedForestInformation = $ForestInformation
        }
        if ($GPOName) {
            $getGPOZaurrADSplat['GPOName'] = $GPOName
        } elseif ($GPOGuid) {
            $getGPOZaurrADSplat['GPOGUID'] = $GPOGuid
        }
        $Objects = Get-GPOZaurrAD @getGPOZaurrADSplat
        foreach ($_ in $Objects) {
            $Count++
            Write-Verbose "Get-GPOZaurrOwner - Processing GPO [$Count/$($Objects.Count)]: $($_.DisplayName) from domain: $($_.DomainName)"
            $ACL = Get-ADACLOwner -ADObject $_.GPODistinguishedName -Resolve -Verbose:$false
            $Object = [ordered] @{
                DisplayName = $_.DisplayName
                DomainName  = $_.DomainName
                GUID        = $_.GUID
                Status      = [System.Collections.Generic.List[string]]::new()
                Owner       = $ACL.OwnerName
                OwnerSid    = $ACL.OwnerSid
                OwnerType   = $ACL.OwnerType
            }
            if ($IncludeSysvol) {
                $FileOwner = Get-FileOwner -JustPath -Path $_.Path -Resolve -Verbose:$false
                $Object['SysvolOwner'] = $FileOwner.OwnerName
                $Object['SysvolSid'] = $FileOwner.OwnerSid
                $Object['SysvolType'] = $FileOwner.OwnerType
                $Object['SysvolPath'] = $_.Path
                $Object['IsOwnerConsistent'] = if ($ACL.OwnerName -eq $FileOwner.OwnerName) { $true } else { $false }
                $Object['IsOwnerAdministrative'] = if ($Object['SysvolType'] -eq 'Administrative' -and $Object['OwnerType'] -eq 'Administrative') { $true } else { $false }
                if (Test-Path -LiteralPath $Object['SysvolPath']) {
                    $Object['SysvolExists'] = $true
                } else {
                    $Object['SysvolExists'] = $false
                }
            } else {
                $Object['IsOwnerAdministrative'] = if ($Object['OwnerType'] -eq 'Administrative') { $true } else { $false }
            }
            if ($Object['IsOwnerAdministrative'] -eq $true) {
                $Object['Status'].Add('Administrative')
            } else {
                $Object['Status'].Add('NotAdministrative')
            }
            if ($Object['IsOwnerConsistent']) {
                $Object['Status'].Add('Consistent')
            } else {
                $Object['Status'].Add('Inconsistent')
            }
            if ($Object['IsOwnerConsistent'] -eq $true -and $Object['IsOwnerAdministrative'] -eq $false) {
                # We want to approve only OWNER if it's consistent and not administrative, otherwise it makes no sense
                # This is mostly here to allow for use of AGPM or similar approved owner of GPOs
                foreach ($Owner in $ApprovedOwner) {
                    if ($Owner -eq $Object['Owner']) {
                        $Object['Status'].Add('Approved')
                        break
                    } elseif ($Owner -eq $Object['OwnerSid']) {
                        $Object['Status'].Add('Approved')
                        break
                    }
                }
            }
            if ($SkipBroken -and $Object['SysvolExists'] -eq $false) {
                continue
            }
            $Object['DistinguishedName'] = $_.GPODistinguishedName
            [PSCUstomObject] $Object
        }
    }
    End {

    }
}
function Get-GPOZaurrPassword {
    <#
    .SYNOPSIS
    Tries to find CPassword in Group Policies or given path and translate it to readable value
 
    .DESCRIPTION
    Tries to find CPassword in Group Policies or given path and translate it to readable value
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER GPOPath
    Path where Group Policy content is located or where backup is located
 
    .EXAMPLE
    Get-GPOZaurrPassword -GPOPath 'C:\Users\przemyslaw.klys\Desktop\GPOExport_2020.10.12'
 
    .EXAMPLE
    Get-GPOZaurrPassword
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string[]] $GPOPath
    )
    if ($GPOPath) {
        foreach ($Path in $GPOPath) {
            $Items = Get-ChildItem -LiteralPath $Path -Recurse -Filter *.xml -ErrorAction SilentlyContinue -ErrorVariable err
            $Output = foreach ($XMLFileName in $Items) {
                $Password = Find-GPOPassword -Path $XMLFileName.FullName
                if ($Password) {
                    if ($XMLFileName.FullName -match '{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}}') {
                        $GPOGUID = $matches[0]
                    }
                    [PSCustomObject] @{
                        RootPath     = $Path
                        PasswordFile = $XMLFileName.FullName
                        GUID         = $GPOGUID
                        Password     = $Password
                    }
                }
            }
        }
    } else {
        $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        foreach ($Domain in $ForestInformation.Domains) {
            $Path = -join ('\\', $Domain, '\SYSVOL\', $Domain, '\Policies')
            #Extract the all XML files in the Folder
            $Items = Get-ChildItem -LiteralPath $Path -Recurse -Filter *.xml -ErrorAction SilentlyContinue -ErrorVariable err
            $Output = foreach ($XMLFileName in $Items) {
                $Password = Find-GPOPassword -Path $XMLFileName.FullName
                if ($Password) {
                    # match regex
                    if ($XMLFileName.FullName -match '{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}}') {
                        $GPOGUID = $matches[0]
                        $GPO = Get-GPOZaurrAD -GPOGuid $GPOGUID -IncludeDomains $Domain
                        [PSCustomObject] @{
                            DisplayName  = $GPO.DisplayName
                            DomainName   = $GPO.DomainName
                            GUID         = $GPO.GUID
                            PasswordFile = $XMLFileName.FullName
                            Password     = $Password
                            Created      = $GPO.Created
                            Modified     = $GPO.Modified
                            Description  = $GPO.Description
                        }
                    } else {
                        [PSCustomObject] @{
                            DisplayName  = ''
                            DomainName   = ''
                            GUID         = ''
                            PasswordFile = $XMLFileName.FullName
                            Password     = $Password
                            Created      = ''
                            Modified     = ''
                            Description  = ''
                        }
                    }
                }
            }
            foreach ($e in $err) {
                Write-Warning "Get-GPOZaurrPassword - $($e.Exception.Message) ($($e.CategoryInfo.Reason))"
            }
            $Output
        }
    }
}
function Get-GPOZaurrPermission {
    [cmdletBinding(DefaultParameterSetName = 'GPO' )]
    param(
        [Parameter(ParameterSetName = 'GPOName')]
        [string] $GPOName,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [alias('GUID', 'GPOID')][string] $GPOGuid,

        [string[]] $Principal,
        [validateset('DistinguishedName', 'Name', 'NetbiosName', 'Sid')][string] $PrincipalType = 'Sid',

        [validateSet('AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', 'All')][string[]] $Type = 'All',

        [switch] $SkipWellKnown,
        [switch] $SkipAdministrative,
        #[switch] $ResolveAccounts,

        [switch] $IncludeOwner,
        [Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType,
        [Microsoft.GroupPolicy.GPPermissionType[]] $ExcludePermissionType,
        [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'All',

        [string[]] $ExcludePrincipal,
        [validateset('DistinguishedName', 'Name', 'Sid')][string] $ExcludePrincipalType = 'Sid',

        [switch] $IncludeGPOObject,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [System.Collections.IDictionary] $ADAdministrativeGroups,
        [switch] $ReturnSecurityWhenNoData, # if no data return all data
        [switch] $ReturnSingleObject # forces return of single object per GPO as one for ForEach-Object processing
    )
    Begin {
        $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended
        if (-not $ADAdministrativeGroups) {
            $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        }
        if ($Type -eq 'Unknown') {
            if ($SkipAdministrative -or $SkipWellKnown) {
                Write-Warning "Get-GPOZaurrPermission - Using SkipAdministrative or SkipWellKnown while looking for Unknown doesn't make sense as only Unknown will be displayed."
            }
        }
    }
    Process {
        foreach ($Domain in $ForestInformation.Domains) {
            $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
            if ($GPOName) {
                $getGPOSplat = @{
                    Name        = $GPOName
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
                $TextForError = "Error running Get-GPO (QueryServer: $QueryServer / Domain: $Domain / Name: $GPOName) with:"
            } elseif ($GPOGuid) {
                $getGPOSplat = @{
                    Guid        = $GPOGuid
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
                $TextForError = "Error running Get-GPO (QueryServer: $QueryServer / Domain: $Domain / GUID: $GPOGuid) with:"
            } else {
                $getGPOSplat = @{
                    All         = $true
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
                $TextForError = "Error running Get-GPO (QueryServer: $QueryServer / Domain: $Domain / All: $True) with:"
            }
            Try {
                Get-GPO @getGPOSplat | ForEach-Object -Process {
                    $GPOSecurity = $_.GetSecurityInfo()
                    $getPrivPermissionSplat = @{
                        Principal                 = $Principal
                        PrincipalType             = $PrincipalType
                        PermitType                = $PermitType
                        #Accounts = $Accounts
                        Type                      = $Type
                        GPO                       = $_
                        SkipWellKnown             = $SkipWellKnown.IsPresent
                        SkipAdministrative        = $SkipAdministrative.IsPresent
                        IncludeOwner              = $IncludeOwner.IsPresent
                        IncludeGPOObject          = $IncludeGPOObject.IsPresent
                        IncludePermissionType     = $IncludePermissionType
                        ExcludePermissionType     = $ExcludePermissionType
                        ExcludePrincipal          = $ExcludePrincipal
                        ExcludePrincipalType      = $ExcludePrincipalType
                        ADAdministrativeGroups    = $ADAdministrativeGroups
                        ExtendedForestInformation = $ForestInformation
                        SecurityRights            = $GPOSecurity
                    }
                    try {
                        $Output = Get-PrivPermission @getPrivPermissionSplat
                    } catch {
                        $Output = $null
                        Write-Warning "Get-GPOZaurrPermission - Error running Get-PrivPermission: $($_.Exception.Message)"
                    }
                    if (-not $Output) {
                        if ($ReturnSecurityWhenNoData) {
                            # there is no data to return, but we need to have GPO information to process ADD permissions.
                            $ReturnObject = [PSCustomObject] @{
                                DisplayName      = $_.DisplayName # : ALL | Enable RDP
                                GUID             = $_.ID
                                DomainName       = $_.DomainName  # : ad.evotec.xyz
                                Enabled          = $_.GpoStatus
                                Description      = $_.Description
                                CreationDate     = $_.CreationTime
                                ModificationTime = $_.ModificationTime
                                GPOObject        = $_
                                GPOSecurity      = $GPOSecurity
                            }
                            $ReturnObject
                        }
                    } else {
                        if ($ReturnSingleObject) {
                            , $Output
                        } else {
                            $Output
                        }
                    }
                }
            } catch {
                Write-Warning "Get-GPOZaurrPermission - $TextForError $($_.Exception.Message)"
            }
        }
    }
    End {

    }
}
function Get-GPOZaurrPermissionAnalysis {
    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,

        [Array] $Permissions
    )
    if (-not $ADAdministrativeGroups) {
        $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }

    if (-not $Permissions) {
        $Permissions = Get-GPOZaurrPermission -IncludePermissionType GpoEditDeleteModifySecurity, GpoApply, GpoCustom, GpoRead -ReturnSecurityWhenNoData -IncludeGPOObject -ReturnSingleObject -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    foreach ($GPO in $Permissions) {
        $AdministrativeExists = [ordered] @{
            DisplayName                  = $GPO[0].DisplayName
            DomainName                   = $GPO[0].DomainName
            GUID                         = $GPO[0].GUID
            Status                       = $false
            Administrative               = $false
            AuthenticatedUsers           = $false
            System                       = $false
            Unknown                      = $false
            #StatusAuthenticatedUsers = $false
            #StatusSystem = $false
            DomainAdmins                 = $false
            EnterpriseAdmins             = $false
            AuthenticatedUsersPermission = $null
            DomainAdminsPermission       = $null
            EnterpriseAdminsPermission   = $null
            SystemPermission             = $null
            UnknownPermission            = $null
        }
        foreach ($Permission in $GPO) {
            if ($Permission.GPOSecurityPermissionItem) {
                # We are looking for administrative but we need to make sure we got correct administrative
                $AdministrativeGroup = $ADAdministrativeGroups['BySID'][$Permission.PrincipalSid]
                if ($AdministrativeGroup) {
                    $PermissionType = [Microsoft.GroupPolicy.GPPermissionType]::GpoEditDeleteModifySecurity, [Microsoft.GroupPolicy.GPPermissionType]::GpoCustom
                    if ($Permission.Permission -in $PermissionType) {
                        if ($AdministrativeGroup.SID -like '*-519') {
                            $AdministrativeExists['EnterpriseAdmins'] = $true
                            $AdministrativeExists['EnterpriseAdminsPermission'] = $Permission.Permission
                        } elseif ($AdministrativeGroup.SID -like '*-512') {
                            $AdministrativeExists['DomainAdmins'] = $true
                            $AdministrativeExists['DomainAdminsPermission'] = $Permission.Permission
                        }
                    } else {
                        if ($AdministrativeGroup.SID -like '*-519') {
                            $AdministrativeExists['EnterpriseAdminsPermission'] = $Permission.Permission
                        } elseif ($AdministrativeGroup.SID -like '*-512') {
                            $AdministrativeExists['DomainAdminsPermission'] = $Permission.Permission
                        }
                    }
                }
                if ($AdministrativeExists['DomainAdmins'] -and $AdministrativeExists['EnterpriseAdmins']) {
                    $AdministrativeExists['Administrative'] = $true
                }
                if ($Permission.PrincipalSid -eq 'S-1-5-11') {
                    $PermissionType = [Microsoft.GroupPolicy.GPPermissionType]::GpoApply, [Microsoft.GroupPolicy.GPPermissionType]::GpoRead
                    if ($Permission.Permission -in $PermissionType) {
                        $AdministrativeExists['AuthenticatedUsers'] = $true
                    }
                    $AdministrativeExists['AuthenticatedUsersPermission'] = $Permission.Permission
                }
                if ($Permission.PrincipalSid -eq 'S-1-5-18') {
                    $PermissionType = [Microsoft.GroupPolicy.GPPermissionType]::GpoEditDeleteModifySecurity
                    if ($Permission.Permission -in $PermissionType) {
                        $AdministrativeExists['System'] = $true
                    }
                    $AdministrativeExists['SystemPermission'] = $Permission.Permission
                }
                if ($Permission.PrincipalSidType -eq 'Unknown') {
                    $AdministrativeExists['Unknown'] = $true
                    $AdministrativeExists['UnknownPermission'] = $Permission.Permission
                }
                if ( $AdministrativeExists['System'] -eq $true -and $AdministrativeExists['AuthenticatedUsers'] -eq $true -and $AdministrativeExists['Administrative'] -eq $true -and $AdministrativeExists['Unknown'] -eq $false) {
                    $AdministrativeExists['Status'] = $true
                }
            }
        }
        [PSCustomObject] $AdministrativeExists
    }
}
function Get-GPOZaurrPermissionConsistency {
    [cmdletBinding(DefaultParameterSetName = 'Type')]
    param(
        [Parameter(ParameterSetName = 'GPOName')][string] $GPOName,
        [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid,
        [Parameter(ParameterSetName = 'Type')][validateSet('Consistent', 'Inconsistent', 'All')][string[]] $Type = 'All',
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $IncludeGPOObject,
        [switch] $VerifyInheritance
    )
    Begin {
        $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }
    Process {
        foreach ($Domain in $ForestInformation.Domains) {
            $TimeLog = Start-TimeLog
            Write-Verbose "Get-GPOZaurrPermissionConsistency - Starting process for $Domain"
            $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
            if ($GPOName) {
                $getGPOSplat = @{
                    Name        = $GPOName
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
            } elseif ($GPOGuid) {
                $getGPOSplat = @{
                    Guid        = $GPOGuid
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
            } else {
                $getGPOSplat = @{
                    All         = $true
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
            }
            $GroupPolicies = Get-GPO @getGPOSplat
            $Count = 0
            $GroupPolicies | ForEach-Object -Process {
                $GPO = $_
                $Count++
                Write-Verbose "Get-GPOZaurrPermissionConsistency - Processing [$($_.DomainName)]($Count/$($GroupPolicies.Count)) $($_.DisplayName)"
                $SysVolpath = -join ('\\', $Domain, '\sysvol\', $Domain, '\Policies\{', $GPO.ID.GUID, '}')
                if (Test-Path -LiteralPath $SysVolpath) {
                    try {
                        $IsConsistent = $GPO.IsAclConsistent()
                        $ErrorMessage = ''
                    } catch {
                        $ErrorMessage = $_.Exception.Message
                        Write-Warning "Get-GPOZaurrPermissionConsistency - Processing $($GPO.DisplayName) / $($GPO.DomainName) failed to get consistency with error: $($_.Exception.Message)."
                        $IsConsistent = 'Not available'
                    }
                } else {
                    Write-Warning "Get-GPOZaurrPermissionConsistency - Processing $($GPO.DisplayName) / $($GPO.DomainName) failed as path $SysvolPath doesn't exists!"
                    $IsConsistent = $false
                }
                if ($VerifyInheritance) {
                    if ($IsConsistent -eq $true) {
                        $FolderPermissions = Get-WinADSharePermission -Path $SysVolpath -Verbose:$false
                        if ($FolderPermissions) {
                            [Array] $NotInheritedPermissions = foreach ($File in $FolderPermissions) {
                                if ($File.Path -ne $SysVolpath -and $File.IsInherited -eq $false) {
                                    $File
                                }
                            }
                            if ($NotInheritedPermissions.Count -eq 0) {
                                $ACLConsistentInside = $true
                            } else {
                                $ACLConsistentInside = $false
                            }
                        } else {
                            $ACLConsistentInside = 'Not available'
                            $NotInheritedPermissions = $null
                        }
                    } else {
                        # Since top level permissions are inconsistent we don't even try to asses inside permissions
                        $ACLConsistentInside = $IsConsistent
                        $NotInheritedPermissions = $null
                    }
                }
                $Object = [ordered] @{
                    DisplayName   = $_.DisplayName     # : New Group Policy Object
                    DomainName    = $_.DomainName      # : ad.evotec.xyz
                    ACLConsistent = $IsConsistent
                }
                if ($VerifyInheritance) {
                    $Object['ACLConsistentInside'] = $ACLConsistentInside
                }
                $Object['Owner'] = $_.Owner           # : EVOTEC\Enterprise Admins
                $Object['Path'] = $_.Path
                $Object['SysVolPath '] = $SysvolPath
                $Object['Id '] = $_.Id              # : 8a7bc515-d7fd-4d1f-90b8-e47c15f89295
                $Object['GpoStatus'] = $_.GpoStatus       # : AllSettingsEnabled
                $Object['Description'] = $_.Description     # :
                $Object['CreationTime'] = $_.CreationTime    # : 04.03.2020 17:19:42
                $Object['ModificationTime'] = $_.ModificationTime# : 06.05.2020 10:30:36
                $Object['UserVersion'] = $_.UserVersion     # : AD Version: 0, SysVol Version: 0
                $Object['ComputerVersion'] = $_.ComputerVersion # : AD Version: 1, SysVol Version: 1
                $Object['WmiFilter'] = $_.WmiFilter       # :
                $Object['Error'] = $ErrorMessage
                if ($IncludeGPOObject) {
                    $Object['IncludeGPOObject'] = $_
                }
                if ($VerifyInheritance) {
                    $Object['ACLConsistentInsideDetails'] = $NotInheritedPermissions
                }
                if ($Type -eq 'All') {
                    [PSCustomObject] $Object
                } elseif ($Type -eq 'Inconsistent') {
                    if ($VerifyInheritance) {
                        if (-not ($IsConsistent -eq $true) -or (-not $ACLConsistentInside -eq $true)) {
                            [PSCustomObject] $Object
                        }
                    } else {
                        if (-not ($IsConsistent -eq $true)) {
                            [PSCustomObject] $Object
                        }
                    }
                } elseif ($Type -eq 'Consistent') {
                    if ($VerifyInheritance) {
                        if ($IsConsistent -eq $true -and $ACLConsistentInside -eq $true) {
                            [PSCustomObject] $Object
                        }
                    } else {
                        if ($IsConsistent -eq $true) {
                            [PSCustomObject] $Object
                        }
                    }
                }
            }
            $TimeEnd = Stop-TimeLog -Time $TimeLog -Option OneLiner
            Write-Verbose "Get-GPOZaurrPermissionConsistency - Finishing process for $Domain (Time to process: $TimeEnd)"
        }
    }
    End {

    }
}
function Get-GPOZaurrPermissionIssue {
    <#
    .SYNOPSIS
    Detects Group Policy missing Authenticated Users permission while not having higher permissions.
 
    .DESCRIPTION
    Detects Group Policy missing Authenticated Users permission while not having higher permissions.
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    $Issues = Get-GPOZaurrPermissionIssue
    $Issues | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation -Extended
    foreach ($Domain in $ForestInformation.Domains) {
        $TimeLog = Start-TimeLog
        Write-Verbose "Get-GPOZaurrPermissionIssue - Starting process for $Domain"
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $SystemsContainer = $ForestInformation['DomainsExtended'][$Domain].SystemsContainer
        if ($SystemsContainer) {
            $PoliciesSearchBase = -join ("CN=Policies,", $SystemsContainer)
            $Properties = 'DisplayName', 'Name', 'DistinguishedName', 'ObjectClass', 'WhenCreated', 'WhenChanged'
            $PoliciesInAD = Get-ADObject -SearchBase $PoliciesSearchBase -SearchScope OneLevel -Filter * -Server $QueryServer -Properties $Properties
            foreach ($Policy in $PoliciesInAD) {
                $GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName
                $GUIDFromDN = $GUIDFromDN -replace '{' -replace '}'
                $GUID = $Policy.Name -replace '{' -replace '}'
                [PSCustomObject] @{
                    DisplayName       = $Policy.DisplayName
                    DomainName        = $Domain
                    PermissionIssue   = -not ($GUID -and $GUIDFromDN)
                    ObjectClass       = $Policy.ObjectClass
                    Name              = $Policy.Name
                    DistinguishedName = $Policy.DistinguishedName
                    GUID              = $GUIDFromDN
                    WhenCreated       = $Policy.WhenCreated
                    WhenChanged       = $Policy.WhenChanged
                }
            }
        }
        $TimeEnd = Stop-TimeLog -Time $TimeLog -Option OneLiner
        Write-Verbose "Get-GPOZaurrPermissionIssue - Finishing process for $Domain (Time to process: $TimeEnd)"
    }
}
function Get-GPOZaurrPermissionRoot {
    [cmdletBinding()]
    param(
        [ValidateSet('GpoRootCreate', 'GpoRootOwner')][string[]] $IncludePermissionType,
        [ValidateSet('GpoRootCreate', 'GpoRootOwner')][string[]] $ExcludePermissionType,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $SkipNames
    )
    Begin {
        $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Extended
    }
    Process {
        foreach ($Domain in $ForestInformation.Domains) {
            $DomainDistinguishedName = $ForestInformation['DomainsExtended'][$Domain].DistinguishedName
            $QueryServer = $ForestInformation['QueryServers'][$Domain].HostName[0]
            $getADACLSplat = @{
                ADObject                       = "CN=Policies,CN=System,$DomainDistinguishedName"
                IncludeActiveDirectoryRights   = 'GenericAll', 'CreateChild', 'WriteOwner', 'WriteDACL'
                IncludeObjectTypeName          = 'All', 'Group-Policy-Container'
                IncludeInheritedObjectTypeName = 'All', 'Group-Policy-Container'
                ADRightsAsArray                = $true
                ResolveTypes                   = $true
            }
            $GPOPermissionsGlobal = Get-ADACL @getADACLSplat -Verbose:$false
            $GPOs = Get-ADObject -SearchBase "CN=Policies,CN=System,$DomainDistinguishedName" -SearchScope OneLevel -Filter * -Properties DisplayName -Server $QueryServer -Verbose:$false
            foreach ($Permission in $GPOPermissionsGlobal) {
                $CustomPermission = foreach ($_ in $Permission.ActiveDirectoryRights) {
                    if ($_ -in 'WriteDACL', 'WriteOwner', 'GenericAll' ) {
                        'GpoRootOwner'
                    }
                    if ($_ -in 'CreateChild', 'GenericAll') {
                        'GpoRootCreate'
                    }
                }
                $CustomPermission = $CustomPermission | Sort-Object -Unique
                foreach ($SinglePermission in $CustomPermission) {
                    if ($SinglePermission -in $ExcludePermissionType) {
                        continue
                    }
                    if ($IncludePermissionType.Count -gt 0 -and $SinglePermission -notin $IncludePermissionType) {
                        continue
                    }
                    $OutputEntry = [ordered] @{
                        PrincipalName        = $Permission.Principal
                        Permission           = $SinglePermission
                        PermissionType       = $Permission.AccessControlType
                        PrincipalSidType     = $Permission.PrincipalType
                        PrincipalObjectClass = $Permission.PrincipalObjectType
                        PrincipalDomainName  = $Permission.PrincipalObjectDomain
                        PrincipalSid         = $Permission.PrincipalObjectSid
                        DomainName           = $Domain
                        GPOCount             = $GPOs.Count
                    }
                    if (-not $SkipNames) {
                        $OutputEntry['GPONames'] = $GPOs.DisplayName
                    }
                    [PSCustomObject] $OutputEntry
                }
            }
        }
    }
    End {}
}
function Get-GPOZaurrPermissionSummary {
    [cmdletBinding()]
    param(
        [validateSet('AuthenticatedUsers', 'DomainComputers', 'Unknown', 'WellKnownAdministrative', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'Administrative', 'All')][string[]] $Type = 'All',
        [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'All',
        [ValidateSet('GpoApply', 'GpoEdit', 'GpoCustom', 'GpoEditDeleteModifySecurity', 'GpoRead', 'GpoOwner', 'GpoRootCreate', 'GpoRootOwner')][string[]] $IncludePermissionType,
        [ValidateSet('GpoApply', 'GpoEdit', 'GpoCustom', 'GpoEditDeleteModifySecurity', 'GpoRead', 'GpoOwner', 'GpoRootCreate', 'GpoRootOwner')][string[]] $ExcludePermissionType,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [string] $Separator
    )

    $IncludePermTypes = [System.Collections.Generic.List[Microsoft.GroupPolicy.GPPermissionType]]::new()
    $ExcludePermTypes = [System.Collections.Generic.List[Microsoft.GroupPolicy.GPPermissionType]]::new()
    $CustomPermissions = [System.Collections.Generic.List[string]]::new()
    foreach ($PermType in $IncludePermissionType) {
        if ($PermType -in 'GpoApply', 'GpoEdit', 'GPOCustom', 'GpoEditDeleteModifySecurity', 'GPORead') {
            $IncludePermTypes.Add([Microsoft.GroupPolicy.GPPermissionType]::$PermType)
        } elseif ($PermType -in 'GpoOwner') {
            $IncludeOwner = $true
        } elseif ($PermType -in 'GpoRootCreate', 'GpoRootOwner') {
            $CustomPermissions.Add($PermType)
        }
    }
    foreach ($PermType in $ExcludePermissionType) {
        if ($PermType -in 'GpoApply', 'GpoEdit', 'GPOCustom', 'GpoEditDeleteModifySecurity', 'GPORead') {
            $ExcludePermTypes.Add([Microsoft.GroupPolicy.GPPermissionType]::$PermType)
        } elseif ($PermType -in 'GpoOwner') {
            $IncludeOwner = $false
        } elseif ($PermType -in 'GpoRootCreate', 'GpoRootOwner') {
            $CustomPermissions.Add($PermType)
        }
    }

    $RootPermissions = Get-GPOZaurrPermissionRoot -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    $Permissions = Get-GPOZaurrPermission -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -IncludePermissionType $IncludePermTypes -ExcludePermissionType $ExcludePermissionType -Type $Type -PermitType $PermitType -IncludeOwner:$IncludeOwner
    $Entries = @(
        foreach ($Permission in $Permissions) {
            [PSCustomObject] @{
                PrincipalName        = $Permission.PrincipalName
                PrincipalDomainName  = $Permission.PrincipalDomainName
                Permission           = $Permission.Permission
                PermissionType       = $Permission.PermissionType
                PrincipalSid         = $Permission.PrincipalSid
                PrincipalSidType     = $Permission.PrincipalSidType
                PrincipalObjectClass = $Permission.PrincipalObjectClass
                DisplayName          = $Permission.DisplayName
                DomainName           = $Permission.DomainName
            }
        }
        foreach ($RootPermission in $RootPermissions) {
            [PSCustomObject] @{
                PrincipalName        = $RootPermission.PrincipalName
                PrincipalDomainName  = $RootPermission.PrincipalDomainName
                Permission           = $RootPermission.Permission
                PermissionType       = $RootPermission.PermissionType
                PrincipalSid         = $RootPermission.PrincipalSid
                PrincipalSidType     = $RootPermission.PrincipalSidType
                PrincipalObjectClass = $RootPermission.PrincipalObjectClass
                DisplayName          = $RootPermission.GPONames
                DomainName           = $RootPermission.DomainName
            }
        }
    )
    $PermissionsData = [ordered] @{}
    foreach ($Entry in $Entries) {
        $Key = -join ($Entry.Permission, $Entry.PrincipalName, $Entry.PrincipalDomainName)
        if (-not $PermissionsData[$Key]) {
            $PermissionsData[$Key] = [PSCustomObject] @{
                Permission          = $Entry.Permission
                PrincipalName       = $Entry.PrincipalName
                PrincipalDomainName = $Entry.PrincipalDomainName
                PrincipalSidType    = $Entry.PrincipalSidType
                DomainName          = $Entry.DomainName
                PermissionType      = $Entry.PermissionType
                GPOCOunt            = 0
                GPONames            = [System.Collections.Generic.List[string]]::new()
            }
        }
        #if ($IncludeNames) {
        $PermissionsData[$Key].GPONames.Add($Entry.DisplayName)
        #}
        $PermissionsData[$Key].GPOCOunt = $PermissionsData[$Key].GPOCOunt + ($Entry.DisplayName).Count
    }
    $PermissionsData.Values

    <#
    ($Entries | Group-Object -Property Permission, PrincipalSidType, PrincipalName, PrincipalDomainName, DomainName, PermissionType) | ForEach-Object {
        $Property = $_.Name -split ', '
        Write-Verbose "$Property - $($Property.Count)"
        if ($Property[0] -eq 'GpoOwner') {
            [PSCustomObject] @{
                Permission = $Property[0]
                PrincipalSidType = $Property[1]
                PrincipalName = $Property[2]
                PrincipalDomainName = $Property[3]
                DomainName = $Property[4]
                PermissionType = 'Allow'
                GPOCount = $_.Count
                GPONames = if ($Separator) { $_.Group.DisplayName -join $Separator } else { $_.Group.DisplayName }
            }
        } elseif ($Property.Count -eq 5) {
            [PSCustomObject] @{
                Permission = $Property[0]
                PrincipalSidType = $Property[1]
                PrincipalName = $Property[2]
                PrincipalDomainName = $Property[3]
                DomainName = $Property[4]
                PermissionType = if ($Property[5]) { $Property[5] } else { 'Owner' }
                GPOCount = $_.Count
                GPONames = if ($Separator) { $_.Group.DisplayName -join $Separator } else { $_.Group.DisplayName }
            }
        } else {
            [PSCustomObject] @{
                Permission = $Property[0]
                PrincipalSidType = $Property[1]
                PrincipalName = ''
                PrincipalDomainName = ''
                DomainName = $Property[2]
                PermissionType = if ($Property[3]) { $Property[3] } else { 'Owner' }
                GPOCount = $_.Count
                GPONames = if ($Separator) { $_.Group.DisplayName -join $Separator } else { $_.Group.DisplayName }
            }
        }
    }
    #>

}
function Get-GPOZaurrSysvolDFSR {
    <#
    .SYNOPSIS
    Gets DFSR information from the SYSVOL DFSR
 
    .DESCRIPTION
    Gets DFSR information from the SYSVOL DFSR
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExcludeDomainControllers
    Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER IncludeDomainControllers
    Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER SkipRODC
    Skip Read-Only Domain Controllers. By default all domain controllers are included.
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER SearchDFSR
    Define DFSR Share. By default it uses SYSVOL Share
 
    .EXAMPLE
    $DFSR = Get-GPOZaurrSysvolDFSR
    $DFSR | Format-Table *
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string] $SearchDFSR = 'SYSVOL Share'
    )
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        Write-Verbose "Get-GPOZaurrSysvolDFSR - Processing $Domain"
        foreach ($DC in $ForestInformation.DomainDomainControllers[$Domain]) {
            Write-Verbose "Get-GPOZaurrSysvolDFSR - Processing $Domain \ $($DC.HostName)"
            #$QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
            $DFSRConfig = Get-CimInstance -Namespace 'root\microsoftdfs' -Class 'dfsrreplicatedfolderconfig' -ComputerName $($DC.HostName) | Where-Object { $_.ReplicatedFolderName -eq $SearchDFSR }
            $DFSR = Get-CimInstance -Namespace 'root\microsoftdfs' -Class 'dfsrreplicatedfolderinfo' -ComputerName $($DC.HostName) | Where-Object { $_.ReplicatedFolderName -eq $SearchDFSR }
            if ($DFSR -and $DFSRConfig -and ($DFSR.ReplicatedFolderGuid -eq $DFSRConfig.ReplicatedFolderGuid)) {
                [PSCustomObject] @{
                    ComputerName             = $DFSR.PSComputerName
                    DomainName               = $Domain
                    ConflictPath             = $DFSRConfig.ConflictPath
                    LastConflictCleanupTime  = $DFSR.LastConflictCleanupTime
                    CurrentConflictSizeInMb  = $DFSR.CurrentConflictSizeInMb
                    MaximumConflictSizeInMb  = $DFSRConfig.ConflictSizeInMb
                    LastErrorCode            = $DFSR.LastErrorCode
                    LastErrorMessageId       = $DFSR.LastErrorMessageId
                    LastTombstoneCleanupTime = $DFSR.LastTombstoneCleanupTime
                    ReplicatedFolderGuid     = $DFSR.ReplicatedFolderGuid
                    DFSRConfig               = $DFSRConfig
                    DFSR                     = $DFSR
                }
            } else {
                Write-Warning "Get-GPOZaurrSysvolDFSR - Couldn't process $($DC.HostName). Conditions not met."
            }
        }
    }
}
function Get-GPOZaurrUpdates {
    <#
    .SYNOPSIS
    Gets the list of GPOs created or updated in the last X number of days.
 
    .DESCRIPTION
    Gets the list of GPOs created or updated in the last X number of days.
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
Ä…
    .PARAMETER DateFrom
    Provide a date from which to start the search, by default the last X days are used
 
    .PARAMETER DateTo
    Provide a date to which to end the search, by default the last X days are used
 
    .PARAMETER DateRange
    Provide a date range to search for, by default the last X days are used
 
    .PARAMETER DateProperty
    Choose a date property. It can be WhenCreated or WhenChanged or both. By default whenCreated is used for comparison purposes
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    Get-GPOZaurrUpdates -DateRange Last14Days -DateProperty WhenCreated, WhenChanged -Verbose -IncludeDomains 'ad.evotec.pl' | Format-List
 
    .EXAMPLE
    Get-GPOZaurrUpdates -DateRange Last14Days -DateProperty WhenCreated -Verbose | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'DateRange')]
    param(
        [parameter(ParameterSetName = 'Dates')]
        [parameter(ParameterSetName = 'DateRange')]
        [alias('ForestName')][string] $Forest,
        [parameter(ParameterSetName = 'Dates')]
        [parameter(ParameterSetName = 'DateRange')]
        [string[]] $ExcludeDomains,
        [parameter(ParameterSetName = 'Dates')]
        [parameter(ParameterSetName = 'DateRange')]
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [parameter(Mandatory, ParameterSetName = 'Dates')][DateTime] $DateFrom,
        [parameter(Mandatory, ParameterSetName = 'Dates')][DateTime] $DateTo,
        [parameter(Mandatory, ParameterSetName = 'DateRange')][ValidateSet('PastHour', 'CurrentHour', 'PastDay', 'CurrentDay', 'PastMonth', 'CurrentMonth', 'PastQuarter', 'CurrentQuarter', 'Last14Days', 'Last21Days', 'Last30Days', 'Last7Days', 'Last3Days', 'Last1Days')][string] $DateRange,
        [parameter(ParameterSetName = 'Dates')]
        [parameter(ParameterSetName = 'DateRange')]
        [ValidateSet('WhenCreated', 'WhenChanged')][string[]] $DateProperty = 'WhenCreated',
        [parameter(ParameterSetName = 'Dates')]
        [parameter(ParameterSetName = 'DateRange')]
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $getGPOZaurrADSplat = @{
        Forest                    = $Forest
        IncludeDomains            = $IncludeDomains
        ExcludeDomains            = $ExcludeDomains
        ExtendedForestInformation = $ExtendedForestInformation
        DateFrom                  = $DateFrom
        DateTo                    = $DateTo
        DateRange                 = $DateRange
        DateProperty              = $DateProperty
    }
    Remove-EmptyValue -Hashtable $getGPOZaurrADSplat
    # lets get all the links including sites

    if ($DateRange) {
        Write-Verbose -Message "Get-GPOZaurrUpdates - Get group policies for defined range $DateRange"
    } elseif ($DateFrom -and $DateTo) {
        Write-Verbose -Message "Get-GPOZaurrUpdates - Get group policies for defined range $DateFrom to $DateTo"
    } else {
        Write-Warning -Message "Get-GPOZaurrUpdates - No range is selected. Try again."
        return
    }
    $LinksSummaryCache = Get-GPOZaurrLink -AsHashTable -Summary -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation

    $OUCache = [ordered] @{}
    foreach ($Domain in $ForestInformation.Domains) {
        Write-Verbose -Message "Get-GPOZaurrUpdates - Getting OU's for $Domain"
        $OrganizationalUnits = Get-ADOrganizationalUnit -Filter * -Properties gpOptions, canonicalName -Server $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        $OUCache[$OrganizationalUnits.DistinguishedName] = if ($OrganizationalUnits.gpOptions -eq 1) { $true } else { $false } # blocked inheritance
    }

    $CurrentCount = 0
    Write-Verbose -Message "Get-GPOZaurrUpdates - Getting GPO information"
    [Array] $GPOs = Get-GPOZaurrAD @getGPOZaurrADSplat
    foreach ($GPO in $GPOs) {
        $CurrentCount++
        Write-Verbose -Message "Get-GPOZaurrUpdates - Processing $($GPO.DisplayName) / $($GPO.DomainName) [$CurrentCount/$($GPOs.Count)]"
        $GPOLinkData = $LinksSummaryCache["$($GPO.DomainName)$($GPO.GUID)"]
        #$GPOLinkData
        [Array] $LinksDN = if ($GPOLinkData.Links.Count -gt 0) {
            foreach ($Link in $GPOLinkData.LinksObjects) {
                If ($Link.Enabled -eq $true) {
                    $Link.DistinguishedName
                }
            }
        }
        if ($LinksDN.Count -gt 0) {
            $OrganizationalUnitsObjects = Get-ADOrganizationalUnitObject -OrganizationalUnit $LinksDN -Summary -IncludeAffectedOnly
        } else {
            # GPO is not linked
            $OrganizationalUnitsObjects = [PSCUstomObject] @{
                ObjectsTotalCount              = 0
                ObjectsBlockedInheritanceCount = 0
                ObjectsClasses                 = @()
            }
        }
        if ($GPO.Owner) {
            $Owner = Get-WinADObject -Identity $GPO.Owner -AddType
        } else {
            $Owner = [PSCustomObject] @{
                Name        = 'Unknown'
                Type        = 'Unknown'
                ObjectClass = 'Unknown'
            }
        }
        [PSCustomObject] @{
            DisplayName             = $GPO.DisplayName
            GUID                    = ConvertFrom-DistinguishedName -DistinguishedName $GPO.GPODistinguishedName
            DomainName              = $GPO.DomainName
            Owner                   = $GPO.Owner
            OwnerName               = $Owner.Name
            OwnerType               = $Owner.Type
            OwnerClass              = $Owner.ObjectClass
            LinksCount              = if ($GPOLinkData) { $GPOLinkData.LinksCount } else { 0 }
            LinksEnabledCount       = if ($GPOLinkData) { $GPOLinkData.LinksEnabledCount } else { 0 }
            AffectedCount           = $OrganizationalUnitsObjects.ObjectsTotalCount
            BlockedInheritanceCount = $OrganizationalUnitsObjects.ObjectsBlockedInheritanceCount
            AffectedClasses         = $OrganizationalUnitsObjects.ObjectsClasses.GetEnumerator().Name
            Created                 = $GPO.Created
            Changed                 = $GPO.Modified
            LinksEnabled            = $LinksDN
        }
    }
}
function Get-GPOZaurrWMI {
    <#
    .SYNOPSIS
    Get Group Policy WMI filter
 
    .DESCRIPTION
    Get Group Policy WMI filter
 
    .PARAMETER Guid
    Search for specific filter using GUID
 
    .PARAMETER Name
    Search for specific filter using Name
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER AsHashtable
    Return output as hashtable
 
    .EXAMPLE
    Get-GPOZaurrWMI -AsHashtable
 
    .EXAMPLE
    Get-GPOZaurrWMI | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    Param(
        [Guid[]] $Guid,
        [string[]] $Name,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [switch] $AsHashtable
    )
    $Dictionary = [ordered] @{}
    $wmiFilterAttr = 'msWMI-Name', 'msWMI-Parm1', 'msWMI-Parm2', 'msWMI-Author', 'msWMI-ID', 'CanonicalName', 'Created', 'Modified'
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        $Objects = @(
            if ($Name) {
                foreach ($N in $Name) {
                    try {
                        $ldapFilter = "(&(objectClass=msWMI-Som)(msWMI-Name=$N))"
                        Get-ADObject -LDAPFilter $ldapFilter -Properties $wmiFilterAttr -Server $QueryServer
                    } catch {
                        Write-Warning "Get-GPOZaurrWMI - Error processing WMI for $Domain`: $($_.Error.Exception)"
                    }
                }
            } elseif ($GUID) {
                foreach ($G in $GUID) {
                    try {
                        $ldapFilter = "(&(objectClass=msWMI-Som)(Name={$G}))"
                        Get-ADObject -LDAPFilter $ldapFilter -Properties $wmiFilterAttr -Server $QueryServer
                    } catch {
                        Write-Warning "Get-GPOZaurrWMI - Error processing WMI for $Domain`: $($_.Error.Exception)"
                    }
                }
            } else {
                try {
                    $ldapFilter = '(objectClass=msWMI-Som)'
                    Get-ADObject -LDAPFilter $ldapFilter -Properties $wmiFilterAttr -Server $QueryServer
                } catch {
                    Write-Warning "Get-GPOZaurrWMI - Error processing WMI for $Domain`: $($_.Error.Exception)"
                }
            }
        )
        foreach ($_ in $Objects) {
            $WMI = $_.'msWMI-Parm2' -split ';' #$WMI = $_.'msWMI-Parm2'.Split(';',8)
            [Array] $Data = for ($i = 0; $i -lt $WMI.length; $i += 6) {
                if ($WMI[$i + 5]) {
                    #[PSCustomObject] @{
                    # NameSpace = $WMI[$i + 5]
                    # Query = $WMI[$i + 6]
                    #}
                    -join ($WMI[$i + 5], ';' , $WMI[$i + 6])
                }
            }
            $WMIObject = [PSCustomObject] @{
                DisplayName       = $_.'msWMI-Name'
                Description       = $_.'msWMI-Parm1'
                DomainName        = $Domain
                #NameSpace = $WMI[$i + 5]
                #Query = $WMI[$i + 6]
                QueryCount        = $Data.Count
                Query             = $Data -join ','
                Author            = $_.'msWMI-Author'
                ID                = $_.'msWMI-ID'
                Created           = $_.Created
                Modified          = $_.Modified
                ObjectGUID        = $_.'ObjectGUID'
                CanonicalName     = $_.CanonicalName
                DistinguishedName = $_.'DistinguishedName'
            }
            if (-not $AsHashtable) {
                $WMIObject
            } else {
                $Dictionary[$WMIObject.ID] = $WMIObject
            }
        }

    }
    if ($AsHashtable) {
        $Dictionary
    }
}
<#
CanonicalName : ad.evotec.xyz/System/WMIPolicy/SOM/{E988C890-BDBC-4946-87B5-BF70F39F4686}
CN : {E988C890-BDBC-4946-87B5-BF70F39F4686}
Created : 08.04.2020 19:04:06
createTimeStamp : 08.04.2020 19:04:06
Deleted :
Description :
DisplayName :
DistinguishedName : CN={E988C890-BDBC-4946-87B5-BF70F39F4686},CN=SOM,CN=WMIPolicy,CN=System,DC=ad,DC=evotec,DC=xyz
dSCorePropagationData : {01.01.1601 01:00:00}
instanceType : 4
isDeleted :
LastKnownParent :
Modified : 08.04.2020 19:04:06
modifyTimeStamp : 08.04.2020 19:04:06
msWMI-Author : przemyslaw.klys@evotec.pl
msWMI-ChangeDate : 20200408170406.280000-000
msWMI-CreationDate : 20200408170406.280000-000
msWMI-ID : {E988C890-BDBC-4946-87B5-BF70F39F4686}
msWMI-Name : Virtual Machines
msWMI-Parm1 : Oh my description
msWMI-Parm2 : 1;3;10;66;WQL;root\CIMv2;SELECT * FROM Win32_ComputerSystem WHERE Model = "Virtual Machine";
Name : {E988C890-BDBC-4946-87B5-BF70F39F4686}
nTSecurityDescriptor : System.DirectoryServices.ActiveDirectorySecurity
ObjectCategory : CN=ms-WMI-Som,CN=Schema,CN=Configuration,DC=ad,DC=evotec,DC=xyz
ObjectClass : msWMI-Som
ObjectGUID : c1ee708d-7a67-46e2-b13f-d11a573d2597
ProtectedFromAccidentalDeletion : False
sDRightsEffective : 15
showInAdvancedViewOnly : True
uSNChanged : 12785589
uSNCreated : 12785589
whenChanged : 08.04.2020 19:04:06
whenCreated : 08.04.2020 19:04:06
#>

function Invoke-GPOZaurr {
    <#
    .SYNOPSIS
    Single cmdlet that provides 360 degree overview of Group Policies in Active Directory Forest.
 
    .DESCRIPTION
    Single cmdlet that provides 360 degree overview of Group Policies in Active Directory Forest with ability to pick reports and export to HTML.
 
    .PARAMETER Exclusions
    Allows to mark as excluded some Group Policies or Organizational Units depending on type.
    Can be a scriptblock or array depending on supported way by underlying report.
    Not every report support exclusions.
    Not every report support exclusions the same way.
    Exclusions should be used only if there is single report being asked for.
 
    .PARAMETER FilePath
    Path to the file where the report will be saved.
 
    .PARAMETER Type
    Type of report to be generated from a list of available reports.
 
    .PARAMETER PassThru
    Returns created objects after the report is done
 
    .PARAMETER HideHTML
    Do not auto open HTML report in default browser
 
    .PARAMETER HideSteps
    Do not show steps in report
 
    .PARAMETER ShowError
    Show errors in HTML report. Useful in case the report is being run as Scheduled Task
 
    .PARAMETER ShowWarning
    Show warnings in HTML report. Useful in case the report is being run as Scheduled Task
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER Online
    Forces report to use online resources in HTML (using CDN most of the time), by default it is run offline, and inlines all CSS/JS code.
 
    .EXAMPLE
    Invoke-GPOZaurr
 
    .EXAMPLE
    Invoke-GPOZaurr -Type GPOOrganizationalUnit -Online -FilePath $PSScriptRoot\Reports\GPOZaurrOU.html -Exclusions @(
        '*OU=Production,DC=ad,DC=evotec,DC=pl'
    )
 
    .NOTES
    General notes
    #>

    [alias('Show-GPOZaurr', 'Show-GPO')]
    [cmdletBinding()]
    param(
        [alias('ExcludeGroupPolicies', 'ExclusionsCode')][Parameter(Position = 1)][object] $Exclusions,
        [string] $FilePath,
        [Parameter(Position = 0)][string[]] $Type,
        [switch] $PassThru,
        [switch] $HideHTML,
        [switch] $HideSteps,
        [switch] $ShowError,
        [switch] $ShowWarning,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [switch] $Online
    )
    Reset-GPOZaurrStatus # This makes sure types are at it's proper status

    $Script:Reporting = [ordered] @{}
    $Script:Reporting['Version'] = Get-GitHubVersion -Cmdlet 'Invoke-GPOZaurr' -RepositoryOwner 'evotecit' -RepositoryName 'GPOZaurr'
    $Script:Reporting['Settings'] = @{
        ShowError   = $ShowError.IsPresent
        ShowWarning = $ShowWarning.IsPresent
        HideSteps   = $HideSteps.IsPresent
    }
    Write-Color '[i]', "[GPOZaurr] ", 'Version', ' [Informative] ', $Script:Reporting['Version'] -Color Yellow, DarkGray, Yellow, DarkGray, Magenta

    # Verify requested types are supported
    $Supported = [System.Collections.Generic.List[string]]::new()
    [Array] $NotSupported = foreach ($T in $Type) {
        if ($T -notin $Script:GPOConfiguration.Keys ) {
            $T
        } else {
            $Supported.Add($T)
        }
    }
    if ($Supported) {
        Write-Color '[i]', "[GPOZaurr] ", 'Supported types', ' [Informative] ', "Chosen by user: ", ($Supported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
    }
    if ($NotSupported) {
        Write-Color '[i]', "[GPOZaurr] ", 'Not supported types', ' [Informative] ', "Following types are not supported: ", ($NotSupported -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
        Write-Color '[i]', "[GPOZaurr] ", 'Not supported types', ' [Informative] ', "Please use one/multiple from the list: ", ($Script:GPOConfiguration.Keys -join ', ') -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
        return
    }
    $DisplayForest = if ($Forest) { $Forest } else { 'Not defined. Using current one' }
    $DisplayIncludedDomains = if ($IncludeDomains) { $IncludeDomains -join "," } else { 'Not defined. Using all domains of forest' }
    $DisplayExcludedDomains = if ($ExcludeDomains) { $ExcludeDomains -join ',' } else { 'No exclusions provided' }
    Write-Color '[i]', "[GPOZaurr] ", 'Domain Information', ' [Informative] ', "Forest: ", $DisplayForest -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
    Write-Color '[i]', "[GPOZaurr] ", 'Domain Information', ' [Informative] ', "Included Domains: ", $DisplayIncludedDomains -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta
    Write-Color '[i]', "[GPOZaurr] ", 'Domain Information', ' [Informative] ', "Excluded Domains: ", $DisplayExcludedDomains -Color Yellow, DarkGray, Yellow, DarkGray, Yellow, Magenta

    # Lets make sure we only enable those types which are requestd by user
    if ($Type) {
        foreach ($T in $Script:GPOConfiguration.Keys) {
            $Script:GPOConfiguration[$T].Enabled = $false
        }
        # Lets enable all requested ones
        foreach ($T in $Type) {
            $Script:GPOConfiguration[$T].Enabled = $true
        }
    }

    # Build data
    foreach ($T in $Script:GPOConfiguration.Keys) {
        if ($Script:GPOConfiguration[$T].Enabled -eq $true) {
            $Script:Reporting[$T] = [ordered] @{
                Name              = $Script:GPOConfiguration[$T].Name
                ActionRequired    = $null
                Data              = $null
                Exclusions        = $null
                WarningsAndErrors = $null
                Time              = $null
                Summary           = $null
                Variables         = Copy-Dictionary -Dictionary $Script:GPOConfiguration[$T]['Variables']
            }
            if ($Exclusions) {
                if ($Exclusions -is [scriptblock]) {
                    $Script:Reporting[$T]['Exclusions'] = $Exclusions
                    #$Script:Reporting[$T]['ExclusionsCode'] = $Exclusions
                }
                if ($Exclusions -is [Array]) {
                    $Script:Reporting[$T]['Exclusions'] = $Exclusions
                }
            }

            $TimeLogGPOList = Start-TimeLog
            Write-Color -Text '[i]', '[Start] ', $($Script:GPOConfiguration[$T]['Name']) -Color Yellow, DarkGray, Yellow
            $OutputCommand = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Execute'] -WarningVariable CommandWarnings -ErrorVariable CommandErrors -ArgumentList $Forest, $ExcludeDomains, $IncludeDomains
            if ($OutputCommand -is [System.Collections.IDictionary]) {
                # in some cases the return will be wrapped in Hashtable/orderedDictionary and we need to handle this without an array
                $Script:Reporting[$T]['Data'] = $OutputCommand
            } else {
                # since sometimes it can be 0 or 1 objects being returned we force it being an array
                $Script:Reporting[$T]['Data'] = [Array] $OutputCommand
            }
            Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Processing']
            $Script:Reporting[$T]['WarningsAndErrors'] = @(
                if ($ShowWarning) {
                    foreach ($War in $CommandWarnings) {
                        [PSCustomObject] @{
                            Type       = 'Warning'
                            Comment    = $War
                            Reason     = ''
                            TargetName = ''
                        }
                    }
                }
                if ($ShowError) {
                    foreach ($Err in $CommandErrors) {
                        [PSCustomObject] @{
                            Type       = 'Error'
                            Comment    = $Err
                            Reason     = $Err.CategoryInfo.Reason
                            TargetName = $Err.CategoryInfo.TargetName
                        }
                    }
                }
            )
            #if ($Script:GPOConfiguration[$T]['Summary']) {
            # $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Summary']
            #}
            $TimeEndGPOList = Stop-TimeLog -Time $TimeLogGPOList -Option OneLiner
            $Script:Reporting[$T]['Time'] = $TimeEndGPOList
            Write-Color -Text '[i]', '[End ] ', $($Script:GPOConfiguration[$T]['Name']), " [Time to execute: $TimeEndGPOList]" -Color Yellow, DarkGray, Yellow, DarkGray
        }
    }

    # Generate pretty HTML
    $TimeLogHTML = Start-TimeLog
    if (-not $FilePath) {
        $FilePath = Get-FileName -Extension 'html' -Temporary
    }
    Write-Color -Text '[i]', '[HTML ] ', "Generating HTML report ($FilePath)" -Color Yellow, DarkGray, Yellow
    New-HTML -Author 'PrzemysÅ‚aw KÅ‚ys' -TitleText 'GPOZaurr Report' {
        New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
        New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
        New-HTMLPanelStyle -BorderRadius 0px
        New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin

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

        if ($Type.Count -eq 1) {
            foreach ($T in $Script:GPOConfiguration.Keys) {
                if ($Script:GPOConfiguration[$T].Enabled -eq $true) {
                    if ($Script:GPOConfiguration[$T]['Summary']) {
                        $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Summary']
                    }
                    & $Script:GPOConfiguration[$T]['Solution']
                }
            }
        } else {
            foreach ($T in $Script:GPOConfiguration.Keys) {
                if ($Script:GPOConfiguration[$T].Enabled -eq $true) {
                    if ($Script:GPOConfiguration[$T]['Summary']) {
                        $Script:Reporting[$T]['Summary'] = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Summary']
                    }
                    New-HTMLTab -Name $Script:GPOConfiguration[$T]['Name'] {
                        & $Script:GPOConfiguration[$T]['Solution']
                    }
                }
            }
        }
    } -Online:$Online.IsPresent -ShowHTML:(-not $HideHTML) -FilePath $FilePath
    $TimeLogEndHTML = Stop-TimeLog -Time $TimeLogHTML -Option OneLiner
    Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report', " [Time to execute: $TimeLogEndHTML]" -Color Yellow, DarkGray, Yellow, DarkGray
    if ($PassThru) {
        $Script:Reporting
    }
    Reset-GPOZaurrStatus # This makes sure types are at it's proper status

}

[scriptblock] $SourcesAutoCompleter = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $Script:GPOConfiguration.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" }
}

Register-ArgumentCompleter -CommandName Invoke-GPOZaurr -ParameterName Type -ScriptBlock $SourcesAutoCompleter
function Invoke-GPOZaurrContent {
    [alias('Find-GPO')]
    [cmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(ParameterSetName = 'Default')][alias('ForestName')][string] $Forest,
        [Parameter(ParameterSetName = 'Default')][string[]] $ExcludeDomains,
        [Parameter(ParameterSetName = 'Default')][alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [Parameter(ParameterSetName = 'Default')][System.Collections.IDictionary] $ExtendedForestInformation,

        [Parameter(ParameterSetName = 'Local')][string] $GPOPath,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [string[]] $Type,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [string] $Splitter = [System.Environment]::NewLine,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [switch] $FullObjects,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [ValidateSet('HTML', 'Object')][string[]] $OutputType = 'Object',

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [string] $OutputPath,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [switch] $Open,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [switch] $Online,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [switch] $CategoriesOnly,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [switch] $SingleObject,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [switch] $SkipNormalize,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [switch] $SkipCleanup,

        [switch] $Extended
    )
    if ($Type.Count -eq 0) {
        $Type = $Script:GPODitionary.Keys
    }
    if ($GPOPath) {
        Write-Verbose "Invoke-GPOZaurrContent - Reading GPOs from $GPOPath"
        if (Test-Path -LiteralPath $GPOPath) {
            $GPOFiles = Get-ChildItem -LiteralPath $GPOPath -Recurse -File -Filter *.xml
            [Array] $GPOs = foreach ($File in $GPOFiles) {
                if ($File.Name -ne 'GPOList.xml') {
                    try {
                        [xml] $GPORead = Get-Content -LiteralPath $File.FullName
                    } catch {
                        Write-Warning "Invoke-GPOZaurrContent - Couldn't process $($File.FullName) error: $($_.Exception.message)"
                        continue
                    }
                    [PSCustomObject] @{
                        DisplayName = $GPORead.GPO.Name
                        DomainName  = $GPORead.GPO.Identifier.Domain.'#text'
                        GUID        = $GPORead.GPO.Identifier.Identifier.'#text' -replace '{' -replace '}'
                        GPOOutput   = $GPORead
                    }
                }
            }
        } else {
            Write-Warning "Invoke-GPOZaurrContent - $GPOPath doesn't exists."
            return
        }
    } else {
        Write-Verbose "Invoke-GPOZaurrContent - Query AD for GPOs"
        [Array] $GPOs = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }
    # This caches single reports.
    $TemporaryCachedSingleReports = [ordered] @{}
    $TemporaryCachedSingleReports['ReportsSingle'] = [ordered] @{}
    # This will be returned
    $Output = [ordered] @{}
    $Output['Reports'] = [ordered] @{}
    $Output['CategoriesFull'] = [ordered] @{}

    Write-Verbose "Invoke-GPOZaurrContent - Loading GPO Report to Categories"
    $CountGPO = 0
    [Array] $GPOCategories = foreach ($GPO in $GPOs) {
        $CountGPO++
        Write-Verbose "Invoke-GPOZaurrContent - Processing [$CountGPO/$($GPOs.Count)] $($GPO.DisplayName)"
        if ($GPOPath) {
            $GPOOutput = $GPO.GPOOutput
        } else {
            [xml] $GPOOutput = Get-GPOReport -Guid $GPO.GUID -Domain $GPO.DomainName -ReportType Xml
        }
        Get-GPOCategories -GPO $GPO -GPOOutput $GPOOutput.GPO -Splitter $Splitter -FullObjects:$FullObjects -CachedCategories $Output['CategoriesFull']
    }
    $Output['Categories'] = $GPOCategories | Select-Object -Property * -ExcludeProperty DataSet
    if ($CategoriesOnly) {
        # Return Categories only
        return $Output['Categories']
    }
    # We check our dictionary for reports that are based on reports to make sure we run CodeSingle separatly
    [Array] $FindRequiredSingle = foreach ($Key in $Script:GPODitionary.Keys) {
        $Script:GPODitionary[$Key].ByReports.Report
    }
    # Build reports based on categories
    if ($Output['CategoriesFull'].Count -gt 0) {
        foreach ($Report in $Type) {
            Write-Verbose "Invoke-GPOZaurrContent - Processing report type $Report"
            foreach ($CategoryType in $Script:GPODitionary[$Report].Types) {
                $Category = $CategoryType.Category
                $Settings = $CategoryType.Settings
                # Those are checks for making sure we have data to be even able to process it
                if (-not $Output['CategoriesFull'][$Category]) {
                    continue
                }
                if (-not $Output['CategoriesFull'][$Category][$Settings]) {
                    continue
                }
                # Translation
                $CategorizedGPO = $Output['CategoriesFull'][$Category][$Settings]
                foreach ($GPO in $CategorizedGPO) {
                    if (-not $Output['Reports'][$Report]) {
                        $Output['Reports'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new()
                    }
                    # Create temporary storage for "single gpo" reports
                    # it's required if we want to base reports on other reports later on
                    if (-not $TemporaryCachedSingleReports['ReportsSingle'][$Report]) {
                        $TemporaryCachedSingleReports['ReportsSingle'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new()
                    }
                    # Make sure translated gpo is null
                    $TranslatedGpo = $null
                    if ($SingleObject -or ($Report -in $FindRequiredSingle)) {
                        # We either create 1 GPO with multiple settings to return it as user requested it
                        # Or we process it only because we need to base it for reports based on other reports
                        if (-not $Script:GPODitionary[$Report]['CodeSingle']) {
                            # sometimes code and code single are identical. To not define things two times, one can just skip it
                            If ($Script:GPODitionary[$Report]['Code']) {
                                $Script:GPODitionary[$Report]['CodeSingle'] = $Script:GPODitionary[$Report]['Code']
                            }
                        }
                        if ($Script:GPODitionary[$Report]['CodeSingle']) {
                            #Write-Verbose "Invoke-GPOZaurrContent - Processing $Report single entry mode"
                            $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['CodeSingle']
                            if ($Report -in $FindRequiredSingle) {
                                foreach ($T in $TranslatedGpo) {
                                    $TemporaryCachedSingleReports['ReportsSingle'][$Report].Add($T)
                                }
                            }
                            if ($SingleObject) {
                                foreach ($T in $TranslatedGpo) {
                                    $Output['Reports'][$Report].Add($T)
                                }
                            }
                        }
                    }
                    if (-not $SingleObject) {
                        # We want each GPO to be listed multiple times if it makes sense for reporting
                        # think drive mapping - showing 1 mapping of a drive per object even if there are 50 drive mappings within 1 gpo
                        # this would result in 50 objects created
                        if ($Script:GPODitionary[$Report]['Code']) {
                            #Write-Verbose "Invoke-GPOZaurrContent - Processing $Report multi entry mode"
                            $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['Code']
                            foreach ($T in $TranslatedGpo) {
                                $Output['Reports'][$Report].Add($T)
                            }
                        }
                    }
                }
            }
        }
    }
    # Those reports are based on other reports (for example already processed registry settings)
    # This is useful where going thru registry collections may not be efficient enough to try and read it directly again
    foreach ($Report in $Type) {
        foreach ($ReportType in $Script:GPODitionary[$Report].ByReports) {
            if (-not $Output['Reports'][$Report]) {
                $Output['Reports'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new()
            }
            $FindReport = $ReportType.Report
            Write-Verbose "Invoke-GPOZaurrContent - Processing reports based on other report $Report ($FindReport)"
            foreach ($GPO in $TemporaryCachedSingleReports['ReportsSingle'][$FindReport]) {
                $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['CodeReport']
                foreach ($T in $TranslatedGpo) {
                    $Output['Reports'][$Report].Add($T)
                }
            }
        }
    }
    # Normalize - meaning that before we return each GPO report we make sure that each entry has the same column names regardless which one is first.
    # Normally if you would have a GPO with just 2 entries for given subject (say LAPS), and then another GPO having 5 settings for the same type
    # and you would display them one after another - all entries would be shown using first object which has less properties then 2nd or 3rd object
    # to make sure all objects are having same (even empty) properties we "normalize" it
    if (-not $SkipNormalize) {
        foreach ($Report in [string[]] $Output['Reports'].Keys) {
            $FirstProperties = 'DisplayName', 'DomainName', 'GUID', 'GpoType'
            #$EndProperties = 'CreatedTime', 'ModifiedTime', 'ReadTime', 'Filters', 'Linked', 'LinksCount', 'Links'
            $EndProperties = 'Filters', 'Linked', 'LinksCount', 'Links'
            $Properties = $Output['Reports'][$Report] | Select-Properties -ExcludeProperty ($FirstProperties + $EndProperties) -AllProperties -WarningAction SilentlyContinue
            $DisplayProperties = @(
                $FirstProperties
                foreach ($Property in $Properties) {
                    if ($Property -notin $FirstProperties -and $Property -notin $EndProperties) {
                        $Property
                    }
                }
                $EndProperties
            )
            $Output['Reports'][$Report] = $Output['Reports'][$Report] | Select-Object -Property $DisplayProperties
        }
    }

    $Output['PoliciesTotal'] = $Output.Reports.Policies.PolicyCategory | Group-Object | Select-Object Name, Count | Sort-Object -Property Name #-Descending
    #$Output['PoliciesTotal'] = $Output.Reports.Policies.PolicyCategory | Group-Object | Select-Object Name, Count | Sort-Object -Property Count -Descending

    if (-not $SkipCleanup) {
        Write-Verbose "Invoke-GPOZaurrContent - Cleaning up output"
        Remove-EmptyValue -Hashtable $Output -Recursive
    }
    if ($Extended) {
        $Output
    } else {
        if ($Output.Reports) {
            if ($OutputType -eq 'Object') {
                $Output.Reports
            }
            if ($OutputType -eq 'HTML') {
                if (-not $OutputPath) {
                    $OutputPath = Get-FileName -Extension 'html' -Temporary
                    Write-Warning "Invoke-GPOZaurrContent - OutputPath not given. Using $OutputPath"
                }
                Write-Verbose "Invoke-GPOZaurrContent - Generating HTML output"
                New-HTML {
                    New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
                    New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
                    New-HTMLTableOption -DataStore JavaScript
                    foreach ($Key in  $Output.Reports.Keys) {
                        New-HTMLTab -Name $Key {
                            Write-Verbose "Invoke-GPOZaurrContent - Generating HTML Table for $Key"
                            New-HTMLTable -DataTable $Output.Reports[$Key] -Filtering -Title $Key
                        }
                    }
                } -FilePath $OutputPath -ShowHTML:$Open -Online:$Online
            }
        } else {
            Write-Warning "Invoke-GPOZaurrContent - There was no data output for requested types."
        }
    }
}

[scriptblock] $SourcesAutoCompleter = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)

    $Script:GPODitionary.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" }
}
Register-ArgumentCompleter -CommandName Invoke-GPOZaurrContent -ParameterName Type -ScriptBlock $SourcesAutoCompleter
function Invoke-GPOZaurrPermission {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [parameter(Position = 0)]
        [scriptblock] $PermissionRules,

        # ParameterSet1
        [Parameter(ParameterSetName = 'GPOName')][string] $GPOName,

        # ParameterSet2
        [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid,

        # ParameterSet3
        [parameter(ParameterSetName = 'Level', Mandatory)][int] $Level,
        [parameter(ParameterSetName = 'Level', Mandatory)][int] $Limit,

        # ParameterSet4
        [parameter(ParameterSetName = 'Linked', Mandatory)][validateset('Root', 'DomainControllers', 'Site', 'OrganizationalUnit')][string] $Linked,

        # ParameterSet5
        [parameter(ParameterSetName = 'ADObject', ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Microsoft.ActiveDirectory.Management.ADObject[]] $ADObject,

        # ParameterSet6
        [parameter(ParameterSetName = 'Filter')][string] $Filter = "(objectClass -eq 'organizationalUnit' -or objectClass -eq 'domainDNS' -or objectClass -eq 'site')",
        [parameter(ParameterSetName = 'Filter')][string] $SearchBase,
        [parameter(ParameterSetName = 'Filter')][Microsoft.ActiveDirectory.Management.ADSearchScope] $SearchScope,

        # All other paramerrs are for for all parametersets
        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [validateSet('Unknown', 'NotWellKnown', 'NotWellKnownAdministrative', 'NotAdministrative', 'All')][string[]] $Type,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [Array] $ApprovedGroups,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [alias('Principal')][Array] $Trustee,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [Microsoft.GroupPolicy.GPPermissionType] $TrusteePermissionType,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [alias('PrincipalType')][validateset('DistinguishedName', 'Name', 'Sid')][string] $TrusteeType = 'DistinguishedName',

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [System.Collections.IDictionary] $GPOCache,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [alias('ForestName')][string] $Forest,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [string[]] $ExcludeDomains,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [parameter(ParameterSetName = 'Level')]
        [switch] $LimitAdministrativeGroupsToDomain,

        [Parameter(ParameterSetName = 'GPOGUID')]
        [Parameter(ParameterSetName = 'GPOName')]
        [parameter(ParameterSetName = 'Filter')]
        [parameter(ParameterSetName = 'ADObject')]
        [parameter(ParameterSetName = 'Linked')]
        [switch] $SkipDuplicates
    )
    if ($PermissionRules) {
        $Rules = & $PermissionRules
    } else {
        Write-Warning "Invoke-GPOZaurrPermission - No rules defined. Stopping processing."
        return
    }
    $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    if ($LimitAdministrativeGroupsToDomain) {
        # This will get administrative based on IncludeDomains if given. It means that if GPO has Domain admins added from multiple domains it will only find one, and remove all other Domain Admins (if working with Domain Admins that is)
        $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation
    } else {
        $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest #-IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation
    }
    if ($PSCmdlet.ParameterSetName -ne 'Level') {
        $Splat = @{
            Forest                    = $Forest
            IncludeDomains            = $IncludeDomains
            ExcludeDomains            = $ExcludeDomains
            ExtendedForestInformation = $ForestInformation
            SkipDuplicates            = $SkipDuplicates.IsPresent
        }
        if ($ADObject) {
            $Splat['ADObject'] = $ADObject
        } elseif ($Linked) {
            $Splat['Linked'] = $Linked
        } elseif ($GPOName) {

        } elseif ($GPOGuid) {

        } else {
            if ($Filter) {
                $Splat['Filter'] = $Filter
            }
            if ($SearchBase) {
                $Splat['SearchBase'] = $SearchBase
            }
            if ($SearchScope) {
                $Splat['SearchScope'] = $SearchScope
            }
        }



        Get-GPOZaurrLink @Splat | ForEach-Object -Process {
            $GPO = $_
            foreach ($Rule in $Rules) {
                if ($Rule.Action -eq 'Owner') {
                    if ($Rule.Type -eq 'Administrative') {
                        # We check for Owner (sometimes it can be empty)
                        if ($GPO.Owner) {
                            $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($GPO.Owner)"]
                        } else {
                            $AdministrativeGroup = $null
                        }
                        if (-not $AdministrativeGroup) {
                            $DefaultPrincipal = $ADAdministrativeGroups["$($GPO.DomainName)"]['DomainAdmins']
                            Write-Verbose "Invoke-GPOZaurrPermission - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) to $DefaultPrincipal"
                            #Set-ADACLOwner -ADObject $GPO.GPODistinguishedName -Principal $DefaultPrincipal -Verbose:$false -WhatIf:$WhatIfPreference
                            Set-GPOZaurrOwner -GPOGuid $GPO.Guid -IncludeDomains $GPO.Domain -Principal $DefaultPrincipal -WhatIf:$WhatIfPreference
                        }
                    } elseif ($Rule.Type -eq 'Default') {
                        Write-Verbose "Invoke-GPOZaurrPermission - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) to $($Rule.Principal)"
                        #Set-ADACLOwner -ADObject $GPO.GPODistinguishedName -Principal $Rule.Principal -Verbose:$false -WhatIf:$WhatIfPreference
                        Set-GPOZaurrOwner -GPOGuid $GPO.Guid -IncludeDomains $GPO.Domain -Principal $Rule.Principal -WhatIf:$WhatIfPreference
                    }
                } elseif ($Rule.Action -eq 'Remove') {
                    $GPOPermissions = Get-GPOZaurrPermission -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName -IncludePermissionType $Rule.IncludePermissionType -ExcludePermissionType $Rule.ExcludePermissionType -Type $Rule.Type -IncludeGPOObject -PermitType $Rule.PermitType -Principal $Rule.Principal -PrincipalType $Rule.PrincipalType -ExcludePrincipal $Rule.ExcludePrincipal -ExcludePrincipalType $Rule.ExcludePrincipalType -ADAdministrativeGroups $ADAdministrativeGroups
                    foreach ($Permission in $GPOPermissions) {
                        Remove-PrivPermission -Principal $Permission.PrincipalSid -PrincipalType Sid -GPOPermission $Permission -IncludePermissionType $Permission.Permission
                    }
                } elseif ($Rule.Action -eq 'Add') {
                    # Initially we were askng for same domain as user requested, but in fact we need to apply GPODomain as it can be linked to different domain
                    $SplatPermissions = @{
                        #Forest = $Forest
                        IncludeDomains         = $GPO.DomainName
                        #ExcludeDomains = $ExcludeDomains
                        #ExtendedForestInformation = $ForestInformation

                        GPOGuid                = $GPO.GUID
                        IncludePermissionType  = $Rule.IncludePermissionType
                        Type                   = $Rule.Type
                        PermitType             = $Rule.PermitType
                        Principal              = $Rule.Principal
                        ADAdministrativeGroups = $ADAdministrativeGroups
                    }
                    if ($Rule.PrincipalType) {
                        $SplatPermissions.PrincipalType = $Rule.PrincipalType
                    }
                    Add-GPOZaurrPermission @SplatPermissions
                }
            }
        }
    } else {
        # This is special case based on different command
        $Report = Get-GPOZaurrLinkSummary -Report OneLink
        $AffectedGPOs = foreach ($GPO in $Report) {
            $Property = "Level$($Level)"
            if ($GPO."$Property" -gt $Limit) {
                foreach ($Rule in $Rules) {
                    if ($Rule.Action -eq 'Owner') {
                        if ($Rule.Type -eq 'Administrative') {
                            # We check for Owner (sometimes it can be empty)
                            if ($GPO.Owner) {
                                $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($GPO.Owner)"]
                            } else {
                                $AdministrativeGroup = $null
                            }
                            if (-not $AdministrativeGroup) {
                                $DefaultPrincipal = $ADAdministrativeGroups["$($GPO.DomainName)"]['DomainAdmins']
                                Write-Verbose "Invoke-GPOZaurrPermission - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) to $DefaultPrincipal"
                                #Set-ADACLOwner -ADObject $GPO.GPODistinguishedName -Principal $DefaultPrincipal -Verbose:$false -WhatIf:$WhatIfPreference
                                Set-GPOZaurrOwner -GPOGuid $GPO.Guid -IncludeDomains $GPO.Domain -Principal $DefaultPrincipal -WhatIf:$WhatIfPreference
                            }
                        } elseif ($Rule.Type -eq 'Default') {
                            Write-Verbose "Invoke-GPOZaurrPermission - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) to $($Rule.Principal)"
                            #Set-ADACLOwner -ADObject $GPO.GPODistinguishedName -Principal $Rule.Principal -Verbose:$false -WhatIf:$WhatIfPreference
                            Set-GPOZaurrOwner -GPOGuid $GPO.Guid -IncludeDomains $GPO.Domain -Principal $Rule.Principal -WhatIf:$WhatIfPreference
                        }
                    } elseif ($Rule.Action -eq 'Remove') {
                        $GPOPermissions = Get-GPOZaurrPermission -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName -IncludePermissionType $Rule.IncludePermissionType -ExcludePermissionType $Rule.ExcludePermissionType -Type $Rule.Type -IncludeGPOObject -PermitType $Rule.PermitType -Principal $Rule.Principal -PrincipalType $Rule.PrincipalType -ExcludePrincipal $Rule.ExcludePrincipal -ExcludePrincipalType $Rule.ExcludePrincipalType -ADAdministrativeGroups $ADAdministrativeGroups
                        foreach ($Permission in $GPOPermissions) {
                            Remove-PrivPermission -Principal $Permission.Sid -PrincipalType Sid -GPOPermission $Permission -IncludePermissionType $Permission.Permission
                        }
                    } elseif ($Rule.Action -eq 'Add') {
                        # Initially we were askng for same domain as user requested, but in fact we need to apply GPODomain as it can be linked to different domain
                        $SplatPermissions = @{
                            #Forest = $Forest
                            IncludeDomains         = $GPO.DomainName
                            #ExcludeDomains = $ExcludeDomains
                            #ExtendedForestInformation = $ForestInformation

                            GPOGuid                = $GPO.GUID
                            IncludePermissionType  = $Rule.IncludePermissionType
                            Type                   = $Rule.Type
                            PermitType             = $Rule.PermitType
                            Principal              = $Rule.Principal
                            ADAdministrativeGroups = $ADAdministrativeGroups
                        }
                        if ($Rule.PrincipalType) {
                            $SplatPermissions.PrincipalType = $Rule.PrincipalType
                        }
                        Add-GPOZaurrPermission @SplatPermissions
                    }
                }
            }
        }
        $AffectedGPOs
    }
}
function Invoke-GPOZaurrSupport {
    [cmdletBinding()]
    param(
        [ValidateSet('NativeHTML', 'HTML', 'XML', 'Object')][string] $Type = 'HTML',
        [alias('Server')][string] $ComputerName,
        [alias('User')][string] $UserName,
        [string] $Path,
        [string] $Splitter = [System.Environment]::NewLine,
        [switch] $PreventShow,
        [switch] $Online
    )
    # if user didn't choose anything, lets run as currently logged in user locally
    if (-not $UserName -and -not $ComputerName) {
        $UserName = $Env:USERNAME
        # we can also check if the session is Administrative and if so request computer policies
        if (([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
            $ComputerName = $Env:COMPUTERNAME
        }
    }
    If ($Type -eq 'HTML') {
        $Exists = Get-Command -Name 'New-HTML' -ErrorAction SilentlyContinue
        if (-not $Exists) {
            Write-Warning "Invoke-GPOZaurrSupport - PSWriteHTML module is required for HTML functionality. Use XML, Object or NativeHTML option instead."
            return
        }
    }
    $Command = Get-Command -Name 'Get-GPResultantSetOfPolicy' -ErrorAction SilentlyContinue
    $NativeCommand = Get-Command -Name 'gpresult' -ErrorAction SilentlyContinue
    if (-not $Command -and -not $NativeCommand) {
        Write-Warning "Invoke-GPOZaurrSupport - Neither gpresult or Get-GPResultantSetOfPolicy are available. Terminating."
        return
    }

    $SplatPolicy = @{}
    if ($Type -in 'Object', 'XML', 'HTML') {
        if ($Path) {
            $SplatPolicy['Path'] = $Path
        } else {
            $SplatPolicy['Path'] = [io.path]::GetTempFileName().Replace('.tmp', ".xml")
        }
        $SplatPolicy['ReportType'] = 'xml'
    } elseif ($Type -eq 'NativeHTML') {
        if ($Path) {
            $SplatPolicy['Path'] = $Path
        } else {
            $SplatPolicy['Path'] = [io.path]::GetTempFileName().Replace('.tmp', ".html")
        }
        $SplatPolicy['ReportType'] = 'html'
    }
    if ($ComputerName) {
        $SplatPolicy['Computer'] = $ComputerName
    }
    if ($UserName) {
        $SplatPolicy['User'] = $UserName
    }

    $SplatPolicy['TempXmlPath'] = [io.path]::GetTempFileName().Replace('.tmp', ".xml")
    # Originally planned to use Get-GPResultantSetOfPolicy but it only works with administrative rights
    # if ($Command -and -not $ForceGPResult) {
    # try {
    # Write-Verbose "Invoke-GPOZaurrSupport - ComputerName: $($SplatPolicy['Computer']), UserName: $($SplatPolicy['User']), ReportType: $($SplatPolicy['ReportType']), Path: $($SplatPolicy['Path'])"
    # $ResultantSetPolicy = Get-GPResultantSetOfPolicy @SplatPolicy -ErrorAction Stop
    # } catch {
    # if ($_.Exception.Message -eq 'Exception from HRESULT: 0x80041003') {
    # Write-Warning "Invoke-GPOZaurrSupport - Are you running as admin? $($_.Exception.Message)"
    # return
    # } else {
    # $ErrorMessage = $($_.Exception.Message).Replace([Environment]::NewLine, ' ')
    # Write-Warning "Invoke-GPOZaurrSupport - Error: $ErrorMessage"
    # return
    # }
    # }
    # } else {
    $Arguments = @(
        if ($SplatPolicy['Computer']) {
            "/S $ComputerName"
        }
        if ($SplatPolicy['User']) {
            "/USER $($SplatPolicy['User'])"
        }
        if ($SplatPolicy['ReportType'] -eq 'HTML') {
            '/H'
            $SplatPolicy['Path']
        } elseif ($SplatPolicy['ReportType'] -eq 'XML') {
            '/X'
            $SplatPolicy['TempXmlPath']
        }
        "/F"
    )
    Write-Verbose "Invoke-GPOZaurrSupport - GPResult Arguments: $Arguments"
    Start-Process -NoNewWindow -FilePath 'gpresult' -ArgumentList $Arguments -Wait
    #}
    if ($Type -eq 'NativeHTML') {
        if (-not $PreventShow) {
            Write-Verbose "Invoke-GPOZaurrSupport - Opening up file $($SplatPolicy['Path'])"
            Start-Process -FilePath $SplatPolicy['Path']
        }
        return
    }
    # Loads created XML by resultant Output
    if ($SplatPolicy.TempXmlPath -and (Test-Path -LiteralPath $SplatPolicy.TempXmlPath)) {
        [xml] $PolicyContent = Get-Content -LiteralPath $SplatPolicy.TempXmlPath
        if ($PolicyContent) {
            # lets remove temporary XML file
            Remove-Item -LiteralPath $SplatPolicy.TempXmlPath
        } else {
            Write-Warning "Invoke-GPOZaurrSupport - Couldn't load XML file from drive $($SplatPolicy.TempXmlPath). Terminating."
            return
        }
    } else {
        Write-Warning "Invoke-GPOZaurrSupport - Couldn't find XML file on drive $($SplatPolicy.TempXmlPath). Terminating."
        return
    }
    if ($ComputerName) {
        if (-not $PolicyContent.Rsop.'ComputerResults'.EventsDetails) {
            Write-Warning "Invoke-GPOZaurrSupport - Windows Events for Group Policy are missing. Amount of data will be limited. Firewall issue?"
        }
    }
    if (-not $ComputerName) {
        # Ok, user haven't given computername, and we're not admin, so RSOP won't be there, but we will use $ComputerName further down to get additional data, even without administrative rights
        # Also for display purposes
        $ComputerName = $Env:COMPUTERNAME
    }

    if ($Type -eq 'XML') {
        $PolicyContent.Rsop
    } else {
        if ($VerbosePreference -ne 'SilentlyContinue') {
            $Verbose = $true
        } else {
            $Verbose = $false
        }
        $Output = [ordered] @{
            ComputerInformation = Get-Computer -ComputerName $ComputerName -Verbose:$Verbose
            #ResultantSetPolicy = $ResultantSetPolicy

            <# ResultantSetPolicy = $ResultantSetPolicy
            RunspaceId : 5a4931ea-915e-42d3-80d8-6a86b16eb271
            RsopMode : Logging
            Namespace : \\EVOSPEED\Root\Rsop\NS103F4892_39E9_42B8_B0FF_E438EC0796B5
            LoggingComputer : EVOSPEED
            LoggingUser : EVOTEC\przemyslaw.klys
            LoggingMode : UserAndComputer
            #>

        }
        if ($PolicyContent.Rsop.ComputerResults) {
            $Output.ComputerResults = ConvertFrom-XMLRSOP -Content $PolicyContent.Rsop -ResultsType 'ComputerResults' -Splitter $Splitter
        }
        if ($PolicyContent.Rsop.UserResults) {
            $Output.UserResults = ConvertFrom-XMLRSOP -Content $PolicyContent.Rsop -ResultsType 'UserResults' -Splitter $Splitter
        }
        New-GPOZaurrReportConsole -Results $Output -ComputerName $ComputerName
        if ($Type -contains 'Object') {
            $Output
        } elseif ($Type -contains 'HTML') {
            New-GPOZaurrReportHTML -Path $Path -Online:$Online -Open:(-not $PreventShow) -Support $Output
        }
    }
}

<#
 
 
GPRESULT [/S system [/U username [/P [password]]]] [/SCOPE scope]
           [/USER targetusername] [/R | /V | /Z] [(/X | /H) <filename> [/F]]
 
Description:
    This command line tool displays the Resultant Set of Policy (RSoP)
    information for a target user and computer.
 
Parameter List:
    /S system Specifies the remote system to connect to.
 
    /U [domain\]user Specifies the user context under which the
                               command should run.
                               Can not be used with /X, /H.
 
    /P [password] Specifies the password for the given user
                               context. Prompts for input if omitted.
                               Cannot be used with /X, /H.
 
    /SCOPE scope Specifies whether the user or the
                               computer settings need to be displayed.
                               Valid values: "USER", "COMPUTER".
 
    /USER [domain\]user Specifies the user name for which the
                               RSoP data is to be displayed.
 
    /X <filename> Saves the report in XML format at the
                               location and with the file name specified
                               by the <filename> parameter. (valid in Windows
                               Vista SP1 and later and Windows Server 2008 and later)
 
    /H <filename> Saves the report in HTML format at the
                               location and with the file name specified by
                               the <filename> parameter. (valid in Windows
                               at least Vista SP1 and at least Windows Server 2008)
 
    /F Forces Gpresult to overwrite the file name
                               specified in the /X or /H command.
 
    /R Displays RSoP summary data.
 
    /V Specifies that verbose information should
                               be displayed. Verbose information provides
                               additional detailed settings that have
                               been applied with a precedence of 1.
 
    /Z Specifies that the super-verbose
                               information should be displayed. Super-
                               verbose information provides additional
                               detailed settings that have been applied
                               with a precedence of 1 and higher. This
                               allows you to see if a setting was set in
                               multiple places. See the Group Policy
                               online help topic for more information.
 
    /? Displays this help message.
 
 
Examples:
    GPRESULT /R
    GPRESULT /H GPReport.html
    GPRESULT /USER targetusername /V
    GPRESULT /S system /USER targetusername /SCOPE COMPUTER /Z
    GPRESULT /S system /U username /P password /SCOPE USER /V
#>

function New-GPOZaurrWMI {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory)][string] $Name,
        [string] $Description = ' ',
        [string] $Namespace = 'root\CIMv2',
        [parameter(Mandatory)][string] $Query,
        [switch] $SkipQueryCheck,
        [switch] $Force,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    if (-not $Forest -and -not $ExcludeDomains -and -not $IncludeDomains -and -not $ExtendedForestInformation) {
        $IncludeDomains = $Env:USERDNSDOMAIN
    }

    if (-not $SkipQueryCheck) {
        try {
            $null = Get-CimInstance -Query $Query -ErrorAction Stop -Verbose:$false
        } catch {
            Write-Warning "New-GPOZaurrWMI - Query error $($_.Exception.Message). Terminating."
            return
        }
    }

    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        $DomainInformation = Get-ADDomain -Server $QueryServer
        $defaultNamingContext = $DomainInformation.DistinguishedName
        #$defaultNamingContext = (Get-ADRootDSE).defaultnamingcontext
        [string] $Author = (([ADSI]"LDAP://<SID=$([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value)>").UserPrincipalName).ToString()
        [string] $GUID = "{" + ([System.Guid]::NewGuid()) + "}"

        [string] $DistinguishedName = -join ("CN=", $GUID, ",CN=SOM,CN=WMIPolicy,CN=System,", $defaultNamingContext)
        $CurrentTime = (Get-Date).ToUniversalTime()
        [string] $CurrentDate = -join (
            ($CurrentTime.Year).ToString("0000"),
            ($CurrentTime.Month).ToString("00"),
            ($CurrentTime.Day).ToString("00"),
            ($CurrentTime.Hour).ToString("00"),
            ($CurrentTime.Minute).ToString("00"),
            ($CurrentTime.Second).ToString("00"),
            ".",
            ($CurrentTime.Millisecond * 1000).ToString("000000"),
            "-000"
        )

        [Array] $ExistingWmiFilter = Get-GPOZaurrWMI -ExtendedForestInformation $ForestInformation -IncludeDomains $Domain -Name $Name
        if ($ExistingWmiFilter.Count -eq 0) {
            [string] $WMIParm2 = -join ("1;3;10;", $Query.Length.ToString(), ";WQL;$Namespace;", $Query , ";")
            $OtherAttributes = @{
                "msWMI-Name"             = $Name
                "msWMI-Parm1"            = $Description
                "msWMI-Parm2"            = $WMIParm2
                "msWMI-Author"           = $Author
                "msWMI-ID"               = $GUID
                "instanceType"           = 4
                "showInAdvancedViewOnly" = "TRUE"
                "distinguishedname"      = $DistinguishedName
                "msWMI-ChangeDate"       = $CurrentDate
                "msWMI-CreationDate"     = $CurrentDate
            }
            $WMIPath = -join ("CN=SOM,CN=WMIPolicy,CN=System,", $defaultNamingContext)

            try {
                Write-Verbose "New-GPOZaurrWMI - Creating WMI filter $Name in $Domain"
                New-ADObject -Name $GUID -Type "msWMI-Som" -Path $WMIPath -OtherAttributes $OtherAttributes -Server $QueryServer
            } catch {
                Write-Warning "New-GPOZaurrWMI - Creating GPO filter error $($_.Exception.Message). Terminating."
                return
            }
        } else {
            foreach ($_ in $ExistingWmiFilter) {
                Write-Warning "New-GPOZaurrWMI - Skipping creation of GPO because name: $($_.DisplayName) guid: $($_.ID) for $($_.DomainName) already exists."
            }
        }
    }
}
function Optimize-GPOZaurr {
    <#
    .SYNOPSIS
    Enables or disables user/computer section of group policy based on it's content.
 
    .DESCRIPTION
    Enables or disables user/computer section of group policy based on it's content.
 
    .PARAMETER ExcludeGroupPolicies
    Provide a list of group policies to skip using Skip-GroupPolicy cmdlet
 
    .PARAMETER LimitProcessing
    Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    Optimize-GPOZaurr -All -WhatIf -Verbose -LimitProcessing 2
 
    .EXAMPLE
    Optimize-GPOZaurr -All -WhatIf -Verbose -LimitProcessing 2 {
        Skip-GroupPolicy -Name 'TEST | Drive Mapping 1'
        Skip-GroupPolicy -Name 'TEST | Drive Mapping 2'
    }
    .NOTES
    General notes
    #>

    [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'GPOName')]
    param(
        [Parameter(Position = 1)][scriptblock] $ExcludeGroupPolicies,
        [alias('Name', 'DisplayName')][Parameter(ParameterSetName = 'GPOName', Mandatory)][string] $GPOName,
        [Parameter(ParameterSetName = 'GPOGUID', Mandatory)][alias('GUID', 'GPOID')][string] $GPOGuid,
        [Parameter(ParameterSetName = 'All', Mandatory)][switch] $All,
        [int] $LimitProcessing,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    Begin {
        $Count = 0
    }
    Process {
        $getGPOZaurrSplat = @{
            GpoName                   = $GPOName
            GPOGUID                   = $GPOGuid
            Forest                    = $Forest
            IncludeDomains            = $IncludeDomains
            ExcludeDomains            = $ExcludeDomains
            ExtendedForestInformation = $ExtendedForestInformation
            ExcludeGroupPolicies      = $ExcludeGroupPolicies
        }
        Remove-EmptyValue -Hashtable $getGPOZaurrSplat

        Get-GPOZaurr @getGPOZaurrSplat | ForEach-Object {
            $GPO = $_
            if (-not $GPO.Exclude) {
                if ($GPO.Optimized -eq $false -and $GPO.Problem -eq $false) {
                    if ($GPO.Empty) {
                        Write-Verbose "Optimize-GPOZaurr - GPO ($($GPO.DisplayName)) / $($GPO.DomainName)) is not optimized, but GPO is empty, so leaving as is."
                    } else {
                        if ($GPO.UserSettingsAvailable -and $GPO.ComputerSettingsAvailable) {
                            Write-Verbose "Optimize-GPOZaurr - "
                            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Enabling computer and user settings in domain $($GPO.DomainName)")) {
                                try {
                                    $GPO.GPOObject.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::AllSettingsEnabled
                                } catch {
                                    Write-Warning -Message "Optimize-GPOZaurr - Couldn't set $($GPO.DisplayName) / $($GPO.DomainName) to $Status. Error $($_.Exception.Message)"
                                }
                            }
                        } elseif ($GPO.UserSettingsAvailable) {
                            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Disabling computer setings in domain $($GPO.DomainName)")) {
                                try {
                                    $GPO.GPOObject.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::ComputerSettingsDisabled
                                } catch {
                                    Write-Warning -Message "Optimize-GPOZaurr - Couldn't set $($GPO.DisplayName) / $($GPO.DomainName) to $Status. Error $($_.Exception.Message)"
                                }
                            }
                        } elseif ($GPO.ComputerSettingsAvailable) {
                            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, "Disabling user setings in domain $($GPO.DomainName)")) {
                                try {
                                    $GPO.GPOObject.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled
                                } catch {
                                    Write-Warning -Message "Optimize-GPOZaurr - Couldn't set $($GPO.DisplayName) / $($GPO.DomainName) to $Status. Error $($_.Exception.Message)"
                                }
                            }
                        }
                    }

                    $Count++
                    if ($LimitProcessing -eq $Count) {
                        break
                    }
                } elseif ($GPO.Optimized -eq $false) {
                    Write-Warning "Optimize-GPOZaurr - GPO ($($GPO.DisplayName)) / $($GPO.DomainName)) is not optimized, but GPO is marked as one with problem. Skipping."
                }
            }
        }
    }
}
function Remove-GPOPermission {
    [cmdletBinding()]
    param(
        [validateSet('Unknown', 'NotWellKnown', 'NotWellKnownAdministrative', 'Administrative', 'NotAdministrative', 'All')][string[]] $Type,
        [Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType,
        [Microsoft.GroupPolicy.GPPermissionType[]] $ExcludePermissionType,
        [validateSet('Allow', 'Deny', 'All')][string] $PermitType = 'Allow',

        [string[]] $Principal,
        [validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'Sid',

        [string[]] $ExcludePrincipal,
        [validateset('DistinguishedName', 'Name', 'Sid')][string] $ExcludePrincipalType = 'Sid'
    )

    if ($Type) {
        @{
            Action                = 'Remove'
            Type                  = $Type
            IncludePermissionType = $IncludePermissionType
            ExcludePermissionType = $ExcludePermissionType
            PermitType            = $PermitType
            Principal             = $Principal
            PrincipalType         = $PrincipalType
            ExcludePrincipal      = $ExcludePrincipal
            ExcludePrincipalType  = $ExcludePrincipalType
        }
    }
}
function Remove-GPOZaurr {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Position = 1)][scriptblock] $ExcludeGroupPolicies,
        [parameter(Position = 0, Mandatory)][validateset('Empty', 'Unlinked', 'Disabled', 'NoApplyPermission')][string[]] $Type,
        [int] $LimitProcessing,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string[]] $GPOPath,
        [string] $BackupPath,
        [switch] $BackupDated,
        [int] $RequireDays
    )
    Begin {
        if ($BackupPath) {
            $BackupRequired = $true
            if ($BackupDated) {
                $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))"
            } else {
                $BackupFinalPath = $BackupPath
            }
            Write-Verbose "Remove-GPOZaurr - Backing up to $BackupFinalPath"
            $null = New-Item -ItemType Directory -Path $BackupFinalPath -Force
        } else {
            $BackupRequired = $false
        }
        $Count = 0
    }
    Process {
        $getGPOZaurrSplat = @{
            Forest                    = $Forest
            IncludeDomains            = $IncludeDomains
            ExcludeDomains            = $ExcludeDomains
            ExtendedForestInformation = $ExtendedForestInformation
            GPOPath                   = $GPOPath
            ExcludeGroupPolicies      = $ExcludeGroupPolicies
        }

        Get-GPOZaurr @getGPOZaurrSplat | ForEach-Object {
            $DeleteRequired = $false

            if ($Type -contains 'Empty') {
                if ($_.Empty -eq $true) {
                    $DeleteRequired = $true
                }
            }
            if ($Type -contains 'Unlinked') {
                if ($_.Linked -eq $false) {
                    $DeleteRequired = $true
                }
            }
            if ($Type -contains 'Disabled') {
                if ($_.Enabled -eq $false) {
                    $DeleteRequired = $true
                }
            }
            if ($Type -contains 'NoApplyPermission') {
                if ($_.ApplyPermission -eq $false) {
                    $DeleteRequired = $true
                }
            }
            if ($RequireDays) {
                if ($RequireDays -ge $_.Days) {
                    # GPO was modified recently and we don't want to touch it yet, maybe edit is in progress
                    $DeleteRequired = $false
                }
            }
            if ($_.Exclude -eq $true) {
                Write-Verbose "Remove-GPOZaurr - Excluded GPO $($_.DisplayName) from $($_.DomainName). Skipping!"
            } elseif ($DeleteRequired) {
                if ($BackupRequired) {
                    try {
                        Write-Verbose "Remove-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName)"
                        $BackupInfo = Backup-GPO -Guid $_.Guid -Domain $_.DomainName -Path $BackupFinalPath -ErrorAction Stop
                        $BackupInfo
                        $BackupOK = $true
                    } catch {
                        Write-Warning "Remove-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName) failed: $($_.Exception.Message)"
                        $BackupOK = $false
                    }
                }
                if (($BackupRequired -and $BackupOK) -or (-not $BackupRequired)) {
                    try {
                        Write-Verbose "Remove-GPOZaurr - Removing GPO $($_.DisplayName) from $($_.DomainName)"
                        Remove-GPO -Domain $_.DomainName -Guid $_.Guid -ErrorAction Stop
                    } catch {
                        Write-Warning "Remove-GPOZaurr - Removing GPO $($_.DisplayName) from $($_.DomainName) failed: $($_.Exception.Message)"
                    }
                }
                $Count++
                if ($LimitProcessing -eq $Count) {
                    break
                }
            }
        }
    }
    End {

    }
}
function Remove-GPOZaurrBroken {
    <#
    .SYNOPSIS
    Finds and removes broken Group Policies from SYSVOL or AD or both.
 
    .DESCRIPTION
    Finds and removes broken Group Policies from SYSVOL or AD or both. Assesment is based on Get-GPOZaurrBroken and there are 3 supported types:
    - AD - meaning GPOs which have no SYSVOL content will be deleted from AD
    - SYSVOL - meaning GPOs which have no AD content will be deleted from SYSVOL
    - ObjectClass - meaning GPOs which have ObjectClass category of Container rather than groupPolicyContainer will be deleted from AD & SYSVOL
 
    .PARAMETER Type
    Choose one or more types to delete. Options are AD, ObjectClass, SYSVOL
 
    .PARAMETER BackupPath
    Path to optional backup of SYSVOL content before deletion
 
    .PARAMETER BackupDated
    Forces backup to be created within folder that has date in it
 
    .PARAMETER LimitProcessing
    Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    Remove-GPOZaurrBroken -Verbose -WhatIf -Type AD, SYSVOL
 
    .EXAMPLE
    Remove-GPOZaurrBroken -Verbose -WhatIf -Type AD, SYSVOL -IncludeDomains 'ad.evotec.pl' -LimitProcessing 2
 
    .EXAMPLE
    Remove-GPOZaurrBroken -Verbose -IncludeDomains 'ad.evotec.xyz' -BackupPath $Env:UserProfile\Desktop\MyBackup1 -WhatIf -Type AD, SYSVOL
 
    .NOTES
    General notes
    #>

    [alias('Remove-GPOZaurrOrphaned')]
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [parameter(Mandatory, Position = 0)][ValidateSet('SYSVOL', 'AD', 'ObjectClass')][string[]] $Type,
        [string] $BackupPath,
        [switch] $BackupDated,
        [int] $LimitProcessing = [int32]::MaxValue,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    if ($BackupPath) {
        if ($BackupDated) {
            $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))"
        } else {
            $BackupFinalPath = $BackupPath
        }
    } else {
        $BackupFinalPath = ''
    }
    Get-GPOZaurrBroken -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object {
        if ($Type -contains 'SYSVOL') {
            if ($_.Status -eq 'Not available in AD') {
                $_
            }
        }
        if ($Type -contains 'AD') {
            if ($_.Status -eq 'Not available on SYSVOL') {
                $_
            }
        }
        if ($Type -contains 'ObjectClass') {
            if ($_.Status -eq 'ObjectClass issue') {
                $_
            }
        }
    } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object {
        $GPO = $_
        if ($GPO.Status -in 'Not available in AD', 'ObjectClass issue') {
            Write-Verbose "Remove-GPOZaurrBroken - Removing from SYSVOL [$($GPO.Status)] $($GPO.Path)"
            if ($BackupFinalPath) {
                Try {
                    Write-Verbose "Remove-GPOZaurrBroken - Backing up $($GPO.Path)"
                    Copy-Item -LiteralPath $GPO.Path -Recurse -Destination $BackupFinalPath -ErrorAction Stop
                    $BackupWorked = $true
                } catch {
                    Write-Warning "Remove-GPOZaurrBroken - Error backing up $($GPO.Path) error: $($_.Exception.Message)"
                    $BackupWorked = $false
                }
            }
            if ($BackupWorked -or $BackupFinalPath -eq '') {
                Write-Verbose "Remove-GPOZaurrBroken - Removing $($GPO.Path)"
                try {
                    Remove-Item -Recurse -Force -LiteralPath $GPO.Path -ErrorAction Stop
                } catch {
                    Write-Warning "Remove-GPOZaurrBroken - Failed to remove file $($GPO.Path): $($_.Exception.Message)."
                }
            }
        }
        if ($GPO.Status -in 'Not available on SYSVOL', 'ObjectClass issue') {
            Write-Verbose "Remove-GPOZaurrBroken - Removing from AD [$($GPO.Status)] $($GPO.DistinguishedName)"
            <#
            try {
                $ExistingObject = Get-ADObject -Identity $GPO.DistinguishedName -Server $GPO.DomainName -ErrorAction Stop
            } catch {
                Write-Warning "Remove-GPOZaurrBroken - Error getting $($GPO.DistinguishedName) from AD error: $($_.Exception.Message)"
                $ExistingObject = $null
            }
            #>

            if ($GPO -and $GPO.ObjectClass -in 'groupPolicyContainer', 'Container') {
                Write-Verbose "Remove-GPOZaurrBroken - Removing DN: $($GPO.DistinguishedName) / ObjectClass: $($GPO.ObjectClass)"
                try {
                    Remove-ADObject -Server $GPO.DomainName -Identity $GPO.DistinguishedName -Recursive -Confirm:$false -ErrorAction Stop
                } catch {
                    Write-Warning "Remove-GPOZaurrBroken - Failed to remove $($GPO.DistinguishedName) from AD error: $($_.Exception.Message)"
                }
            } else {
                Write-Warning "Remove-GPOZaurrBroken - DistinguishedName $($GPO.DistinguishedName) not found or ObjectClass is not groupPolicyContainer/Container ($($GPO.ObjectClass))"
            }
        }
    }
}
function Remove-GPOZaurrDuplicateObject {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [int] $LimitProcessing = [int32]::MaxValue,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $getGPOZaurrDuplicateObjectSplat = @{
        Forest                    = $Forest
        IncludeDomains            = $IncludeDomains
        ExcludeDomains            = $ExcludeDomains
        ExtendedForestInformation = $ExtendedForestInformation
    }

    $DuplicateGpoObjects = Get-GPOZaurrDuplicateObject @getGPOZaurrDuplicateObjectSplat
    foreach ($Duplicate in $DuplicateGpoObjects | Select-Object -First $LimitProcessing) {
        try {
            Remove-ADObject -Identity $Duplicate.ObjectGUID -Recursive -ErrorAction Stop -Server $Duplicate.DomainName -Confirm:$false
        } catch {
            Write-Warning "Remove-GPOZaurrDuplicateObject - Deleting $($Duplicate.ConflictDN) / $($Duplicate.DomainName) via GUID: $($Duplicate.ObjectGUID) failed with error: $($_.Exception.Message)"
        }
    }
}
function Remove-GPOZaurrFolders {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [string] $BackupPath,
        [switch] $BackupDated,
        [ValidateSet('All', 'Netlogon', 'Sysvol')][string[]] $Type = 'All',
        [Parameter(Mandatory)][ValidateSet('NTFRS', 'Empty')][string] $FolderType,
        [string[]] $FolderName,
        [int] $LimitProcessing = [int32]::MaxValue,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    if ($BackupPath) {
        if ($BackupDated) {
            $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))"
        } else {
            $BackupFinalPath = $BackupPath
        }
    } else {
        $BackupFinalPath = ''
    }

    Get-GPOZaurrFolders -Type $Type -FolderType $FolderType -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object {
        if ($FolderName) {
            foreach ($Folder in $FolderName) {
                if ($_.Name -eq $Folder) {
                    $_
                }
            }
        } else {
            $_
        }
    } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object {
        if ($BackupFinalPath) {
            $SYSVOLRoot = "\\$($_.DomainName)\SYSVOL\$($_.DomainName)\"
            $DestinationFile = ($_.FullName).Replace($SYSVOLRoot, '')
            #$DestinationMissingFolder = $DestinationFile.Replace($DestinationFile, '')
            $DestinationFilePath = [system.io.path]::Combine($BackupFinalPath, $DestinationFile)
            #$DestinationFolderPath = [system.io.path]::Combine($BackupFinalPath, $DestinationMissingFolder)

            Write-Verbose "Remove-GPOZaurrFolders - Backing up $($_.FullName)"
            Try {
                Copy-Item -LiteralPath $_.FullName -Recurse -Destination $DestinationFilePath -ErrorAction Stop -Force
                $BackupWorked = $true
            } catch {
                Write-Warning "Remove-GPOZaurrFolders - Error backing up error: $($_.Exception.Message)"
                $BackupWorked = $false
            }

        }
        if ($BackupWorked -or $BackupFinalPath -eq '') {
            try {
                Write-Verbose "Remove-GPOZaurrFolders - Removing $($_.FullName)"
                Remove-Item -Path $_.FullName -Force -Recurse
            } catch {
                Write-Warning "Remove-GPOZaurrFolders - Failed to remove directory $($_.FullName): $($_.Exception.Message)."
            }
        }
    }
}
function Remove-GPOZaurrLegacyFiles {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [string] $BackupPath,
        [switch] $BackupDated,
        [switch] $RemoveEmptyFolders,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,

        [int] $LimitProcessing = [int32]::MaxValue
    )
    if ($BackupPath) {
        if ($BackupDated) {
            $BackupFinalPath = "$BackupPath\$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss'))"
        } else {
            $BackupFinalPath = $BackupPath
        }
    } else {
        $BackupFinalPath = ''
    }
    $Splat = @{
        Forest                    = $Forest
        IncludeDomains            = $IncludeDomains
        ExcludeDomains            = $ExcludeDomains
        ExtendedForestInformation = $ExtendedForestInformation
        Verbose                   = $VerbosePreference
    }
    [Array] $Deleted = Get-GPOZaurrLegacyFiles @Splat | Select-Object -First $LimitProcessing | ForEach-Object {
        Write-Verbose "Remove-GPOZaurrLegacyFiles - Processing $($_.FullName)"
        if ($BackupFinalPath) {
            $SYSVOLRoot = "\\$($_.DomainName)\SYSVOL\$($_.DomainName)\policies\"
            $DestinationFile = ($_.FullName).Replace($SYSVOLRoot, '')
            #$DestinationMissingFolder = $DestinationFile.Replace($DestinationFile, '')
            $DestinationFilePath = [system.io.path]::Combine($BackupFinalPath, $DestinationFile)
            #$DestinationFolderPath = [system.io.path]::Combine($BackupFinalPath, $DestinationMissingFolder)

            Write-Verbose "Remove-GPOZaurrLegacyFiles - Backing up $($_.FullName)"
            $Created = New-Item -ItemType File -Path $DestinationFilePath -Force
            if ($Created) {
                Try {
                    Copy-Item -LiteralPath $_.FullName -Recurse -Destination $DestinationFilePath -ErrorAction Stop -Force
                    $BackupWorked = $true
                } catch {
                    Write-Warning "Remove-GPOZaurrLegacyFiles - Error backing up error: $($_.Exception.Message)"
                    $BackupWorked = $false
                }
            } else {
                $BackupWorked = $false
            }
        }
        if ($BackupWorked -or $BackupFinalPath -eq '') {
            try {
                Write-Verbose "Remove-GPOZaurrLegacyFiles - Deleting $($_.FullName)"
                Remove-Item -Path $_.FullName -ErrorAction Stop -Force
                $_
            } catch {
                Write-Warning "Remove-GPOZaurrLegacyFiles - Failed to remove file $($_.FullName): $($_.Exception.Message)."
            }
        }
    }
    if ($Deleted.Count -gt 0) {
        if ($RemoveEmptyFolders) {
            $FoldersToCheck = $Deleted.DirectoryName | Sort-Object -Unique
            foreach ($Folder in $FoldersToCheck) {
                $FolderName = $Folder.Substring($Folder.Length - 4)
                if ($FolderName -eq '\Adm') {
                    try {
                        $MeasureCount = Get-ChildItem -LiteralPath $Folder -Force -ErrorAction Stop | Select-Object -First 1 | Measure-Object
                    } catch {
                        Write-Warning "Remove-GPOZaurrLegacyFiles - Couldn't verify if folder $Folder is empty. Skipping. Error: $($_.Exception.Message)."
                        continue
                    }
                    if ($MeasureCount.Count -eq 0) {
                        Write-Verbose "Remove-GPOZaurrLegacyFiles - Deleting empty folder $($Folder)"
                        try {
                            Remove-Item -LiteralPath $Folder -Force -Recurse:$false
                        } catch {
                            Write-Warning "Remove-GPOZaurrLegacyFiles - Failed to remove folder $($Folder): $($_.Exception.Message)."
                        }
                    } else {
                        Write-Verbose "Remove-GPOZaurrLegacyFiles - Skipping not empty folder from deletion $($Folder)"
                    }
                }
            }
        }
    }
}
function Remove-GPOZaurrLinkEmptyOU {
    [cmdletbinding(SupportsShouldProcess)]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [string[]] $ExcludeOrganizationalUnit,

        [int] $LimitProcessing = [int32]::MaxValue
    )

    $Processed = 0
    $OrganizationalUnits = Get-GPOZaurrOrganizationalUnit -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -Option Unlink -ExcludeOrganizationalUnit $ExcludeOrganizationalUnit
    foreach ($OU in $OrganizationalUnits) {
        if ($OU.Status -contains 'Unlink GPO') {
            if ($OU.OrganizationalUnit -in $ExcludeOrganizationalUnit) {
                Write-Verbose "Remove-GPOZaurrLinkEmptyOU - Processing $($OU.Organizationalunit) was skipped as it's excluded."
                continue
            }
            Write-Verbose "Remove-GPOZaurrLinkEmptyOU - Processing $($OU.Organizationalunit) found OU with GPOs to unlink"
            $Processed++
            foreach ($GPO in $OU.GPO) {
                Write-Verbose "Remove-GPOZaurrLinkEmptyOU - Removing $($GPO.DisplayName) link from $($OU.Organizationalunit)"
                try {
                    Remove-GPLink -ErrorAction Stop -Guid $GPO.GUID -Domain $GPO.DomainName -Target $OU.Organizationalunit
                } catch {
                    Write-Warning "Remove-GPOZaurrLinkEmptyOU - Error removing link of $($GPO.DisplayName) from $($OU.OrganizationalUnit) error: $($_.Exception.Message)"
                }
            }
            if ($Processed -ge $LimitProcessing) {
                Write-Verbose "Remove-GPOZaurrLinkEmptyOU - Limit processing hit, stopping."
                break
            }
        }
    }
}
function Remove-GPOZaurrPermission {
    [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Global')]
    param(
        [Parameter(ParameterSetName = 'GPOName', Mandatory)]
        [string] $GPOName,

        [Parameter(ParameterSetName = 'GPOGUID', Mandatory)]
        [alias('GUID', 'GPOID')][string] $GPOGuid,

        [string[]] $Principal,
        [validateset('DistinguishedName', 'Name', 'NetbiosName', 'Sid')][string] $PrincipalType = 'Sid',

        [validateset('Unknown', 'NotAdministrative', 'Default')][string[]] $Type = 'Default',

        [alias('PermissionType')][Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType,
        [Microsoft.GroupPolicy.GPPermissionType[]] $ExcludePermissionType,
        [switch] $SkipWellKnown,
        [switch] $SkipAdministrative,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [int] $LimitProcessing
    )
    Begin {
        $Count = 0
        $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation
        if ($Type -eq 'Unknown') {
            if ($SkipAdministrative -or $SkipWellKnown) {
                Write-Warning "Remove-GPOZaurrPermission - Using SkipAdministrative or SkipWellKnown while looking for Unknown doesn't make sense as only Unknown will be displayed."
            }
        }
    }
    Process {
        if ($Type -contains 'Named' -and $Principal.Count -eq 0) {
            Write-Warning "Remove-GPOZaurrPermission - When using type Named you need to provide names to remove. Terminating."
            return
        }
        # $GPOPermission.GPOSecurity.RemoveTrustee($GPOPermission.Sid)
        #void RemoveTrustee(string trustee)
        #void RemoveTrustee(System.Security.Principal.IdentityReference identity)
        #$GPOPermission.GPOSecurity.Remove
        #void RemoveAt(int index)
        #void IList[GPPermission].RemoveAt(int index)
        #void IList.RemoveAt(int index)

        foreach ($Domain in $ForestInformation.Domains) {
            $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
            if ($GPOName) {
                $getGPOSplat = @{
                    Name        = $GPOName
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
            } elseif ($GPOGuid) {
                $getGPOSplat = @{
                    Guid        = $GPOGuid
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
            } else {
                $getGPOSplat = @{
                    All         = $true
                    Domain      = $Domain
                    Server      = $QueryServer
                    ErrorAction = 'SilentlyContinue'
                }
            }
            Get-GPO @getGPOSplat | ForEach-Object -Process {
                $GPOSecurity = $_.GetSecurityInfo()
                $getPrivPermissionSplat = @{
                    Principal              = $Principal
                    PrincipalType          = $PrincipalType
                    #Accounts = $Accounts
                    GPO                    = $_
                    SkipWellKnown          = $SkipWellKnown.IsPresent
                    SkipAdministrative     = $SkipAdministrative.IsPresent
                    IncludeOwner           = $false
                    IncludeGPOObject       = $true
                    IncludePermissionType  = $IncludePermissionType
                    ExcludePermissionType  = $ExcludePermissionType
                    ADAdministrativeGroups = $ADAdministrativeGroups
                    SecurityRights         = $GPOSecurity
                }
                if ($Type -ne 'Default') {
                    $getPrivPermissionSplat['Type'] = $Type
                }
                [Array] $GPOPermissions = Get-PrivPermission @getPrivPermissionSplat
                if ($GPOPermissions.Count -gt 0) {
                    foreach ($Permission in $GPOPermissions) {
                        Remove-PrivPermission -Principal $Permission.PrincipalSid -PrincipalType Sid -GPOPermission $Permission -IncludePermissionType $Permission.Permission #-IncludeDomains $GPO.DomainName
                    }
                    $Count++
                    if ($Count -eq $LimitProcessing) {
                        # skipping skips per removed permission not per gpo.
                        break
                    }
                }
            }
        }
        <#
        Get-GPOZaurrPermission @Splat | ForEach-Object -Process {
            $GPOPermission = $_
            if ($Type -contains 'Unknown') {
                if ($GPOPermission.SidType -eq 'Unknown') {
                    #Write-Verbose "Remove-GPOZaurrPermission - Removing $($GPOPermission.Sid) from $($GPOPermission.DisplayName) at $($GPOPermission.DomainName)"
                    if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, "Removing $($GPOPermission.Sid) from $($GPOPermission.DisplayName) at $($GPOPermission.DomainName)")) {
                        try {
                            Write-Verbose "Remove-GPOZaurrPermission - Removing permission $($GPOPermission.Permission) for $($GPOPermission.Sid)"
                            $GPOPermission.GPOSecurity.RemoveTrustee($GPOPermission.Sid)
                            $GPOPermission.GPOObject.SetSecurityInfo($GPOPermission.GPOSecurity)
                            #$GPOPermission.GPOSecurity.RemoveAt($GPOPermission.GPOSecurityPermissionItem)
                            #$GPOPermission.GPOObject.SetSecurityInfo($GPOPermission.GPOSecurity)
                        } catch {
                            Write-Warning "Remove-GPOZaurrPermission - Removing permission $($GPOPermission.Permission) for $($GPOPermission.Sid) with error: $($_.Exception.Message)"
                        }
                        # Set-GPPPermission doesn't work on Unknown Accounts
                    }
                    $Count++
                    if ($Count -eq $LimitProcessing) {
                        # skipping skips per removed permission not per gpo.
                        break
                    }
                }
            }
            if ($Type -contains 'Named') {
 
            }
            if ($Type -contains 'NotAdministrative') {
 
            }
            if ($Type -contains 'Default') {
                Remove-PrivPermission -Principal $Principal -PrincipalType $PrincipalType -GPOPermission $GPOPermission -IncludePermissionType $IncludePermissionType
            }
            #Set-GPPermission -PermissionLevel None -TargetName $GPOPermission.Sid -Verbose -DomainName $GPOPermission.DomainName -Guid $GPOPermission.GUID #-WhatIf
            #Set-GPPermission -PermissionLevel GpoRead -TargetName 'Authenticated Users' -TargetType Group -Verbose -DomainName $Domain -Guid $_.GUID -WhatIf
 
        }
        #>

    }
    End {}
}
function Remove-GPOZaurrWMI {
    [CmdletBinding(SupportsShouldProcess)]
    Param (
        [Guid[]] $Guid,
        [string[]] $Name,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    if (-not $Forest -and -not $ExcludeDomains -and -not $IncludeDomains -and -not $ExtendedForestInformation) {
        $IncludeDomains = $Env:USERDNSDOMAIN
    }
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        [Array] $Objects = @(
            if ($Guid) {
                Get-GPOZaurrWMI -Guid $Guid -ExtendedForestInformation $ForestInformation -IncludeDomains $Domain
            }
            if ($Name) {
                Get-GPOZaurrWMI -Name $Name -ExtendedForestInformation $ForestInformation -IncludeDomains $Domain
            }
        )
        $Objects | ForEach-Object -Process {
            if ($_.DistinguishedName) {
                Write-Verbose "Remove-GPOZaurrWMI - Removing WMI Filter $($_.DistinguishedName)"
                Remove-ADObject $_.DistinguishedName -Confirm:$false -Server $QueryServer
            }
        }
    }
}
function Repair-GPOZaurrBrokenLink {
    <#
    .SYNOPSIS
    Removes any link to GPO that no longer exists.
 
    .DESCRIPTION
    Removes any link to GPO that no longer exists. It scans all site, organizational unit or domain root making sure every single link that may be linking to GPO that doesn't exists anymore is gone.
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER LimitProcessing
    Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed
 
    .EXAMPLE
    Repair-GPOZaurrBrokenLink -Verbose -LimitProcessing 1 -WhatIf
 
    .EXAMPLE
    Repair-GPOZaurrBrokenLink -Verbose -IncludeDomains ad.evotec.pl -LimitProcessing 1 -WhatIf
 
    .NOTES
    General notes
    #>

    [cmdletBinding(SupportsShouldProcess)]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [int] $LimitProcessing
    )
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -Extended
    $Links = Get-GPOZaurrBrokenLink -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ForestInformation
    $Cache = @{}
    foreach ($Link in $Links) {
        if (-not $Cache[$Link.DistinguishedName]) {
            $Cache[$Link.DistinguishedName] = [System.Collections.Generic.List[PSCustomObject]]::new()
        }
        $Cache[$Link.DistinguishedName].Add($Link)
    }
    $Count = 0
    foreach ($Key in $Cache.Keys) {
        $Count++
        Write-Verbose "Repair-GPOZaurrBrokenLink - processing [$Count/$($Cache.Keys.Count)] $Key "
        $Domain = ConvertFrom-DistinguishedName -ToDomainCN -DistinguishedName $Key
        $Server = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        $Object = Get-ADObject -Identity $Key -Server $Server -Properties gpLink
        #$MatchLinks = [Regex]::Matches($Object.gpLink, '(?<=\[)(.*?)(?=\])').Value
        $MatchLinks = [Regex]::Matches($Object.gpLink, '(?<=LDAP:\/\/)(.*?)(?=])').Value
        $Found = $false
        $FixedLinks = foreach ($Match in $MatchLinks) {
            $SplittedValue = $Match -split ';'
            $GPODN = $SplittedValue[0]
            # Check if list of non-existing GPOs contains legitimate GPO
            if ($Cache[$Key].GPODistinguishedName -notcontains $GPODN) {
                "[LDAP://$Match]"
                Write-Verbose "Repair-GPOZaurrBrokenLink - legitimate link to GPO $GPODN ($Key)"
            } else {
                $Found = $true
                Write-Verbose "Repair-GPOZaurrBrokenLink - preparing for removal link to $GPODN ($Key)"
            }
        }
        if ($Found) {
            $NewGpLink = $($FixedLinks -join '')
            if ($NewGpLink) {
                try {
                    Write-Verbose "Repair-GPOZaurrBrokenLink - setting gpLink to $Key - $NewGPLink"
                    Set-ADObject -Identity $Key -Server $Server -Replace @{ gPLink = $NewGpLink } -ErrorAction Stop
                } catch {
                    Write-Warning "Repair-GPOZaurrBrokenLink - setting gpLink to $Key - $NewGpLink failed! Error $($_.Exception.Message)"
                }
            } else {
                try {
                    Write-Verbose "Repair-GPOZaurrBrokenLink - clearing gpLink for $Key (no other links)"
                    Set-ADObject -Identity $Key -Server $Server -Clear gPLink -ErrorAction Stop
                } catch {
                    Write-Warning "Repair-GPOZaurrBrokenLink - clearing gpLink for $Key failed! Error $($_.Exception.Message)"
                }
            }
            if ($LimitProcessing -eq $Count) {
                break
            }
        }
    }
}
function Repair-GPOZaurrNetLogonOwner {
    <#
    .SYNOPSIS
    Sets new owner to each file in NetLogon share.
 
    .DESCRIPTION
    Sets new owner to each file in NetLogon share.
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER Principal
    Provide named owner. If not provided default S-1-5-32-544 is used.
 
    .PARAMETER LimitProcessing
    Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed
 
    .EXAMPLE
    Repair-GPOZaurrNetLogonOwner -WhatIf -Verbose -IncludeDomains ad.evotec.pl
 
    .NOTES
    General notes
    #>

    [cmdletBinding(SupportsShouldProcess)]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [string] $Principal = 'S-1-5-32-544',
        [int] $LimitProcessing = [int32]::MaxValue
    )
    $Identity = Convert-Identity -Identity $Principal -Verbose:$false
    if ($Identity.Error) {
        Write-Warning "Repair-GPOZaurrNetLogonOwner - couldn't convert Identity $Principal to desired name. Error: $($Identity.Error)"
        return
    }
    $Principal = $Identity.Name

    $getGPOZaurrNetLogonSplat = @{
        OwnerOnly                 = $true
        Forest                    = $Forest
        IncludeDomains            = $IncludeDomains
        ExcludeDomains            = $ExcludeDomains
        ExtendedForestInformation = $ExtendedForestInformation
    }

    Get-GPOZaurrNetLogon @getGPOZaurrNetLogonSplat -Verbose | Where-Object {
        if ($_.OwnerSid -ne 'S-1-5-32-544') {
            $_
        }
    } | Select-Object -First $LimitProcessing | ForEach-Object {
        if ($PSCmdlet.ShouldProcess($_.FullName, "Setting NetLogon Owner to $($Principal)")) {
            Set-FileOwner -JustPath -Path $_.FullName -Owner $Principal -Verbose:$true -WhatIf:$WhatIfPreference
        }
    }
}
function Repair-GPOZaurrPermission {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][ValidateSet('AuthenticatedUsers', 'Unknown', 'System', 'Administrative', 'All')][string[]] $Type,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [int] $LimitProcessing = [int32]::MaxValue
    )
    Get-GPOZaurrPermissionAnalysis -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains | Where-Object {
        $RequiresProcessing = $false
        if ($_.Status -eq $false) {
            if ($Type -contains 'System' -or $Type -contains 'All') {
                if ($_.System -eq $false) {
                    $RequiresProcessing = $true
                }
            }
            if ($Type -contains 'Administrative' -or $Type -contains 'All') {
                if ($_.Administrative -eq $false) {
                    $RequiresProcessing = $true
                }
            }
            if ($Type -contains 'AuthenticatedUsers' -or $Type -contains 'All') {
                if ($_.AuthenticatedUsers -eq $false) {
                    $RequiresProcessing = $true
                }
            }
            if ($Type -contains 'Unknown' -or $Type -contains 'All') {
                if ($_.Unknown -eq $true) {
                    $RequiresProcessing = $true
                }
            }
            if ($RequiresProcessing -eq $true) {
                $_
            }
        }
    } | Select-Object -First $LimitProcessing | ForEach-Object {
        $GPO = $_
        if ($GPO.Status -eq $false) {
            if ($GPO.System -eq $false) {
                Add-GPOZaurrPermission -Type WellKnownAdministrative -PermissionType GpoEditDeleteModifySecurity -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName
            }
            if ($GPO.Administrative -eq $false) {
                Add-GPOZaurrPermission -Type Administrative -PermissionType GpoEditDeleteModifySecurity -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName
            }
            if ($GPO.AuthenticatedUsers -eq $false) {
                Add-GPOZaurrPermission -Type AuthenticatedUsers -PermissionType GpoRead -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName
            }
            if ($GPO.Unknown -eq $true) {
                Remove-GPOZaurrPermission -Type Unknown -GPOGuid $GPO.GUID -IncludeDomains $GPO.DomainName
            }
        }
    }
}
function Repair-GPOZaurrPermissionConsistency {
    [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Default')]
    param(
        [Parameter(ParameterSetName = 'GPOName')][string] $GPOName,
        [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [int] $LimitProcessing = [int32]::MaxValue
    )
    $ConsistencySplat = @{
        Forest                    = $Forest
        IncludeDomains            = $IncludeDomains
        ExcludeDomains            = $ExcludeDomains
        ExtendedForestInformation = $ExtendedForestInformation
        Verbose                   = $VerbosePreference
    }
    if ($GPOName) {
        $ConsistencySplat['GPOName'] = $GPOName
    } elseif ($GPOGuid) {
        $ConsistencySplat['GPOGuid'] = $GPOGUiD
    } else {
        $ConsistencySplat['Type'] = 'Inconsistent'
    }

    Get-GPOZaurrPermissionConsistency @ConsistencySplat -IncludeGPOObject | Where-Object {
        if ($_.ACLConsistent -eq $false) {
            $_
        }
    } | Select-Object -First $LimitProcessing | ForEach-Object {
        #Write-Verbose "Repair-GPOZaurrPermissionConsistency - Repairing GPO consistency $($_.DisplayName) from domain: $($_.DomainName)"
        if ($PSCmdlet.ShouldProcess($_.DisplayName, "Reparing GPO permissions consistency in domain $($_.DomainName)")) {
            try {
                $_.IncludeGPOObject.MakeAclConsistent()
            } catch {
                $ErrorMessage = $_.Exception.Message
                Write-Warning "Repair-GPOZaurrPermissionConsistency - Failed to set consistency: $($ErrorMessage)."
            }
        }
    }
}
function Restore-GPOZaurr {
    [cmdletBinding()]
    param(
        [parameter(Mandatory)][string] $BackupFolder,
        [alias('Name')][string] $DisplayName,
        [string] $NewDisplayName,
        [string] $Domain,
        [switch] $SkipBackupSummary
    )
    if ($BackupFolder) {
        if (Test-Path -LiteralPath $BackupFolder) {
            if ($DisplayName) {
                if (-not $SkipBackupSummary) {
                    $BackupSummary = Get-GPOZaurrBackupInformation -BackupFolder $BackupFolder
                    if ($Domain) {
                        [Array] $FoundGPO = $BackupSummary | Where-Object { $_.DisplayName -eq $DisplayName -and $_.DomainName -eq $Domain }
                    } else {
                        [Array] $FoundGPO = $BackupSummary | Where-Object { $_.DisplayName -eq $DisplayName }
                    }
                    foreach ($GPO in $FoundGPO) {
                        if ($NewDisplayName) {
                            Import-GPO -Path $BackupFolder -BackupId $GPO.ID -Domain $GPO.Domain -TargetName $NewDisplayName -CreateIfNeeded
                        } else {
                            Write-Verbose "Restore-GPOZaurr - Restoring GPO $($GPO.DisplayName) from $($GPO.DomainName) / BackupId: $($GPO.ID)"
                            try {
                                Restore-GPO -Path $BackupFolder -BackupId $GPO.ID -Domain $GPO.DomainName
                            } catch {
                                Write-Warning "Restore-GPOZaurr - Restoring GPO $($GPO.DisplayName) from $($GPO.DomainName) failed: $($_.Exception.Message)"
                            }
                        }
                    }
                } else {
                    if ($Domain) {
                        Write-Verbose "Restore-GPOZaurr - Restoring GPO $($Name) from $($Domain)"
                        try {
                            Restore-GPO -Path $BackupFolder -Name $Name -Domain $Domain
                        } catch {
                            Write-Warning "Restore-GPOZaurr - Restoring GPO $($Name) from $($Domain) failed: $($_.Exception.Message)"
                        }
                    } else {
                        Write-Verbose "Restore-GPOZaurr - Restoring GPO $($Name)"
                        try {
                            Restore-GPO -Path $BackupFolder -Name $Name
                        } catch {
                            Write-Warning "Restore-GPOZaurr - Restoring GPO $($Name) failed: $($_.Exception.Message)"
                        }
                    }
                }
            } else {
                $BackupSummary = Get-GPOZaurrBackupInformation -BackupFolder $BackupFolder
                foreach ($GPO in $BackupSummary) {
                    Write-Verbose "Restore-GPOZaurr - Restoring GPO $($GPO.DisplayName) from $($GPO.DomainName) / BackupId: $($GPO.ID)"
                    try {
                        Restore-GPO -Path $BackupFolder -Domain $GPO.DomainName -BackupId $GPO.ID
                    } catch {
                        Write-Warning "Restore-GPOZaurr - Restoring GPO $($GPO.DisplayName) from $($GPO.DomainName) failed: $($_.Exception.Message)"
                    }
                }
            }
        } else {
            Write-Warning "Restore-GPOZaurr - BackupFolder incorrect ($BackupFolder)"
        }
    }
}
function Save-GPOZaurrFiles {
    <#
    .SYNOPSIS
    Exports GPO XML data to files and saves it to a given path
 
    .DESCRIPTION
    Exports GPO XML data to files and saves it to a given path
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER GPOPath
    Path where to save XML files from GPOReport
 
    .PARAMETER DeleteExisting
    Delete existing files before saving new ones
 
    .EXAMPLE
    Save-GPOZaurrFiles -GPOPath 'C:\Support\GitHub\GpoZaurr\Ignore\GPOExportEvotec' -DeleteExisting -Verbose
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string[]] $GPOPath,
        [switch] $DeleteExisting
    )
    if ($GPOPath) {
        if ($DeleteExisting) {
            $Test = Test-Path -LiteralPath $GPOPath
            if ($Test) {
                Write-Verbose "Save-GPOZaurrFiles - Removing existing content in $GPOPath"
                Remove-Item -LiteralPath $GPOPath -Recurse
            }
        }
        $null = New-Item -ItemType Directory -Path $GPOPath -Force
        Write-Verbose "Save-GPOZaurrFiles - Gathering GPO data"
        $Count = 0
        $GPOs = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        foreach ($GPO in $GPOS) {
            $Count++
            Write-Verbose "Save-GPOZaurrFiles - Processing GPO ($Count/$($GPOS.Count)) $($GPO.DomainName) | $($GPO.DisplayName)"
            $XMLContent = Get-GPOReport -ID $GPO.Guid -ReportType XML -Domain $GPO.DomainName
            $GPODOmainFolder = [io.path]::Combine($GPOPath, $GPO.DomainName)
            if (-not (Test-Path -Path $GPODOmainFolder)) {
                $null = New-Item -ItemType Directory -Path $GPODOmainFolder -Force
            }
            $Path = [io.path]::Combine($GPODOmainFolder, "$($GPO.Guid).xml")
            $XMLContent | Set-Content -LiteralPath $Path -Force -Encoding Unicode
        }
        $GPOListPath = [io.path]::Combine($GPOPath, "GPOList.xml")
        $GPOs | Export-Clixml -Depth 5 -Path $GPOListPath
    }
}
function Set-GPOOwner {
    <#
    .SYNOPSIS
    Used within Invoke-GPOZaurrPermission only. Set new group policy owner.
 
    .DESCRIPTION
    Used within Invoke-GPOZaurrPermission only. Set new group policy owner.
 
    .PARAMETER Type
    Choose Owner Type. When chosing Administrative Type, owner will be set to Domain Admins for current GPO domain. When Default is set Owner will be set to Principal given in another parameter.
 
    .PARAMETER Principal
    Choose Owner Name to set for Group Policy
 
    .EXAMPLE
    Invoke-GPOZaurrPermission -Verbose -SearchBase 'OU=Computers,OU=Production,DC=ad,DC=evotec,DC=xyz' {
        Set-GPOOwner -Type Administrative
        Remove-GPOPermission -Type NotAdministrative, NotWellKnownAdministrative -IncludePermissionType GpoEdit, GpoEditDeleteModifySecurity
        Add-GPOPermission -Type Administrative -IncludePermissionType GpoEditDeleteModifySecurity
        Add-GPOPermission -Type WellKnownAdministrative -IncludePermissionType GpoEditDeleteModifySecurity
    } -WhatIf
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [validateset('Administrative', 'Default')][string] $Type = 'Default',
        [string] $Principal
    )
    if ($Type -eq 'Default') {
        if ($Principal) {
            @{
                Action    = 'Owner'
                Type      = 'Default'
                Principal = $Principal
            }
        }
    } elseif ($Type -eq 'Administrative') {
        @{
            Action    = 'Owner'
            Type      = 'Administrative'
            Principal = ''
        }
    }
}
function Set-GPOZaurrOwner {
    <#
    .SYNOPSIS
    Sets GPO Owner to Domain Admins or other choosen account
 
    .DESCRIPTION
    Sets GPO Owner to Domain Admins or other choosen account. GPO Owner is set in AD and SYSVOL unless specified otherwise. If account doesn't require change, no change is done.
 
    .PARAMETER Type
    Unknown - finds unknown Owners and sets them to Administrative (Domain Admins) or chosen principal
    NotMatching - find administrative groups only and if sysvol and gpo doesn't match - replace with chosen principal or Domain Admins if not specified
    Inconsistent - same as not NotMatching
    NotAdministrative - combination of Unknown/NotMatching and NotAdministrative - replace with chosen principal or Domain Admins if not specified
    All - if Owner is known it checks if it's Administrative, if it sn't it fixes that. If owner is unknown it fixes it
    .PARAMETER GPOName
    Name of GPO. By default all GPOs are targetted
 
    .PARAMETER GPOGuid
    GUID of GPO. By default all GPOs are targetted
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER Principal
    Parameter description
 
    .PARAMETER SkipSysvol
    Set GPO Owner only in Active Directory. By default GPO Owner is being set in both places
 
    .PARAMETER LimitProcessing
    Allows to specify maximum number of items that will be fixed in a single run. It doesn't affect amount of GPOs processed
 
    .PARAMETER Force
    Pushes new owner regardless if it's already set or not
 
    .EXAMPLE
    Set-GPOZaurrOwner -Type All -Verbose -WhatIf -LimitProcessing 2
 
    .NOTES
    General notes
    #>

    [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Type')]
    param(
        [Parameter(ParameterSetName = 'Type', Mandatory)]
        [validateset('Unknown', 'NotAdministrative', 'NotMatching', 'Inconsistent', 'All')][string] $Type,

        [Parameter(ParameterSetName = 'Named')][string] $GPOName,
        [Parameter(ParameterSetName = 'Named')][alias('GUID', 'GPOID')][string] $GPOGuid,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [alias('ForestName')][string] $Forest,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [string[]] $ExcludeDomains,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [System.Collections.IDictionary] $ExtendedForestInformation,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [string] $Principal,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [switch] $SkipSysvol,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [int] $LimitProcessing = [int32]::MaxValue,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [alias('Exclusion', 'Exclusions')][string[]] $ApprovedOwner,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [validateset('OnlyAD', 'OnlyFileSystem')][string] $Action,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [switch] $Force
    )
    Begin {
        $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }
    Process {
        $getGPOZaurrOwnerSplat = @{
            IncludeSysvol             = -not $SkipSysvol.IsPresent
            Forest                    = $Forest
            IncludeDomains            = $IncludeDomains
            ExcludeDomains            = $ExcludeDomains
            ExtendedForestInformation = $ExtendedForestInformation
            ADAdministrativeGroups    = $ADAdministrativeGroups
            Verbose                   = $VerbosePreference
            SkipBroken                = $true
            ApprovedOwner             = $ApprovedOwner
        }
        if ($GPOName) {
            $getGPOZaurrOwnerSplat['GPOName'] = $GPOName
        } elseif ($GPOGuid) {
            $getGPOZaurrOwnerSplat['GPOGuid'] = $GPOGUiD
        }
        $Count = 0
        Get-GPOZaurrOwner @getGPOZaurrOwnerSplat | Where-Object {
            $Count++
            <#
            if ($_.Owner) {
                $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($_.Owner)"]
            } else {
                $AdministrativeGroup = $null
            }
 
            if (-not $SkipSysvol) {
                if ($_.SysvolOwner) {
                    $AdministrativeGroupSysvol = $ADAdministrativeGroups['ByNetBIOS']["$($_.SysvolOwner)"]
                } else {
                    $AdministrativeGroupSysvol = $null
                }
            }
 
            #>

            if ($Force) {
                Write-Verbose "Set-GPOZaurrOwner - Force was used to push new owner to $($_.DisplayName) from domain: $($_.DomainName) - owner $($_.Owner) / sysvol owner $($_.SysvolOwner)."
                $_
            } else {
                if ($Type -eq 'NotAdministrative') {
                    if ($_.Status -contains 'NotAdministrative' -and $_.Status -notcontains 'Approved') {
                        $_
                    } elseif ($_.Status -contains 'Inconsistent') {
                        $_
                    }
                    <#
                    if (-not $AdministrativeGroup -or (-not $AdministrativeGroupSysvol -and -not $SkipSysvol)) {
                        $_
                    } else {
                        if ($AdministrativeGroup -ne $AdministrativeGroupSysvol) {
                            Write-Verbose "Set-GPOZaurrOwner - Detected mismatch GPO: $($_.DisplayName) from domain: $($_.DomainName) - owner $($_.Owner) / sysvol owner $($_.SysvolOwner). Fixing required."
                            $_
                        }
                    }
                    #>

                } elseif ($Type -eq 'Unknown') {
                    if (-not $_.Owner -or (-not $_.SysvolOwner -and -not $SkipSysvol)) {
                        $_
                    }
                } elseif ($Type -in 'NotMatching', 'Inconsistent') {
                    if ($SkipSysvol) {
                        Write-Verbose "Set-GPOZaurrOwner - Detected mismatch GPO: $($_.DisplayName) from domain: $($_.DomainName) - owner $($_.Owner) / sysvol owner $($_.SysvolOwner). SysVol scanning is disabled. Skipping."
                    } else {
                        if ($_.Status -contains 'Inconsistent') {
                            $_
                        }
                        <#
                        if ($AdministrativeGroup -ne $AdministrativeGroupSysvol) {
                            #Write-Verbose "Set-GPOZaurrOwner - Detected mismatch GPO: $($_.DisplayName) from domain: $($_.DomainName) - owner $($_.Owner) / sysvol owner $($_.SysvolOwner). Fixing required."
                            $_
                        }
                        #>

                    }
                } else {
                    # we run with no type, that means we need to either set it to principal or to Administrative
                    if ($_.Owner) {
                        # we check if Principal is not set
                        if ($_.Status -contains 'NotAdministrative' -and $_.Status -notcontains 'Approved') {
                            $_
                        } elseif ($_.Status -contains 'Inconsistent') {
                            $_
                        }
                        <#
                        $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($_.Owner)"]
                        if (-not $SkipSysvol -and $_.SysvolOwner) {
                            $AdministrativeGroupSysvol = $ADAdministrativeGroups['ByNetBIOS']["$($_.SysvolOwner)"]
                            if (-not $AdministrativeGroup -or -not $AdministrativeGroupSysvol) {
                                $_
                            }
                        } else {
                            if (-not $AdministrativeGroup) {
                                $_
                            }
                        }
                        #>

                    } else {
                        $_
                    }
                }
            }
        } | Select-Object -First $LimitProcessing | ForEach-Object -Process {
            $GPO = $_
            if (-not $Principal) {
                $DefaultPrincipal = $ADAdministrativeGroups["$($_.DomainName)"]['DomainAdmins']
            } else {
                $DefaultPrincipal = $Principal
            }
            if ($Action -eq 'OnlyAD') {
                Write-Verbose "Set-GPOZaurrOwner - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) (SID: $($GPO.OwnerSID)) to $DefaultPrincipal"
                Set-ADACLOwner -ADObject $GPO.DistinguishedName -Principal $DefaultPrincipal -Verbose:$false -WhatIf:$WhatIfPreference
            } elseif ($Action -eq 'OnlyFileSystem') {
                if (-not $SkipSysvol) {
                    Write-Verbose "Set-GPOZaurrOwner - Changing Sysvol Owner GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.SysvolOwner) (SID: $($GPO.SysvolSid)) to $DefaultPrincipal"
                    Set-FileOwner -JustPath -Path $GPO.SysvolPath -Owner $DefaultPrincipal -Verbose:$true -WhatIf:$WhatIfPreference
                }
            } else {
                Write-Verbose "Set-GPOZaurrOwner - Changing GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.Owner) (SID: $($GPO.OwnerSID)) to $DefaultPrincipal"
                Set-ADACLOwner -ADObject $GPO.DistinguishedName -Principal $DefaultPrincipal -Verbose:$false -WhatIf:$WhatIfPreference
                if (-not $SkipSysvol) {
                    Write-Verbose "Set-GPOZaurrOwner - Changing Sysvol Owner GPO: $($GPO.DisplayName) from domain: $($GPO.DomainName) from owner $($GPO.SysvolOwner) (SID: $($GPO.SysvolSid)) to $DefaultPrincipal"
                    Set-FileOwner -JustPath -Path $GPO.SysvolPath -Owner $DefaultPrincipal -Verbose:$true -WhatIf:$WhatIfPreference
                }
            }
        }
    }
    End {

    }
}
function Set-GPOZaurrStatus {
    <#
    .SYNOPSIS
    Enables or disables user/computer section of Group Policy.
 
    .DESCRIPTION
    Enables or disables user/computer section of Group Policy.
 
    .PARAMETER GPOName
    Provide Group Policy Name
 
    .PARAMETER GPOGuid
    Provide Group Policy GUID
 
    .PARAMETER Status
    Choose a status for provided Group Policy
 
    .PARAMETER Forest
    Choose forest to target.
 
    .PARAMETER ExcludeDomains
    Exclude domains from trying to find Group Policy Name or GUID
 
    .PARAMETER IncludeDomains
    Include domain (one or more) to find Group Policy Name or GUID
 
    .PARAMETER ExtendedForestInformation
    Provide Extended Forest Information
 
    .EXAMPLE
    Set-GPOZaurrStatus -Name 'TEST | Empty GPO - AD.EVOTEC.PL CrossDomain GPO' -Status AllSettingsEnabled -Verbose
 
    .EXAMPLE
    Set-GPOZaurrStatus -Name 'TEST | Empty GPO - AD.EVOTEC.PL CrossDomain GPO' -DomainName ad.evotec.pl -Status AllSettingsEnabled -Verbose
 
    .NOTES
    General notes
    #>

    [cmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'GPOName')]
    param(
        [alias('Name', 'DisplayName')][Parameter(ParameterSetName = 'GPOName', Mandatory)][string] $GPOName,
        [Parameter(ParameterSetName = 'GPOGUID', Mandatory)][alias('GUID', 'GPOID')][string] $GPOGuid,
        [Parameter(Mandatory)][Microsoft.GroupPolicy.GpoStatus] $Status,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains', 'DomainName')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    Begin {

    }
    Process {
        $getGPOZaurrSplat = @{
            Forest                    = $Forest
            IncludeDomains            = $IncludeDomains
            ExcludeDomains            = $ExcludeDomains
            ExtendedForestInformation = $ExtendedForestInformation
            GpoName                   = $GPOName
            GPOGUID                   = $GPOGuid
        }
        Remove-EmptyValue -Hashtable $getGPOZaurrSplat
        Get-GPOZaurr @getGPOZaurrSplat | ForEach-Object {
            $GPO = $_

            if ($Status -eq [Microsoft.GroupPolicy.GpoStatus]::AllSettingsEnabled) {
                $Text = "Enabling computer and user settings in domain $($GPO.DomainName)"
            } elseif ($Status -eq [Microsoft.GroupPolicy.GpoStatus]::ComputerSettingsDisabled) {
                $Text = "Disabling computer setings in domain $($GPO.DomainName)"
            } elseif ($Status -eq [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled) {
                $Text = "Disabling user setings in domain $($GPO.DomainName)"
            } else {
                $Text = "Disabling computer user settings in domain $($GPO.DomainName)"
            }
            if ($PSCmdlet.ShouldProcess($GPO.DisplayName, $Text)) {
                try {
                    $GPO.GPOObject.GpoStatus = $Status
                } catch {
                    Write-Warning -Message "Set-GPOZaurrStatus - Couldn't set $($GPO.DisplayName) / $($GPO.DomainName) to $Status. Error $($_.Exception.Message)"
                }
            }
        }
    }
}
function Skip-GroupPolicy {
    <#
    .SYNOPSIS
    Used within ScriptBlocks only. Allows to exclude Group Policy from being affected by fixes
 
    .DESCRIPTION
    Used within ScriptBlocks only. Allows to exclude Group Policy from being affected by fixes. Only some commands support it. The goal is to support all cmdlets.
 
    .PARAMETER Name
    Define Group Policy Name to skip
 
    .PARAMETER DomaiName
    Define DomainName where Group Policy is located. Otherwise each domain will be checked and skipped if found with same name.
 
    .EXAMPLE
    Optimize-GPOZaurr -All -WhatIf -Verbose -LimitProcessing 2 {
        Skip-GroupPolicy -Name 'TEST | Drive Mapping 1'
        Skip-GroupPolicy -Name 'TEST | Drive Mapping 2'
    }
 
    .EXAMPLE
    Remove-GPOZaurr -Type Empty, Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -BackupDated -LimitProcessing 2 -Verbose -WhatIf {
        Skip-GroupPolicy -Name 'TEST | Drive Mapping 1'
        Skip-GroupPolicy -Name 'TEST | Drive Mapping 2' -DomaiName 'ad.evotec.pl'
    }
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'Name')]
    param(
        #[ValidateSet('GPOList')][string] $Type,
        [parameter(ParameterSetName = 'Name')][alias('GpoName', 'DisplayName')][string] $Name,
        [parameter(ParameterSetName = 'Guid')]
        [alias('ID')][string] $GUID,
        [parameter(ParameterSetName = 'Name')]
        [parameter(ParameterSetName = 'Guid')]
        [string] $DomaiName
    )
    $Output = @{
        Name       = $Name
        ID         = $GUID
        DomainName = $DomaiName
    }
    Remove-EmptyValue -Hashtable $Output
    [PSCustomObject] $Output
}


$ModuleFunctions = @{
    GroupPolicy     = @{
        'Add-GPOPermission'                    = ''
        'Add-GPOZaurrPermission'               = ''
        'Backup-GPOZaurr'                      = ''
        'Clear-GPOZaurrSysvolDFSR'             = ''
        'ConvertFrom-CSExtension'              = ''
        'Find-CSExtension'                     = ''
        'Get-GPOZaurr'                         = ''
        'Get-GPOZaurrAD'                       = ''
        'Get-GPOZaurrBackupInformation'        = ''
        'Get-GPOZaurrBroken'                   = 'Get-GPOZaurrSysvol'
        'Get-GPOZaurrDictionary'               = ''
        'Get-GPOZaurrDuplicateObject'          = ''
        'Get-GPOZaurrFiles'                    = ''
        'Get-GPOZaurrFilesPolicyDefinition'    = 'Get-GPOZaurrFilesPolicyDefinitions'
        'Get-GPOZaurrFolders'                  = ''
        'Get-GPOZaurrInheritance'              = ''
        'Get-GPOZaurrLegacyFiles'              = ''
        'Get-GPOZaurrLink'                     = ''
        'Get-GPOZaurrLinkSummary'              = ''
        'Get-GPOZaurrNetLogon'                 = ''
        'Get-GPOZaurrOwner'                    = ''
        'Get-GPOZaurrPassword'                 = ''
        'Get-GPOZaurrPermission'               = ''
        'Get-GPOZaurrPermissionConsistency'    = ''
        'Get-GPOZaurrPermissionRoot'           = ''
        'Get-GPOZaurrPermissionSummary'        = ''
        'Get-GPOZaurrSysvolDFSR'               = ''
        'Get-GPOZaurrWMI'                      = ''
        'Invoke-GPOZaurr'                      = 'Show-GPOZaurr', 'Show-GPO'
        'Invoke-GPOZaurrPermission'            = ''
        'Invoke-GPOZaurrSupport'               = ''
        'New-GPOZaurrWMI'                      = ''
        'Optimize-GPOZaurr'                    = ''
        'Remove-GPOPermission'                 = ''
        'Remove-GPOZaurr'                      = ''
        'Remove-GPOZaurrBroken'                = 'Remove-GPOZaurrOrphaned'
        'Remove-GPOZaurrDuplicateObject'       = ''
        'Remove-GPOZaurrFolders'               = ''
        'Remove-GPOZaurrLegacyFiles'           = ''
        'Remove-GPOZaurrPermission'            = ''
        'Remove-GPOZaurrWMI'                   = ''
        'Repair-GPOZaurrNetLogonOwner'         = ''
        'Repair-GPOZaurrPermissionConsistency' = ''
        'Restore-GPOZaurr'                     = ''
        'Save-GPOZaurrFiles'                   = ''
        'Set-GPOOwner'                         = ''
        'Set-GPOZaurrOwner'                    = ''
        'Find-GPO'                             = ''
        'Get-GPOZaurrFilesPolicyDefinitions'   = ''
        'Get-GPOZaurrSysvol'                   = ''
        'Remove-GPOZaurrOrphaned'              = ''
        'Show-GPO'                             = ''
        'Show-GPOZaurr'                        = ''
    }
    ActiveDirectory = @{
        'Add-GPOPermission'                    = ''
        'Add-GPOZaurrPermission'               = ''
        'Backup-GPOZaurr'                      = ''
        'Clear-GPOZaurrSysvolDFSR'             = ''
        'ConvertFrom-CSExtension'              = ''
        'Find-CSExtension'                     = ''
        'Get-GPOZaurr'                         = ''
        'Get-GPOZaurrAD'                       = ''
        'Get-GPOZaurrBackupInformation'        = ''
        'Get-GPOZaurrBroken'                   = 'Get-GPOZaurrSysvol'
        'Get-GPOZaurrDictionary'               = ''
        'Get-GPOZaurrDuplicateObject'          = ''
        'Get-GPOZaurrFiles'                    = ''
        'Get-GPOZaurrFilesPolicyDefinition'    = 'Get-GPOZaurrFilesPolicyDefinitions'
        'Get-GPOZaurrFolders'                  = ''
        'Get-GPOZaurrInheritance'              = ''
        'Get-GPOZaurrLegacyFiles'              = ''
        'Get-GPOZaurrLink'                     = ''
        'Get-GPOZaurrLinkSummary'              = ''
        'Get-GPOZaurrNetLogon'                 = ''
        'Get-GPOZaurrOwner'                    = ''
        'Get-GPOZaurrPassword'                 = ''
        'Get-GPOZaurrPermission'               = ''
        'Get-GPOZaurrPermissionConsistency'    = ''
        'Get-GPOZaurrPermissionRoot'           = ''
        'Get-GPOZaurrPermissionSummary'        = ''
        'Get-GPOZaurrSysvolDFSR'               = ''
        'Get-GPOZaurrWMI'                      = ''
        'Invoke-GPOZaurr'                      = 'Show-GPOZaurr', 'Show-GPO'
        'Invoke-GPOZaurrPermission'            = ''
        'Invoke-GPOZaurrSupport'               = ''
        'New-GPOZaurrWMI'                      = ''
        'Optimize-GPOZaurr'                    = ''
        'Remove-GPOPermission'                 = ''
        'Remove-GPOZaurr'                      = ''
        'Remove-GPOZaurrBroken'                = 'Remove-GPOZaurrOrphaned'
        'Remove-GPOZaurrDuplicateObject'       = ''
        'Remove-GPOZaurrFolders'               = ''
        'Remove-GPOZaurrLegacyFiles'           = ''
        'Remove-GPOZaurrPermission'            = ''
        'Remove-GPOZaurrWMI'                   = ''
        'Repair-GPOZaurrNetLogonOwner'         = ''
        'Repair-GPOZaurrPermissionConsistency' = ''
        'Restore-GPOZaurr'                     = ''
        'Save-GPOZaurrFiles'                   = ''
        'Set-GPOOwner'                         = ''
        'Set-GPOZaurrOwner'                    = ''
        'Find-GPO'                             = ''
        'Get-GPOZaurrFilesPolicyDefinitions'   = ''
        'Get-GPOZaurrSysvol'                   = ''
        'Remove-GPOZaurrOrphaned'              = ''
        'Show-GPO'                             = ''
        'Show-GPOZaurr'                        = ''
    }
}
[Array] $FunctionsAll = 'Add-GPOPermission', 'Add-GPOZaurrPermission', 'Backup-GPOZaurr', 'Clear-GPOZaurrSysvolDFSR', 'ConvertFrom-CSExtension', 'Find-CSExtension', 'Get-GPOZaurr', 'Get-GPOZaurrAD', 'Get-GPOZaurrBackupInformation', 'Get-GPOZaurrBroken', 'Get-GPOZaurrBrokenLink', 'Get-GPOZaurrDictionary', 'Get-GPOZaurrDuplicateObject', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinition', 'Get-GPOZaurrFolders', 'Get-GPOZaurrInheritance', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrNetLogon', 'Get-GPOZaurrOrganizationalUnit', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionAnalysis', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrPermissionIssue', 'Get-GPOZaurrPermissionRoot', 'Get-GPOZaurrPermissionSummary', 'Get-GPOZaurrSysvolDFSR', 'Get-GPOZaurrUpdates', 'Get-GPOZaurrWMI', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrContent', 'Invoke-GPOZaurrPermission', 'Invoke-GPOZaurrSupport', 'New-GPOZaurrWMI', 'Optimize-GPOZaurr', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrBroken', 'Remove-GPOZaurrDuplicateObject', 'Remove-GPOZaurrFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrLinkEmptyOU', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrBrokenLink', 'Repair-GPOZaurrNetLogonOwner', 'Repair-GPOZaurrPermission', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Set-GPOOwner', 'Set-GPOZaurrOwner', 'Set-GPOZaurrStatus', 'Skip-GroupPolicy'
[Array] $AliasesAll = 'Find-GPO', 'Get-GPOZaurrFilesPolicyDefinitions', 'Get-GPOZaurrSysvol', 'Remove-GPOZaurrOrphaned', 'Show-GPO', 'Show-GPOZaurr'
$AliasesToRemove = [System.Collections.Generic.List[string]]::new()
$FunctionsToRemove = [System.Collections.Generic.List[string]]::new()
foreach ($Module in $ModuleFunctions.Keys) {
    try {
        Import-Module -Name $Module -ErrorAction Stop
    } catch {
        foreach ($Function in $ModuleFunctions[$Module].Keys) {
            $FunctionsToRemove.Add($Function)
            $ModuleFunctions[$Module][$Function] | ForEach-Object {
                if ($_) {
                    $AliasesToRemove.Add($_)
                }
            }
        }
    }
}
$FunctionsToLoad = foreach ($Function in $FunctionsAll) {
    if ($Function -notin $FunctionsToRemove) {
        $Function
    }
}
$AliasesToLoad = foreach ($Alias in $AliasesAll) {
    if ($Alias -notin $AliasesToRemove) {
        $Alias
    }
}

Export-ModuleMember -Function @($FunctionsToLoad) -Alias @($AliasesToLoad)
# SIG # Begin signature block
# MIIdWQYJKoZIhvcNAQcCoIIdSjCCHUYCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUw9I/yLdE12h2wuez9Kz+mg+K
# xAWgghhnMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
# Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg
# +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT
# XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5
# a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g
# 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1
# roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
# GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3
# cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr
# EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+
# fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q
# Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu
# 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw
# 8jCCBP4wggPmoAMCAQICEA1CSuC+Ooj/YEAhzhQA8N0wDQYJKoZIhvcNAQELBQAw
# cjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVk
# IElEIFRpbWVzdGFtcGluZyBDQTAeFw0yMTAxMDEwMDAwMDBaFw0zMTAxMDYwMDAw
# MDBaMEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4G
# A1UEAxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjEwggEiMA0GCSqGSIb3DQEBAQUA
# A4IBDwAwggEKAoIBAQDC5mGEZ8WK9Q0IpEXKY2tR1zoRQr0KdXVNlLQMULUmEP4d
# yG+RawyW5xpcSO9E5b+bYc0VkWJauP9nC5xj/TZqgfop+N0rcIXeAhjzeG28ffnH
# bQk9vmp2h+mKvfiEXR52yeTGdnY6U9HR01o2j8aj4S8bOrdh1nPsTm0zinxdRS1L
# sVDmQTo3VobckyON91Al6GTm3dOPL1e1hyDrDo4s1SPa9E14RuMDgzEpSlwMMYpK
# jIjF9zBa+RSvFV9sQ0kJ/SYjU/aNY+gaq1uxHTDCm2mCtNv8VlS8H6GHq756Wwog
# L0sJyZWnjbL61mOLTqVyHO6fegFz+BnW/g1JhL0BAgMBAAGjggG4MIIBtDAOBgNV
# HQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDBBBgNVHSAEOjA4MDYGCWCGSAGG/WwHATApMCcGCCsGAQUFBwIBFhtodHRwOi8v
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHwYDVR0jBBgwFoAU9LbhIB3+Ka7S5GGlsqIl
# ssgXNW4wHQYDVR0OBBYEFDZEho6kurBmvrwoLR1ENt3janq8MHEGA1UdHwRqMGgw
# MqAwoC6GLGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMu
# Y3JsMDKgMKAuhixodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk
# LXRzLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz
# cC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKGQ2h0dHA6Ly9jYWNlcnRzLmRpZ2lj
# ZXJ0LmNvbS9EaWdpQ2VydFNIQTJBc3N1cmVkSURUaW1lc3RhbXBpbmdDQS5jcnQw
# DQYJKoZIhvcNAQELBQADggEBAEgc3LXpmiO85xrnIA6OZ0b9QnJRdAojR6OrktIl
# xHBZvhSg5SeBpU0UFRkHefDRBMOG2Tu9/kQCZk3taaQP9rhwz2Lo9VFKeHk2eie3
# 8+dSn5On7UOee+e03UEiifuHokYDTvz0/rdkd2NfI1Jpg4L6GlPtkMyNoRdzDfTz
# ZTlwS/Oc1np72gy8PTLQG8v1Yfx1CAB2vIEO+MDhXM/EEXLnG2RJ2CKadRVC9S0y
# OIHa9GCiurRS+1zgYSQlT7LfySmoc0NR2r1j1h9bm/cuG08THfdKDXF+l7f0P4Tr
# weOjSaH6zqe/Vs+6WXZhiV9+p7SOZ3j5NpjhyyjaW4emii8wggUwMIIEGKADAgEC
# AhAECRgbX9W7ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEw
# MjIxMjAwMDBaFw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV
# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEi
# MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7
# RZmxOttE9X/lqJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p
# 0WfTxvspJ8fTeyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj
# 6YgsIJWuHEqHCN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grk
# V7tKtel05iv+bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHy
# DxL0xY4PwaLoLFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMB
# AAGjggHNMIIByTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAT
# BgNVHSUEDDAKBggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG
# GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh
# Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCB
# gQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgG
# CmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
# Y29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1
# DlgwHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEL
# BQADggEBAD7sDVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q
# 3yBVN7Dh9tGSdQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/
# kLEbBw6RFfu6r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dc
# IFzZcbEMj7uo+MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6
# dGRrsutmQ9qzsIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT
# +hKUGIUukpHqaGxEMrJmoecYpJpkUe8wggUxMIIEGaADAgECAhAKoSXW1jIbfkHk
# Bdo2l8IVMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE
# aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMT
# G0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xNjAxMDcxMjAwMDBaFw0z
# MTAxMDcxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0
# IFNIQTIgQXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
# AQUAA4IBDwAwggEKAoIBAQC90DLuS82Pf92puoKZxTlUKFe2I0rEDgdFM1EQfdD5
# fU1ofue2oPSNs4jkl79jIZCYvxO8V9PD4X4I1moUADj3Lh477sym9jJZ/l9lP+Cb
# 6+NGRwYaVX4LJ37AovWg4N4iPw7/fpX786O6Ij4YrBHk8JkDbTuFfAnT7l3ImgtU
# 46gJcWvgzyIQD3XPcXJOCq3fQDpct1HhoXkUxk0kIzBdvOw8YGqsLwfM/fDqR9mI
# UF79Zm5WYScpiYRR5oLnRlD9lCosp+R1PrqYD4R/nzEU1q3V8mTLex4F0IQZchfx
# FwbvPc3WTe8GQv2iUypPhR3EHTyvz9qsEPXdrKzpVv+TAgMBAAGjggHOMIIByjAd
# BgNVHQ4EFgQU9LbhIB3+Ka7S5GGlsqIlssgXNW4wHwYDVR0jBBgwFoAUReuir/SS
# y4IxLVGLp6chnfNtyA8wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMC
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j
# cnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwUAYDVR0gBEkw
# RzA4BgpghkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj
# ZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4IBAQBxlRLp
# UYdWac3v3dp8qmN6s3jPBjdAhO9LhL/KzwMC/cWnww4gQiyvd/MrHwwhWiq3BTQd
# aq6Z+CeiZr8JqmDfdqQ6kw/4stHYfBli6F6CJR7Euhx7LCHi1lssFDVDBGiy23UC
# 4HLHmNY8ZOUfSBAYX4k4YU1iRiSHY4yRUiyvKYnleB/WCxSlgNcSR3CzddWThZN+
# tpJn+1Nhiaj1a5bA9FhpDXzIAbG5KHW3mWOFIoxhynmUfln8jA/jb7UBJrZspe6H
# USHkWGCbugwtK22ixH67xCUrRwIIfEmuE7bhfEJCKMYYVs9BNLZmXbZ0e/VWMyIv
# IjayS6JKldj1po5SMIIFPTCCBCWgAwIBAgIQBNXcH0jqydhSALrNmpsqpzANBgkq
# hkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT
# SEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMDYyNjAwMDAwMFoX
# DTIzMDcwNzEyMDAwMFowejELMAkGA1UEBhMCUEwxEjAQBgNVBAgMCcWabMSFc2tp
# ZTERMA8GA1UEBxMIS2F0b3dpY2UxITAfBgNVBAoMGFByemVteXPFgmF3IEvFgnlz
# IEVWT1RFQzEhMB8GA1UEAwwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7KB3iyBrhkLUbbFe9qxhKKPBYqD
# Bqlnr3AtpZplkiVjpi9dMZCchSeT5ODsShPuZCIxJp5I86uf8ibo3vi2S9F9AlfF
# jVye3dTz/9TmCuGH8JQt13ozf9niHecwKrstDVhVprgxi5v0XxY51c7zgMA2g1Ub
# +3tii0vi/OpmKXdL2keNqJ2neQ5cYly/GsI8CREUEq9SZijbdA8VrRF3SoDdsWGf
# 3tZZzO6nWn3TLYKQ5/bw5U445u/V80QSoykszHRivTj+H4s8ABiforhi0i76beA6
# Ea41zcH4zJuAp48B4UhjgRDNuq8IzLWK4dlvqrqCBHKqsnrF6BmBrv+BXQIDAQAB
# o4IBxTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0O
# BBYEFBixNSfoHFAgJk4JkDQLFLRNlJRmMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE
# DDAKBggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2Ny
# bDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUw
# QzA3BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNl
# cnQuY29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNp
# Z25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAmr1s
# z4lsLARi4wG1eg0B8fVJFowtect7SnJUrp6XRnUG0/GI1wXiLIeow1UPiI6uDMsR
# XPHUF/+xjJw8SfIbwava2eXu7UoZKNh6dfgshcJmo0QNAJ5PIyy02/3fXjbUREHI
# NrTCvPVbPmV6kx4Kpd7KJrCo7ED18H/XTqWJHXa8va3MYLrbJetXpaEPpb6zk+l8
# Rj9yG4jBVRhenUBUUj3CLaWDSBpOA/+sx8/XB9W9opYfYGb+1TmbCkhUg7TB3gD6
# o6ESJre+fcnZnPVAPESmstwsT17caZ0bn7zETKlNHbc1q+Em9kyBjaQRcEQoQQNp
# ezQug9ufqExx6lHYDjGCBFwwggRYAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYD
# VQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAv
# BgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EC
# EATV3B9I6snYUgC6zZqbKqcwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI
# oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB
# CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFE4DJa2g9GCz7afujslQ
# CIrcK69rMA0GCSqGSIb3DQEBAQUABIIBADfSkdbRX7dnw/QADM4pF0KW17npmfgb
# OTnyjbLZfmJKbDL3rfkGcKY7JYtRpZdIaQ9fPNY1qJ5i/rPrwqSF6Offxzlp0exJ
# FUTxvBi8QuHV+Vdl4v1uBhBYeNdJJAZiqPtmPFhjLukqTypJ/HElWGQq3uF86hI+
# jne6RDs0auCM7z4skeB7G9rGaoaQ701/GmHXyr7+4OiQmSoujRJSWziUeMf77HAT
# C6DbKujbp25rVR63CI2wB/6GbNQ4EJpydfAmhxDfqkcsApZTM5vMH7YQlVtIN1Y3
# yyIu5fXe9xLO7FTgNpnUzAU+Z+12fJBqQcqjOpXg9pzPvuut+uw6fwehggIwMIIC
# LAYJKoZIhvcNAQkGMYICHTCCAhkCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G
# A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQQIQ
# DUJK4L46iP9gQCHOFADw3TANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzEL
# BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIxMTIwNjA4MzcwMVowLwYJKoZI
# hvcNAQkEMSIEIMXkgABSVuCESpDHpVfiT8wquRaVuUMP/ZgFzr7Y/7QlMA0GCSqG
# SIb3DQEBAQUABIIBAKjWrGWG9mL5qL+0Cb6/MJmZIEXXP8teltIpfhemqQbUp5Ox
# GuIvdzS5tMj1nw45dlrVp1Vfr/13zOIvRvfv5xGlIT7ntp6+G+yyDWtg34MSAwD3
# /I1inLrJAnlUGd8qZ+v+DsUgSkSpygJAETfOhuRxmRb+Hq5HufcCJ6qroKJY5YNR
# iM6iVEjqYnWARqAmdGvlEOBteO+fZLXRymWEYCRWrfJ9Q+HhhOLFCWOUWvUYJmW2
# y8VJ1GQjC+QymqShoi4wSUjx7bwzYg38wH+gkDyn8miZXH534B0FdxN35+5bz+Ki
# bJFsFKug36wJaTprqmiMPW2F7hHLcpMlrt6gbBQ=
# SIG # End signature block