GPOZaurr.psm1

function ConvertFrom-DistinguishedName { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER DistinguishedName
    Parameter description
 
    .PARAMETER ToOrganizationalUnit
    Parameter description
 
    .PARAMETER ToDC
    Parameter description
 
    .PARAMETER ToDomainCN
    Parameter description
 
    .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
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName,
        [switch] $ToOrganizationalUnit,
        [switch] $ToDC,
        [switch] $ToDomainCN)
    process {
        foreach ($Distinguished in $DistinguishedName) {
            if ($ToDomainCN) {
                $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
                $CN = $DN -replace ',DC=', '.' -replace "DC="
                $CN
            } elseif ($ToOrganizationalUnit) { [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value } elseif ($ToDC) { $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' } else {
                $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$'
                $Output = foreach ($_ in $Distinguished) {
                    $_ -match $Regex
                    $Matches
                }
                $Output.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 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)][Array] $ADObject,
        [string] $ForestName,
        [switch] $Extended,
        [switch] $ResolveTypes,
        [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
        }
    }
    Process {
        foreach ($Object in $ADObject) {
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                [string] $DistinguishedName = $Object.DistinguishedName
                [string] $CanonicalName = $Object.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
                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)" }
            $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) { 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
                $ReturnObject = [ordered] @{}
                $ReturnObject['DistinguishedName' ] = $DistinguishedName
                if ($CanonicalName) { $ReturnObject['CanonicalName'] = $CanonicalName }
                if ($ObjectClass) { $ReturnObject['ObjectClass'] = $ObjectClass }
                $ReturnObject['AccessControlType'] = $ACL.AccessControlType
                $ReturnObject['Principal'] = $IdentityReference
                if ($ResolveTypes) {
                    $IdentityResolve = Get-WinADObject -Identity $IdentityReference -AddType
                    if (-not $IdentityResolve) {
                        $ConvertIdentity = Convert-Identity -Identity $IdentityReference
                        $ReturnObject['PrincipalType'] = $ConvertIdentity.Type
                        $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 { 
    [cmdletBinding()]
    param([Array] $ADObject,
        [switch] $Resolve,
        [System.Collections.IDictionary] $ADAdministrativeGroups,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation)
    Begin {
        if (-not $ADAdministrativeGroups -and $Resolve) {
            $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
        }
    }
    Process {
        foreach ($Object in $ADObject) {
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                [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
                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
                $Hash = [ordered] @{DistinguishedName = $DistinguishedName
                    Owner                             = $ACLs.Owner
                    ACLs                              = $ACLs
                }
                $ErrorMessage = ''
            } catch {
                $Hash = [ordered] @{DistinguishedName = $DistinguishedName
                    Owner                             = $null
                    ACLs                              = $null
                }
                $ErrorMessage = $_.Exception.Message
            }
            if ($Resolve) {
                if ($null -eq $Hash.Owner) { $Identity = $null } else { $Identity = Convert-Identity -Identity $Hash.Owner }
                if ($Identity) {
                    $Hash['OwnerName'] = $Identity.Name
                    $Hash['OwnerSid'] = $Identity.SID
                    $Hash['OwnerType'] = $Identity.Type
                } else {
                    $Hash['OwnerName'] = ''
                    $Hash['OwnerSid'] = ''
                    $Hash['OwnerType'] = ''
                }
            }
            $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-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
                    }
                }
                if ($ComputerSplit -eq 0) {
                    $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments -Verbose:$false
                    $OutputKeys = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumKey -Arguments $Arguments -Verbose:$false
                } else {
                    $Output2 = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumValues -Arguments $Arguments -ComputerName $Computers[$ComputerSplit] -Verbose:$false
                    $OutputKeys = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName EnumKey -ComputerName $Computers[$ComputerSplit] -Arguments $Arguments -Verbose:$false
                }
                foreach ($Entry in $Output2) {
                    $RegistryOutput = [ordered] @{}
                    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])"]
                            if ($ComputerSplit -eq 0) { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -Verbose:$false } else { $Values = Invoke-CimMethod -Namespace root\cimv2 -ClassName StdRegProv -MethodName $MethodName -Arguments $Arguments -ComputerName $Entry.PSComputerName -Verbose:$false }
                            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)
    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $DC = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        $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) {
            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
            $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
                if (Select-String -SimpleMatch "\0ACNF:" -InputObject $ConflictObject.ConflictDn) {
                    $SplitConfDN = $ConflictObject.ConflictDn -split "0ACNF:"
                    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 {
                    $SplitConfDN = $ConflictObject.ConflictDn -split "CNF:"
                    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 ($_ in $ForestInformation.Domains) {
            if ($IncludeDomains) {
                if ($_ -in $IncludeDomains) { $_.ToLower() }
                continue
            }
            if ($_ -notin $ExcludeDomains) { $_.ToLower() }
        }
        foreach ($Domain in $ForestInformation.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
        }
        [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] $IncludeGroupMembership,
        [switch] $IncludeAllTypes,
        [switch] $AddType,
        [switch] $Cache)
    Begin {
        if ($Cache -and -not $Script:CacheObjectsWinADObject) { $Script:CacheObjectsWinADObject = @{} }
        Add-Type -AssemblyName System.DirectoryServices.AccountManagement
        $GroupTypes = @{'2' = @{Name = 'Distribution Group - Global'
                Type                 = 'Distribution'
                Scope                = 'Global'
            }
            '4'             = @{Name = 'Distribution Group - Domain Local'
                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'
                Type               = 'Security'
                Scope              = 'Builtin local'
            }
            '-2147483644'   = @{Name = 'Security Group - Domain Local'
                Type               = 'Security'
                Scope              = 'Domain local'
            }
            '-2147483646'   = @{Name = 'Security Group - Global'
                Type               = 'Security'
                Scope              = 'Global'
            }
        }
    }
    process {
        foreach ($Ident in $Identity) {
            $ResolvedIdentity = $null
            if ($Ident.DistinguishedName) { $Ident = $Ident.DistinguishedName }
            $TemporaryName = $Ident
            $TemporaryDomainName = $DomainName
            if ($Cache -and $Script:CacheObjectsWinADObject[$TemporaryName]) {
                Write-Verbose "Get-WinADObject - Requesting $TemporaryName from Cache"
                $Script:CacheObjectsWinADObject[$TemporaryName]
                continue
            }
            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 '*@*') {
                    $CNConversion = $Ident -split '@', 2
                    $TemporaryDomainName = $CNConversion[1]
                    $Ident = $CNConversion[0]
                } elseif ($Ident -like '*DC=*') {
                    $DNConversion = ConvertFrom-DistinguishedName -DistinguishedName $Ident -ToDomainCN
                    $TemporaryDomainName = $DNConversion
                } 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]
                    }
                }
            }
            $Search = [System.DirectoryServices.DirectorySearcher]::new()
            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)" } }
            Try {
                $IdentityGUID = ""
                ([System.Guid]$Ident).ToByteArray() | ForEach-Object { $IdentityGUID += $("\{0:x2}" -f $_) }
            } Catch { $IdentityGUID = "null" }
            $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') {
                    if ($IncludeGroupMembership) {
                        $GroupMembers = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($Context, $Ident).Members
                        [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 '') {
                    $DisplayName = $ResolvedIdentity.Name
                    if ($DisplayName -like '*\*') {
                        $NetbiosWithName = $DisplayName -split '\\'
                        if ($NetbiosWithName.Count -eq 2) {
                            $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]
                    WhenCreated                         = $Object.properties.whencreated -as [string]
                    WhenChanged                         = $Object.properties.whenchanged -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
                    Description                         = $Object.properties.description -as [string]
                }
                if ($AddType) {
                    if (-not $ResolvedIdentity) { $ResolvedIdentity = ConvertFrom-SID -SID $ReturnObject['ObjectSID'] }
                    $ReturnObject['Type'] = $ResolvedIdentity.Type
                }
                if ($ReturnObject['Type'] -eq 'WellKnownAdministrative') { if (-not $TemporaryDomainName) { $ReturnObject['DomainName'] = '' } }
                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([Array] $ACL,
        [string] $Principal,
        [System.DirectoryServices.ActiveDirectoryRights] $AccessRule,
        [System.Security.AccessControl.AccessControlType] $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow)
    foreach ($SubACL in $ACL) {
        $OutputRequiresCommit = @(if ($Principal -like '*-*-*-*') { $Identity = [System.Security.Principal.SecurityIdentifier]::new($Principal) } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) }
            if (-not $AccessRule) {
                Write-Verbose "Remove-ADACL - Removing access for $($Identity) / All Rights"
                try {
                    $SubACL.ACL.RemoveAccess($Identity, $AccessControlType)
                    $true
                } catch {
                    Write-Warning "Remove-ADACL - Removing permissions for $($SubACL.DistinguishedName) failed: $($_.Exception.Message)"
                    $false
                }
            } else {
                foreach ($Rule in $AccessRule) {
                    $AccessRuleToRemove = [System.DirectoryServices.ActiveDirectoryAccessRule]::new($Identity, $Rule, $AccessControlType)
                    Write-Verbose "Remove-ADACL - Removing access for $($AccessRuleToRemove.IdentityReference) / $($AccessRuleToRemove.ActiveDirectoryRights)"
                    $SubACL.ACL.RemoveAccessRule($AccessRuleToRemove)
                }
            })
        if ($OutputRequiresCommit -notcontains $false -and $OutputRequiresCommit -contains $true) {
            Write-Verbose "Remove-ADACL - Saving permissions for $($SubACL.DistinguishedName)"
            Set-Acl -Path $SubACL.Path -AclObject $SubACL.ACL -ErrorAction Stop
        } elseif ($OutputRequiresCommit -contains $false) { Write-Warning "Remove-ADACL - Skipping saving permissions for $($SubACL.DistinguishedName) due to errors." }
    }
}
function Remove-EmptyValue { 
    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param([alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun)
    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) { $Hashtable.Remove($Key) } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if ($null -eq $Hashtable[$Key] -or ($Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') -or ($Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0)) { $Hashtable.Remove($Key) } } } else { if ($null -eq $Hashtable[$Key] -or ($Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') -or ($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([Array] $ADObject,
        [Parameter(Mandatory)][string] $Principal)
    Begin {
        if ($Principal -is [string]) {
            if ($Principal -like '*/*') {
                $SplittedName = $Principal -split '/'
                [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($SplittedName[0], $SplittedName[1])
            } else { [System.Security.Principal.IdentityReference] $Identity = [System.Security.Principal.NTAccount]::new($Principal) }
        } else { return }
    }
    Process {
        foreach ($Object in $ADObject) {
            if ($Object -is [Microsoft.ActiveDirectory.Management.ADOrganizationalUnit] -or $Object -is [Microsoft.ActiveDirectory.Management.ADEntity]) {
                [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
                if (-not (Get-PSDrive -Name $DNConverted -ErrorAction SilentlyContinue)) {
                    Write-Warning "Set-ADACLOwner - Drive $DNConverted not mapped. Terminating..."
                    return
                }
            }
            $PathACL = "$DNConverted`:\$($DistinguishedName)"
            $ACLs = Get-Acl -Path $PathACL -ErrorAction Stop
            $CurrentOwner = $ACLs.Owner
            Write-Verbose "Set-ADACLOwner - Changing owner from $($CurrentOwner) to $Identity for $($ACLs.Path)"
            try { $ACLs.SetOwner($Identity) } catch {
                Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $Identity for $($ACLs.Path): $($_.Exception.Message)"
                break
            }
            try { Set-Acl -Path $PathACL -AclObject $ACLs -ErrorAction Stop } catch { Write-Warning "Set-ADACLOwner - Unable to change owner from $($CurrentOwner) to $Identity for $($ACLs.Path): $($_.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,
        [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] }
        try { if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop } } catch { $PSCmdlet.WriteError($_) }
    }
}
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
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param([string] $OperatingSystem,
        [string] $OperatingSystemVersion)
    if ($OperatingSystem -like '*Windows 10*') {
        $Systems = @{'10.0 (19042)' = 'Windows 10 Insider Preview Build 19042.421 (20H2)'
            '10.0 (19041)'          = 'Windows 10 2004'
            '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 (18898)'          = 'Windows 10 Insider Preview'
            '10.0.19042'            = 'Windows 10 Insider Preview Build 19042.421 (20H2)'
            '10.0.19041'            = 'Windows 10 2004'
            '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.18898'            = 'Windows 10 Insider Preview'
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) { $System = $OperatingSystem }
    } elseif ($OperatingSystem -like '*Windows Server*') {
        $Systems = @{'5.2 (3790)' = 'Windows Server 2003'
            '6.1 (7601)'          = 'Windows Server 2008 R2'
            '10.0 (18362)'        = "Windows Server, version 1903 (Semi-Annual Channel) 1903"
            '10.0 (17763)'        = "Windows Server 2019 (Long-Term Servicing Channel) 1809"
            '10.0 (17134)'        = "Windows Server, version 1803 (Semi-Annual Channel) 1803"
            '10.0 (14393)'        = "Windows Server 2016 (Long-Term Servicing Channel) 1607"
            '10.0.18362'          = "Windows Server, version 1903 (Semi-Annual Channel) 1903"
            '10.0.17763'          = "Windows Server 2019 (Long-Term Servicing Channel) 1809"
            '10.0.17134'          = "Windows Server, version 1803 (Semi-Annual Channel) 1803"
            '10.0.14393'          = "Windows Server 2016 (Long-Term Servicing Channel) 1607"
        }
        $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-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-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
                    $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 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-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)] = [int] $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)] = [int] $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-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-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 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-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-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-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-PrivGPOZaurrLink {
    [cmdletBinding()]
    param(
        [Microsoft.ActiveDirectory.Management.ADObject] $Object,
        [switch] $Limited,
        [System.Collections.IDictionary] $GPOCache
    )
    if ($Object.GpLink -and $Object.GpLink.Trim() -ne '') {
        #$Object.GpLink -split { $_ -eq '[' -or $_ -eq ']' } -replace ';0' -replace 'LDAP://'
        $Object.GpLink -split '\[LDAP://' -split ';' | ForEach-Object -Process {
            #Write-Verbose $_
            if ($_.Length -gt 10) {
                $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $_ -ToDomainCN
                $Output = [ordered] @{
                    DistinguishedName = $Object.DistinguishedName
                    CanonicalName     = if ($Object.CanonicalName) { $Object.CanonicalName.TrimEnd('/') } else { $Object.CanonicalName }
                    Guid              = [Regex]::Match( $_, '(?={)(.*)(?<=})').Value -replace '{' -replace '}'
                }
                $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 $_ -ToDC
                        $Output['GPODistinguishedName'] = $_
                        [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 $_ -ToDC
                    $Output['GPODistinguishedName'] = $_
                    [PSCustomObject] $Output
                }
            }
        }
    } elseif ($Object.LinkedGroupPolicyObjects -and $Object.LinkedGroupPolicyObjects.Trim() -ne '') {
        $Object.LinkedGroupPolicyObjects -split '\[LDAP://' -split ';' | ForEach-Object -Process {
            if ($_.Length -gt 10) {
                $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $_ -ToDomainCN
                $Output = [ordered] @{
                    DistinguishedName = $Object.DistinguishedName
                    CanonicalName     = if ($Object.CanonicalName) { $Object.CanonicalName.TrimEnd('/') } else { $Object.CanonicalName }
                    Guid              = [Regex]::Match( $_, '(?={)(.*)(?<=})').Value -replace '{' -replace '}'
                }
                $Search = -join ($DomainCN, $Output['Guid'])
                if ($GPOCache -and -not $Limited) {
                    if ($GPOCache[$Search]) {
                        $Output['Name'] = $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 $_ -ToDC
                        $Output['GPODistinguishedName'] = $_
                        [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 $_ -ToDC
                    $Output['GPODistinguishedName'] = $_
                    [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-XMLGPO {
    [cmdletBinding()]
    param(
        [XML] $XMLContent,
        [Microsoft.GroupPolicy.Gpo] $GPO,
        [switch] $PermissionsOnly,
        [switch] $OwnerOnly,
        [System.Collections.IDictionary] $ADAdministrativeGroups,
        [string] $Splitter = [System.Environment]::NewLine,
        [switch] $ReturnObject
    )
    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
        }
    } else {
        $Linked = $false
        $LinksEnabledCount = 0
        $LinksDisabledCount = 0
        $LinksTotalCount = 0
    }
    #if ($null -eq $XMLContent.GPO.Computer.ExtensionData -and $null -eq $XMLContent.GPO.User.ExtensionData) {
    # $Empty = $true
    #} else {
    # $Empty = $false
    #}
    # 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
    }
    if ($XMLContent.GPO.User.Enabled -eq 'False') {
        $UserEnabled = $false
    } elseif ($XMLContent.GPO.User.Enabled -eq 'True') {
        $UserEnabled = $true
    }
    # Translate Enabled to same as GPO GUI
    if ($UserEnabled -eq $True -and $ComputerEnabled -eq $true) {
        $Enabled = 'Enabled'
    } elseif ($UserEnabled -eq $false -and $ComputerEnabled -eq $false) {
        $Enabled = 'All settings disabled'
    } elseif ($UserEnabled -eq $true -and $ComputerEnabled -eq $false) {
        $Enabled = 'Computer configuration settings disabled'
    } elseif ($UserEnabled -eq $false -and $ComputerEnabled -eq $true) {
        $Enabled = 'User configuration settings disabled'
    }

    $ComputerSettingsAvailable = if ($null -eq $XMLContent.GPO.Computer.ExtensionData) { $false } else { $true }
    $UserSettingsAvailable = if ($null -eq $XMLContent.GPO.User.ExtensionData) { $false } else { $true }

    if ($ComputerSettingsAvailable -eq $false -and $UserSettingsAvailable -eq $false) {
        $Empty = $true
    } else {
        $Empty = $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[]' }

    $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-XMLStandard - things went sideways $($_.Exception.Message)"
                continue
            }
        }
        $KeysToLoop
    }
    $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-XMLStandard - things went sideways $($_.Exception.Message)"
                continue
            }
        }
        $KeysToLoop
    }

    $ComputerSettingsAvailableReal = if ($OutputComputer) { $true } else { $false }
    $UserSettingsAvailableReal = if ($OutputUser) { $true } else { $false }

    if (-not $ComputerSettingsAvailableReal -and -not $UserSettingsAvailableReal) {
        $EmptyMaybe = $true
    } else {
        $EmptyMaybe = $false
    }

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

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

    if (-not $PermissionsOnly) {
        if ($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'
            }
        } else {
            $OwnerType = 'Unknown'
        }
    }
    if ($PermissionsOnly) {
        [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) {
        [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 {
        [PsCustomObject] @{
            'DisplayName'                       = $XMLContent.GPO.Name
            'DomainName'                        = $XMLContent.GPO.Identifier.Domain.'#text'
            'GUID'                              = $XMLContent.GPO.Identifier.Identifier.InnerText -replace '{' -replace '}'
            'Empty'                             = $Empty
            'EmptyMaybe'                        = $EmptyMaybe
            'Linked'                            = $Linked
            'LinksCount'                        = $LinksTotalCount
            'LinksEnabledCount'                 = $LinksEnabledCount
            'LinksDisabledCount'                = $LinksDisabledCount
            'Enabled'                           = $Enabled
            'ComputerEnabled'                   = $ComputerEnabled
            'ComputerOptimized'                 = $ComputerOptimized
            'ComputerProblem'                   = $ComputerProblem
            'UserEnabled'                       = $UserEnabled
            'UserOptimized'                     = $UserOptimized
            'UserProblem'                       = $UserProblem
            'ComputerSettingsAvailable'         = $ComputerSettingsAvailable
            'UserSettingsAvailable'             = $UserSettingsAvailable
            'ComputerSettingsAvailableReal'     = $ComputerSettingsAvailableReal
            'UserSettingsAvailableReal'         = $UserSettingsAvailableReal
            'ComputerSettingsTypes'             = $OutputComputer.Name
            'UserSettingsTypes'                 = $OutputUser.Name
            '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
            'ComputerPolicies'                  = $XMLContent.GPO.Computer.ExtensionData.Name -join ", "
            'UserPolicies'                      = $XMLContent.GPO.User.ExtensionData.Name -join ", "
            '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'                             = @(
                $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
                    }
                }
            }
            'GPOObject'                         = $GPO
        }
    }
}
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        = {
        Get-GPOZaurrInheritance -IncludeBlockedObjects -OnlyBlockedInheritance -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {

    }
    Variables      = @{

    }
    Overview       = {

    }
    Solution       = {
        New-HTMLTable -DataTable $Script:Reporting['GPOBlockedInheritance']['Data'] -Filtering
        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
                }
            }
        }
    }
}
$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 meens two different sets of permissions. Group Policy module is making sure the data in both places is correct. "
            "However, for different reasons it's not nessecary the case and often permissions go out of sync between AD and SYSVOL. "
            "This test verifies 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 are 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['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
                }
            }
        }
        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. "
                            New-HTMLText -Text "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."
                        }
                        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
                }
            }
        }
    }
}
$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 PaleGreen, 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['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
                }
            }
        }
        New-HTMLSection -Name 'Steps to fix - Remove duplicate (CNF) objects' {
            New-HTMLContainer {
                New-HTMLSpanStyle -FontSize 10pt {
                    New-HTMLText -Text 'Following steps will guide you how to remove duplicate objects'
                    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 -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-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
                }
            }
        }
    }
}
$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
                }
            }
        }
    }
}
$GPOZaurrList = [ordered] @{
    Name       = 'Group Policy Summary'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing = {
        foreach ($GPO in $Script:Reporting['GPOList']['Data']) {
            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']['GPOValid']++
                $Script:Reporting['GPOList']['Variables']['GPOLinked']++
                $Script:Reporting['GPOList']['Variables']['GPONotEmpty']++
            }
            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']++
            }
        }
        $Script:Reporting['GPOList']['Variables']['GPOTotal'] = $Script:Reporting['GPOList']['Data'].Count
        if ($Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked'] -gt 0) {
            $Script:Reporting['GPOList']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOList']['ActionRequired'] = $false
        }
    }
    Variables  = @{
        GPOWithProblems          = 0
        ComputerOptimizedYes     = 0
        ComputerOptimizedNo      = 0
        ComputerProblemYes       = 0
        ComputerProblemNo        = 0
        UserOptimizedYes         = 0
        UserOptimizedNo          = 0
        UserProblemYes           = 0
        UserProblemNo            = 0
        GPONotLinked             = 0
        GPOLinked                = 0
        GPOEmpty                 = 0
        GPONotEmpty              = 0
        GPOEmptyAndUnlinked      = 0
        GPOEmptyOrUnlinked       = 0
        GPOLinkedButEmpty        = 0
        GPOValid                 = 0
        GPOLinkedButLinkDisabled = 0
        GPOTotal                 = 0
    }
    Overview   = {
        New-HTMLPanel {
            New-HTMLText -Text 'Following chart presents ', 'Linked / Empty and Unlinked Group Policies' -FontSize 10pt -FontWeight normal, bold
            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 to delete: ", $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked'] -FontWeight 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-HTMLText -FontSize 10pt -Text 'Usually empty or unlinked Group Policies are safe to delete.'
            New-HTMLChart -Title 'Group Policies Summary' {
                New-ChartBarOptions -Type barStacked
                #New-ChartLegend -Names 'Unlinked', 'Linked', 'Empty', 'Total' -Color Salmon, PaleGreen, PaleVioletRed, PaleTurquoise
                New-ChartLegend -Names 'Good', 'Bad' -Color PaleGreen, Salmon
                #New-ChartBar -Name 'Group Policies' -Value $Script:Reporting['GPOList']['Variables']['GPONotLinked'], $Script:Reporting['GPOList']['Variables']['GPOLinked'], $Script:Reporting['GPOList']['Variables']['GPOEmpty'], $Script:Reporting['GPOList']['Variables']['GPOTotal']
                New-ChartBar -Name 'Linked' -Value $Script:Reporting['GPOList']['Variables']['GPOLinked'], $Script:Reporting['GPOList']['Variables']['GPONotLinked']
                New-ChartBar -Name 'Empty' -Value $Script:Reporting['GPOList']['Variables']['GPONotEmpty'], $Script:Reporting['GPOList']['Variables']['GPOEmpty']
                New-ChartBar -Name 'Valid' -Value $Script:Reporting['GPOList']['Variables']['GPOValid'], $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']
            } -TitleAlignment center
        }
    }
    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. "
            "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 to delete: ", $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked'] -FontWeight 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
                }
            }
        } -FontSize 10pt
        New-HTMLText -Text "Additionally, we're reviewing Group Policies that have their section disabled, but contain data. Please review them and make sure this configuration is as expected!" -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 "For best performance it's recommended that if there are no settings of certain kind (Computer or User settings) it's best to disable them. " -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 -TextBlock {
            'All empty or unlinked 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
    }
    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 'Empty' -Value $Script:Reporting['GPOList']['Variables']['GPONotEmpty'], $Script:Reporting['GPOList']['Variables']['GPOEmpty']
                    New-ChartBar -Name 'Valid' -Value $Script:Reporting['GPOList']['Variables']['GPOValid'], $Script:Reporting['GPOList']['Variables']['GPOEmptyOrUnlinked']
                    New-ChartBar -Name 'With problem (computers)' -Value $Script:Reporting['GPOList']['Variables']['ComputerProblemNo'], $Script:Reporting['GPOList']['Variables']['ComputerProblemYes']
                    New-ChartBar -Name 'With 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-HTMLTable -DataTable $Script:Reporting['GPOList']['Data'] -Filtering {
                New-HTMLTableCondition -Name 'Empty' -Value $true -BackgroundColor Salmon -ComparisonType string
                New-HTMLTableCondition -Name 'Linked' -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 '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['GPOList']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & 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
                }
            }
        }
        New-HTMLSection -Name 'Steps to fix - Empty & Unlinked 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."
                        }
                        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 'Remove GPOs that are EMPTY or UNLINKED' {
                            New-HTMLText -Text @(
                                "Following command when executed removes every ",
                                "EMPTY"
                                " or "
                                "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."
                            ) -FontWeight normal, bold, normal, bold, normal, bold, normal, normal -Color Black, Red, Black, Red, Black
                            New-HTMLCodeBlock -Code {
                                Remove-GPOZaurr -Type Empty, Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -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 {
                                Remove-GPOZaurr -Type Empty, Unlinked -BackupPath "$Env:UserProfile\Desktop\GPO" -LimitProcessing 2 -Verbose
                            }
                            New-HTMLText -TextBlock {
                                "This command when executed deletes only first empty or 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 on SYSVOL side, 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 empty or unlinked GPOs in the report you're done! Enjoy rest of the day!" -Color BlueDiamond
                        }
                    } -RemoveDoneStepOnNavigateBack -Theme arrows -ToolbarButtonPosition center
                }
            }
        }
    }
}
$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 LightGreen
                    New-ChartPie -Name 'Incorrect Owners' -Value $Script:Reporting['NetLogonOwners']['Variables']['NetLogonOwnersToFix'] -Color Crimson
                } -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
            }
        }
        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
                }
            }
        }
        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-HTMLTab -Name 'NetLogon Owners' {
        # 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 File Owners' {
        # 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-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor LightGreen -ComparisonType string -Operator eq
        # New-HTMLTableCondition -Name 'Status' -Value "OK" -BackgroundColor Salmon -ComparisonType string -Operator ne
        # }
        # }
        # New-HTMLSection -Name 'Steps to fix NetLogon Owners ' {
        # New-HTMLContainer {
        # New-HTMLSpanStyle -FontSize 10pt {
        # New-HTMLText -Text 'Following steps will guide you how to fix NetLogon Owners and make them compliant.'
        # 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 NetLogon
        # }
        # 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 {
        # "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-GPOZaurrNetLogonOwner -Verbose -LimitProcessing 2
        # }
        # 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-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 NetLogon
        # }
        # 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
        # }
        # }
        # }
        # 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
        # }
        # }
        # }
        # }
        #New-HTMLTab -Name 'NetLogon Permissions' {
        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
            }
        }
        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 '
                            ) -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
                }
            }
        }
        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
                }
            }
        }
    }
}
$GPOZaurrOrphans = [ordered] @{
    Name           = 'Orphaned Group Policies'
    Enabled        = $true
    ActionRequired = $null
    Data           = $null
    Execute        = {
        Get-GPOZaurrBroken -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing     = {
        $Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'] = @{}
        $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssuePerDomain'] = @{}
        foreach ($GPO in $Script:Reporting['GPOOrphans']['Data']) {
            if (-not $Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName] = 0
            }
            if (-not $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]) {
                $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName] = 0
            }
            if ($GPO.Status -eq 'Not available in AD') {
                $Script:Reporting['GPOOrphans']['Variables']['NotAvailableInAD']++
                $Script:Reporting['GPOOrphans']['Variables']['ToBeDeleted']++
                $Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
            } elseif ($GPO.Status -eq 'Not available on SYSVOL') {
                $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol']++
                $Script:Reporting['GPOOrphans']['Variables']['ToBeDeleted']++
                $Script:Reporting['GPOOrphans']['Variables']['ToBeDeletedPerDomain'][$GPO.DomainName]++
            } elseif ($GPO.Status -eq 'Permissions issue') {
                $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssue']++
                $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssuePerDomain'][$GPO.DomainName]++
            }
        }
        if ($Script:Reporting['GPOOrphans']['Variables']['ToBeDeleted'] -gt 0) {
            $Script:Reporting['GPOOrphans']['ActionRequired'] = $true
        } else {
            $Script:Reporting['GPOOrphans']['ActionRequired'] = $false
        }
    }
    Variables      = @{
        NotAvailableInAD                     = 0
        NotAvailableOnSysvol                 = 0
        NotAvailablePermissionIssue          = 0
        NotAvailablePermissionIssuePerDomain = $null
        ToBeDeleted                          = 0
        ToBeDeletedPerDomain                 = $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['GPOOrphans']['Variables']['NotAvailableInAD'] -FontWeight normal, bold
                New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold
                New-HTMLListItem -Text "Group Policies which couldn't be assed due to permissions issue: ", $Script:Reporting['GPOOrphans']['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['GPOOrphans']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOOrphans']['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['GPOOrphans']['Variables']['NotAvailableInAD'] -FontWeight normal, bold
            New-HTMLListItem -Text 'Group Policies in AD, but no content on SYSVOL: ', $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol'] -FontWeight normal, bold
            New-HTMLListItem -Text "Group Policies which couldn't be assed due to permissions issue: ", $Script:Reporting['GPOOrphans']['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['GPOOrphans']['Variables']['ToBeDeletedPerDomain'].Keys) {
                New-HTMLListItem -Text "$Domain requires ", $Script:Reporting['GPOOrphans']['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['GPOOrphans']['Summary']
            }
            New-HTMLPanel {
                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['GPOOrphans']['Variables']['NotAvailableInAD'], $Script:Reporting['GPOOrphans']['Variables']['NotAvailableOnSysvol'], $Script:Reporting['GPOOrphans']['Variables']['NotAvailablePermissionIssue']
                } -Title 'Broken / Orphaned Group Policies' -TitleAlignment center
            }
        }
        New-HTMLSection -Name 'Health State of Group Policies' {
            New-HTMLTable -DataTable $Script:Reporting['GPOOrphans']['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 "Permissions issue" -BackgroundColor MediumVioletRed -ComparisonType string -Color White
            } -PagingOptions 10, 20, 30, 40, 50
        }
        New-HTMLSection -Name 'Steps to fix - Not available on SYSVOL / Active Directory' {
            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 GPOOrphans
                            }
                            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 '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 GPOOrphans
                            }
                            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
                }
            }
        }
        if ($Script:Reporting['GPOOrphans']['WarningsAndErrors']) {
            New-HTMLSection -Name 'Warnings & Errors to Review' {
                New-HTMLTable -DataTable $Script:Reporting['GPOOrphans']['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        = {
        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.IsOwnerConsistent) {
                $Script:Reporting['GPOOwners']['Variables']['IsConsistent']++
            } else {
                $Script:Reporting['GPOOwners']['Variables']['IsNotConsistent']++
            }
            if ($GPO.IsOwnerAdministrative) {
                $Script:Reporting['GPOOwners']['Variables']['IsAdministrative']++
            } else {
                $Script:Reporting['GPOOwners']['Variables']['IsNotAdministrative']++
            }
            if (($GPO.IsOwnerAdministrative -eq $false -or $GPO.IsOwnerConsistent -eq $false) -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
        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 -TextBlock {
            "By default GPO creation is usually maintained by Domain Admins or Enterprise Admins. "
            "When GPO is created by member of Domain Admins or Enterprise Admins group the GPO Owner is set to Domain Admins. "
            "When GPO is created by 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 relevant user. "
            "GPO Owners should be Domain Admins or Enterprise Admins to prevent abuse. If that isn't so it means owner is able to fully control GPO and potentially change it's settings in uncontrolled way. "
            "While at the moment of creation of new GPO it's not a problem, in long term it's possible such person may no longer be admin, yet keep their rights over GPO. "
        }
        New-HTMLText -FontSize 10pt -TextBlock {
            "As you're 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 are 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 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
    }
    Solution       = {
        New-HTMLSection -Invisible {
            New-HTMLPanel {
                & $Script:GPOConfiguration['GPOOwners']['Summary']
            }
            New-HTMLPanel {
                New-HTMLChart {
                    New-ChartBarOptions -Type barStacked
                    New-ChartLegend -Name 'Yes', 'No' -Color LightGreen, Salmon
                    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
            }
        }
        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
            } -PagingOptions 10, 20, 30, 40, 50
        }
        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."
                        }
                        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."
                            }
                        }
                        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
                }
            }
        }
        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
                }
            }
        }
    }
}
$GPOZaurrPermissions = [ordered] @{
    Name       = 'Group Policy Permissions'
    Enabled    = $true
    Action     = $null
    Data       = $null
    Execute    = {
        Get-GPOZaurrPermission -Type All -IncludePermissionType GpoEditDeleteModifySecurity, GpoEdit, GpoCustom -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
    }
    Processing = {

    }
    Variables  = @{

    }
    Overview   = {

    }
    Solution   = {
        New-HTMLTable -DataTable $Script:Reporting['GPOPermissions']['Data'] -Filtering
        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
        }
    }
    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: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 LightGreen, 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
        }
        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
                }
            }
        }
        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
                }
            }
        }
    }
}
$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-GPOZaurrReportConsole {
    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $Results
    )
    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 {
        Write-Color -Text 'Computer Settings' -Color White -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
        }

        Write-Color -Text 'User Settings' -Color Yellow -LinesBefore 1
        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] $Offline,
        [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-HTMLTabOptions -SlimTabs `
            -BorderBottomStyleActive solid -BorderBottomColorActive LightSkyBlue -BackgroundColorActive none `
            -TextColorActive Black -Align left -BorderRadius 0px -RemoveShadow -TextColor Grey -TextTransform capitalize
        New-HTMLTab -Name 'Information' {
            New-HTMLTable -DataTable $Support.ResultantSetPolicy -HideFooter
        }
        foreach ($Key in $Support.Keys) {
            if ($Key -eq 'ResultantSetPolicy') {
                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)
                        }
                        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-HTMLTable -DataTable $Support.$Key.ExtensionStatus -Filtering
                        }
                    }
                    New-HTMLSection -HeaderText 'Group Policies' {
                        New-HTMLTable -DataTable $Support.$Key.GroupPolicies -Filtering
                    }
                    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:(-not $Offline.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] @{
    GPOOrphans            = $GPOZaurrOrphans
    GPOOwners             = $GPOZaurrOwners
    GPOConsistency        = $GPOZaurrConsistency
    GPODuplicates         = $GPOZaurrDuplicates
    GPOList               = $GPOZaurrList
    GPOPassword           = $GPOZaurrPassword
    GPOPermissions        = $GPOZaurrPermissions
    GPOPermissionsRead    = $GPOZaurrPermissionsRead
    GPOPermissionsRoot    = $GPOZaurrPermissionsRoot
    GPOFiles              = $GPOZaurrFiles
    GPOBlockedInheritance = $GPOZaurrBlockedInheritance
    GPOAnalysis           = $GPOZaurrAnalysis
    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 = '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-XMLGenericPublicKey -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLGenericPublicKey -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
        }
    }
    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
        }
    }
    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 = $GPOs.ID.GUID
    $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 -replace '{' -replace '}'
        $SysvolHash[$GUID] = $_
    }
    $Files = $SYSVOL.Name -replace '{' -replace '}'
    if ($Files) {
        $Comparing = Compare-Object -ReferenceObject $GPOGUIDS -DifferenceObject $Files -IncludeEqual
        foreach ($_ in $Comparing) {
            if ($_.InputObject -eq 'PolicyDefinitions') {
                # we skip policy definitions
                continue
            }
            if ($_.SideIndicator -eq '==') {
                $Found = 'Exists'
            } elseif ($_.SideIndicator -eq '<=') {
                $Found = 'Not available on SYSVOL'
            } elseif ($_.SideIndicator -eq '=>') {
                if ($PoliciesAD[$_.InputObject]) {
                    $Found = $PoliciesAD[$_.InputObject]
                } else {
                    $Found = 'Not available in AD'
                }
            } else {
                $Found = 'Orphaned GPO'
            }
            $Differences[$_.InputObject] = $Found
        }
    }
    $GPOSummary = @(
        $Count = 0
        foreach ($GPO in $GPOS) {
            $Count++
            Write-Verbose "Get-GPOZaurrBroken - Processing [$($GPO.DomainName)]($Count/$($GPOS.Count)) $($GPO.DisplayName)"
            if ($null -ne $SysvolHash[$GPO.Id.GUID].FullName) {
                $FullPath = $SysvolHash[$GPO.Id.GUID].FullName
                try {
                    $ACL = Get-Acl -Path $SysvolHash[$GPO.Id.GUID].FullName -ErrorAction Stop -Verbose:$false
                    $Owner = $ACL.Owner
                    $ErrorMessage = ''
                } catch {
                    Write-Warning "Get-GPOZaurrBroken - ACL reading (1) failed for $FullPath with error: $($_.Exception.Message)"
                    $ACL = $null
                    $Owner = ''
                    $ErrorMessage = $_.Exception.Message
                }
            } else {
                $FullPath = -join ($SysVolPath, "\{$($GPO.Id.Guid)}")
                $ACL = $null
                $Owner = ''
                $ErrorMessage = 'Not found on SYSVOL'
            }
            if ($null -eq $Differences[$GPO.Id.Guid]) {
                $SysVolStatus = 'Unknown Issue'
            } else {
                $SysVolStatus = $Differences[$GPO.Id.Guid]
            }
            [PSCustomObject] @{
                DisplayName       = $GPO.DisplayName
                Status            = $SysVolStatus
                DomainName        = $GPO.DomainName
                SysvolServer      = $Server
                SysvolStatus      = $SysVolStatus
                GpoStatus         = $GPO.GpoStatus
                Owner             = $GPO.Owner
                FileOwner         = $Owner
                Id                = $GPO.Id.Guid
                Path              = $FullPath
                DistinguishedName = -join ("CN={", $GPO.Id.Guid, "},", $PoliciesSearchBase)
                Description       = $GPO.Description
                CreationTime      = $GPO.CreationTime
                ModificationTime  = $GPO.ModificationTime
                UserVersion       = $GPO.UserVersion
                ComputerVersion   = $GPO.ComputerVersion
                WmiFilter         = $GPO.WmiFilter
                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', 'Permissions issue') {
                $FullPath = $SysvolHash[$_].FullName
                try {
                    $ACL = Get-Acl -Path $FullPath -ErrorAction Stop
                    $Owner = $ACL.Owner
                    $ErrorMessage = ''
                } catch {
                    Write-Warning "Get-GPOZaurrBroken - ACL reading (2) failed for $FullPath with error: $($_.Exception.Message)"
                    $ACL = $null
                    $Owner = $null
                    $ErrorMessage = $_.Exception.Message
                }

                [PSCustomObject] @{
                    DisplayName       = $SysvolHash[$_].BaseName
                    Status            = $Differences[$_]
                    DomainName        = $Domain
                    SysvolServer      = $Server
                    SysvolStatus      = 'Exists' #$Differences[$GPO.Id.Guid]
                    GpoStatus         = $Differences[$_]
                    Owner             = ''
                    FileOwner         = $Owner
                    Id                = $_
                    Path              = $FullPath
                    DistinguishedName = -join ("CN={", $_, "},", $PoliciesSearchBase)
                    Description       = $null
                    CreationTime      = $SysvolHash[$_].CreationTime
                    ModificationTime  = $SysvolHash[$_].LastWriteTime
                    UserVersion       = $null
                    ComputerVersion   = $null
                    WmiFilter         = $null
                    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

    $AdministrativeExists = @{
        DomainAdmins     = $false
        EnterpriseAdmins = $false
    }
    $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 {
            # Prepare data to clean
            $Skip = $false
            $AdministrativeExists['EnterpriseAdmins'] = $false
            $AdministrativeExists['DomainAdmins'] = $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 $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 ($AdministrativeExists['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 ($AdministrativeExists['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 {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [int] $LimitProcessing,
        [validateset('All', 'Empty', 'Unlinked')][string[]] $Type = 'All',
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string[]] $GPOPath,
        [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 -GPOPath $GPOPath | ForEach-Object {
            if ($Type -contains 'All') {
                Write-Verbose "Backup-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName)"
                $Count++
                try {
                    $BackupInfo = Backup-GPO -Guid $_.GUID -Domain $_.DomainName -Path $BackupFinalPath -ErrorAction Stop #-Server $QueryServer
                    $BackupInfo
                } catch {
                    Write-Warning "Backup-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName) failed: $($_.Exception.Message)"
                }
                if ($LimitProcessing -eq $Count) {
                    break
                }
            }
            if ($Type -notcontains 'All' -and $Type -contains 'Empty') {
                if ($_.ComputerSettingsAvailable -eq $false -and $_.UserSettingsAvailable -eq $false) {
                    Write-Verbose "Backup-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName)"
                    $Count++
                    try {
                        $BackupInfo = Backup-GPO -Guid $_.GUID -Domain $_.DomainName -Path $BackupFinalPath -ErrorAction Stop #-Server $QueryServer
                        $BackupInfo
                    } catch {
                        Write-Warning "Backup-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName) failed: $($_.Exception.Message)"
                    }
                    if ($LimitProcessing -eq $Count) {
                        break
                    }
                }
            }
            if ($Type -notcontains 'All' -and $Type -contains 'Unlinked') {
                if ($_.Linked -eq $false) {
                    Write-Verbose "Backup-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName)"
                    $Count++
                    try {
                        $BackupInfo = Backup-GPO -Guid $_.GUID -Domain $_.DomainName -Path $BackupFinalPath -ErrorAction Stop #-Server $QueryServer
                        $BackupInfo
                    } catch {
                        Write-Warning "Backup-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.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 {
    [cmdletBinding()]
    param(
        [string] $GPOName,
        [alias('GUID', 'GPOID')][string] $GPOGuid,

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

        [switch] $PermissionsOnly,
        [switch] $OwnerOnly,
        [switch] $Limited,
        [switch] $ReturnObject,
        [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
        }
    }
    Process {
        if (-not $GPOPath) {
            foreach ($Domain in $ForestInformation.Domains) {
                $QueryServer = $ForestInformation.QueryServers[$Domain]['HostName'][0]
                $Count = 0
                if ($GPOName) {
                    $GroupPolicies = Get-GPO -Name $GPOName -Domain $Domain -Server $QueryServer -ErrorAction SilentlyContinue
                    $GroupPolicies | ForEach-Object {
                        $Count++
                        #Write-Verbose "Get-GPOZaurr - Getting GPO $($_.DisplayName) / ID: $($_.ID) from $Domain"
                        Write-Verbose "Get-GPOZaurr - Processing [$($_.DomainName)]($Count/$($GroupPolicies.Count)) $($_.DisplayName)"
                        if (-not $Limited) {
                            try {
                                $XMLContent = Get-GPOReport -ID $_.ID -ReportType XML -Server $ForestInformation.QueryServers[$Domain].HostName[0] -Domain $Domain -ErrorAction Stop
                            } catch {
                                Write-Warning "Get-GPOZaurr - Failed to get GPOReport: $($_.Exception.Message). Skipping."
                                continue
                            }
                            Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -GPO $_ -PermissionsOnly:$PermissionsOnly.IsPresent -ADAdministrativeGroups $ADAdministrativeGroups -ReturnObject:$ReturnObject.IsPresent
                        } else {
                            $_
                        }
                    }
                } elseif ($GPOGuid) {
                    $GroupPolicies = Get-GPO -Guid $GPOGuid -Domain $Domain -Server $QueryServer -ErrorAction SilentlyContinue
                    $GroupPolicies | ForEach-Object {
                        $Count++
                        #Write-Verbose "Get-GPOZaurr - Getting GPO $($_.DisplayName) / ID: $($_.ID) from $Domain"
                        Write-Verbose "Get-GPOZaurr - Processing [$($_.DomainName)]($Count/$($GroupPolicies.Count)) $($_.DisplayName)"
                        if (-not $Limited) {
                            try {
                                $XMLContent = Get-GPOReport -ID $_.ID -ReportType XML -Server $ForestInformation.QueryServers[$Domain].HostName[0] -Domain $Domain -ErrorAction Stop
                            } catch {
                                Write-Warning "Get-GPOZaurr - Failed to get GPOReport: $($_.Exception.Message). Skipping."
                                continue
                            }
                            Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -GPO $_ -PermissionsOnly:$PermissionsOnly.IsPresent -ADAdministrativeGroups $ADAdministrativeGroups -ReturnObject:$ReturnObject.IsPresent
                        } else {
                            $_
                        }
                    }
                } else {
                    $GroupPolicies = Get-GPO -All -Server $QueryServer -Domain $Domain -ErrorAction SilentlyContinue
                    $GroupPolicies | ForEach-Object {
                        $Count++
                        #Write-Verbose "Get-GPOZaurr - Getting GPO $($_.DisplayName) / ID: $($_.ID) from $Domain"
                        Write-Verbose "Get-GPOZaurr - Processing [$($_.DomainName)]($Count/$($GroupPolicies.Count)) $($_.DisplayName)"
                        if (-not $Limited) {
                            try {
                                $XMLContent = Get-GPOReport -ID $_.ID -ReportType XML -Server $ForestInformation.QueryServers[$Domain].HostName[0] -Domain $Domain -ErrorAction Stop
                            } catch {
                                Write-Warning "Get-GPOZaurr - Failed to get GPOReport: $($_.Exception.Message). Skipping."
                                continue
                            }
                            Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -GPO $_ -PermissionsOnly:$PermissionsOnly.IsPresent -ADAdministrativeGroups $ADAdministrativeGroups -ReturnObject:$ReturnObject.IsPresent
                        } else {
                            $_
                        }
                    }
                }
            }
        } else {
            foreach ($Path in $GPOPath) {
                Write-Verbose "Get-GPOZaurr - Getting GPO content from XML files"
                Get-ChildItem -LiteralPath $Path -Recurse -Filter *.xml | ForEach-Object {
                    $XMLContent = [XML]::new()
                    $XMLContent.Load($_.FullName)
                    Get-XMLGPO -OwnerOnly:$OwnerOnly.IsPresent -XMLContent $XMLContent -PermissionsOnly:$PermissionsOnly.IsPresent
                }
                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,
        [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]
                }
            }
            Get-ADObject @Splat -Properties DisplayName, Name, Created, Modified, 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['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 {
    [alias('Get-GPOZaurrSysvol')]
    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [Array] $GPOs,
        [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
            foreach ($Policy in $PoliciesInAD) {
                $GUIDFromDN = ConvertFrom-DistinguishedName -DistinguishedName $Policy.DistinguishedName
                $GUIDFromDN = $GUIDFromDN -replace '{' -replace '}'
                $GUID = $Policy.Name -replace '{' -replace '}'
                if ($GUID -and $GUIDFromDN) {
                    $PoliciesAD[$GUIDFromDN] = 'Exists'
                } else {
                    $PoliciesAD[$GUIDFromDN] = 'Permissions issue'
                }
            }
        }
        Try {
            [Array]$GPOs = Get-GPO -All -Domain $Domain -Server $QueryServer
        } catch {
            Write-Warning "Get-GPOZaurrBroken - Couldn't get GPOs from $Domain. Error: $($_.Exception.Message)"
            continue
        }
        if ($GPOs.Count -ge 2) {
            if (-not $VerifyDomainControllers) {
                Test-SysVolFolders -GPOs $GPOs -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 $GPOs -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-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 {
    [cmdletBinding()]
    param(
        [switch] $IncludeBlockedObjects,
        [switch] $OnlyBlockedInheritance,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    Begin {
        $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }
    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 }
                }
                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
                        }
                    }
                    if ($OnlyBlockedInheritance) {
                        if ($InheritanceInformation.BlockedInheritance -eq $true) {
                            [PSCustomObject] $InheritanceInformation
                        }
                    } else {
                        [PSCustomObject] $InheritanceInformation
                    }
                }
                $InheritanceInformation['DistinguishedName'] = $OU.DistinguishedName
            }
        }
    }
}

<#
 
$OrganizationalUnits = Get-ADOrganizationalUnit -Filter *
$Output = foreach ($OU in $OrganizationalUnits) {
    Get-GPInheritance -Target $OU.DistinguishedName
}
$Output | Format-Table
#>

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()]
    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', 'Site', 'Other')][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
    )
    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) {
                $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                Get-GPO -All -DomainName $Domain -Server $QueryServer | ForEach-Object {
                    $GPOCache["$Domain$($_.ID.Guid)"] = $_
                }
            }
        }
    }
    Process {
        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']
                        #if ($SearchBase -notlike "*$DomainDistinguishedName") {
                        # we check if SearchBase is part of domain distinugishname. If it isn't we skip
                        # continue
                        #}
                        $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
                        $Splat['SearchBase'] = $SearchBase
                        try {
                            $ADObjectGPO = Get-ADObject @Splat
                        } catch {
                            Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                        }
                        foreach ($_ in $ADObjectGPO) {
                            $OutputGPOs = Get-PrivGPOZaurrLink -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
                                    }
                                }
                            }
                        }
                    }
                    if ($Linked -contains 'Root') {
                        $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                        #if ($SearchBase -notlike "*$DomainDistinguishedName") {
                        # we check if SearchBase is part of domain distinugishname. If it isn't we skip
                        # continue
                        # }
                        $Splat['Filter'] = "objectClass -eq 'domainDNS'"
                        $Splat['SearchBase'] = $SearchBase
                        try {
                            $ADObjectGPO = Get-ADObject @Splat
                        } catch {
                            Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                        }
                        foreach ($_ in $ADObjectGPO) {
                            $OutputGPOs = Get-PrivGPOZaurrLink -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
                                    }
                                }
                            }
                        }
                    }
                    if ($Linked -contains 'Site') {
                        # Sites are defined only in primary domain
                        if ($ForestInformation['DomainsExtended'][$Domain]['DNSRoot'] -eq $ForestInformation['DomainsExtended'][$Domain]['Forest']) {
                            $SearchBase = -join ("CN=Configuration,", $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName'])
                            # if ($SearchBase -notlike "*$DomainDistinguishedName") {
                            # we check if SearchBase is part of domain distinugishname. If it isn't we skip
                            #continue
                            #}
                            $Splat['Filter'] = "(objectClass -eq 'site')"
                            $Splat['SearchBase'] = $SearchBase
                            try {
                                $ADObjectGPO = Get-ADObject @Splat
                            } catch {
                                Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                            }
                            foreach ($_ in $ADObjectGPO) {
                                Get-PrivGPOZaurrLink -Object $_ -Limited:$Limited.IsPresent -GPOCache $GPOCache
                            }
                        }
                    }
                    if ($Linked -contains 'Other') {
                        $SearchBase = $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']
                        #if ($SearchBase -notlike "*$DomainDistinguishedName") {
                        # we check if SearchBase is part of domain distinugishname. If it isn't we skip
                        # continue
                        #}
                        $Splat['Filter'] = "(objectClass -eq 'organizationalUnit')"
                        $Splat['SearchBase'] = $SearchBase
                        try {
                            $ADObjectGPO = Get-ADObject @Splat
                        } catch {
                            Write-Warning "Get-GPOZaurrLink - Get-ADObject error $($_.Exception.Message)"
                        }
                        foreach ($_ in $ADObjectGPO) {
                            if ($_.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DistinguishedName']) {
                                # other skips Domain Root
                            } elseif ($_.DistinguishedName -eq $ForestInformation['DomainsExtended'][$Domain]['DomainControllersContainer']) {
                                # other skips Domain Controllers
                            } else {
                                $OutputGPOs = Get-PrivGPOZaurrLink -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
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                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)"
                    }
                    foreach ($_ in $ADObjectGPO) {
                        $OutputGPOs = Get-PrivGPOZaurrLink -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
                                }
                            }
                        }
                    }
                }
            }
        } else {
            foreach ($Object in $ADObject) {
                Get-PrivGPOZaurrLink -Object $Object -Limited:$Limited.IsPresent -GPOCache $GPOCache
            }
        }
    }
    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
    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")
        $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))"
        }
        foreach ($File in $Files) {
            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] @{ 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-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
 
    .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
    )
    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 -ADAdministrativeGroups $ADAdministrativeGroups -Verbose:$false
            $Object = [ordered] @{
                DisplayName = $_.DisplayName
                DomainName  = $_.DomainName
                GUID        = $_.GUID
                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 ($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."
            }
        }
        <#
        if ($ResolveAccounts) {
            $Accounts = @{ }
            foreach ($Domain in $ForestInformation.Domains) {
                $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
                $DomainInformation = Get-ADDomain -Server $QueryServer
                $Users = Get-ADUser -Filter * -Server $QueryServer -Properties PasswordLastSet, LastLogonDate, UserPrincipalName
                foreach ($User in $Users) {
                    $U = -join ($DomainInformation.NetBIOSName, '\', $User.SamAccountName)
                    $Accounts[$U] = $User
                }
                $Groups = Get-ADGroup -Filter * -Server $QueryServer
                foreach ($Group in $Groups) {
                    $G = -join ($DomainInformation.NetBIOSName, '\', $Group.SamAccountName)
                    $Accounts[$G] = $Group
                }
            }
        }
        #>

    }
    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-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 {
    [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-GPOZaurrWMI {
    [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 {
    [alias('Show-GPOZaurr', 'Show-GPO')]
    [cmdletBinding()]
    param(
        [string] $FilePath,
        [string[]] $Type,
        [switch] $PassThru,
        [switch] $HideHTML,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains
    )
    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'
    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
                WarningsAndErrors = $null
                Time              = $null
                Summary           = $null
                Variables         = Copy-Dictionary -Dictionary $Script:GPOConfiguration[$T]['Variables']
            }
            $TimeLogGPOList = Start-TimeLog
            Write-Color -Text '[i]', '[Start] ', $($Script:GPOConfiguration[$T]['Name']) -Color Yellow, DarkGray, Yellow
            $Script:Reporting[$T]['Data'] = Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Execute'] -WarningVariable CommandWarnings -ErrorVariable CommandErrors -ArgumentList $Forest, $ExcludeDomains, $IncludeDomains
            Invoke-Command -ScriptBlock $Script:GPOConfiguration[$T]['Processing']
            $Script:Reporting[$T]['WarningsAndErrors'] = @(
                foreach ($War in $CommandWarnings) {
                    [PSCustomObject] @{
                        Type       = 'Warning'
                        Comment    = $War
                        Reason     = ''
                        TargetName = ''
                    }
                }
                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
    Write-Color -Text '[i]', '[HTML ] ', 'Generating HTML report' -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

        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) {
                    & $Script:GPOConfiguration[$T]['Solution']
                }
            }
        } else {
            foreach ($T in $Script:GPOConfiguration.Keys) {
                if ($Script:GPOConfiguration[$T].Enabled -eq $true) {
                    New-HTMLTab -Name $Script:GPOConfiguration[$T]['Name'] {
                        & $Script:GPOConfiguration[$T]['Solution']
                    }
                }
            }
        }
    } -Online -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"
    [Array] $GPOCategories = foreach ($GPO in $GPOs) {
        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) {
                    $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', 'Other')][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.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
                }
            }
        }
    } 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] $Offline,
        [switch] $ForceGPResult
    )
    # 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
    }
    if ($Command -and -not $ForceGPResult) {
        try {
            #Write-Verbose "Request-GPOZaurr - ComputerName: $($SplatPolicy['Computer']) UserName: $($SplatPolicy['User'])"
            $ResultantSetPolicy = Get-GPResultantSetOfPolicy @SplatPolicy -ErrorAction Stop
        } catch {
            if ($_.Exception.Message -eq 'Exception from HRESULT: 0x80041003') {
                Write-Warning "Request-GPOZaurr - Are you running as admin? $($_.Exception.Message)"
                return
            } else {
                $ErrorMessage = $($_.Exception.Message).Replace([Environment]::NewLine, ' ')
                Write-Warning "Request-GPOZaurr - Error: $ErrorMessage"
                return
            }
        }
    } else {
        $Arguments = @(
            if ($SplatPolicy['Computer']) {
                "/S $ComputerName"
            }
            if ($SplatPolicy['User']) {
                "/USER $($SplatPolicy['User'])"
            }
            if ($SplatPolicy['ReportType'] -eq 'HTML') {
                '/H'
            } elseif ($SplatPolicy['ReportType'] -eq 'XML') {
                '/X'
            }
            $SplatPolicy['Path']
            "/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.Path -and (Test-Path -LiteralPath $SplatPolicy.Path)) {
        [xml] $PolicyContent = Get-Content -LiteralPath $SplatPolicy.Path
        if ($PolicyContent) {
            # lets remove temporary XML file
            Remove-Item -LiteralPath $SplatPolicy.Path
        } else {
            Write-Warning "Request-GPOZaurr - Couldn't load XML file from drive $($SplatPolicy.Path). Terminating."
            return
        }
    } else {
        Write-Warning "Request-GPOZaurr - Couldn't find XML file on drive $($SplatPolicy.Path). Terminating."
        return
    }
    if ($ComputerName) {
        if (-not $PolicyContent.Rsop.'ComputerResults'.EventsDetails) {
            Write-Warning "Request-GPOZaurr - Windows Events for Group Policy are missing. Amount of data will be limited. Firewall issue?"
        }
    }
    if ($Type -eq 'XML') {
        $PolicyContent.Rsop
    } else {
        $Output = [ordered] @{
            ResultantSetPolicy = $ResultantSetPolicy
        }
        if ($PolicyContent.Rsop.ComputerResults) {
            $Output.ComputerResults = ConvertFrom-XMLRSOP -Content $PolicyContent.Rsop -ResultantSetPolicy $ResultantSetPolicy -ResultsType 'ComputerResults' -Splitter $Splitter
        }
        if ($PolicyContent.Rsop.UserResults) {
            $Output.UserResults = ConvertFrom-XMLRSOP -Content $PolicyContent.Rsop -ResultantSetPolicy $ResultantSetPolicy -ResultsType 'UserResults' -Splitter $Splitter
        }

        New-GPOZaurrReportConsole -Results $Output
        if ($Type -contains 'Object') {
            $Output
        } elseif ($Type -contains 'HTML') {
            New-GPOZaurrReportHTML -Path $Path -Offline:$Offline -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 {
    [cmdletBinding()]
    param(

    )

    $GPOS = Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($GPO in $GPOS) {
        if ($GPO.UserSettingsAvailable -eq $false -and $GPO.ComputerSettingsAvailable -eq $false) {
            if ($GPO.Enabled -ne 'All setttings disabled') {
                # $GPO
            }
        } elseif ($GPO.UserSettingsAvailable -eq $false) {

        } elseif ($GPO.ComputerSettingsAvailable -eq $false) {

        }
    }
}
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(Mandatory)][validateset('Empty', 'Unlinked')][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
    )
    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 {
        Get-GPOZaurr -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation -GPOPath $GPOPath | ForEach-Object {
            if ($Type -contains 'Empty') {
                if ($_.ComputerSettingsAvailable -eq $false -and $_.UserSettingsAvailable -eq $false) {
                    if ($BackupRequired) {
                        try {
                            Write-Verbose "Remove-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName)"
                            $BackupInfo = Backup-GPO -Guid $_.Guid -Domain $_.DomainName -Path $BackupFinalPath -ErrorAction Stop #-Server $QueryServer
                            $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 #-Server $QueryServer
                        } catch {
                            Write-Warning "Remove-GPOZaurr - Removing GPO $($_.DisplayName) from $($_.DomainName) failed: $($_.Exception.Message)"
                        }
                    }
                    $Count++
                    if ($LimitProcessing -eq $Count) {
                        break
                    }
                }
            }
            if ($Type -contains 'Unlinked') {
                if ($_.Linked -eq $false) {
                    if ($BackupRequired) {
                        try {
                            Write-Verbose "Remove-GPOZaurr - Backing up GPO $($_.DisplayName) from $($_.DomainName)"
                            $BackupInfo = Backup-GPO -Guid $_.Guid -Domain $_.DomainName -Path $BackupFinalPath -ErrorAction Stop #-Server $QueryServer
                            $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 #-Server $QueryServer
                        } catch {
                            Write-Warning "Remove-GPOZaurr - Removing GPO $($_.DisplayName) from $($_.DomainName) failed: $($_.Exception.Message)"
                        }
                    }
                    $Count++
                    if ($LimitProcessing -eq $Count) {
                        break
                    }
                }
            }
        }
    }
    End {

    }
}
function Remove-GPOZaurrBroken {
    [alias('Remove-GPOZaurrOrphaned')]
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [ValidateSet('SYSVOL', 'AD')][string[]] $Type = @('SYSVOL', 'AD'),
        [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') {
                $_
            }
        }
    } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object {
        $GPO = $_
        if ($GPO.Status -eq 'Not available in AD') {
            Write-Verbose "Remove-GPOZaurrBroken - Processing [AD] $($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 - Deleting $($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)."
                }
            }
        } elseif ($GPO.Status -eq 'Not available on SYSVOL') {
            Write-Verbose "Remove-GPOZaurrBroken - Processing [SYSVOL] $($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 ($ExistingObject -and $ExistingObject.ObjectClass -eq 'groupPolicyContainer') {
                Write-Verbose "Remove-GPOZaurrBroken - Removing DN: $($GPO.DistinguishedName) / ObjectClass: $($ExistingObject.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 ($($ExistingObject.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
        } 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-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-GPOZaurrNetLogonOwner {
    [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 | Select-Object -First $LimitProcessing | Where-Object {
        if ($_.OwnerSid -ne 'S-1-5-32-544') {
            $_
        }
    } | ForEach-Object {
        if ($PSCmdlet.ShouldProcess($_.FullName, "Setting NetLogon Owner to $($Principal)")) {
            Set-FileOwner -JustPath -Path $_.FullName -Owner $Principal -Verbose:$true -WhatIf:$WhatIfPreference
        }
    }
}
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 {
    [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 {
    [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
    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
 
    .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', '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,

        [switch] $SkipSysvol,

        [Parameter(ParameterSetName = 'Type')]
        [Parameter(ParameterSetName = 'Named')]
        [int] $LimitProcessing = [int32]::MaxValue,
        [switch] $Force
    )
    Begin {
        #Write-Verbose "Set-GPOZaurrOwner - Getting ADAdministrativeGroups"
        $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        #Write-Verbose "Set-GPOZaurrOwner - Processing GPO for Type $Type"
    }
    Process {
        $getGPOZaurrOwnerSplat = @{
            IncludeSysvol             = -not $SkipSysvol.IsPresent
            Forest                    = $Forest
            IncludeDomains            = $IncludeDomains
            ExcludeDomains            = $ExcludeDomains
            ExtendedForestInformation = $ExtendedForestInformation
            ADAdministrativeGroups    = $ADAdministrativeGroups
            Verbose                   = $VerbosePreference
            SkipBroken                = $true
        }
        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 (-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 -eq 'NotMatching') {
                    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 ($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
                        $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 'OnlyGPO') {
                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 {

    }
}


$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-GPOZaurrDictionary', 'Get-GPOZaurrDuplicateObject', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinition', 'Get-GPOZaurrFolders', 'Get-GPOZaurrInheritance', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrNetLogon', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrPermissionIssue', 'Get-GPOZaurrPermissionRoot', 'Get-GPOZaurrPermissionSummary', 'Get-GPOZaurrSysvolDFSR', '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-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrNetLogonOwner', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Set-GPOOwner', 'Set-GPOZaurrOwner'
[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
# MIIgQAYJKoZIhvcNAQcCoIIgMTCCIC0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU6S7OlwjCyk9QjcO1MdLyR1cI
# ONGgghtvMIIDtzCCAp+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
# 8jCCBTAwggQYoAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAw
# ZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBS
# b290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUg
# U2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/
# DhGvZ3cH0wsxSRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2
# qvCchqXYJawOeSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrsk
# acLCUvIUZ4qJRdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/
# 6XzLkqHlOzEcz+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE
# 94zRICUj6whkPlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8
# np+mM6n9Gd8lk9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD
# VR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0w
# azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF
# BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk
# SURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRw
# Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3Js
# ME8GA1UdIARIMEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczov
# L3d3dy5kaWdpY2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7
# KgqjpepxA8Bg+S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823I
# DzANBgkqhkiG9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh
# 134LYP3DPQ/Er4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63X
# X0R58zYUBor3nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPA
# JRHinBRHoXpoaK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC
# /i9yfhzXSUWW6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG
# /AeB+ova+YJJ92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBT0wggQloAMC
# AQICEATV3B9I6snYUgC6zZqbKqcwDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln
# bmluZyBDQTAeFw0yMDA2MjYwMDAwMDBaFw0yMzA3MDcxMjAwMDBaMHoxCzAJBgNV
# BAYTAlBMMRIwEAYDVQQIDAnFmmzEhXNraWUxETAPBgNVBAcTCEthdG93aWNlMSEw
# HwYDVQQKDBhQcnplbXlzxYJhdyBLxYJ5cyBFVk9URUMxITAfBgNVBAMMGFByemVt
# eXPFgmF3IEvFgnlzIEVWT1RFQzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
# ggEBAL+ygd4sga4ZC1G2xXvasYSijwWKgwapZ69wLaWaZZIlY6YvXTGQnIUnk+Tg
# 7EoT7mQiMSaeSPOrn/Im6N74tkvRfQJXxY1cnt3U8//U5grhh/CULdd6M3/Z4h3n
# MCq7LQ1YVaa4MYub9F8WOdXO84DANoNVG/t7YotL4vzqZil3S9pHjaidp3kOXGJc
# vxrCPAkRFBKvUmYo23QPFa0Rd0qA3bFhn97WWczup1p90y2CkOf28OVOOObv1fNE
# EqMpLMx0Yr04/h+LPAAYn6K4YtIu+m3gOhGuNc3B+MybgKePAeFIY4EQzbqvCMy1
# iuHZb6q6ggRyqrJ6xegZga7/gV0CAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrE
# uXsqCqOl6nEDwGD5LfZldQ5YMB0GA1UdDgQWBBQYsTUn6BxQICZOCZA0CxS0TZSU
# ZjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAw
# bjA1oDOgMYYvaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1j
# cy1nMS5jcmwwNaAzoDGGL2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFz
# c3VyZWQtY3MtZzEuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYB
# BQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGE
# BggrBgEFBQcBAQR4MHYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0
# LmNvbTBOBggrBgEFBQcwAoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0U0hBMkFzc3VyZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQC
# MAAwDQYJKoZIhvcNAQELBQADggEBAJq9bM+JbCwEYuMBtXoNAfH1SRaMLXnLe0py
# VK6el0Z1BtPxiNcF4iyHqMNVD4iOrgzLEVzx1Bf/sYycPEnyG8Gr2tnl7u1KGSjY
# enX4LIXCZqNEDQCeTyMstNv931421ERByDa0wrz1Wz5lepMeCqXeyiawqOxA9fB/
# 106liR12vL2tzGC62yXrV6WhD6W+s5PpfEY/chuIwVUYXp1AVFI9wi2lg0gaTgP/
# rMfP1wfVvaKWH2Bm/tU5mwpIVIO0wd4A+qOhEia3vn3J2Zz1QDxEprLcLE9e3Gmd
# G5+8xEypTR23NavhJvZMgY2kEXBEKEEDaXs0LoPbn6hMcepR2A4wggZqMIIFUqAD
# AgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYT
# AlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2Vy
# dC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMTAeFw0xNDEw
# MjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJBgNVBAYTAlVTMREwDwYDVQQK
# EwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQgVGltZXN0YW1wIFJlc3BvbmRl
# cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNkXfx8s+CCNeDg9sYq
# 5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sCSVDZg85vZu7dy4XpX6X51Id0
# iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/USs3OWCmejvmGfrvP9Enh1DqZb
# FP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYtWQJhiGFyGGi5uHzu5uc0LzF3
# gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZbesF6uHjHyQYuRhDIjegEYNu8
# c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJYczQCMxr7GJCkawCwO+k8IkR
# j3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG
# A1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIBtjCCAbIwggGhBglghkgB
# hv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20v
# Q1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAA
# dABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQA
# dQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQA
# aQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIA
# ZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcA
# aABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQA
# IABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4A
# IABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCGSAGG/WwDFTAfBgNVHSME
# GDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNVHQ4EFgQUYVpNJLZJMp1KKnka
# g0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwOKA2oDSGMmh0dHA6Ly9jcmw0
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3JsMHcGCCsGAQUF
# BwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEG
# CCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRB
# c3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAnSV+GzNNsiaBXJuG
# ziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd42yE5FpA+94GAYw3+puxnSR+
# /iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCge5fH9j/n4hFBpr1i2fAnPTgd
# KG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7kA7YUq/OPQ6dxnSHdFMoVXZJB
# 2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7Cqsc21xIJ2bIo4sKHOWV2q7E
# LlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIKSK+w1G7g9BQKOhvjjz3Kr2qN
# e9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArrPye7uhswDQYJKoZIhvcNAQEF
# BQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE
# CxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJ
# RCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowYjELMAkG
# A1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRp
# Z2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKVpYBzQHDSnlZU
# XKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5tHdJ3InECtqvy15r7a2wcTHr
# zzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/2DDD/6b3+6LN
# b3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2xQaPtP77blUjE7h6z8rwMK5nQ
# xl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9IhJtPQLnxTPKvmPv2zkBdXPao
# 8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIUYJX9BwSiCQID
# AQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIGCCsGAQUFBwMB
# BggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCDCCAdIGA1Ud
# IASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6
# Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggr
# BgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAA
# QwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAA
# YQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUA
# cgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4A
# ZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAA
# bABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAA
# aQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIA
# ZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB/wQIMAYBAf8C
# AQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaG
# NGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RD
# QS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz
# c3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e36K+Vw0rZwLN
# MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUA
# A4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvVDQtBs+/sdR90OPKyXGGinJXD
# UOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann4+erYs37iy2Q
# wsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1zh14dpQlc+Qqq8+cdkvtX8JL
# FuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD7eeSDY2xaYxP
# +1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwIVYUiuOsYGk38KiGtSTGDR5V3
# cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYIEOzCCBDcCAQEwgYYwcjELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln
# bmluZyBDQQIQBNXcH0jqydhSALrNmpsqpzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGC
# NwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUP32n/A6X
# pLyBGDpdMZN2FGthvYUwDQYJKoZIhvcNAQEBBQAEggEAacw0CwwxP3wIGCZ4PZwC
# fk1rslxIVrFQ6TB9h4OdHUbTFZOqWuIJt0Aw9k/May/AjD32T4PvkAxb2RDqsDfO
# iUO+a5t4v2apv+YFWu1nCp3qOrr/EM+CvqqXNCxhWN/3CV8aX1wlH/blqYw9IQxi
# wgIOW5VoPdtTC2luipuDWc4vRx86kmu5l2sqsIldGXmMmGwxSOcV3RjvRBVPngrN
# fPK3olXfaesUCTUVK6Tt9+S0aZUC89sMf/Vqqe+7BUskJy9jJ6zWjGvbrBgCG8QR
# 9IbSSo7iiENMGT4VTX3ST2e4lewhSNRoqAZPcJHAPSXMwIu3+syugO/KVlg4FTGt
# f6GCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr
# 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
# BgkqhkiG9w0BCQUxDxcNMjAxMTE4MjIwMzM0WjAjBgkqhkiG9w0BCQQxFgQUgraE
# aGeO++v6KkJQB9SFe/1tVGkwDQYJKoZIhvcNAQEBBQAEggEAcyQFE4GH99g8nKk9
# hWYm85vB8FXnmMIOmaUzxjNZRtPP2VKWZ1bAU4oZ7aikuQDbzujIkMIkqabScmUx
# fUtiu9xN/4DnI4/1N0La4mvdE9dqQpoSuoGnDwUCyFcuPwCFkKNJtZ4DnyCcaPmL
# RzgC6S4nlRZ/cVmbvn6KW++buP/RnwPbmKNMFpU91CI5Z+uHmeXM+783KfCzzxdp
# nuzzn3RQmgfQ08TRzCC2trV8L3rMN0oHwIWzk9JKi7FiXhZ+6X+Lz64ASzCWtUTq
# ybFadeNXWAF52X0y/dVivp6V0za2X52GdV0Y1fkEDfVy2nFAxUviJuBGnlKzxXp8
# ydQuFg==
# SIG # End signature block