GPOZaurr.psm1

function ConvertFrom-DistinguishedName { 
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .PARAMETER DistinguishedName
    Parameter description
 
    .PARAMETER ToOrganizationalUnit
    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([string[]] $DistinguishedName,
        [switch] $ToOrganizationalUnit,
        [switch] $ToDC,
        [switch] $ToDomainCN)
    if ($ToDomainCN) {
        $DN = $DistinguishedName -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
        $CN = $DN -replace ',DC=', '.' -replace "DC="
        $CN
    } elseif ($ToOrganizationalUnit) { return [Regex]::Match($DistinguishedName, '(?=OU=)(.*\n?)(?<=.)').Value } elseif ($ToDC) { $DistinguishedName -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' } else {
        $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$'
        $Output = foreach ($_ in $DistinguishedName) {
            $_ -match $Regex
            $Matches
        }
        $Output.cn
    }
}
function ConvertFrom-SID { 
    [cmdletbinding()]
    param([string[]] $SID,
        [switch] $OnlyWellKnown,
        [switch] $OnlyWellKnownAdministrative)
    $WellKnownAdministrative = @{'S-1-5-18' = 'NT AUTHORITY\SYSTEM' }
    $wellKnownSIDs = @{'S-1-0' = 'Null AUTHORITY'
        'S-1-0-0'              = 'NULL SID'
        'S-1-1'                = 'WORLD AUTHORITY'
        'S-1-1-0'              = 'Everyone'
        'S-1-2'                = 'LOCAL AUTHORITY'
        'S-1-2-0'              = 'LOCAL'
        'S-1-2-1'              = 'CONSOLE LOGON'
        'S-1-3'                = 'CREATOR AUTHORITY'
        'S-1-3-0'              = 'CREATOR OWNER'
        'S-1-3-1'              = 'CREATOR GROUP'
        'S-1-3-2'              = 'CREATOR OWNER SERVER'
        'S-1-3-3'              = 'CREATOR GROUP SERVER'
        'S-1-3-4'              = 'OWNER RIGHTS'
        'S-1-5-80-0'           = 'NT SERVICE\ALL SERVICES'
        'S-1-4'                = 'Non-unique Authority'
        'S-1-5'                = 'NT AUTHORITY'
        'S-1-5-1'              = 'NT AUTHORITY\DIALUP'
        'S-1-5-2'              = 'NT AUTHORITY\NETWORK'
        'S-1-5-3'              = 'NT AUTHORITY\BATCH'
        'S-1-5-4'              = 'NT AUTHORITY\INTERACTIVE'
        'S-1-5-6'              = 'NT AUTHORITY\SERVICE'
        'S-1-5-7'              = 'NT AUTHORITY\ANONYMOUS LOGON'
        'S-1-5-8'              = 'NT AUTHORITY\PROXY'
        'S-1-5-9'              = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS'
        'S-1-5-10'             = 'NT AUTHORITY\SELF'
        'S-1-5-11'             = 'NT AUTHORITY\Authenticated Users'
        'S-1-5-12'             = 'NT AUTHORITY\RESTRICTED'
        'S-1-5-13'             = 'NT AUTHORITY\TERMINAL SERVER USER'
        'S-1-5-14'             = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON'
        'S-1-5-15'             = 'NT AUTHORITY\This Organization'
        'S-1-5-17'             = 'NT AUTHORITY\IUSR'
        'S-1-5-18'             = 'NT AUTHORITY\SYSTEM'
        'S-1-5-19'             = 'NT AUTHORITY\NETWORK SERVICE'
        'S-1-5-20'             = 'NT AUTHORITY\NETWORK SERVICE'
        'S-1-5-32-544'         = 'BUILTIN\Administrators'
        'S-1-5-32-545'         = 'BUILTIN\Users'
        'S-1-5-32-546'         = 'BUILTIN\Guests'
        'S-1-5-32-547'         = 'BUILTIN\Power Users'
        'S-1-5-32-548'         = 'BUILTIN\Account Operators'
        'S-1-5-32-549'         = 'BUILTIN\Server Operators'
        'S-1-5-32-550'         = 'BUILTIN\Print Operators'
        'S-1-5-32-551'         = 'BUILTIN\Backup Operators'
        'S-1-5-32-552'         = 'BUILTIN\Replicators'
        'S-1-5-64-10'          = 'NT AUTHORITY\NTLM Authentication'
        'S-1-5-64-14'          = 'NT AUTHORITY\SChannel Authentication'
        'S-1-5-64-21'          = 'NT AUTHORITY\Digest Authentication'
        'S-1-5-80'             = 'NT SERVICE'
        'S-1-5-83-0'           = 'NT VIRTUAL MACHINE\Virtual Machines'
        'S-1-16-0'             = 'Untrusted Mandatory Level'
        'S-1-16-4096'          = 'Low Mandatory Level'
        'S-1-16-8192'          = 'Medium Mandatory Level'
        'S-1-16-8448'          = 'Medium Plus Mandatory Level'
        'S-1-16-12288'         = 'High Mandatory Level'
        'S-1-16-16384'         = 'System Mandatory Level'
        'S-1-16-20480'         = 'Protected Process Mandatory Level'
        'S-1-16-28672'         = 'Secure Process Mandatory Level'
        'S-1-5-32-554'         = 'BUILTIN\Pre-Windows 2000 Compatible Access'
        'S-1-5-32-555'         = 'BUILTIN\Remote Desktop Users'
        'S-1-5-32-556'         = 'BUILTIN\Network Configuration Operators'
        'S-1-5-32-557'         = 'BUILTIN\Incoming Forest Trust Builders'
        'S-1-5-32-558'         = 'BUILTIN\Performance Monitor Users'
        'S-1-5-32-559'         = 'BUILTIN\Performance Log Users'
        'S-1-5-32-560'         = 'BUILTIN\Windows Authorization Access Group'
        'S-1-5-32-561'         = 'BUILTIN\Terminal Server License Servers'
        'S-1-5-32-562'         = 'BUILTIN\Distributed COM Users'
        'S-1-5-32-569'         = 'BUILTIN\Cryptographic Operators'
        'S-1-5-32-573'         = 'BUILTIN\Event Log Readers'
        'S-1-5-32-574'         = 'BUILTIN\Certificate Service DCOM Access'
        'S-1-5-32-575'         = 'BUILTIN\RDS Remote Access Servers'
        'S-1-5-32-576'         = 'BUILTIN\RDS Endpoint Servers'
        'S-1-5-32-577'         = 'BUILTIN\RDS Management Servers'
        'S-1-5-32-578'         = 'BUILTIN\Hyper-V Administrators'
        'S-1-5-32-579'         = 'BUILTIN\Access Control Assistance Operators'
        'S-1-5-32-580'         = 'BUILTIN\Remote Management Users'
    }
    foreach ($S in $SID) {
        if ($OnlyWellKnownAdministrative) {
            if ($WellKnownAdministrative[$S]) {
                [PSCustomObject] @{Name = $WellKnownAdministrative[$S]
                    SID                 = $S
                    Type                = 'WellKnownAdministrative'
                    Error               = ''
                }
            }
        } elseif ($OnlyWellKnown) {
            if ($wellKnownSIDs[$S]) {
                [PSCustomObject] @{Name = $wellKnownSIDs[$S]
                    SID                 = $S
                    Type                = 'WellKnownGroup'
                    Error               = ''
                }
            }
        } else {
            if ($wellKnownSIDs[$S]) {
                [PSCustomObject] @{Name = $wellKnownSIDs[$S]
                    SID                 = $S
                    Type                = 'WellKnownGroup'
                    Error               = ''
                }
            } else {
                try {
                    [PSCustomObject] @{Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value
                        SID                 = $S
                        Type                = 'Standard'
                        Error               = ''
                    }
                } catch {
                    [PSCustomObject] @{Name = $S
                        SID                 = $S
                        Error               = $_.Exception.Message -replace [environment]::NewLine, ' '
                        Type                = 'Unknown'
                    }
                }
            }
        }
    }
}
function Get-ADACL { 
    [cmdletbinding()]
    param([Parameter(ValueFromPipeline)][Array] $ADObject,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [Object] $Server,
        [string] $ForestName,
        [switch] $Extended,
        [switch] $ResolveTypes,
        [switch] $Inherited,
        [switch] $NotInherited,
        [switch] $Bundle,
        [System.DirectoryServices.ActiveDirectoryRights[]] $IncludeActiveDirectoryRights,
        [System.DirectoryServices.ActiveDirectoryRights[]] $ExcludeActiveDirectoryRights,
        [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $IncludeActiveDirectorySecurityInheritance,
        [System.DirectoryServices.ActiveDirectorySecurityInheritance[]] $ExcludeActiveDirectorySecurityInheritance)
    Begin {
        if (-not $Script:ForestGUIDs) {
            Write-Verbose "Get-ADACL - Gathering Forest GUIDS"
            $Script:ForestGUIDs = Get-WinADForestGUIDs
        }
        if ($ResolveTypes) {
            if (-not $Script:ForestCache) {
                Write-Verbose "Get-ADACL - Building Cache"
                $Script:ForestCache = Get-WinADCache -ByNetBiosName
            }
        }
    }
    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-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 ($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 } }
                if ($ACL.IdentityReference -like '*\*') {
                    if ($ResolveTypes -and $Script:ForestCache) {
                        $TemporaryIdentity = $Script:ForestCache["$($ACL.IdentityReference)"]
                        $IdentityReferenceType = $TemporaryIdentity.ObjectClass
                        $IdentityReference = $ACL.IdentityReference.Value
                    } else {
                        $IdentityReferenceType = ''
                        $IdentityReference = $ACL.IdentityReference.Value
                    }
                } elseif ($ACL.IdentityReference -like '*-*-*-*') {
                    $ConvertedSID = ConvertFrom-SID -SID $ACL.IdentityReference
                    if ($ResolveTypes -and $Script:ForestCache) {
                        $TemporaryIdentity = $Script:ForestCache["$($ConvertedSID.Name)"]
                        $IdentityReferenceType = $TemporaryIdentity.ObjectClass
                    } else { $IdentityReferenceType = '' }
                    $IdentityReference = $ConvertedSID.Name
                } else {
                    $IdentityReference = $ACL.IdentityReference
                    $IdentityReferenceType = 'Unknown'
                }
                $ReturnObject = [ordered] @{}
                $ReturnObject['DistinguishedName' ] = $DistinguishedName
                if ($CanonicalName) { $ReturnObject['CanonicalName'] = $CanonicalName }
                if ($ObjectClass) { $ReturnObject['ObjectClass'] = $ObjectClass }
                $ReturnObject['AccessControlType'] = $ACL.AccessControlType
                $ReturnObject['Principal'] = $IdentityReference
                if ($ResolveTypes) { $ReturnObject['PrincipalType'] = $IdentityReferenceType }
                $ReturnObject['ObjectTypeName'] = $Script:ForestGUIDs["$($ACL.objectType)"]
                $ReturnObject['InheritedObjectTypeName'] = $Script:ForestGUIDs["$($ACL.inheritedObjectType)"]
                $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-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-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-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
                    $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 Select-Properties { 
    [CmdLetBinding()]
    param([Array] $Objects,
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [switch] $AllProperties)
    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
    }
    if ($Objects.Count -eq 0) {
        Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.'
        return
    }
    if ($Objects[0] -is [System.Collections.IDictionary]) {
        if ($AllProperties) {
            [Array] $All = foreach ($_ in $Objects) { $_.Keys }
            $FirstObjectProperties = Select-Unique -Object $All
        } else { $FirstObjectProperties = $Objects[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
                }
            }
        }
    } else {
        if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $Objects = $Objects | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty } elseif ($Property.Count -gt 0) { $Objects = $Objects | Select-Object -Property $Property } elseif ($ExcludeProperty.Count -gt 0) { $Objects = $Objects | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty }
        if ($AllProperties) {
            [Array] $All = foreach ($_ in $Objects) { $_.PSObject.Properties.Name }
            $FirstObjectProperties = Select-Unique -Object $All
        } else { $FirstObjectProperties = $Objects[0].PSObject.Properties.Name }
    }
    return $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 = $_
                        $ACL = Get-Acl -Path $File
                        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
                                } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" }
                            }
                        } }
                } else {
                    Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive | ForEach-Object -Process { $File = $_
                        $ACL = Get-Acl -Path $File.FullName
                        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
                                } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" }
                            }
                        } }
                }
            }
        }
    }
    End {}
}
function Convert-Identity { 
    [cmdletBinding(DefaultParameterSetName = 'Identity')]
    param([parameter(ParameterSetName = 'Identity', Position = 0)][string[]] $Identity,
        [parameter(ParameterSetName = 'SID', Mandatory)][System.Security.Principal.SecurityIdentifier[]] $SID,
        [parameter(ParameterSetName = 'Name', Mandatory)][string[]] $Name)
    Begin {
        if (-not $Script:GlobalCacheSidConvert) {
            $Script:GlobalCacheSidConvert = @{'S-1-0' = 'Null AUTHORITY'
                'S-1-0-0'                             = 'NULL SID'
                'S-1-1'                               = 'WORLD AUTHORITY'
                'S-1-1-0'                             = 'Everyone'
                'S-1-2'                               = 'LOCAL AUTHORITY'
                'S-1-2-0'                             = 'LOCAL'
                'S-1-2-1'                             = 'CONSOLE LOGON'
                'S-1-3'                               = 'CREATOR AUTHORITY'
                'S-1-3-0'                             = 'CREATOR OWNER'
                'S-1-3-1'                             = 'CREATOR GROUP'
                'S-1-3-2'                             = 'CREATOR OWNER SERVER'
                'S-1-3-3'                             = 'CREATOR GROUP SERVER'
                'S-1-3-4'                             = 'OWNER RIGHTS'
                'S-1-5-80-0'                          = 'NT SERVICE\ALL SERVICES'
                'S-1-4'                               = 'Non-unique Authority'
                'S-1-5'                               = 'NT AUTHORITY'
                'S-1-5-1'                             = 'NT AUTHORITY\DIALUP'
                'S-1-5-2'                             = 'NT AUTHORITY\NETWORK'
                'S-1-5-3'                             = 'NT AUTHORITY\BATCH'
                'S-1-5-4'                             = 'NT AUTHORITY\INTERACTIVE'
                'S-1-5-6'                             = 'NT AUTHORITY\SERVICE'
                'S-1-5-7'                             = 'NT AUTHORITY\ANONYMOUS LOGON'
                'S-1-5-8'                             = 'NT AUTHORITY\PROXY'
                'S-1-5-9'                             = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS'
                'S-1-5-10'                            = 'NT AUTHORITY\SELF'
                'S-1-5-11'                            = 'NT AUTHORITY\Authenticated Users'
                'S-1-5-12'                            = 'NT AUTHORITY\RESTRICTED'
                'S-1-5-13'                            = 'NT AUTHORITY\TERMINAL SERVER USER'
                'S-1-5-14'                            = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON'
                'S-1-5-15'                            = 'NT AUTHORITY\This Organization'
                'S-1-5-17'                            = 'NT AUTHORITY\IUSR'
                'S-1-5-18'                            = 'NT AUTHORITY\SYSTEM'
                'S-1-5-19'                            = 'NT AUTHORITY\NETWORK SERVICE'
                'S-1-5-20'                            = 'NT AUTHORITY\NETWORK SERVICE'
                'S-1-5-32-544'                        = 'BUILTIN\Administrators'
                'S-1-5-32-545'                        = 'BUILTIN\Users'
                'S-1-5-32-546'                        = 'BUILTIN\Guests'
                'S-1-5-32-547'                        = 'BUILTIN\Power Users'
                'S-1-5-32-548'                        = 'BUILTIN\Account Operators'
                'S-1-5-32-549'                        = 'BUILTIN\Server Operators'
                'S-1-5-32-550'                        = 'BUILTIN\Print Operators'
                'S-1-5-32-551'                        = 'BUILTIN\Backup Operators'
                'S-1-5-32-552'                        = 'BUILTIN\Replicators'
                'S-1-5-64-10'                         = 'NT AUTHORITY\NTLM Authentication'
                'S-1-5-64-14'                         = 'NT AUTHORITY\SChannel Authentication'
                'S-1-5-64-21'                         = 'NT AUTHORITY\Digest Authentication'
                'S-1-5-80'                            = 'NT SERVICE'
                'S-1-5-83-0'                          = 'NT VIRTUAL MACHINE\Virtual Machines'
                'S-1-16-0'                            = 'Untrusted Mandatory Level'
                'S-1-16-4096'                         = 'Low Mandatory Level'
                'S-1-16-8192'                         = 'Medium Mandatory Level'
                'S-1-16-8448'                         = 'Medium Plus Mandatory Level'
                'S-1-16-12288'                        = 'High Mandatory Level'
                'S-1-16-16384'                        = 'System Mandatory Level'
                'S-1-16-20480'                        = 'Protected Process Mandatory Level'
                'S-1-16-28672'                        = 'Secure Process Mandatory Level'
                'S-1-5-32-554'                        = 'BUILTIN\Pre-Windows 2000 Compatible Access'
                'S-1-5-32-555'                        = 'BUILTIN\Remote Desktop Users'
                'S-1-5-32-556'                        = 'BUILTIN\Network Configuration Operators'
                'S-1-5-32-557'                        = 'BUILTIN\Incoming Forest Trust Builders'
                'S-1-5-32-558'                        = 'BUILTIN\Performance Monitor Users'
                'S-1-5-32-559'                        = 'BUILTIN\Performance Log Users'
                'S-1-5-32-560'                        = 'BUILTIN\Windows Authorization Access Group'
                'S-1-5-32-561'                        = 'BUILTIN\Terminal Server License Servers'
                'S-1-5-32-562'                        = 'BUILTIN\Distributed COM Users'
                'S-1-5-32-569'                        = 'BUILTIN\Cryptographic Operators'
                'S-1-5-32-573'                        = 'BUILTIN\Event Log Readers'
                'S-1-5-32-574'                        = 'BUILTIN\Certificate Service DCOM Access'
                'S-1-5-32-575'                        = 'BUILTIN\RDS Remote Access Servers'
                'S-1-5-32-576'                        = 'BUILTIN\RDS Endpoint Servers'
                'S-1-5-32-577'                        = 'BUILTIN\RDS Management Servers'
                'S-1-5-32-578'                        = 'BUILTIN\Hyper-V Administrators'
                'S-1-5-32-579'                        = 'BUILTIN\Access Control Assistance Operators'
                'S-1-5-32-580'                        = 'BUILTIN\Remote Management Users'
            }
        }
        $wellKnownSIDs = @{'S-1-0' = 'WellKnown'
            'S-1-0-0'              = 'WellKnown'
            'S-1-1'                = 'WellKnown'
            'S-1-1-0'              = 'WellKnown'
            'S-1-2'                = 'WellKnown'
            'S-1-2-0'              = 'WellKnown'
            'S-1-2-1'              = 'WellKnown'
            'S-1-3'                = 'WellKnown'
            'S-1-3-0'              = 'WellKnown'
            'S-1-3-1'              = 'WellKnown'
            'S-1-3-2'              = 'WellKnown'
            'S-1-3-3'              = 'WellKnown'
            'S-1-3-4'              = 'WellKnown'
            'S-1-5-80-0'           = 'WellKnown'
            'S-1-4'                = 'WellKnown'
            'S-1-5'                = 'WellKnown'
            'S-1-5-1'              = 'WellKnown'
            'S-1-5-2'              = 'WellKnown'
            'S-1-5-3'              = 'WellKnown'
            'S-1-5-4'              = 'WellKnown'
            'S-1-5-6'              = 'WellKnown'
            'S-1-5-7'              = 'WellKnown'
            'S-1-5-8'              = 'WellKnown'
            'S-1-5-9'              = 'WellKnown'
            'S-1-5-10'             = 'WellKnown'
            'S-1-5-11'             = 'WellKnown'
            'S-1-5-12'             = 'WellKnown'
            'S-1-5-13'             = 'WellKnown'
            'S-1-5-14'             = 'WellKnown'
            'S-1-5-15'             = 'WellKnown'
            'S-1-5-17'             = 'WellKnown'
            'S-1-5-18'             = 'WellKnownAdministrative'
            'S-1-5-19'             = 'WellKnown'
            'S-1-5-20'             = 'WellKnown'
            'S-1-5-32-544'         = 'WellKnownAdministrative'
            'S-1-5-32-545'         = 'WellKnown'
            'S-1-5-32-546'         = 'WellKnown'
            'S-1-5-32-547'         = 'WellKnown'
            'S-1-5-32-548'         = 'WellKnown'
            'S-1-5-32-549'         = 'WellKnown'
            'S-1-5-32-550'         = 'WellKnown'
            'S-1-5-32-551'         = 'WellKnown'
            'S-1-5-32-552'         = 'WellKnown'
            'S-1-5-64-10'          = 'WellKnown'
            'S-1-5-64-14'          = 'WellKnown'
            'S-1-5-64-21'          = 'WellKnown'
            'S-1-5-80'             = 'WellKnown'
            'S-1-5-83-0'           = 'WellKnown'
            'S-1-16-0'             = 'WellKnown'
            'S-1-16-4096'          = 'WellKnown'
            'S-1-16-8192'          = 'WellKnown'
            'S-1-16-8448'          = 'WellKnown'
            'S-1-16-12288'         = 'WellKnown'
            'S-1-16-16384'         = 'WellKnown'
            'S-1-16-20480'         = 'WellKnown'
            'S-1-16-28672'         = 'WellKnown'
            'S-1-5-32-554'         = 'WellKnown'
            'S-1-5-32-555'         = 'WellKnown'
            'S-1-5-32-556'         = 'WellKnown'
            'S-1-5-32-557'         = 'WellKnown'
            'S-1-5-32-558'         = 'WellKnown'
            'S-1-5-32-559'         = 'WellKnown'
            'S-1-5-32-560'         = 'WellKnown'
            'S-1-5-32-561'         = 'WellKnown'
            'S-1-5-32-562'         = 'WellKnown'
            'S-1-5-32-569'         = 'WellKnown'
            'S-1-5-32-573'         = 'WellKnown'
            'S-1-5-32-574'         = 'WellKnown'
            'S-1-5-32-575'         = 'WellKnown'
            'S-1-5-32-576'         = 'WellKnown'
            'S-1-5-32-577'         = 'WellKnown'
            'S-1-5-32-578'         = 'WellKnown'
            'S-1-5-32-579'         = 'WellKnown'
            'S-1-5-32-580'         = 'WellKnown'
        }
    }
    Process {
        if ($Identity) {
            foreach ($Ident in $Identity) {
                if ([Regex]::IsMatch($Ident, "^S-\d-\d+-(\d+-){1,14}\d+$")) {
                    if ($Script:GlobalCacheSidConvert[$Ident]) {
                        Write-Verbose "Convert-Identity - Processing SID $Ident from cache."
                        if ($Script:GlobalCacheSidConvert[$Ident] -is [string]) {
                            [PSCustomObject] @{Name = $Script:GlobalCacheSidConvert[$Ident]
                                SID                 = $Ident
                                Type                = $wellKnownSIDs[$Ident]
                                Error               = ''
                            }
                        } else { $Script:GlobalCacheSidConvert[$Ident] }
                    } else {
                        Write-Verbose "Convert-Identity - Processing SID $Ident"
                        try {
                            [string] $Name = (([System.Security.Principal.SecurityIdentifier]::new($Ident)).Translate([System.Security.Principal.NTAccount])).Value
                            $ErrorMessage = ''
                            if ($Ident -like "S-1-5-21-*-519" -or $Ident -like "S-1-5-21-*-512") { $Type = 'Administrative' } elseif ($wellKnownSIDs[$Ident]) { $Type = $wellKnownSIDs[$Ident] } else { $Type = 'NotAdministrative' }
                        } catch {
                            [string] $Name = ''
                            $ErrorMessage = $_.Exception.Message
                            $Type = 'Unknown'
                        }
                        $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Name
                            SID                                                         = $Ident
                            Type                                                        = $Type
                            Error                                                       = $ErrorMessage
                        }
                        $Script:GlobalCacheSidConvert[$Ident]
                    }
                } else {
                    Write-Verbose "Convert-Identity - Processing $Ident"
                    if ($Script:GlobalCacheSidConvert[$Ident]) { $Script:GlobalCacheSidConvert[$Ident] } else {
                        if ($Ident -like '*DC=*') {
                            Write-Verbose "Convert-Identity - Processing DistinguishedName $Ident"
                            try {
                                $Object = [adsi]"LDAP://$($Ident)"
                                if ($Object) {
                                    $SIDValue = [System.Security.Principal.SecurityIdentifier]::new($Object.objectSid.Value, 0).Value
                                    [string] $Name = (([System.Security.Principal.SecurityIdentifier]::new($SIDValue)).Translate([System.Security.Principal.NTAccount])).Value
                                } else {
                                    [string] $Name = $Ident
                                    $SIDValue = $null
                                }
                                $ErrorMessage = ''
                                if ($Ident -like "S-1-5-21-*-519" -or $Ident -like "S-1-5-21-*-512") { $Type = 'Administrative' } elseif ($wellKnownSIDs[$Ident]) { $Type = $wellKnownSIDs[$Ident] } else { $Type = 'NotAdministrative' }
                            } catch {
                                [string] $Name = $Ident
                                $Type = 'Unknown'
                                $ErrorMessage = $_.Exception.Message
                                $SIDValue = $null
                            }
                            $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Name
                                SID                                                         = $SIDValue
                                Type                                                        = $Type
                                Error                                                       = $ErrorMessage
                            }
                            $Script:GlobalCacheSidConvert[$Ident]
                        } else {
                            try {
                                $SIDValue = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value
                                if ($SIDValue -like "S-1-5-21-*-519" -or $SIDValue -like "S-1-5-21-*-512") { $Type = 'Administrative' } elseif ($wellKnownSIDs[$SIDValue]) { $Type = $wellKnownSIDs[$SIDValue] } else { $Type = 'NotAdministrative' }
                                $ErrorMessage = ''
                            } catch {
                                $Type = 'Unknown'
                                $ErrorMessage = $_.Exception.Message
                            }
                            $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{Name = $Ident
                                SID                                                         = $SIDValue
                                Type                                                        = $Type
                                Error                                                       = $ErrorMessage
                            }
                            $Script:GlobalCacheSidConvert[$Ident]
                        }
                    }
                }
            }
        } else {
            if ($SID) {
                foreach ($S in $SID) {
                    if ($Script:GlobalCacheSidConvert[$S]) { $Script:GlobalCacheSidConvert[$S] } else {
                        $Script:GlobalCacheSidConvert[$S] = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value
                        $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 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 (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.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 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-FilePermission { 
    [alias('Get-PSPermissions', 'Get-FilePermissions')]
    [cmdletBinding()]
    param([Array] $Path,
        [switch] $Inherited,
        [switch] $NotInherited,
        [switch] $ResolveTypes,
        [switch] $Extended,
        [switch] $IncludeACLObject,
        [switch] $AsHashTable)
    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) {
            $ACLS = (Get-Acl -Path $FullPath)
            $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-WinADCache { 
    [alias('Get-ADCache')]
    [cmdletbinding()]
    param([switch] $ByDN,
        [switch] $ByNetBiosName)
    $ForestObjectsCache = [ordered] @{}
    $Forest = Get-ADForest
    foreach ($Domain in $Forest.Domains) {
        $Server = Get-ADDomainController -Discover -DomainName $Domain
        try {
            $DomainInformation = Get-ADDomain -Server $Server.Hostname[0]
            $Users = Get-ADUser -Filter * -Server $Server.Hostname[0]
            $Groups = Get-ADGroup -Filter * -Server $Server.Hostname[0]
            $Computers = Get-ADComputer -Filter * -Server $Server.Hostname[0]
        } catch {
            Write-Warning "Get-ADCache - Can't process domain $Domain - $($_.Exception.Message)"
            continue
        }
        if ($ByDN) {
            foreach ($_ in $Users) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ }
            foreach ($_ in $Groups) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ }
            foreach ($_ in $Computers) { $ForestObjectsCache["$($_.DistinguishedName)"] = $_ }
        } elseif ($ByNetBiosName) {
            foreach ($_ in $Users) {
                $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName))
                $ForestObjectsCache["$Identity"] = $_
            }
            foreach ($_ in $Groups) {
                $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName))
                $ForestObjectsCache["$Identity"] = $_
            }
            foreach ($_ in $Computers) {
                $Identity = -join ($DomainInformation.NetBIOSName, '\', $($_.SamAccountName))
                $ForestObjectsCache["$Identity"] = $_
            }
        } else { Write-Warning "Get-ADCache - No choice made." }
    }
    $ForestObjectsCache
}
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 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-XMLAccountPolicy {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO
    )
    $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)] = $GPOEntry.SettingNumber
        }
    }
    $CreateGPO['Linked'] = $GPO.Linked
    $CreateGPO['LinksCount'] = $GPO.LinksCount
    $CreateGPO['Links'] = $GPO.Links
    [PSCustomObject] $CreateGPO
}
function ConvertTo-XMLAudit {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [switch] $FullObject
    )
    $CreateGPO = [ordered]@{
        DisplayName          = $GPO.DisplayName
        DomainName           = $GPO.DomainName
        GUID                 = $GPO.GUID
        GpoType              = $GPO.GpoType
        #GpoCategory = $GPOEntry.GpoCategory
        #GpoSettings = $GPOEntry.GpoSettings
        AuditAccountLogon    = 'No auditing'
        AuditAccountManage   = 'No auditing'
        AuditDSAccess        = 'No auditing'
        AuditLogonEvents     = 'No auditing'
        AuditObjectAccess    = 'No auditing'
        AuditPolicyChange    = 'No auditing'
        AuditPrivilegeUse    = 'No auditing'
        AuditProcessTracking = 'No auditing'
        AuditSystemEvents    = 'No auditing'
    }
    foreach ($GPOEntry in $GPO.DataSet) {
        $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 = 'No auditing'
        }
        $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-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-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 } elseif ($Entry.bypassErrors -eq '0') { $false } else { $Entry.bypassErrors };
            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 } elseif ($Entry.bypassErrors -eq '0') { $false } else { $Entry.bypassErrors };
            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        #: True
        $CreateGPO['LinksCount'] = $GPOEntry.LinksCount    #: 1
        $CreateGPO['Links'] = $GPOEntry.Links         #: area1.local
        [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
        <#
        [Array] $CreateGPO['Settings'] = foreach ($Registry in $GPO.DataSet.Registry) {
            [PSCustomObject] @{
                Changed = [DateTime] $Registry.changed
                GPOSettingOrder = $Registry.GPOSettingOrder
                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
            }
        }
        #>

        $CreateGPO['Count'] = $CreateGPO['Settings'].Count
        $CreateGPO['Linked'] = $GPO.Linked
        $CreateGPO['LinksCount'] = $GPO.LinksCount
        $CreateGPO['Links'] = $GPO.Links
        [PSCustomObject] $CreateGPO
    } else {
        <#
        if ($GPO.DataSet.Registry) {
            Get-XMLNestedRegistry -GPO $GPO -RegistryCollection $GPO.DataSet.Registry
        }
        if ($GPO.DataSet.Collection) {
            Get-XMLNestedRegistry -GPO $GPO -RegistryCollection $GPO.DataSet.Collection
        }
        #>

        Get-XMLNestedRegistry -GPO $GPO -DataSet $GPO.DataSet

        <#
        foreach ($Registry in $GPO.DataSet.Registry) {
            $CreateGPO = [ordered]@{
                DisplayName = $GPO.DisplayName
                DomainName = $GPO.DomainName
                GUID = $GPO.GUID
                GpoType = $GPO.GpoType
                #GpoCategory = $GPOEntry.GpoCategory
                #GpoSettings = $GPOEntry.GpoSettings
                Changed = [DateTime] $Registry.changed
                GPOSettingOrder = $Registry.GPOSettingOrder
                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
            }
            $CreateGPO['Linked'] = $GPO.Linked
            $CreateGPO['LinksCount'] = $GPO.LinksCount
            $CreateGPO['Links'] = $GPO.Links
            [PSCustomObject] $CreateGPO
        }
        #>

    }
}
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 Find-MissingProperties {
    [cmdletBinding()]
    param(
        [Array] $Objects,
        [string[]] $PossibleProperties
    )
    $AllProperties = Select-Properties -AllProperties -Objects $Objects
    $MissingProperties = $AllProperties | Where-Object { $_ -notin 'DisplayName', 'DomainName', 'GUID', 'Linked', 'LinksCount', 'Links', 'GPOType', 'GPOCategory', 'GPOSettings' }
    [Array] $ConsiderAdding = foreach ($Property in $MissingProperties) {
        if ($Property -notin $PossibleProperties) {
            $Property
        }
    }
    $ConsiderAdding
}
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', '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 ($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') {
                    $UserMerge = -join ($GPOPermission.Trustee.Domain, '\', $GPOPermission.Trustee.Name)
                    if ($Principal -notcontains $UserMerge -and $Principal -notcontains $GPOPermission.Trustee.Name) {
                        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') {
                    $UserMerge = -join ($GPOPermission.Trustee.Domain, '\', $GPOPermission.Trustee.Name)
                    if ($ExcludePrincipal -contains $UserMerge -or $ExcludePrincipal -contains $GPOPermission.Trustee.Name) {
                        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    = if ($GPOPermission.Denied -eq $true) { 'Deny' } else { 'Allow' }
                Permission        = $GPOPermission.Permission  # : GpoEditDeleteModifySecurity
                Inherited         = $GPOPermission.Inherited   # : False
                Domain            = $GPOPermission.Trustee.Domain  #: EVOTEC
                DistinguishedName = $GPOPermission.Trustee.DSPath  #: CN = Domain Admins, CN = Users, DC = ad, DC = evotec, DC = xyz
                Name              = $GPOPermission.Trustee.Name    #: Domain Admins
                Sid               = $GPOPermission.Trustee.Sid.Value     #: S - 1 - 5 - 21 - 853615985 - 2870445339 - 3163598659 - 512
                SidType           = $GPOPermission.Trustee.SidType #: Group
            }
            if ($Accounts) {
                $A = -join ($GPOPermission.Trustee.Domain, '\', $GPOPermission.Trustee.Name)
                if ($A -and $Accounts[$A]) {
                    $ReturnObject['UserPrincipalName'] = $Accounts[$A].UserPrincipalName
                    $ReturnObject['AccountEnabled'] = $Accounts[$A].Enabled
                    $ReturnObject['DistinguishedName'] = $Accounts[$A].DistinguishedName
                    $ReturnObject['PasswordLastSet'] = if ($Accounts[$A].PasswordLastSet) { $Accounts[$A].PasswordLastSet } else { '' }
                    $ReturnObject['LastLogonDate'] = if ($Accounts[$A].LastLogonDate ) { $Accounts[$A].LastLogonDate } else { '' }
                    if (-not $ReturnObject['Sid']) {
                        $ReturnObject['Sid'] = $Accounts[$A].Sid.Value
                    }
                    if ($Accounts[$A].ObjectClass -eq 'group') {
                        $ReturnObject['SidType'] = 'Group'
                    } elseif ($Accounts[$A].ObjectClass -eq 'user') {
                        $ReturnObject['SidType'] = 'User'
                    } elseif ($Accounts[$A].ObjectClass -eq 'computer') {
                        $ReturnObject['SidType'] = 'Computer'
                    } else {
                        $ReturnObject['SidType'] = 'EmptyOrUnknown'
                    }
                } else {
                    $ReturnObject['UserPrincipalName'] = ''
                    $ReturnObject['AccountEnabled'] = ''
                    $ReturnObject['PasswordLastSet'] = ''
                    $ReturnObject['LastLogonDate'] = ''
                }
            }
            if ($IncludeGPOObject) {
                $ReturnObject['GPOObject'] = $GPO
                $ReturnObject['GPOSecurity'] = $SecurityRights
                $ReturnObject['GPOSecurityPermissionItem'] = $GPOPermission
            }
            [PSCustomObject] $ReturnObject
        }
        if ($IncludeOwner.IsPresent) {
            if ($GPO.Owner) {
                $SplittedOwner = $GPO.Owner.Split('\')
                $DomainOwner = $SplittedOwner[0]  #: EVOTEC
                $DomainUserName = $SplittedOwner[1]   #: Domain Admins
                $SID = $ADAdministrativeGroups['ByNetBIOS']["$($GPO.Owner)"].Sid.Value
                if ($SID) {
                    $SIDType = 'Group'
                    $DistinguishedName = $ADAdministrativeGroups['ByNetBIOS']["$($GPO.Owner)"].DistinguishedName
                } else {
                    $SIDType = ''
                    $DistinguishedName = ''
                }
            } else {
                $DomainOwner = $GPO.Owner
                $DomainUserName = ''
                $SID = ''
                $SIDType = 'EmptyOrUnknown'
                $DistinguishedName = ''
            }
            $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
                Permission        = 'GpoOwner'  # : GpoEditDeleteModifySecurity
                Inherited         = $false  # : False
                Domain            = $DomainOwner
                DistinguishedName = $DistinguishedName  #: CN = Domain Admins, CN = Users, DC = ad, DC = evotec, DC = xyz
                Name              = $DomainUserName
                Sid               = $SID     #: S - 1 - 5 - 21 - 853615985 - 2870445339 - 3163598659 - 512
                SidType           = $SIDType # #: Group
            }
            if ($Accounts) {
                $A = $GPO.Owner
                if ($A -and $Accounts[$A]) {
                    $ReturnObject['UserPrincipalName'] = $Accounts[$A].UserPrincipalName
                    $ReturnObject['AccountEnabled'] = $Accounts[$A].Enabled
                    $ReturnObject['DistinguishedName'] = $Accounts[$A].DistinguishedName
                    $ReturnObject['PasswordLastSet'] = if ($Accounts[$A].PasswordLastSet) { $Accounts[$A].PasswordLastSet } else { '' }
                    $ReturnObject['LastLogonDate'] = if ($Accounts[$A].LastLogonDate ) { $Accounts[$A].LastLogonDate } else { '' }
                    if (-not $ReturnObject['Sid']) {
                        $ReturnObject['Sid'] = $Accounts[$A].Sid.Value
                    }
                    if ($Accounts[$A].ObjectClass -eq 'group') {
                        $ReturnObject['SidType'] = 'Group'
                    } elseif ($Accounts[$A].ObjectClass -eq 'user') {
                        $ReturnObject['SidType'] = 'User'
                    } elseif ($Accounts[$A].ObjectClass -eq 'computer') {
                        $ReturnObject['SidType'] = 'Computer'
                    } else {
                        $ReturnObject['SidType'] = 'EmptyOrUnknown'
                    }
                } else {
                    $ReturnObject['UserPrincipalName'] = ''
                    $ReturnObject['AccountEnabled'] = ''
                    $ReturnObject['PasswordLastSet'] = ''
                    $ReturnObject['LastLogonDate'] = ''
                }
            }
            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
    )
    if ($XMLContent.GPO.LinksTo) {
        $Linked = $true
        $LinksCount = ([Array] $XMLContent.GPO.LinksTo).Count
    } else {
        $Linked = $false
        $LinksCount = 0
    }

    # 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'
    }
    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 = 'EmptyOrUnknown'
        }
    }
    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 '}'
            'Linked'                            = $Linked
            'LinksCount'                        = $LinksCount
            'Enabled'                           = $Enabled
            'ComputerEnabled'                   = $ComputerEnabled
            'UserEnabled'                       = $UserEnabled
            'ComputerSettingsAvailable'         = if ($null -eq $XMLContent.GPO.Computer.ExtensionData) { $false } else { $true }
            'UserSettingsAvailable'             = if ($null -eq $XMLContent.GPO.User.ExtensionData) { $false } else { $true }
            '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 ($_) {
                    [PSCustomObject] @{
                        CanonicalName = $_.SOMPath
                        Enabled       = $_.Enabled
                        NoOverride    = $_.NoOverride
                    }
                }
            }
            <#
        SOMName SOMPath Enabled NoOverride
        ------- ------- ------- ----------
        ad ad.evotec.xyz true false
        #>


            #| Select-Object -ExpandProperty SOMPath

        }
    }
    #break
}
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 ($Limited) {
                [PSCustomObject] @{
                    Collection      = $Collection
                    Changed         = [DateTime] $Registry.changed
                    GPOSettingOrder = $Registry.GPOSettingOrder
                    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
                }
            } else {
                $CreateGPO = [ordered]@{
                    DisplayName     = $GPO.DisplayName
                    DomainName      = $GPO.DomainName
                    GUID            = $GPO.GUID
                    GpoType         = $GPO.GpoType
                    #GpoCategory = $GPOEntry.GpoCategory
                    #GpoSettings = $GPOEntry.GpoSettings
                    Collection      = $Collection
                    Changed         = [DateTime] $Registry.changed
                    GPOSettingOrder = $Registry.GPOSettingOrder
                    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
                }
                $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
                        Changed         = [DateTime] $Registry.changed
                        GPOSettingOrder = $Registry.GPOSettingOrder
                        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
                    }
                } else {
                    $CreateGPO = [ordered]@{
                        DisplayName     = $GPO.DisplayName
                        DomainName      = $GPO.DomainName
                        GUID            = $GPO.GUID
                        GpoType         = $GPO.GpoType
                        #GpoCategory = $GPOEntry.GpoCategory
                        #GpoSettings = $GPOEntry.GpoSettings
                        Collection      = $Collection
                        Changed         = [DateTime] $Registry.changed
                        GPOSettingOrder = $Registry.GPOSettingOrder
                        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
                    }
                    $CreateGPO['Linked'] = $GPO.Linked
                    $CreateGPO['LinksCount'] = $GPO.LinksCount
                    $CreateGPO['Links'] = $GPO.Links
                    [PSCustomObject] $CreateGPO
                }
            } else {
                if ($Registry.Registry) {
                    #$Collection = $Registry.name
                    $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) {
                    if ($Collection) {
                        $Collection = "$Collection\$($Registry.Collection.name)"
                    } else {
                        $Collection = "$($Registry.name)\$($Registry.Collection.name)"
                    }
                    Get-XMLNestedRegistry -GPO $GPO -DataSet $Registry.Collection -Collection $Collection
                }
            }
        }

    }
}
function Get-XMLOutput {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [System.Xml.XmlElement[]] $GPOOutput,
        [string] $Splitter,
        [switch] $FullObjects
    )
    $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
                        }
                        [PSCustomObject] $Template
                    }
                    continue

                    foreach ($GpoSettings in $KeysToLoop.Name) {
                        foreach ($Key in $ExtensionType.$GpoSettings) {
                            $Template = [ordered] @{
                                DisplayName = $GPO.DisplayName
                                DomainName  = $GPO.DomainName
                                GUID        = $GPO.Guid
                                GpoType     = $GpoType
                                GpoCategory = $GPOSettingTypeSplit[1]
                                GpoSettings = $GpoSettings
                            }
                            try {
                                $Properties = ($Key | Get-Member -MemberType Properties -ErrorAction Stop).Name
                            } catch {
                                Write-Warning "Get-XMLStandard - things went sideways 1 $($_.Exception.Message)"
                                $Properties = $null
                            }
                            foreach ($Property in $Properties) {
                                $Template["$Property"] = $Key.$Property
                            }
                            $Template['Linked'] = $LinksInformation.Linked
                            $Template['LinksCount'] = $LinksInformation.LinksCount
                            $Template['Links'] = $LinksInformation.Links
                            <#
                            $Template['IncludeComments'] = $GPOOutput.IncludeComments # : true #: true
                            $Template['CreatedTime'] = $GPOOutput.CreatedTime # : 2020-06-17T11:23:22 #: 2020-06-17T11:23:22
                            $Template['ModifiedTime'] = $GPOOutput.ModifiedTime # : 2020-06-28T15:49:52 #: 2020-06-28T15:49:52
                            $Template['ReadTime'] = $GPOOutput.ReadTime # : 2020-06-28T16:14:09.2209011Z #: 2020-06-28T16:14:09.2209011Z
                            $Template['SecurityDescriptor'] = $GPOOutput.SecurityDescriptor # : SecurityDescriptor #: SecurityDescriptor
                            $Template['FilterDataAvailable'] = $GPOOutput.FilterDataAvailable # : true #: true
                            #>

                            [PSCustomObject] $Template
                        }
                    }
                }
            }
        }
    }
}
function Get-XMLStandard {
    [cmdletBinding()]
    param(
        [PSCustomObject] $GPO,
        [System.Xml.XmlElement[]] $GPOOutput,
        [string] $Splitter,
        [switch] $FullObjects
    )
    $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) {
                        foreach ($Key in $ExtensionType.$GpoSettings) {
                            $Template = [ordered] @{
                                DisplayName = $GPO.DisplayName
                                DomainName  = $GPO.DomainName
                                GUID        = $GPO.Guid
                                GpoType     = $GpoType
                                GpoCategory = $GPOSettingTypeSplit[1]
                                GpoSettings = $GpoSettings
                            }
                            try {
                                $Properties = ($Key | Get-Member -MemberType Properties -ErrorAction Stop).Name
                            } catch {
                                Write-Warning "Get-XMLStandard - things went sideways 1 $($_.Exception.Message)"
                                $Properties = $null
                            }
                            foreach ($Property in $Properties) {
                                $Template["$Property"] = $Key.$Property
                            }
                            $Template['Linked'] = $LinksInformation.Linked
                            $Template['LinksCount'] = $LinksInformation.LinksCount
                            $Template['Links'] = $LinksInformation.Links
                            <#
                            $Template['IncludeComments'] = $GPOOutput.IncludeComments # : true #: true
                            $Template['CreatedTime'] = $GPOOutput.CreatedTime # : 2020-06-17T11:23:22 #: 2020-06-17T11:23:22
                            $Template['ModifiedTime'] = $GPOOutput.ModifiedTime # : 2020-06-28T15:49:52 #: 2020-06-28T15:49:52
                            $Template['ReadTime'] = $GPOOutput.ReadTime # : 2020-06-28T16:14:09.2209011Z #: 2020-06-28T16:14:09.2209011Z
                            $Template['SecurityDescriptor'] = $GPOOutput.SecurityDescriptor # : SecurityDescriptor #: SecurityDescriptor
                            $Template['FilterDataAvailable'] = $GPOOutput.FilterDataAvailable # : true #: true
                            #>

                            [PSCustomObject] $Template
                        }
                    }
                }
            }
        }
    }
}
function Invoke-GPOTranslation {
    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $InputData,
        [string] $Report,
        [string] $Category,
        [string] $Settings
    )
    if ($Category -and $Settings -and $InputData) {
        if ($Script:GPODitionary[$Report]['Code']) {
            $Script:GPOList = $InputData.$Category.$Settings
            return & $Script:GPODitionary[$Report]['Code']
        }
    }
}
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) {
            # This doesn't work because no Domain and no $Server
            $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 Remove-PrivPermission {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [string] $Principal,
        [validateset('DistinguishedName', 'Name', 'Sid')][string] $PrincipalType = 'DistinguishedName',
        [PSCustomObject] $GPOPermission,
        [alias('PermissionType')][Microsoft.GroupPolicy.GPPermissionType[]] $IncludePermissionType
    )
    if ($GPOPermission.Name) {
        $Text = "Removing SID: $($GPOPermission.Sid), Name: $($GPOPermission.Domain)\$($GPOPermission.Name), SidType: $($GPOPermission.SidType) from domain $($GPOPermission.DomainName)"
    } else {
        $Text = "Removing SID: $($GPOPermission.Sid), Name: EMPTY, SidType: $($GPOPermission.SidType) 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.Name) / 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.Sid -eq $Principal -and $GPOPermission.Permission -eq $IncludePermissionType) {
            if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, $Text)) {
                try {
                    Write-Verbose "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType for $($Principal) / $($GPOPermission.Name) / 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.Name -eq $Principal -and $GPOPermission.Permission -eq $IncludePermissionType) {
            if ($PSCmdlet.ShouldProcess($GPOPermission.DisplayName, $Text)) {
                try {
                    Write-Verbose "Remove-GPOZaurrPermission - Removing permission $IncludePermissionType for $($Principal) / $($GPOPermission.Name) / 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)"
                }
            }
        }
    }
}

$Script:Actions = @{
    C = 'Create'
    D = 'Delete'
    U = 'Update'
    R = 'Replace'
}
$Script:GPODitionary = [ordered] @{
    AccountPolicies      = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'Account'
            }
        )
        GPOPath    = 'Computer Configuration -> Policies -> Windows Settings -> Security Settings -> Account Policies'
        Code       = {
            ConvertTo-XMLAccountPolicy -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLAccountPolicy -GPO $GPO
        }
    }
    Audit                = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'Audit'
            }
        )
        GPOPath    = ''
        Code       = {
            ConvertTo-XMLAudit -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLAudit -GPO $GPO
        }
    }
    Autologon            = [ordered] @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'RegistrySettings'
            }
        )
        Code       = {
            ConvertTo-XMLRegistryAutologon -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLRegistryAutologon -GPO $GPO
        }
    }
    DriveMapping         = [ordered] @{
        Types      = @(
            @{
                Category = 'DriveMapSettings'
                Settings = 'DriveMapSettings'
            }
        )
        GPOPath    = ''
        Code       = {
            ConvertTo-XMLDriveMapSettings -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLDriveMapSettings -GPO $GPO -SingleObject
        }
    }
    EventLog             = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'EventLog'
            }
        )
        GPOPath    = ''
        Code       = {
            ConvertTo-XMLEventLog -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLEventLog -GPO $GPO
        }
    }
    LocalUsers           = [ordered] @{
        Types      = @(
            @{
                Category = 'LugsSettings'
                Settings = 'LocalUsersAndGroups'
            }
        )
        Code       = {
            ConvertTo-XMLLocalUser -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLLocalUser -GPO $GPO -SingleObject
        }
    }
    LocalGroups          = [ordered] @{
        Types      = @(
            @{
                Category = 'LugsSettings'
                Settings = 'LocalUsersAndGroups'
            }
        )
        Code       = {
            ConvertTo-XMLLocalGroups -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLLocalGroups -GPO $GPO -SingleObject
        }
    }
    Policies             = @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'Policy'
            }
        )
        Code       = {
            ConvertTo-XMLPolicies -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLPolicies -GPO $GPO -SingleObject
        }
    }
    Printers             = @{
        Types      = @(
            @{
                Category = 'PrintersSettings'
                Settings = 'Printers'
            }
            @{
                Category = 'PrinterConnectionSettings'
                Settings = 'PrinterConnection'
            }
        )
        Code       = {
            ConvertTo-XMLPrinter -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLPrinter -GPO $GPO -SingleObject
        }
    }
    RegistrySettings     = [ordered] @{
        Types      = @(
            @{
                Category = 'RegistrySettings'
                Settings = 'RegistrySettings'
            }
        )
        Code       = {
            ConvertTo-XMLRegistrySettings -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLRegistrySettings -GPO $GPO -SingleObject
        }
    }
    Scripts              = [ordered] @{
        Types      = @(
            @{
                Category = 'Scripts'
                Settings = 'Script'
            }
        )
        Code       = {
            ConvertTo-XMLScripts -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLScripts -GPO $GPO -SingleObject
        }
    }
    SecurityOptions      = [ordered] @{
        Types      = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'SecurityOptions'
            }
        )
        Code       = {
            ConvertTo-XMLSecurityOptions -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLSecurityOptions -GPO $GPO -SingleObject
        }
    }
    SoftwareInstallation = [ordered] @{
        Types      = @(
            @{
                Category = 'SoftwareInstallationSettings'
                Settings = 'MsiApplication'
            }
        )
        Code       = {
            ConvertTo-XMLSoftwareInstallation -GPO $GPO
        }
        CodeSingle = {
            ConvertTo-XMLSoftwareInstallation -GPO $GPO -SingleObject
        }
    }
    SystemServices       = [ordered] @{
        Types       = @(
            @{
                Category = 'SecuritySettings'
                Settings = 'SystemServices'
            }
        )
        Description = ''
        GPOPath     = 'Computer Configuration -> 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     = 'Computer Configuration -> Preferences -> Control Pannel Settings -> Services'
        Code        = {
            ConvertTo-XMLSystemServicesNT -GPO $GPO
        }
        CodeSingle  = {
            ConvertTo-XMLSystemServicesNT -GPO $GPO -SingleObject
        }
    }
    TaskScheduler        = [ordered] @{
        Types       = @(
            @{
                Category = 'ScheduledTasksSettings'
                Settings = 'ScheduledTasks'
            }
        )
        Description = ''
        GPOPath     = ''
        Code        = {
            ConvertTo-XMLTaskScheduler -GPO $GPO
        }
        CodeSingle  = {
            ConvertTo-XMLTaskScheduler -GPO $GPO -SingleObject
        }
    }
}
$Script:GPOPropetiesComputers = [ordered] @{
    'Account'                       = ''
    'Audit'                         = ''
    'AuditSetting'                  = ''
    'AutoEnrollmentSettings'        = ''
    'Blocked'                       = ''
    'certSettingsTrustedPublishers' = ''
    'DataSourcesSettings'           = ''
    'DomainProfile'                 = ''
    'Dot3SvcSetting'                = ''
    'EFSRecoveryAgent'              = ''
    'EFSSettings'                   = ''
    'EnvironmentVariables'          = ''
    'EventLog'                      = ''
    'File'                          = ''
    'FilesSettings'                 = ''
    'Folders'                       = ''
    'General'                       = ''
    'Global'                        = ''
    'GlobalSettings'                = ''
    'InboundFirewallRules'          = ''
    'IntermediateCACertificate'     = ''
    'InternetZoneRule'              = ''
    'LocalUsersAndGroups'           = ''
    'MsiApplication'                = ''
    'NetworkOptions'                = ''
    'NetworkShares'                 = ''
    'NTServices'                    = ''
    'OutboundFirewallRules'         = ''
    'PathRule'                      = ''
    'Policy'                        = ''
    'PowerOptions'                  = ''
    'PrinterConnection'             = ''
    'Printers'                      = ''
    'PrivateProfile'                = ''
    'PublicProfile'                 = ''
    'Registry'                      = ''
    'RegistrySetting'               = ''
    'RegistrySettings'              = ''
    'RestrictedGroups'              = ''
    'RootCertificate'               = ''
    'RootCertificateSettings'       = ''
    'ScheduledTasks'                = ''
    'Script'                        = ''
    'SecurityOptions'               = ''
    'ShortcutSettings'              = ''
    'SystemServices'                = ''
    'TrustedPublishersCertificate'  = ''
    'type'                          = ''
    'UserRightsAssignment'          = ''
    'WLanSvcSetting'                = ''
}
$Script:GPOPropertiesUsers = [ordered] @{
    'AutoDetectConfigSettings'     = ''
    'AutoEnrollmentSettings'       = ''
    'AutomaticConfiguration'       = ''
    'AutoSetupSetting'             = ''
    'Blocked'                      = ''
    'BrowserTitle'                 = ''
    'CustomSetupSetting'           = ''
    'DataSourcesSettings'          = ''
    'DefinesConnectionSettings'    = ''
    'DefinesEscOffSettings'        = ''
    'DefinesEscOnSettings'         = ''
    'DeleteChannels'               = ''
    'DriveMapSettings'             = ''
    'EscOffLocalSites'             = ''
    'EscOffSecurityZoneAndPrivacy' = ''
    'EscOffTrustedSites'           = ''
    'EscOnLocalSites'              = ''
    'EscOnSecurityZoneAndPrivacy'  = ''
    'EscOnTrustedSites'            = ''
    'FavoriteURL'                  = ''
    'FilesSettings'                = ''
    'Folder'                       = ''
    'FolderOptions'                = ''
    'Folders'                      = ''
    'General'                      = ''
    'HomePage'                     = ''
    'ImportedContentRatings'       = ''
    'InternetOptions'              = ''
    'LocalUsersAndGroups'          = ''
    'MsiApplication'               = ''
    'NetworkOptions'               = ''
    'PathRule'                     = ''
    'PlaceFavoritesAtTop'          = ''
    'Policy'                       = ''
    'PowerOptions'                 = ''
    'PreferenceMode'               = ''
    'PrinterConnection'            = ''
    'Printers'                     = ''
    'Programs'                     = ''
    'ProxySettings'                = ''
    'RegionalOptionsSettings'      = ''
    'RegistrySetting'              = ''
    'RegistrySettings'             = ''
    'RestartSetupSetting'          = ''
    'ScheduledTasks'               = ''
    'Script'                       = ''
    'SearchBar'                    = ''
    'ShortcutSettings'             = ''
    'StartMenuSettings'            = ''
    'ToolsSetting'                 = ''
    'TrustedPublisherLockdown'     = ''
    'type'                         = ''
}
function Test-SysVolFolders {
    [cmdletBinding()]
    param(
        [Array] $GPOs,
        [string] $Server,
        [string] $Domain
    )
    $Differences = @{ }
    $SysvolHash = @{ }

    $GPOGUIDS = $GPOs.ID.GUID
    try {
        $SYSVOL = Get-ChildItem -Path "\\$($Server)\SYSVOL\$Domain\Policies" -ErrorAction Stop
    } 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 ($_.SideIndicator -eq '==') {
                $Found = 'Exists'
            } elseif ($_.SideIndicator -eq '<=') {
                $Found = 'Not available on SYSVOL'
            } elseif ($_.SideIndicator -eq '=>') {
                $Found = 'Orphaned GPO'
            } else {
                $Found = 'Orphaned GPO'
            }
            $Differences[$_.InputObject] = $Found
        }
    }
    $GPOSummary = @(
        foreach ($GPO in $GPOS) {
            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
                    $Owner = $ACL.Owner
                    $ErrorMessage = ''
                } catch {
                    Write-Warning "Get-GPOZaurrSysvol - ACL reading (1) failed for $FullPath with error: $($_.Exception.Message)"
                    $ACL = $null
                    $Owner = ''
                    $ErrorMessage = $_.Exception.Message
                }
            } else {
                $ACL = $null
            }
            if ($null -eq $Differences[$GPO.Id.Guid]) {
                $SysVolStatus = 'Not available on SYSVOL'
            } else {
                $SysVolStatus = $Differences[$GPO.Id.Guid]
            }
            [PSCustomObject] @{
                DisplayName      = $GPO.DisplayName
                Status           = $Differences[$GPO.Id.Guid]
                DomainName       = $GPO.DomainName
                SysvolServer     = $Server
                SysvolStatus     = $SysVolStatus
                Owner            = $GPO.Owner
                FileOwner        = $Owner
                Id               = $GPO.Id.Guid
                GpoStatus        = $GPO.GpoStatus
                Path             = $FullPath
                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
        foreach ($_ in $Differences.Keys) {
            if ($Differences[$_] -eq 'Orphaned GPO') {
                if ($SysvolHash[$_].BaseName -notcontains 'PolicyDefinitions') {
                    $FullPath = $SysvolHash[$_].FullName
                    try {
                        $ACL = Get-Acl -Path $FullPath -ErrorAction Stop
                        $Owner = $ACL.Owner
                        $ErrorMessage = ''
                    } catch {
                        Write-Warning "Get-GPOZaurrSysvol - ACL reading (2) failed for $FullPath with error: $($_.Exception.Message)"
                        $ACL = $null
                        $Owner = $null
                        $ErrorMessage = $_.Exception.Message
                    }

                    [PSCustomObject] @{
                        DisplayName      = $SysvolHash[$_].BaseName
                        Status           = 'Orphaned GPO'
                        DomainName       = $Domain
                        SysvolServer     = $Server
                        SysvolStatus     = $Differences[$GPO.Id.Guid]
                        Owner            = ''
                        FileOwner        = $Owner
                        Id               = $_
                        GpoStatus        = 'Orphaned'
                        Path             = $FullPath
                        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 = 'GPOGUID')]
    param(
        [Parameter(ParameterSetName = 'GPOName', Mandatory)]
        [string] $GPOName,

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

        [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
    )
    $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['ExcludePermissionType'] = $ExcludePermissionType
    $Splat['IncludePermissionType'] = $PermissionType
    $Splat['SkipWellKnown'] = $SkipWellKnown.IsPresent
    $Splat['SkipAdministrative'] = $SkipAdministrative.IsPresent

    $AdministrativeExists = @{
        DomainAdmins     = $false
        EnterpriseAdmins = $false
    }

    # This should always return results. When no data is found it should return basic information that will allow us to add credentials.
    [Array] $GPOPermissions = Get-GPOZaurrPermission @Splat -ReturnSecurityWhenNoData
    # 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
                return
            } 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.SID]
                    if ($AdministrativeGroup.SID -like '*-519') {
                        $AdministrativeExists['EnterpriseAdmins'] = $true
                    } elseif ($AdministrativeGroup.SID -like '*-512') {
                        $AdministrativeExists['DomainAdmins'] = $true
                    }
                    <#
                    if ($AdministrativeGroup) {
                        $DomainAdminsSID = -join ($ForestInformation['DomainsExtended'][$GPOPermission.DomainName].DomainSID, '-512')
                        $EnterpriseAdminsSID = -join ($ForestInformation['DomainsExtended'][$GPOPermission.DomainName].DomainSID, '-519')
                        if ($GPOPermission.SID -eq $DomainAdminsSID) {
                            $AdministrativeExists['DomainAdmins'] = $true
                        } elseif ($GPOPermission.SID -eq $EnterpriseAdminsSID) {
                            $AdministrativeExists['EnterpriseAdmins'] = $true
                        }
                    }
                    #>

                }
            } elseif ($Type -eq 'WellKnownAdministrative') {
                # this is for SYSTEM account
                return
            } elseif ($Type -eq 'AuthenticatedUsers') {
                # this is for Authenticated Users
                return
            }
        }
    }
    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)"
                    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)"
                    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)"
                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)"
                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)"
                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)"
                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)"
                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)"
                    }
                }
            }
        }

    }
}
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 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,

        [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]
                if ($GPOName) {
                    Get-GPO -Name $GPOName -Domain $Domain -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object {
                        Write-Verbose "Get-GPOZaurr - Getting GPO $($_.DisplayName) / ID: $($_.ID) from $Domain"
                        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
                        } else {
                            $_
                        }
                    }
                } elseif ($GPOGuid) {
                    Get-GPO -Guid $GPOGuid -Domain $Domain -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object {
                        Write-Verbose "Get-GPOZaurr - Getting GPO $($_.DisplayName) / ID: $($_.ID) from $Domain"
                        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
                        } else {
                            $_
                        }
                    }
                } else {
                    Get-GPO -All -Server $QueryServer -Domain $Domain -ErrorAction SilentlyContinue | ForEach-Object {
                        Write-Verbose "Get-GPOZaurr - Getting GPO $($_.DisplayName) / ID: $($_.ID) from $Domain"
                        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
                        } else {
                            $_
                        }
                    }
                }
            }
        } else {
            foreach ($Path in $GPOPath) {
                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
                }
            }
        }
    }
    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-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] $Limited,
        [switch] $AsHashTable,
        [switch] $Extended,
        [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'
                    }
                    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) {
                    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 ($Limited) {
                $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
            }
            if ($Signature) {
                $DigitalSignature = Get-AuthenticodeSignature -FilePath $_.Fullname
                $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
                }
            }
            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-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-GPOZaurrFilesPolicyDefinitions - 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-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-GPOZaurrLegacy - $($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-GPOZaurrOwner {
    [cmdletbinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(ParameterSetName = 'GPOName')][string] $GPOName,
        [Parameter(ParameterSetName = 'GPOGUID')][alias('GUID', 'GPOID')][string] $GPOGuid,

        [switch] $IncludeSysvol,

        [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
        }
    }
    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) {
            Write-Verbose "Get-GPOZaurrOwner - Processing GPO: $($_.DisplayName) from domain: $($_.DomainName)"
            $ACL = Get-ADACLOwner -ADObject $_.GPODistinguishedName -Resolve -ADAdministrativeGroups $ADAdministrativeGroups
            $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
                $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['DistinguishedName'] = $_.GPODistinguishedName
            [PSCUstomObject] $Object
        }
    }
    End {

    }
}
function Get-GPOZaurrPassword {
    [cmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation,
        [string[]] $GPOPath
    )
    if (-not $GPOPath) {
        if (-not $ExtendedForestInformation) {
            $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains
        } else {
            $ForestInformation = $ExtendedForestInformation
        }

        [Array] $GPOPath = foreach ($Domain in $ForestInformation.Domains) {
            -join ('\\', $Domain, '\SYSVOL\', $Domain, '\Policies')
        }
    }
    if (-not $GPOPath) {
        return
    }
    foreach ($Path in $GPOPath) {
        #Extract the all XML files in the Folders
        $Items = Get-ChildItem -LiteralPath $Path -Recurse -Filter *.xml
        $Output = foreach ($XMLFileName in $Items) {
            #Convert XML in a String file
            [string]$XMLString = Get-Content ($XMLFileName.FullName)
            #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 = ''
                }
                #[string]$GPOguid = [regex]::matches($XMLFileName.DirectoryName, '(?<=\{).+?(?=\})')
                #$GPODetail = Get-GPO -guid $GPOguid
                [xml] $XMLContent = $XMLString

                #if (-not $XMLContent.gpo.Computer.ExtensionData.Extension.LocalUsersAndGroups.User.Properties.cpassword -and -not $XMLContent.gpo.User.ExtensionData.Extension.DriveMapSettings.Drive.Properties.cpassword) {
                #Write-Host ''
                #}
                if ($Password) {
                    $PasswordStatus = $true
                } else {
                    $PasswordStatus = $false
                }

                [PsCustomObject] @{
                    'Name'                              = $XMLContent.GPO.Name
                    'Links'                             = $XMLContent.GPO.LinksTo #| Select-Object -ExpandProperty SOMPath
                    'Enabled'                           = $XMLContent.GPO.GpoStatus
                    'PasswordStatus'                    = $PasswordStatus
                    #'GPO' = $XMLContent.gpo.Computer.ExtensionData.Extension.LocalUsersAndGroups
                    'User'                              = $XMLContent.gpo.Computer.ExtensionData.Extension.LocalUsersAndGroups.User.name
                    'Cpassword'                         = $XMLContent.gpo.Computer.ExtensionData.Extension.LocalUsersAndGroups.User.Properties.cpassword
                    'CpasswordMap'                      = $XMLContent.gpo.User.ExtensionData.Extension.DriveMapSettings.Drive.Properties.cpassword
                    'Password'                          = $Password
                    'GUID'                              = $XMLContent.GPO.Identifier.Identifier.InnerText

                    'Domain'                            = $XMLContent.GPO.Identifier.Domain

                    'ComputerSettingsAvailable'         = if ($null -eq $XMLContent.GPO.Computer.ExtensionData) { $false } else { $true }
                    'ComputerSettingsStatus'            = if ($XMLContent.GPO.Computer.VersionDirectory -eq 0 -and $XMLContent.GPO.Computer.VersionSysvol -eq 0) { "NeverModified" } else { "Modified" }
                    'ComputerEnabled'                   = [bool] $XMLContent.GPO.Computer.Enabled
                    'ComputerSetttingsVersionIdentical' = if ($XMLContent.GPO.Computer.VersionDirectory -eq $XMLContent.GPO.Computer.VersionSysvol) { $true } else { $false }
                    'ComputerSettings'                  = $XMLContent.GPO.Computer.ExtensionData.Extension

                    'UserSettingsAvailable'             = if ($null -eq $XMLContent.GPO.User.ExtensionData) { $false } else { $true }
                    'UserEnabled'                       = [bool] $XMLContent.GPO.User.Enabled
                    '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


                    'CreationTime'                      = [DateTime] $XMLContent.GPO.CreatedTime
                    'ModificationTime'                  = [DateTime] $XMLContent.GPO.ModifiedTime
                    'ReadTime'                          = [DateTime] $XMLContent.GPO.ReadTime

                    'WMIFilter'                         = $GPO.WmiFilter.name
                    'WMIFilterDescription'              = $GPO.WmiFilter.Description
                    'Path'                              = $GPO.Path
                    #'SDDL' = if ($Splitter -ne '') { $XMLContent.GPO.SecurityDescriptor.SDDL.'#text' -join $Splitter } else { $XMLContent.GPO.SecurityDescriptor.SDDL.'#text' }
                    'ACL'                               = $XMLContent.GPO.SecurityDescriptor.Permissions.TrusteePermissions | ForEach-Object -Process {
                        [PSCustomObject] @{
                            'User'            = $_.trustee.name.'#Text'
                            'Permission Type' = $_.type.PermissionType
                            'Inherited'       = $_.Inherited
                            'Permissions'     = $_.Standard.GPOGroupedAccessEnum
                        }
                    }

                }
                #Write-Host "I find a Password [ " $Password " ] The GPO named:" $GPODetail" and th file is:" $XMLFileName

            } #if($XMLContent.Contains("cpassword")
        }
        $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', '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
    )
    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 {
                        $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) {
            $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 {
                try {
                    $IsConsistent = $_.IsAclConsistent()
                    $ErrorMessage = ''
                } catch {
                    $ErrorMessage = $_.Exception.Message
                    Write-Warning "Get-GPOZaurrPermissionConsistency - Failed to get consistency: $($_.Exception.Message)."
                    $IsConsistent = 'Not available.'
                }
                $SysVolpath = -join ('\\', $Domain, '\sysvol\', $Domain, '\Policies\{', $_.ID.GUID, '}')
                if ($VerifyInheritance) {
                    $FolderPermissions = Get-WinADSharePermission -Path $SysVolpath
                    [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 = $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 -or -not $ACLConsistentInside) {
                            [PSCustomObject] $Object
                        }
                    } else {
                        if (-not $IsConsistent) {
                            [PSCustomObject] $Object
                        }
                    }
                } elseif ($Type -eq 'Consistent') {
                    if ($VerifyInheritance) {
                        if ($IsConsistent -and $ACLConsistentInside) {
                            [PSCustomObject] $Object
                        }
                    } else {
                        if ($IsConsistent) {
                            [PSCustomObject] $Object
                        }
                    }
                }
            }
        }
    }
    End {

    }
}
function 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
    foreach ($Domain in $ForestInformation.Domains) {
        Write-Verbose "Get-WinADGPOSysvolFolders - Processing $Domain"
        $QueryServer = $ForestInformation['QueryServers']["$Domain"].HostName[0]
        Try {
            [Array]$GPOs = Get-GPO -All -Domain $Domain -Server $QueryServer
        } catch {
            Write-Warning "Get-GPOZaurrSysvol - 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
            } else {
                foreach ($Server in $ForestInformation['DomainDomainControllers']["$Domain"]) {
                    Write-Verbose "Get-GPOZaurrSysvol - Processing $Domain \ $($Server.HostName.Trim())"
                    Test-SysVolFolders -GPOs $GPOs -Server $Server.Hostname -Domain $Domain
                }
            }
        } else {
            Write-Warning "Get-GPOZaurrSysvol - GPO count for $Domain is less then 2. This is not expected for fully functioning domain. Skipping processing SYSVOL folder."
        }
    }
}
function Get-WMIFilter {
    param(

    )

}

function Get-GPOZaurrWMI {
    [cmdletBinding()]
    Param(
        [Guid[]] $Guid,
        [string[]] $Name,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $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])
                }
            }
            [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'
            }
        }

    }
}
<#
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('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')]
        [switch] $NoTranslation,

        [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', 'Excel')][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] $CategoriesOnly,

        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'Local')]
        [switch] $SingleObject
    )
    if ($Type.Count -eq 0) {
        $Type = $Script:GPODitionary.Keys
    }
    if ($GPOPath) {
        if (Test-Path -LiteralPath $GPOPath) {
            <#
            $GPOListPath = [io.path]::Combine($GPOPath, "GPOList.xml")
            if ($GPOListPath) {
                $GPOs = Import-Clixml -Path $GPOListPath
            } else {
 
            }
            #>

            $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-GPOZaurr - 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 "Find-GPO - $GPOPath doesn't exists."
            return
        }
    } else {
        [Array] $GPOs = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    }
    $Output = [ordered] @{}
    $OutputByGPO = [ordered] @{}
    $TranslatedOutput = [ordered] @{}
    $CachedCategories = [ordered] @{}

    [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 $CachedCategories
    }

    # Return Categories or save it
    if ($CategoriesOnly) {
        return $GPOCategories | Select-Object -Property * -ExcludeProperty DataSet
    } else {
        $Output['Categories'] = $GPOCategories | Select-Object -Property * -ExcludeProperty DataSet
    }
    # Save Cached Categories
    $Output['CategoriesFull'] = $CachedCategories
    # Process Reporting
    $Output['Reports'] = [ordered] @{}

    if ($CachedCategories.Count -gt 0) {
        foreach ($Report in $Type) {

            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 $CachedCategories[$Category]) {
                    continue
                }
                if (-not $CachedCategories[$Category][$Settings]) {
                    continue
                }
                # Translation
                $CategorizedGPO = $CachedCategories[$Category][$Settings]
                foreach ($GPO in $CategorizedGPO) {
                    if (-not $Output['Reports'][$Report]) {
                        $Output['Reports'][$Report] = [System.Collections.Generic.List[PSCustomObject]]::new()
                    }
                    $TranslatedGpo = $null
                    if ($SingleObject) {
                        if ($Script:GPODitionary[$Report]['CodeSingle']) {
                            $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['CodeSingle']
                        }
                    } else {
                        if ($Script:GPODitionary[$Report]['Code']) {
                            $TranslatedGpo = Invoke-Command -ScriptBlock $Script:GPODitionary[$Report]['Code']
                        }
                    }
                    foreach ($T in $TranslatedGpo) {
                        $Output['Reports'][$Report].Add($T)
                    }
                }
            }
        }
    }
    return $Output
    <#
    foreach ($GPO in $GPOs) {
        if ($GPOPath) {
            $GPOOutput = $GPO.GPOOutput
        } else {
            [xml] $GPOOutput = Get-GPOReport -Guid $GPO.GUID -Domain $GPO.DomainName -ReportType Xml
        }
        [Array] $Data = Get-XMLStandard -GPO $GPO -GPOOutput $GPOOutput.GPO -Splitter $Splitter -FullObjects:$FullObjects
        foreach ($D in $Data) {
            if (-not $Output["$($D.GpoCategory)"]) {
                $Output["$($D.GpoCategory)"] = [ordered] @{}
            }
            if (-not $Output["$($D.GpoCategory)"]["$($D.GpoSettings)"]) {
                $Output["$($D.GpoCategory)"]["$($D.GpoSettings)"] = [System.Collections.Generic.List[PSCustomObject]]::new()
            }
            $Output["$($D.GpoCategory)"]["$($D.GpoSettings)"].Add($D)
 
            if (-not $OutputByGPO["$($D.DomainName)"]) {
                $OutputByGPO["$($D.DomainName)"] = [ordered] @{}
            }
            if (-not $OutputByGPO[$D.DomainName][$D.DisplayName]) {
                $OutputByGPO[$D.DomainName][$D.DisplayName] = [System.Collections.Generic.List[PSCustomObject]]::new()
            }
            $OutputByGPO[$D.DomainName][$D.DisplayName].Add($D)
        }
 
    }
    if ($NoTranslation) {
        if ($OutputType -contains 'Object') {
            $Output
        }
    } else {
        foreach ($Report in $Type) {
            $Category = $Script:GPODitionary[$Report]['Category']
            $Settings = $Script:GPODitionary[$Report]['Settings']
            $TranslatedOutput[$Report] = Invoke-GPOTranslation -InputData $Output -Category $Category -Settings $Settings -Report $Report
        }
        if ($OutputType -contains 'Object') {
            $TranslatedOutput
        }
    }
 
    if ($NoTranslation) {
        $SingleSource = $Output
    } else {
        $SingleSource = $TranslatedOutput
    }
 
    if ($OutputPath) {
        $FolderPath = $OutputPath
    } else {
        $FolderPath = [io.path]::GetTempPath()
    }
    if ($OutputType -contains 'HTML') {
        $FilePathHTML = [io.path]::Combine($FolderPath, "GPOZaurr-Summary-$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss')).html")
        Write-Warning "Invoke-GPOZaurr - $FilePathHTML"
        New-HTML {
            foreach ($GPOCategory in $SingleSource.Keys) {
                New-HTMLTab -Name $GPOCategory {
                    if ($SingleSource["$GPOCategory"] -is [System.Collections.IDictionary]) {
                        foreach ($GpoSettings in $SingleSource["$GPOCategory"].Keys) {
                            New-HTMLTab -Name $GpoSettings {
                                if ($SingleSource[$GPOCategory][$GpoSettings].Count -gt 0) {
                                    New-HTMLTable -DataTable $SingleSource[$GPOCategory][$GpoSettings] -ScrollX -DisablePaging -AllProperties -Title $GpoSettings
                                }
                            }
                        }
                    } else {
                        if ($SingleSource[$GPOCategory].Count -gt 0) {
                            New-HTMLTable -DataTable $SingleSource[$GPOCategory] -ScrollX -DisablePaging -AllProperties -Title $GpoSettings
                        }
                    }
                }
            }
        } -Online -ShowHTML:$Open.IsPresent -FilePath $FilePathHTML
    }
    if ($OutputType -contains 'Excel') {
        $FilePathExcel = [io.path]::Combine($FolderPath, "GPOZaurr-Summary-$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss')).xlsx")
        Write-Warning "Invoke-GPOZaurr - $FilePathExcel"
        foreach ($GPOCategory in $SingleSource.Keys) {
            if ($SingleSource["$GPOCategory"] -is [System.Collections.IDictionary]) {
                foreach ($GpoSettings in $SingleSource["$GPOCategory"].Keys) {
                    if ($SingleSource[$GPOCategory][$GpoSettings].Count -gt 0) {
                        ConvertTo-Excel -DataTable $SingleSource[$GPOCategory][$GpoSettings] -AllProperties -ExcelWorkSheetName $GpoSettings -FilePath $FilePathExcel -AutoFilter -AutoFit -Option Rename
                    }
                }
            } else {
                if ($SingleSource[$GPOCategory].Count -gt 0) {
                    ConvertTo-Excel -DataTable $SingleSource[$GPOCategory] -AllProperties -ExcelWorkSheetName $GPOCategory -FilePath $FilePathExcel -AutoFilter -AutoFit -Option Rename
                }
            }
        }
        if ($Open) {
            Invoke-Item -Path $FilePathExcel
        }
    }
    #>


}

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

    $Script:GPODitionary.Keys | Sort-Object | Where-Object { $_ -like "*$wordToComplete*" }
}
Register-ArgumentCompleter -CommandName Find-GPO -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 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 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-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-GPOZaurrOrphanedSysvolFolders {
    [cmdletBinding(SupportsShouldProcess)]
    param(
        [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-GPOZaurrSysvol -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation | Where-Object {
        if ($_.Status -eq 'Orphaned GPO') {
            $_
        }
    } | Select-Object | Select-Object -First $LimitProcessing | ForEach-Object {
        Write-Verbose "Remove-GPOZaurrOrphanedSysvolFolders - Processing $($_.Path)"
        if ($BackupFinalPath) {
            Try {
                Write-Verbose "Remove-GPOZaurrOrphanedSysvolFolders - Backing up $($_.Path)"
                Copy-Item -LiteralPath $_.Path -Recurse -Destination $BackupFinalPath -ErrorAction Stop
                $BackupWorked = $true
            } catch {
                Write-Warning "Remove-GPOZaurrOrphanedSysvolFolders - Error backing up error: $($_.Exception.Message)"
                $BackupWorked = $false
            }
        }
        if ($BackupWorked -or $BackupFinalPath -eq '') {
            Write-Verbose "Remove-GPOZaurrOrphanedSysvolFolders - Deleting $($_.Path)"
            try {
                Remove-Item -Recurse -Force -LiteralPath $_.Path
            } catch {
                $ErrorMessage = $_.Exception.Message
                Write-Warning "Remove-GPOZaurrOrphanedSysvolFolders - Failed to remove file $($_.Path): $($ErrorMessage)."
            }
        }
    }
}
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', '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 {
                $getPrivPermissionSplat = @{
                    Principal              = $Principal
                    PrincipalType          = $PrincipalType
                    Accounts               = $Accounts
                    Type                   = $Type
                    GPO                    = $_
                    SkipWellKnown          = $SkipWellKnown.IsPresent
                    SkipAdministrative     = $SkipAdministrative.IsPresent
                    IncludeOwner           = $false
                    IncludeGPOObject       = $true
                    IncludePermissionType  = $IncludePermissionType
                    ExcludePermissionType  = $ExcludePermissionType
                    ADAdministrativeGroups = $ADAdministrativeGroups
                }
                [Array] $GPOPermissions = Get-PrivPermission @getPrivPermissionSplat
                if ($GPOPermissions.Count -gt 0) {
                    foreach ($Permission in $GPOPermissions) {
                        Remove-PrivPermission -Principal $Permission.Sid -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-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
        $GPOs = Get-GPOZaurrAD -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        foreach ($GPO in $GPOS) {
            $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 Select-GPOTranslation {
    [cmdletbInding()]
    param(
        [Parameter(ValueFromPipeline)][System.Collections.IDictionary] $InputObject,
        [string] $Category,
        [string] $Settings
    )

    $Important = [ordered] @{}
    $AllProperties = Select-Properties -AllProperties -Objects $InputObject.$Category.$Settings
    $MissingProperties = $AllProperties | Where-Object { $_ -notin 'DisplayName', 'DomainName', 'GUID', 'Linked', 'LinksCount', 'Links', 'GPOType', 'GPOCategory', 'GPOSettings' }
    $Types = foreach ($Property in $MissingProperties) {
        ($InputObject.$Category.$Settings | Where-Object { $null -ne $_.$Property }).$Property | ForEach-Object {
            ($_ | Get-Member -MemberType Properties) | Where-Object { $_.Name -notin 'Length' }
        }
    }
    $Important['AllProperties'] = $AllProperties
    $Important['MissingProperties'] = $MissingProperties
    $Important['Types'] = $Types | Select-Object -Unique
    $Important['Data'] = $InputObject.$Category.$Settings
    $Important
}
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
    Short description
 
    .DESCRIPTION
    Long description
 
    .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
    Parameter description
 
    .PARAMETER GPOGuid
    Parameter description
 
    .PARAMETER Forest
    Parameter description
 
    .PARAMETER ExcludeDomains
    Parameter description
 
    .PARAMETER IncludeDomains
    Parameter description
 
    .PARAMETER ExtendedForestInformation
    Parameter description
 
    .PARAMETER Principal
    Parameter description
 
    .PARAMETER SkipSysvol
    Parameter description
 
    .PARAMETER LimitProcessing
    Parameter description
 
    .EXAMPLE
    An example
 
    .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
    )
    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
        }
        if ($GPOName) {
            $getGPOZaurrOwnerSplat['GPOName'] = $GPOName
        } elseif ($GPOGuid) {
            $getGPOZaurrOwnerSplat['GPOGuid'] = $GPOGUiD
        }
        Get-GPOZaurrOwner @getGPOZaurrOwnerSplat | Where-Object {
            if ($_.Owner) {
                $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($_.Owner)"]
            } else {
                $AdministrativeGroup = $null
            }
            if (-not $SkipSysvol) {
                if ($_.SysvolOwner) {
                    $AdministrativeGroupSysvol = $ADAdministrativeGroups['ByNetBIOS']["$($_.SysvolOwner)"]
                } else {
                    $AdministrativeGroupSysvol = $null
                }
            }
            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 {

    }
}



Export-ModuleMember -Function @('Add-GPOPermission', 'Add-GPOZaurrPermission', 'Backup-GPOZaurr', 'Get-GPOZaurr', 'Get-GPOZaurrAD', 'Get-GPOZaurrBackupInformation', 'Get-GPOZaurrFiles', 'Get-GPOZaurrFilesPolicyDefinitions', 'Get-GPOZaurrFolders', 'Get-GPOZaurrLegacyFiles', 'Get-GPOZaurrLink', 'Get-GPOZaurrLinkSummary', 'Get-GPOZaurrOwner', 'Get-GPOZaurrPassword', 'Get-GPOZaurrPermission', 'Get-GPOZaurrPermissionConsistency', 'Get-GPOZaurrSysvol', 'Get-GPOZaurrWMI', 'Get-WMIFilter', 'Invoke-GPOZaurr', 'Invoke-GPOZaurrPermission', 'New-GPOZaurrWMI', 'Remove-GPOPermission', 'Remove-GPOZaurr', 'Remove-GPOZaurrFolders', 'Remove-GPOZaurrLegacyFiles', 'Remove-GPOZaurrOrphanedSysvolFolders', 'Remove-GPOZaurrPermission', 'Remove-GPOZaurrWMI', 'Repair-GPOZaurrPermissionConsistency', 'Restore-GPOZaurr', 'Save-GPOZaurrFiles', 'Select-GPOTranslation', 'Set-GPOOwner', 'Set-GPOZaurrOwner') -Alias @('Find-GPO')
# SIG # Begin signature block
# MIIgQAYJKoZIhvcNAQcCoIIgMTCCIC0CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUu2+LbpElh9Xdo+i/cXzcxS++
# ukWgghtvMIIDtzCCAp+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
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUmPdUPaXW
# yOJE9kT0MstdVqRajFkwDQYJKoZIhvcNAQEBBQAEggEAJZuMblRJdY5opUEh+v4G
# Vfdvah6E7WL7xhkldjW6/C8/HuVuoAIB0UhXWZyVHtR4q52LLlfe5eIaWDvzZ7mQ
# ajeC1KAkVVzLfsjcM1oe0gyeXhwaHeVQ1GcVl0v0qMfVIMzg1XvgmC1LeO78HNXQ
# yz5xfwu/jdqdUUv9pQIv8DuuaxLsnTywgHfI13yUFRj4jyKipilMGj8NZ7UADWX/
# iq4bi3zuUevkYb5eIM/u8z69/gtl/wyrV0bxEfuH0uYTXNR5y1hteBr+XIGxDOg6
# cHIsYt90/9wii2MPCq48hF0i2SWMUzrLq9HH00S8iXb6Vos4fV9B6YUPKxvjcPjI
# NKGCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr
# 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc
# BgkqhkiG9w0BCQUxDxcNMjAwNzI5MTI0MzM0WjAjBgkqhkiG9w0BCQQxFgQUP3yz
# rxDIwrZdlb+lBietF40saeEwDQYJKoZIhvcNAQEBBQAEggEAmaSNvpztRzDfmAHF
# V/cgI85TDClRJ9hrp/1uIrm2oaNAYbnJLZEGJsmaqwz14hCMnxWwPvpPqrmRBN23
# uCtzWbABaInHHaYnxzk30AaUFFSPp4x2TO+nSQKz8/v6tXzulRA8ycoNL2QTzEFy
# V5wMYTrsevtjeUykbQod8cRFu8pzIPkt8ZGq3BOZ/BO8zcDi0zgCCdwq3lWbxqce
# Nk7i8DMbA9BgSMjL76wQK/kujsoHbRmkIfDUP2qIwFb5gYn86LRhIMVwroczymsD
# tDuMjEVraNg4blB53q54WStrp7X6eTdydpHYPAAkDCmTZPif8x+nvb3MipRZCZ3d
# ytZOLw==
# SIG # End signature block