DefenderForIdentity.psm1

<#
Copyright (c) Microsoft Corporation.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#>


#requires -Version 4.0
#requires -Modules ActiveDirectory, GroupPolicy

#region General settings

$script:settings = @{

    gpoNamePrefix                  = 'Microsoft Defender for Identity'

    gpoExtensions                  = @{
        'Core GPO Engine'                                = '00000000-0000-0000-0000-000000000000'
        'Tool Extension GUID (Computer Policy Settings)' = '0F6B957D-509E-11D1-A7CC-0000F87571E3'
        'Security'                                       = '827D319E-6EAC-11D2-A4EA-00C04F79F83A'
        'Computer Restricted Groups'                     = '803E14A0-B4FB-11D0-A0D0-00A0C90F574B'
        'Preference Tool CSE GUID Registry'              = 'BEE07A6A-EC9F-4659-B8C9-0B1937907C83'
        'Preference CSE GUID Registry'                   = 'B087BE9D-ED37-454F-AF9C-04291E351182'
        'Audit Configuration Extension'                  = '0F3F3735-573D-9804-99E4-AB2A69BA5FD4'
        'Audit Policy Configuration'                     = 'F3CCC681-B74C-4060-9F26-CD84525DCA2A'
    }

    ProcessorPerformance           = @{
        GpoName    = '{0} - Processor Performance'
        SchemeGuid = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'
        Key        = 'HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Power\PowerSettings'
        ValueName  = 'ActivePowerScheme'
    }

    RemoteSAM                      = @{
        GpoName           = '{0} - Remote SAM Access'
        GpoRegSet         = @{
            'System\CurrentControlSet\Control\Lsa\RestrictRemoteSAM=1' = 'O:BAG:BAD:(A;;RC;;;BA)(A;;RC;;;{0})'
        }
        RegistrySet       = @{
            'System\CurrentControlSet\Control\Lsa\RestrictRemoteSAM' = 'O:BAG:BAD:(A;;RC;;;BA)(A;;RC;;;{0})'
        }
        DenyGPPermissions = [ordered]@{
            '{0}-516' = 'GpoApply'
        }
    }

    NTLMAuditing                   = @{
        GpoName     = '{0} - NTLM Auditing for DCs'
        RegistrySet = @{
            'System\CurrentControlSet\Control\Lsa\MSV1_0\AuditReceivingNTLMTraffic'   = '2'
            'System\CurrentControlSet\Control\Lsa\MSV1_0\RestrictSendingNTLMTraffic'  = '1|2'
            'System\CurrentControlSet\Services\Netlogon\Parameters\AuditNTLMInDomain' = '7'
        }
    }

    EntraConnectAuditing           = @{
        GpoName  = '{0} - Advanced Audit and URA Policy for Entra Connect'
        GptSet   = @{
            'SeServiceLogonRight' = '*{0},*S-1-5-80-0'
        }
        AuditSet = @'
Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value
,System,Audit Logon,{0cce9215-69ae-11d9-bed3-505054503030},Success and Failure,,3
'@

    }

    CAAuditing                     = @{
        GpoName       = '{0} - Auditing for CAs'
        GpoVal        = @{ 'AuditFilter' = 127 }
        GpoReg        = 'System\CurrentControlSet\Services\CertSvc\Configuration\%DomainName%-%ComputerName%-CA'
        RegPathActive = 'System\CurrentControlSet\Services\CertSvc\Configuration\Active'
        RegistrySet   = @{
            'System\CurrentControlSet\Services\CertSvc\Configuration\{0}\AuditFilter' = 127
        }
        GPPermissions = [ordered]@{
            '{0}-517'  = 'GpoApply'
            '{0}-516'  = 'GpoRead'
            'S-1-5-11' = 'GpoRead'
        }
    }

    AdvancedAuditPolicyDCs         = @{
        GpoName        = '{0} - Advanced Audit Policy for DCs'
        PolicySettings = @'
Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value
,System,Security System Extension,{0CCE9211-69AE-11D9-BED3-505054503030},Success and Failure,,3
,System,Distribution Group Management,{0CCE9238-69AE-11D9-BED3-505054503030},Success and Failure,,3
,System,Security Group Management,{0CCE9237-69AE-11D9-BED3-505054503030},Success and Failure,,3
,System,Computer Account Management,{0CCE9236-69AE-11D9-BED3-505054503030},Success and Failure,,3
,System,User Account Management,{0CCE9235-69AE-11D9-BED3-505054503030},Success and Failure,,3
,System,Directory Service Access,{0CCE923B-69AE-11D9-BED3-505054503030},Success and Failure,,3
,System,Directory Service Changes,{0CCE923C-69AE-11D9-BED3-505054503030},Success and Failure,,3
,System,Credential Validation,{0CCE923F-69AE-11D9-BED3-505054503030},Success and Failure,,3
'@

    }

    AdvancedAuditPolicyCAs         = @{
        GpoName        = '{0} - Advanced Audit Policy for CAs'
        PolicySettings = @'
Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value
,System,Audit Certification Services,{0cce9221-69ae-11d9-bed3-505054503030},Success and Failure,,3
'@

        GPPermissions  = [ordered]@{
            '{0}-517'  = 'GpoApply'
            '{0}-516'  = 'GpoRead'
            'S-1-5-11' = 'GpoRead'
        }
    }

    ObjectAuditing                 = @{
        Path     = '{0}'
        Auditing = @'
SecurityIdentifier,AccessMask,AuditFlagsValue,InheritedObjectAceType,Description,InheritanceType,PropagationFlags
S-1-1-0,852331,1,bf967aba-0de6-11d0-a285-00aa003049e2,Descendant User Objects,2,2
S-1-1-0,852331,1,bf967a9c-0de6-11d0-a285-00aa003049e2,Descendant Group Objects,2,2
S-1-1-0,852331,1,bf967a86-0de6-11d0-a285-00aa003049e2,Descendant Computer Objects,2,2
S-1-1-0,852331,1,ce206244-5827-4a86-ba1c-1c0c386c1b64,Descendant msDS-ManagedServiceAccount Objects,2,2
S-1-1-0,852075,1,7b8b558a-93a5-4af7-adca-c017e67f1057,Descendant msDS-GroupManagedServiceAccount Objects,2,2
S-1-1-0,852075,1,0feb936f-47b3-49f2-9386-1dedc2c23765,Descendant msDS-DelegatedManagedServiceAccount Objects,2,2
'@
 | ConvertFrom-Csv
    }

    ConfigurationContainerAuditing = @{
        Validate = 'LDAP://CN=Microsoft Exchange,CN=Services,CN=Configuration,{0}'
        Path     = 'CN=Configuration,{0}'
        Auditing = @'
SecurityIdentifier,AccessMask,AuditFlagsValue,AceFlagsValue,InheritedObjectAceType,InheritanceType,PropagationFlags
S-1-1-0,32,3,194,00000000-0000-0000-0000-000000000000,1,0
'@
 | ConvertFrom-Csv
    }

    AdfsAuditing                   = @{
        Validate = 'LDAP://CN=ADFS,CN=Microsoft,CN=Program Data,{0}'
        Path     = 'CN=ADFS,CN=Microsoft,CN=Program Data,{0}'
        Auditing = @'
SecurityIdentifier,AccessMask,AuditFlagsValue,AceFlagsValue,InheritedObjectAceType,InheritanceType,PropagationFlags
S-1-1-0,48,3,194,00000000-0000-0000-0000-000000000000,1,0
'@
 | ConvertFrom-Csv
    }

    SensitiveGroups                = @{
        'Administrators'              = 'S-1-5-32-544'
        'Account Operators'           = 'S-1-5-32-548'
        'Backup Operators'            = 'S-1-5-32-551'
        'Domain Admins'               = '{0}-512'
        'Domain Controllers'          = '{0}-516'
        'Enterprise Admins'           = '{0}-519'
        'Group Policy Creator Owners' = '{0}-520'
        'Print Operators'             = 'S-1-5-32-550'
        'Replicators'                 = 'S-1-5-32-552'
        'Schema Admins'               = '{0}-518'
        'Server Operators'            = 'S-1-5-32-549'
        'Cert Publishers'             = '{0}-517'
    }
}

if (Test-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath ($PSUICulture))) {
    Import-LocalizedData -BindingVariable strings
} else {
    Import-LocalizedData -BindingVariable strings -UICulture en-US
}

#endregion

#region General helper functions

function Get-MDIValidationMessage {
    param($Result)
    if ($Result) {
        $strings['Validation_Passed']
    } else {
        $strings['Validation_Failed']
    }
}

function Resolve-MDIPath {
    param(
        [parameter(Mandatory)] $Path
    )
    $return = Resolve-Path -Path $Path -ErrorAction SilentlyContinue -ErrorVariable resolveError
    if ($return.Path) { $return.Path }
    else { $resolveError[0].TargetObject }
}

function Format-Json {
    param(
        [Parameter(Mandatory, ValueFromPipeline)] [String] $json
    )
    $indent = 0;
    ($json -Split '\n' | ForEach-Object {
        if ($_ -match '[\}\]]') {
            $indent--
        }
        $line = (' ' * $indent * 2) + $_.TrimStart().Replace(': ', ': ')
        if ($_ -match '[\{\[]') {
            $indent++
        }
        $line
    }) -join "`n"
}

function Test-MDICAServer {
    [CmdletBinding()]
    param()
    [bool](Get-Service CertSvc -ErrorAction SilentlyContinue)
}

function New-MDIPassword {
    $guid = (New-Guid).guid.split('-')
    $guid[0] += [char](Get-Random -min 65 -max 90)
    $guid[1] += [char](Get-Random -min 65 -max 90)
    ConvertTo-SecureString ($guid -join '-') -AsPlainText -Force
}

#endregion

#region Sensor service helper functions

function Get-MDISensorBinPath {
    [CmdletBinding()]
    param()
    $wmiParams = @{
        Namespace   = 'root\cimv2'
        ClassName   = 'Win32_Service'
        Property    = 'PathName'
        Filter      = 'Name="AATPSensor"'
        ErrorAction = 'Stop'
    }
    Write-Verbose -Message $strings['Sensor_LocateConfigurationFile']
    try {
        $return = (Get-CimInstance @wmiParams | Select-Object -ExpandProperty PathName) -replace '"|Microsoft\.Tri\.Sensor\.exe', ''
    } catch {
        $return = $null
    }
    if ([string]::IsNullOrEmpty($return)) {
        Write-Warning $strings['Sensor_ServiceNotFound']
    }
    $return
}

function Get-MDISenseIdentityBinPath {
    [CmdletBinding()]
    param()
    $processParams = @{
        Name            = "SenseIdentity"
        FileVersionInfo = $true
    }
    $senseIdentityProcess = Get-Process @processParams
    ($senseIdentityProcess).FileName | Split-Path
}

function Stop-MDISensor {
    [CmdletBinding()]
    param()
    Stop-Service -Name AATPSensorUpdater -Force
}

function Start-MDISensor {
    [CmdletBinding()]
    param()
    Start-Service -Name AATPSensorUpdater
}

function Get-MDISensorProcessInformation {
    [CmdletBinding()]
    param()
    [PSCustomObject]@{
        AATPSensor        = $(try { (Get-Service AATPSensor -ErrorAction SilentlyContinue).Status } catch { $null })
        AATPSensorUpdater = $(try { (Get-Service AATPSensorUpdater -ErrorAction SilentlyContinue).Status } catch { $null })
        SenseIdentity     = $(try { if ((Get-Process -Name SenseIdentity -ErrorAction SilentlyContinue).Count -gt 0) { 'Running' } else { $null } } catch { $null })
    }
}
#endregion

#region Service account configuration functions
function Test-MDIKDSRootKey {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $adRootDseParams = @{

    }; if (-not [string]::IsNullOrEmpty($Server)) { $adRootDseParams.Add("Server", $Server) }
    $configurationNamingContext = (Get-ADRootDSE @adRootDseParams).configurationNamingContext
    $null = New-PSDrive -Name $($myDomain.netbiosname) -PSProvider ActiveDirectory -Server $myDomain.ChosenDC -Root "//RootDSE/"
    $kdsPath = '{0}:\CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services,{1}' -f $($myDomain.netbiosname), $configurationNamingContext
    try {
        $kdsGci = Get-ChildItem -Path $kdsPath
        $kdsGet = (Get-KdsRootKey)
    } catch {
        return $false
    }
    Remove-PSDrive -Name $($myDomain.netbiosname)
    return ($kdsGci.distinguishedName.length -gt 0) -or $kdsGet
}

function Get-MDIDSA {
    [CmdletBinding()]
    param(
        [string] $Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $returnVal = $null
    $adAccountParams = @{
        LDAPFilter = '(&(|(samaccountname={0})(samaccountname={0}$))(|(objectClass=user)(objectClass=msDS-GroupManagedServiceAccount)))' -f $Identity
        Properties = "msDS-PrincipalName", "DistinguishedName", "ObjectSid", "ObjectClass", "samaccountname"
    }; if (-not [string]::IsNullOrEmpty($Server)) { $adAccountParams.Add("Server", $Server) }
    try {
        $returnVal = Get-AdObject @adAccountParams -ErrorAction SilentlyContinue
        if ($returnVal -eq $null) {
            throw
        }
    } catch {
        $returnVal = $null
        Write-Warning ($strings['DSA_CannotFindIdentity'] -f $Identity)
    }
    return $returnVal
}

function New-MDIDSA {
    [CmdletBinding(DefaultParameterSetName = "gmsaAccount")]
    Param(
        [parameter(Mandatory = $true, ParameterSetName = "gmsaAccount", Position = 1)]
        [parameter(Mandatory = $true, ParameterSetName = "standardAccount", Position = 1)]
        [ValidateLength(1, 16)]
        [string]$Identity,
        [parameter(Mandatory = $true, ParameterSetName = "gmsaAccount")]
        [ValidateLength(1, 28)]
        [string]$GmsaGroupName,
        [parameter(Mandatory = $false, ParameterSetName = "gmsaAccount")]
        [parameter(Mandatory = $false, ParameterSetName = "standardAccount")]
        [string]$BaseDn,
        [parameter(Mandatory = $false, ParameterSetName = "standardAccount")]
        [switch]$ForceStandardAccount,
        [parameter(Mandatory = $false, ParameterSetName = "gmsaAccount")]
        [parameter(Mandatory = $false, ParameterSetName = "standardAccount")]
        [string]$Server,
        [parameter(Mandatory = $false, ParameterSetName = "gmsaAccount")]
        [parameter(Mandatory = $false, ParameterSetName = "standardAccount")]
        [string]$Domain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $returnVal = $false
    if ($Identity -match '.*\$') {
        $Identity = $Identity.replace('$', '')
    }
    try {
        $adObjectParams = @{
            LDAPFilter = "(objectSid=$(($myDomain).DomainSid)-519)"
        }; if (-not [string]::IsNullOrEmpty($Server)) { $adObjectParams.Add("Server", $Server) }
        $id = [Security.Principal.WindowsIdentity]::GetCurrent()
        $groups = $id.Groups | ForEach-Object { $_.Translate([Security.Principal.NTAccount]) }
        if ([bool]$(($myDomain).ParentDomain)) {
            $eaGroupName = '{0}\{1}' -f (Get-ADDomain -Server $($myDomain.ParentDomain)).netbiosname,
                (Get-ADObject -LDAPFilter "(objectSid=$($myDomain.forestSid)-519)" -Server $($myDomain.ParentDomain)).name
        } else {
            $eaGroupName = '{0}\{1}' -f ($myDomain.NetBIOSName), (Get-ADObject @adObjectParams).name
        }
    } catch {
        Write-Warning -Message $strings['DSA_EnterpriseAdminGroupNotFound']
    }

    if ([string]::IsNullOrEmpty($baseDn)) {
        $baseDn = $myDomain.UsersContainer
        $adObjectParams.LDAPFilter = "(distinguishedName=$baseDn)"
        try {
            $bdnCheck = Get-ADObject @adObjectParams
        } catch {
            $baseDn = $myDomain.DistinguishedName
        }
    }
    if ($forceStandardAccount) {
        $adUserParams = @{
            Identity = $Identity
        }; if (-not [string]::IsNullOrEmpty($Server)) { $adUserParams.Add("Server", $Server) }
        try {
            Get-Aduser @adUserParams -ErrorAction silentlycontinue
        } catch {
            try {
                $securePassword = New-MDIPassword
                $adUserParams.Add("Name", $Identity)
                $adUserParams.Add("AccountPassword", $securePassword)
                $adUserParams.Add("SamAccountName", $Identity)
                $adUserParams.Add("Path", $baseDn)
                $adUserParams.Add("Description", "This account runs the MDI service")
                $adUserParams.Add("Enabled", $true)
                $adUserParams.Remove("Identity")
                $serviceAccount = New-ADUser @adUserParams -PassThru
                $returnVal = $true
            } catch {
                Write-Error $strings['DSA_CannotCreateAccount']
            }
        }
    } else {
        if (-not (Test-MDIKDSRootKey -myDomain $myDomain -Domain $Domain -Server $Server)) {
            if ($eaGroupName -in $groups) {
                try {
                    $null = Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))
                    $strings['DSA_CreatedKDSRootKey']
                } catch {
                    throw $strings['DSA_CannotCreateKDSRootKey']
                }
            }
        } else {
            Write-Verbose $strings['DSA_FoundKDSRootKey']
        }
        if (Test-MDIKDSRootKey -myDomain $myDomain -Domain $Domain -Server $Server) {
            $domainDCs = "$($myDomain.DomainSid)-516"
            try {
                $adObjectParams.LDAPFilter = "(objectSid=$domainDCs)"
                $domainDCsGroupName = (Get-ADGroup @adObjectParams).Name
            } catch {
                Write-Warning $strings['DSA_CannotFindDomainControllersGroup']
            }
            $gmsaGroupParams = @{
                Name           = $GmsaGroupName
                SamAccountName = $GmsaGroupName
                Path           = $baseDn
                GroupScope     = "Universal"
                Description    = $strings['DSA_GroupDescription'] -f $Identity
            }; if (-not [string]::IsNullOrEmpty($Server)) { $gmsaGroupParams.Add("Server", $Server) }
            $getGmsaGroupParams = @{
                Identity = $GmsaGroupName
            }; if (-not [string]::IsNullOrEmpty($Server)) { $getGmsaGroupParams.Add("Server", $Server) }
            try {
                $groupExists = [bool](Get-ADGroup @getGmsaGroupParams -ErrorAction SilentlyContinue)
            } catch {
                $groupExists = $false
            }
            if (-not $groupExists) {
                try {
                    $null = New-ADGroup @gmsaGroupParams
                    Write-Verbose $strings['DSA_CreatedGMSAGroup']
                } catch {
                    Write-Error $strings['DSA_CannotCreateGMSAGroup']
                }
            }
            $newAdServiceAccountParams = @{
                Name                                       = $Identity
                DNSHostName                                = $($myDomain.DNSRoot)
                PrincipalsAllowedToRetrieveManagedPassword = [string[]]$GmsaGroupName
                SamAccountName                             = $gmsaAccountName
            }; if (-not [string]::IsNullOrEmpty($Server)) { $newAdServiceAccountParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($domainDCsGroupName)) {
                $newAdServiceAccountParams.PrincipalsAllowedToRetrieveManagedPassword += $domainDCsGroupName
            }
            try {
                $serviceAccount = New-ADServiceAccount @newAdServiceAccountParams -PassThru
                $returnVal = $true
            } catch {
                try {
                    $newAdServiceAccountParams.Add("Path", $baseDn)
                    $serviceAccount = New-ADServiceAccount @newAdServiceAccountParams -PassThru
                    $returnVal = $true
                } catch {
                    throw
                }
                Write-Error $strings['DSA_CannotCreateGMSAAccount']
            }
        } else {
            Write-Error $strings['DSA_CannotCreateGMSAAccount']
        }
    }
    if ($null -ne $serviceAccount) {
        Set-MDIDeletedObjectsContainerPermission -Identity ("{0}\{1}" -f $myDomain.NetBIOSName, $serviceAccount.SamAccountName) -Server $Server -myDomain $myDomain | Out-Null
    } else {
        Write-Warning $strings['DeletedObjectsPermissions_StatusFail']
    }
    return $returnVal
}

#endregion

#region Sensor configuration helper functions

function Get-MDISensorConfiguration {
    [CmdletBinding()]
    param()
    $sensorProcesses = Get-MDISensorProcessInformation
    if ($sensorProcesses.SenseIdentity -eq 'Running') {
        try {
            $sensorConfiguration = ((Get-ItemProperty 'hklm:\SOFTWARE\Policies\Microsoft\Windows Advanced Threat Protection' OnboardingInfo -ErrorAction SilentlyContinue).OnboardingInfo | ConvertFrom-Json).body | ConvertFrom-Json | Select-Object orgId, geoLocationUrl, datacenter
            if ($null -ne $sensorConfiguration) {
                $proxyUrl = $(try { (Get-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name ProxyServer -ErrorAction SilentlyContinue).ProxyServer } catch { $null })
                $SensorProxyConfiguration = [PSCustomObject]@{
                    IsProxyEnabled       = $(if ($null -ne $proxyUrl) { $true } else { $false })
                    Url                  = $proxyUrl
                    TelemetryProxyServer = $(try { (Get-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name TelemetryProxyServer -ErrorAction SilentlyContinue).TelemetryProxyServer } catch { $null })
                }
            }
            $sensorConfiguration | Add-Member -MemberType NoteProperty -Name SensorProxyConfiguration -Value $SensorProxyConfiguration -Force
        } catch {
            $sensorConfiguration = $null
            Write-Warning -Message $strings['Sensor_ErrorReadingSensorConfiguration']
        }
    } else {
        $sensorBinPath = Get-MDISensorBinPath
        if ($null -eq $sensorBinPath) {
            $sensorConfiguration = $null
            Write-Warning -Message $strings['Sensor_ErrorReadingSensorConfiguration']
        } else {
            Write-Verbose -Message $strings['Sensor_ReadConfigurationFile']
            $sensorConfigurationPath = Join-Path -Path $sensorBinPath -ChildPath 'SensorConfiguration.json'
            $sensorConfiguration = Get-Content -Path $sensorConfigurationPath -Raw | ConvertFrom-Json
        }

        if ($null -ne $sensorConfiguration.SensorProxyConfiguration) {
            $SensorProxyConfiguration = [PSCustomObject]@{
                IsProxyEnabled               = -not [string]::IsNullOrEmpty($sensorConfiguration.SensorProxyConfiguration.Url)
                IsAuthenticationProxyEnabled = -not [string]::IsNullOrEmpty($sensorConfiguration.SensorProxyConfiguration.UserName)
                Url                          = $sensorConfiguration.SensorProxyConfiguration.Url
                UserName                     = $sensorConfiguration.SensorProxyConfiguration.UserName
                EncryptedUserPasswordData    = $sensorConfiguration.SensorProxyConfiguration.EncryptedUserPasswordData.EncryptedBytes
                CertificateThumbprint        = $sensorConfiguration.SensorProxyConfiguration.EncryptedUserPasswordData.CertificateThumbprint

            }
            $sensorConfiguration.SensorProxyConfiguration = $SensorProxyConfiguration
        }
    }
    $sensorConfiguration
}

function Get-MDIEncryptedPassword {
    param(
        [Parameter(Mandatory = $true)] [string] $CertificateThumbprint,
        [Parameter(Mandatory = $true)] [PSCredential] $Credential
    )
    $store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList @(
        [System.Security.Cryptography.X509Certificates.StoreName]::My,
        [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
    )
    $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)

    $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2] $store.Certificates.Find(
        [System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $CertificateThumbprint, $false)[0]

    $rsaPublicKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
    $bytes = [System.Text.Encoding]::Unicode.GetBytes(
        $Credential.GetNetworkCredential().Password
    )
    $encrypted = $rsaPublicKey.Encrypt($bytes, [System.Security.Cryptography.RSAEncryptionPadding]::OaepSHA256)
    $encryptedPassword = [System.Convert]::ToBase64String($encrypted)

    $store.Close()
    $encryptedPassword
}

function Get-MDIDecryptedPassword {
    param(
        [Parameter(Mandatory = $true)] [string] $CertificateThumbprint,
        [Parameter(Mandatory = $true)] [string] $EncryptedString
    )
    $store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList @(
        [System.Security.Cryptography.X509Certificates.StoreName]::My,
        [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
    )
    $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)

    $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2] $store.Certificates.Find(
        [System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $CertificateThumbprint, $false)[0]

    $rsaPublicKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)

    $encrypted = [System.Convert]::FromBase64String($EncryptedString)
    $bytes = $rsaPublicKey.Decrypt($encrypted, [System.Security.Cryptography.RSAEncryptionPadding]::OaepSHA256)
    $decryptedPassword = [System.Text.Encoding]::Unicode.GetString($bytes)

    $store.Close()
    $decryptedPassword
}

function Get-MDISensorProxyConfiguration {
    [CmdletBinding()]
    param()
    $sensorConfiguration = Get-MDISensorConfiguration
    if ($null -eq $sensorConfiguration) {
        $proxyConfiguration = $null
    } else {
        $proxyConfiguration = $sensorConfiguration.SensorProxyConfiguration
    }
    $proxyConfiguration
}

function Set-MDISensorProxyConfiguration {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $false)] [string] $ProxyUrl,
        [Parameter(Mandatory = $false)] [PSCredential] $ProxyCredential
    )
    $operation = if ([string]::IsNullOrEmpty($ProxyUrl)) { 'Clear' } else { 'Set' }
    if ($PSCmdlet.ShouldProcess($strings['Sensor_ProxyConfigurationAction'], $operation)) {
        if ('Set' -eq $operation) {
            [System.Uri] $resultUri = $null
            if (-not [System.Uri]::TryCreate($ProxyUrl, [System.UriKind]::Absolute, [ref] $resultUri)) {
                if (-not $ProxyUrl.StartsWith('http://')) {
                    $ProxyUrl = 'http://' + $ProxyUrl
                }
            }
        }
        $sensorProcesses = Get-MDISensorProcessInformation
        $sensorConfiguration = Get-MDISensorConfiguration
        if ($null -eq $sensorConfiguration) {
            Write-Error $strings['Sensor_ErrorReadingSensorConfiguration'] -ErrorAction Stop
        }
        if ($sensorProcesses.SenseIdentity -eq 'Running') {
            if ([string]::IsNullOrEmpty($ProxyUrl)) {
                try {
                    $null = Remove-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name TelemetryProxyServer -ErrorAction Stop
                    $null = Remove-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name ProxyServer -ErrorAction Stop
                } catch {
                    Write-Warning -Message $strings['Sensor_ProxyConfigurationActionFail']
                }
            } else {
                try {
                    $null = New-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name TelemetryProxyServer -PropertyType String -Value $ProxyUrl.Replace('http://', '') -Force -ErrorAction Stop
                    $null = New-ItemProperty -Path "hklm:\SOFTWARE\Policies\Microsoft\Windows Defender" -Name ProxyServer -PropertyType String -Value $ProxyUrl -Force -ErrorAction Stop
                } catch {
                    Write-Warning -Message $strings['Sensor_ProxyConfigurationActionFail']
                }
            }
        } else {
            if ([string]::IsNullOrEmpty($ProxyUrl)) {
                $sensorConfiguration.SensorProxyConfiguration = $null
            } else {
                if ($ProxyCredential) {
                    $thumbprint = $sensorConfiguration.SecretManagerConfigurationCertificateThumbprint
                    $sensorConfiguration.SensorProxyConfiguration = [PSCustomObject]@{
                        '$type'                   = 'SensorProxyConfiguration'
                        Url                       = $ProxyUrl
                        UserName                  = $ProxyCredential.UserName
                        EncryptedUserPasswordData = [PSCustomObject]@{
                            '$type'               = 'EncryptedData'
                            EncryptedBytes        = Get-MDIEncryptedPassword -CertificateThumbprint $thumbprint -Credential $ProxyCredential
                            SecretVersion         = $null
                            CertificateThumbprint = $sensorConfiguration.SecretManagerConfigurationCertificateThumbprint
                        }
                    }
                } else {
                    $sensorConfiguration.SensorProxyConfiguration = [PSCustomObject]@{
                        '$type' = 'SensorProxyConfiguration'
                        Url     = $ProxyUrl
                    }
                }
            }
            Stop-MDISensor
            Write-Verbose -Message $strings['Sensor_WriteSensorConfigurationFile']
            $sensorConfiguration | ConvertTo-Json | Format-Json |
                Set-Content -Path (Join-Path -Path (Get-MDISensorBinPath) -ChildPath 'SensorConfiguration.json')
            Start-MDISensor
        }
    }
}

function Clear-MDISensorProxyConfiguration {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param()
    if ($PSCmdlet.ShouldProcess($strings['Sensor_ProxyConfigurationAction'], 'Clear')) {
        Set-MDISensorProxyConfiguration -ProxyUrl $null
    }
}

#endregion

#region GPO helper functions

function Get-MDIGPOName {
    param(
        [Parameter(Mandatory)] [string] $Name,
        [Parameter(Mandatory = $false)] [string] $GpoNamePrefix
    )
    if ([string]::IsNullOrEmpty($GpoNamePrefix)) {
        $Name -f $script:settings['gpoNamePrefix']
    } else {
        $Name -f $GpoNamePrefix
    }
}

function New-MDIGPO {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $Name,
        [Parameter(Mandatory = $false)] [switch] $CreateGpoDisabled,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $returnVal = $null
    Write-Verbose -Message ($strings['GPO_Create'] -f $Name)
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $maxWaitTime = (Get-Date).AddSeconds(3)
    $successGpo = $false
    $gpoParams = @{
        Name = $Name
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $gpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoParams.Add("Domain", $myDomain.DNSRoot) }
    try {
        $gpoCreate = New-GPO @gpoParams -ErrorAction silentlycontinue
        $successGpo = $true
    } catch {
        $successGpo = $false
    }

    if ($successGpo) {
        do {
            Start-Sleep -Milliseconds 500
            $gpo = Get-MDIGPO @gpoParams -myDomain $myDomain
        } while (-not (($gpo) -or ($maxWaitTime -lt (Get-Date))))

        if ($gpo) {
            $gPCFileSysPath = $gpo.gPCFileSysPath
            do {
                Start-Sleep -Milliseconds 500
            } while (-not ((Test-Path -Path $gPCFileSysPath) -or ($maxWaitTime -lt (Get-Date))))
            if ($CreateGpoDisabled) {
                $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::AllSettingsDisabled
            } else {
                $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled
            }
            $returnVal = $gpo | Add-Member -MemberType NoteProperty -Name gPCFileSysPath -Value $gPCFileSysPath -PassThru -Force
        } else {
            $returnVal = $null
        }
    } else {
        $returnVal = $null
    }
    return $returnVal
}

function Get-MDIGPO {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $Name,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $maxWaitTime = (Get-Date).AddSeconds(3)
    $gpoParams = @{
        Name = $Name
    }; if (-not [string]::IsNullOrEmpty($Server)) { $gpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoParams.Add("Domain", $myDomain.DNSRoot) }
    try {
        $gpo = Get-Gpo @gpoParams -ErrorAction SilentlyContinue
    } catch {
        if ($null -eq $gpo) {
            Start-Sleep -Milliseconds 500
            try {
                if (-not [string]::IsNullOrEmpty($myDomain.PDCEmulator)) {
                    if (-not ("Server" -in $gpoParams.Keys)) {
                        $gpoParams.Add("Server", $myDomain.PDCEmulator)
                    } else {
                        $gpoParams.Server = $myDomain.PDCEmulator
                    }
                }
                $gpo = Get-Gpo @gpoParams -ErrorAction SilentlyContinue
            } catch {
                $gpo = $null
            }
        }
    }
    if ($null -eq $gpo) {
        Write-Verbose -Message ("'{0}' - {1}" -f $Name, $strings['GPO_NotFound'])
    } else {
        Start-Sleep -Milliseconds 500
        $getAdObjectParams = @{
            Identity   = $gpo.Path
            Properties = "gPCFileSysPath"
        }; if (-not [string]::IsNullOrEmpty($Server)) { $getAdObjectParams.Add("Server", $Server) }
        try {
            $gPCFileSysPath = (Get-ADObject @getAdObjectParams).gPCFileSysPath
        } catch {
            do {
                try {
                    Start-Sleep -Milliseconds 500
                    $gPCFileSysPath = (Get-ADObject @getAdObjectParams).gPCFileSysPath
                } catch {
                }
            } while (!(($gPCFileSysPath) -or ($maxWaitTime -lt (Get-Date))))
        }
        $gPCArray = $gPCFileSysPath.split('\')
        $gPCArray[2] = $Server
        $gPCFileSysPath = $gPCArray -join '\'
        do {
            Start-Sleep -Milliseconds 500
        } while (-not ((Test-Path -Path $gPCFileSysPath) -or ($maxWaitTime -lt (Get-Date))))
        $gpo | Add-Member -MemberType NoteProperty -Name gPCFileSysPath -Value $gPCFileSysPath -Force
    }
    return $gpo
}

function Get-MDIGPOLink {
    param(
        [guid] $Guid,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    Write-Verbose -Message $strings['GPO_GetLinks']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoReportParams = @{
        Guid       = $Guid
        ReportType = "Xml"
    }; if (-not [string]::IsNullOrEmpty($Server)) { $gpoReportParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoReportParams.Add("Domain", $myDomain.DNSRoot) }
    $xml = [xml](Get-GPOReport @gpoReportParams)
    @($xml.GPO.LinksTo)
}

function Test-MDIGPOLink {
    [CmdletBinding()]
    param(
        [guid] $Guid,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $return = $false
    $gpoParams = @{
        Guid = $Guid
    }
    if ($null -ne $myDomain) {
        $gpoParams.Add("myDomain", $myDomain)
    } else {
        if (-not [string]::IsNullOrEmpty($Server)) { $gpoParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoParams.Add("Domain", $myDomain.DNSRoot) }
    }

    $enabledLinks = @(Get-MDIGPOLink @gpoParams | Where-Object { $_.Enabled -eq 'true' })
    if ($enabledLinks.Count -lt 1) {
        Write-Verbose -Message ($strings['GPO_NotLinkedOrEnabled'])
    } else {
        $return = $true
        $enabledLinks | ForEach-Object {
            Write-Verbose -Message ($strings['GPO_LinkedAndEnabled'] -f $_.SOMPath)
        }
    }
    $return
}

function Set-MDIGPOLink {
    param(
        [Parameter(Mandatory)] [guid] $Guid,
        [Parameter(Mandatory)] [string] $Target,
        [Microsoft.GroupPolicy.EnableLink] $LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes,
        [Microsoft.GroupPolicy.EnforceLink] $Enforced = [Microsoft.GroupPolicy.EnforceLink]::Yes,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    Write-Verbose -Message $strings['GPO_SetLink']
    $gpLink = @{
        Guid        = $Guid
        LinkEnabled = $LinkEnabled
        Enforced    = $Enforced
        Target      = $Target
    }; if (-not [string]::IsNullOrEmpty($Server)) { $gpLink.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpLink.Add("Domain", $myDomain.DNSRoot) }
    $link = New-GPLink @gpLink -ErrorAction SilentlyContinue
    if ($null -eq $link) {
        $link = Set-GPLink @gpLink -ErrorAction SilentlyContinue
    }

    if ($null -eq $link) {
        throw $strings['GPO_UnableToUpdateLink']
    }
}

function Set-MDIGpoApplyPermission {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [guid] $Guid,
        [parameter(Mandatory)] [string]$Identity,
        [parameter(Mandatory)] [ValidateSet("Allow", "Deny")] [string]$PermissionType,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $returnVal = $false
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $domainDn = $myDomain.DistinguishedName
    if ([string]::IsNullOrEmpty($Server)) {
        $gpo = [ADSI]"LDAP://CN=`{$($Guid)`},CN=Policies,CN=System,$domainDn"
    } else {
        $gpo = [ADSI]"LDAP://$Server/CN=`{$($Guid)`},CN=Policies,CN=System,$domainDn"
    }
    if ($null -ne $gpo) {
        $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
            [System.Security.Principal.NTAccount]"$($myDomain.NetBIOSName)\$Identity",
            "ExtendedRight",
            $PermissionType,
            [Guid]"edacfd8f-ffb3-11d1-b41d-00a0c968f939"
        )
        $acl = $gpo.ObjectSecurity
        $acl.AddAccessRule($rule) | Out-Null
        try {
            $gpo.CommitChanges() | Out-Null
            $returnVal = $true
        } catch {
            Write-Warning -Message $strings['GPO_UnableToSetPermissions']
            $returnVal = $false
        }
    }
    return $returnVal
}

function Get-MDIGPOMachineVersion {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [guid] $Guid,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoParams = @{
        Guid = $Guid
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $gpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoParams.Add("Domain", $myDomain.DNSRoot) }
    (Get-GPO @gpoParams).Computer | Select-Object -Property *Version
}

function Set-MDIGPOMachineVersion {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [guid] $Guid,
        [Parameter(Mandatory)] [int] $Version,
        [Parameter(Mandatory = $false)] [ValidateSet('Sysvol', 'DS', 'All')] [string] $Mode = 'Sysvol',
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    Write-Verbose -Message $strings['GPO_UpdateVersion']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    if ($Mode -match 'ALL|DS') {
        $Replace = @{versionNumber = $Version }
        $gpoAdObjectPath = 'CN={0},{1}' -f "{$Guid}", (Get-MDIAdPath -myDomain $myDomain -Path 'CN=Policies,CN=System,{0}')
        $adObjectParams = @{
            Identity = $gpoAdObjectPath
            Replace  = $Replace
        }; if (-not [string]::IsNullOrEmpty($Server)) { $adObjectParams.Add("Server", $Server) }
        Set-ADObject @adObjectParams | Out-Null
    }
    if ($Mode -match 'ALL|Sysvol') {
        if ($Server) {
            $filePath = '\\{0}\SYSVOL\{1}\Policies\{2}\GPT.INI' -f $Server, $myDomain.DNSRoot, "{$guid}"
        } else {
            $filePath = '\\{0}\SYSVOL\{0}\Policies\{1}\GPT.INI' -f $myDomain.DNSRoot, "{$guid}"
        }
        $newContent = (([system.io.file]::ReadAllLines($filePath)) -join [environment]::NewLine) -replace 'Version=\d+', ('Version={0}' -f $version)
        [System.io.file]::WriteAllLines($filePath, $newContent, (New-Object System.Text.ASCIIEncoding))
    }
}

function Get-MDIGPOMachineExtension {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [guid] $Guid,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    Write-Verbose -Message $strings['GPO_GetExtension']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoAdObjectPath = 'CN={0},{1}' -f "{$Guid}", (Get-MDIAdPath -Path 'CN=Policies,CN=System,{0}' -myDomain $myDomain)
    $adObjectParams = @{
        Identity   = $gpoAdObjectPath
        Properties = @("gPCMachineExtensionNames", "VersionNumber")
    }; if (-not [string]::IsNullOrEmpty($Server)) { $adObjectParams.Add("Server", $Server) }
    Get-ADObject @adObjectParams
}

function Set-MDIGPOMachineExtension {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [guid] $Guid,
        [Parameter(Mandatory = $false)] [string[]] $Extension,
        [Parameter(Mandatory = $false)] [string] $RawExtension = $null,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    Write-Verbose -Message $strings['GPO_SetExtension']
    $return = $null
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    if ([string]::IsNullOrEmpty($RawExtension)) {
        $extensions = $Extension | ForEach-Object { "{$_}" }
        $extensionGuids = '[{0}]' -f [string]::Join('', $extensions)
        $Replace = @{gPCMachineExtensionNames = $extensionGuids }
    } else {
        $Replace = @{gPCMachineExtensionNames = $RawExtension }
    }
    $gpoAdObjectPath = 'CN={0},{1}' -f "{$Guid}", (Get-MDIAdPath -Path 'CN=Policies,CN=System,{0}' -myDomain $myDomain)
    $adObjectParams = @{
        Identity = $gpoAdObjectPath
        Replace  = $Replace
    }; if (-not [string]::IsNullOrEmpty($Server)) { $adObjectParams.Add("Server", $Server) }
    try {
        $gpoUpdated = Set-ADObject @adObjectParams -PassThru
    } catch {
        Write-Verbose -Message $strings['GPO_UnableToSetExtension']
    }
    if ($gpoUpdated) {
        try {
            $gpoVersionParams = @{
                Guid = $Guid
            }; if (-not [string]::IsNullOrEmpty($Server)) { $gpoVersionParams.Add("Server", $Server) }
            $gpoComputerDSVersion = (Get-MDIGPOMachineVersion @gpoVersionParams -myDomain $myDomain).DSVersion
            if ($gpoComputerDSVersion -lt 2) { $gpoComputerDSVersion = 3 } else { $gpoComputerDSVersion++ }
            $setGpoMachineVersionParams = @{
                Guid    = $Guid
                Version = $gpoComputerDSVersion
                Mode    = "All"
            }; if (-not [string]::IsNullOrEmpty($Server)) { $setGpoMachineVersionParams.Add("Server", $Server) }
            Set-MDIGPOMachineVersion @setGpoMachineVersionParams -myDomain $myDomain
            $return = $gpoComputerDSVersion
        } catch {
            Write-Verbose -Message $strings['GPO_UnableToSetExtension']
        }
    }
    $return
}

function Test-MDIGPOEnabledAndLink {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] $GPO,
        [Parameter(Mandatory = $false)] [switch] $ManualLinkRequired,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $state = $false
    $testMdiGpoLinkParams = @{
        Guid = $GPO.Id.Guid
    }; if (-not [string]::IsNullOrEmpty($Server)) { $testMdiGpoLinkParams.Add("Server", $Server) }
    if (-not ($GPO.GpoStatus -ne [Microsoft.GroupPolicy.GpoStatus]::AllSettingsDisabled)) {
        Write-Verbose -Message $strings['GPO_SettingsDisabled']
    } else {
        if (-not (Test-MDIGPOLink @testMdiGpoLinkParams -myDomain $myDomain)) {
            if ($ManualLinkRequired) {
                Write-Warning -Message ($strings['GPO_ManualLinkRequired'] -f $GPO.DisplayName)
            }
            Write-Verbose -Message $strings['GPO_LinkNotFound']
        } else {
            $state = $true
        }
    }
    $state
}

#endregion

#region Processor Performance helper functions

function Get-MDIProcessorPerformance {
    & "$($env:SystemRoot)\system32\powercfg.exe" @('/GETACTIVESCHEME')
}

function Test-MDIProcessorPerformance {
    [CmdletBinding()]
    param(
        [switch] $Detailed
    )
    Write-Verbose -Message $strings['ProcessorPerformance_Validate']
    $result = $false
    $activeScheme = Get-MDIProcessorPerformance
    if ($activeScheme -match ':\s+(?<guid>[a-fA-F0-9]{8}[-]?([a-fA-F0-9]{4}[-]?){3}[a-fA-F0-9]{12})\s+\((?<name>.*)\)') {
        $result = $Matches.guid -eq '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'
    }
    Write-Verbose -Message (Get-MDIValidationMessage $result)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $result
                Details = $activeScheme
            })
    } else {
        $result
    }
}

function Set-MDIProcessorPerformance {
    & "$($env:SystemRoot)\system32\powercfg.exe" @('/SETACTIVE', '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c')
}

function Get-MDIProcessorPerformanceGPO {
    [CmdletBinding()]
    param(
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.ProcessorPerformance.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    $gpRegValParams = @{
        Guid = $gpo.Id.Guid
        Key  = $settings.ProcessorPerformance.Key
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $gpRegValParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpRegValParams.Add("Domain", $myDomain.DNSRoot) }
    if ($gpo) {
        $gpo | Select-Object -Property *,
        @{N = 'GPRegistryValue'; E = { Get-GPRegistryValue @gpRegValParams } }
    }
}

function Test-MDIProcessorPerformanceGPO {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.ProcessorPerformance.GpoName -GpoNamePrefix $GpoNamePrefix
    Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName)
    $processorPerfGpoParams = @{
        GpoNamePrefix = $GpoNamePrefix
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $processorPerfGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $processorPerfGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $state = $false
    $gpo = Get-MDIProcessorPerformanceGPO @processorPerfGpoParams -myDomain $myDomain
    if ($gpo) {
        $gpSetOk = $gpo.GPRegistryValue.ValueName -eq $settings.ProcessorPerformance.ValueName -and
        $gpo.GPRegistryValue.Value -eq $settings.ProcessorPerformance.SchemeGuid -and
        $gpo.GPRegistryValue.PolicyState -eq [Microsoft.GroupPolicy.PolicyState]::Set
        $mdiGpoTestLinkParams = @{
            GPO = $gpo
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoTestLinkParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoTestLinkParams.Add("Domain", $myDomain.DNSRoot) }
        if ($gpSetOk) {
            $state = Test-MDIGPOEnabledAndLink @mdiGpoTestLinkParams -myDomain $myDomain
        } else {
            Write-Verbose -Message $strings['GPO_SettingsMismatch']
        }
    }
    Write-Verbose -Message (Get-MDIValidationMessage $state)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $state
                Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, GPRegistryValue }
                else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] }
            })
    } else {
        $state
    }
}

function Set-MDIProcessorPerformanceGPO {
    [CmdletBinding()]
    param(
        [switch] $SkipGpoLink,
        [switch] $CreateGpoDisabled,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)] [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.ProcessorPerformance.GpoName -GpoNamePrefix $GpoNamePrefix
    $gpoParams = @{
        GpoNamePrefix = $GpoNamePrefix
    }; if (-not [string]::IsNullOrEmpty($Server)) { $gpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIProcessorPerformanceGPO @gpoParams -myDomain $myDomain
    if ($null -eq $gpo) {
        $gpoParams.Add("CreateGpoDisabled", $CreateGpoDisabled)
        $gpoParams.Add("Name", $gpoName)
        $gpoParams.Remove("GpoNamePrefix")
        $gpo = New-MDIGPO -Name $gpoName -myDomain $myDomain -Server $Server -Domain $Domain -CreateGpoDisabled:$CreateGpoDisabled
    }
    if ($gpo) {
        $gppParams = @{
            Guid      = $gpo.Id.Guid
            Type      = 'String'
            Key       = $settings.ProcessorPerformance.Key
            ValueName = $settings.ProcessorPerformance.ValueName
            Value     = $settings.ProcessorPerformance.SchemeGuid
        }; if (-not [string]::IsNullOrEmpty($Server)) { $gppParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gppParams.Add("Domain", $myDomain.DNSRoot) }
        try {
            $gpoUpdated = Set-GPRegistryValue @gppParams -ErrorAction SilentlyContinue
            if ($null -eq $gpoUpdated) {
                throw
            }
        } catch {
            if (-not ("Server" -in $gppParams.Keys)) {
                $gppParams.Add("Server", $myDomain.PDCEmulator)
            } else {
                $gppParams.Server = $myDomain.PDCEmulator
            }
            Start-Sleep -Milliseconds 750
            $gpoUpdated = Set-GPRegistryValue @gppParams
        }
        Start-Sleep -Milliseconds 750
        if (-not ($CreateGpoDisabled)) { $gpoUpdated.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled }
        $gpoUpdated.MakeAclConsistent()

        if (-not $SkipGpoLink) {
            $gpLinkParams = @{
                Guid        = $gpo.Id.Guid
                LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes
                Enforced    = [Microsoft.GroupPolicy.EnforceLink]::Yes
                Target      = 'OU=Domain Controllers,{0}' -f $myDomain.DistinguishedName
            }; if (-not [string]::IsNullOrEmpty($Server)) { $gpLinkParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpLinkParams.Add("Domain", $myDomain.DNSRoot) }
            Set-MDIGPOLink @gpLinkParams -myDomain $myDomain
        }
    } else {
        throw $strings['GPO_UnableToUpdate']
    }
}

#endregion

#region Directory Services Auditing helper functions

function Get-MDIAdPath {
    param(
        [Parameter(Mandatory)] $Path,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $DefaultNamingContext = (Get-ADRootDSE -Server $Server).defaultNamingContext
    $Path -f $DefaultNamingContext
}

function Get-MDISAcl {
    param(
        [Parameter(Mandatory)] $Path,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    $null = New-PSDrive -Name $($myDomain.netbiosname) -PSProvider ActiveDirectory -Server $myDomain.ChosenDC -Root "//RootDSE/"
    $Path = "$($myDomain.netbiosname):\" + $Path
    $acls = Get-Acl -Path $Path -Audit -ErrorAction Stop
    Remove-PSDrive -Name $($myDomain.netbiosname)
    if ($acls) {
        foreach ($acl in $acls.Audit) {
            [PSCustomObject]@{
                Account                = $acl.IdentityReference.Value
                SecurityIdentifier     = $acl.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value
                AccessMask             = [int]$acl.ActiveDirectoryRights
                AccessMaskDetails      = $acl.ActiveDirectoryRights
                AuditFlags             = $acl.AuditFlags
                AuditFlagsValue        = [int]$acl.AuditFlags
                InheritedObjectAceType = $acl.InheritedObjectType
                InheritanceType        = [int]$acl.InheritanceType
                PropagationFlags       = [int]$acl.PropagationFlags
            }
        }
    }
}

function Set-MDISAcl {
    param(
        [Parameter(Mandatory)] $Auditing,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    $null = New-PSDrive -Name $($myDomain.netbiosname) -PSProvider ActiveDirectory -Server $myDomain.ChosenDC -Root "//RootDSE/"
    $Path = "$($myDomain.netbiosname):\" + $(Get-MDIAdPath -Path $Auditing.Path -myDomain $myDomain)
    $acls = Get-Acl -Path $Path -Audit -ErrorAction SilentlyContinue
    if ($acls) {
        Write-Verbose -Message $strings['ACL_Set']
        foreach ($audit in $Auditing.Auditing) {
            $account = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @(
                    $audit.SecurityIdentifier)).Translate([System.Security.Principal.NTAccount]).Value
            $argumentList = @(
                [Security.Principal.NTAccount] $account,
                [System.DirectoryServices.ActiveDirectoryRights] $audit.AccessMask,
                [System.Security.AccessControl.AuditFlags] $audit.AuditFlagsValue,
                [guid]::Empty.Guid.ToString(),
                [System.DirectoryServices.ActiveDirectorySecurityInheritance] $audit.InheritanceType,
                [guid] $audit.InheritedObjectAceType
            )
            $rule = New-Object -TypeName System.DirectoryServices.ActiveDirectoryAuditRule -ArgumentList $argumentList
            $acls.AddAuditRule($rule)
        }
        Set-Acl -Path $Path -AclObject $acls
    }
    Remove-PSDrive -Name $($myDomain.netbiosname)
}

function Get-MDIDomainObjectAuditing {
    param(
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    try {
        Get-MDISAcl -Path (Get-MDIAdPath -Path $settings.ObjectAuditing.Path -myDomain $myDomain)
    } catch [System.Management.Automation.ActionPreferenceStopException] {
        if ('ObjectNotFound' -eq $_.Exception.ErrorRecord.CategoryInfo.Category) {
            Write-Warning $_.Exception.Message
        } else {
            throw $_
        }
    }
}

function Get-MDIAdfsAuditing {
    param(
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    try {
        Get-MDISAcl -Path (Get-MDIAdPath -Path $settings.AdfsAuditing.Path -myDomain $myDomain)
    } catch [System.Management.Automation.ActionPreferenceStopException] {
        if ('ObjectNotFound' -eq $_.Exception.ErrorRecord.CategoryInfo.Category) {
            Write-Warning $_.Exception.Message
        } else {
            throw $_
        }
    }
}

function Get-MDIConfigurationContainerAuditing {
    param(
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    try {
        Get-MDISAcl -Path (Get-MDIAdPath -Path $settings.ConfigurationContainerAuditing.Path -myDomain $myDomain)
    } catch [System.Management.Automation.ActionPreferenceStopException] {
        if ('ObjectNotFound' -eq $_.Exception.ErrorRecord.CategoryInfo.Category) {
            Write-Warning $_.Exception.Message
        } else {
            throw $_
        }
    }
}

function Test-MDIAuditing {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $Path,
        [Parameter(Mandatory)] [object[]] $ExpectedAuditing,
        [switch] $Detailed,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    try {
        $AppliedAuditing = Get-MDISAcl -Path (Get-MDIAdPath -Path $Path -myDomain $myDomain) -myDomain $myDomain
        $isAuditingOk = @(foreach ($applied in $AppliedAuditing) {
                $ExpectedAuditing | Where-Object { ($_.SecurityIdentifier -eq $applied.SecurityIdentifier) -and
                ($_.AuditFlagsValue -eq $applied.AuditFlagsValue) -and
                ($_.InheritedObjectAceType -eq $applied.InheritedObjectAceType) -and
                ($_.InheritanceType -eq $applied.InheritanceType) -and
                ($_.PropagationFlags -eq $applied.PropagationFlags) -and
                (([System.DirectoryServices.ActiveDirectoryRights]$applied.AccessMask).HasFlag(([System.DirectoryServices.ActiveDirectoryRights]($_.AccessMask)))) }
            }).Count -ge $ExpectedAuditing.Count

    } catch [System.Management.Automation.ActionPreferenceStopException] {
        if ('ObjectNotFound' -eq $_.Exception.ErrorRecord.CategoryInfo.Category) {
            $isAuditingOk = $true
        } else {
            $isAuditingOk = $false
        }
    }
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $isAuditingOk
                Details = $AppliedAuditing
            })
    } else {
        $isAuditingOk
    }
}

function Test-MDIDomainObjectAuditing {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    Write-Verbose -Message $strings['DomainObject_ValidateAuditing']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    $result = Test-MDIAuditing -Path $settings.ObjectAuditing.Path -ExpectedAuditing $settings.ObjectAuditing.Auditing -Detailed:$Detailed -myDomain $myDomain
    if ($Detailed) {
        Write-Verbose -Message (Get-MDIValidationMessage $result.Status)
    } else {
        Write-Verbose -Message (Get-MDIValidationMessage $result)
    }
    $result
}

function Test-MDIAdfsAuditing {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $result = [PSCustomObject]@{
        Status  = $true
        Details = $strings['ADFS_ContainerNotFound']
    }
    Write-Verbose -Message $strings['ADFS_ValidateAuditing']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ([System.DirectoryServices.DirectoryEntry]::Exists((Get-MDIAdPath -Path $settings.AdfsAuditing.Validate -myDomain $myDomain))) {
        $result = Test-MDIAuditing -Path $settings.AdfsAuditing.Path -ExpectedAuditing $settings.AdfsAuditing.Auditing -Detailed:$Detailed -myDomain $myDomain
    } elseif (-not $Detailed) {
        $result = $true
    }
    if ($Detailed) {
        Write-Verbose -Message (Get-MDIValidationMessage $result.Status)
    } else {
        Write-Verbose -Message (Get-MDIValidationMessage $result)
    }
    $result
}

function Test-MDIConfigurationContainerAuditing {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $result = [PSCustomObject]@{
        Status  = $true
        Details = $strings['Exchange_ContainerNotFound']
    }
    Write-Verbose -Message $strings['Exchange_ValidateAuditing']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ([System.DirectoryServices.DirectoryEntry]::Exists((Get-MDIAdPath -Path $settings.ConfigurationContainerAuditing.Validate -myDomain $myDomain))) {
        $result = Test-MDIAuditing -Path $settings.ConfigurationContainerAuditing.Path -ExpectedAuditing $settings.ConfigurationContainerAuditing.Auditing -Detailed:$Detailed -myDomain $myDomain
    } elseif (-not $Detailed) {
        $result = $true
    }
    if ($Detailed) {
        Write-Verbose -Message (Get-MDIValidationMessage $result.Status)
    } else {
        Write-Verbose -Message (Get-MDIValidationMessage $result)
    }
    $result
}

function Set-MDIDomainObjectAuditing {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    Set-MDISAcl -Auditing $settings.ObjectAuditing -myDomain $myDomain
}

function Set-MDIAdfsAuditing {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ([System.DirectoryServices.DirectoryEntry]::Exists((Get-MDIAdPath -Path $settings.AdfsAuditing.Validate -myDomain $myDomain))) {
        Set-MDISAcl -Auditing $settings.AdfsAuditing -myDomain $myDomain
    } else {
        Write-Warning $strings['ADFS_ContainerNotFound']
    }
}

function Set-MDIConfigurationContainerAuditing {
    [CmdletBinding()]
    param(
        [switch] $Force,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($Force -or [System.DirectoryServices.DirectoryEntry]::Exists((Get-MDIAdPath -Path $settings.ConfigurationContainerAuditing.Validate -myDomain $myDomain))) {
        Set-MDISAcl -Auditing $settings.ConfigurationContainerAuditing -myDomain $myDomain
    } else {
        Write-Warning $strings['Exchange_ContainerNotFound']
    }
}

#endregion

#region Active Directory Recycle Bin functions

function Get-MDIAdRecycleBin {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $recycleBinParams = @{
        Filter = 'name -like "Recycle Bin Feature"'
    }; if (-not [string]::IsNullOrEmpty($Server)) { $recycleBinParams.Add("Server", $Server) }
    $value = if ((Get-ADOptionalFeature @recycleBinParams).EnabledScopes) { $true } else { $false }
    if ($value) {
        $recycleBinSetting = $strings['DomainRecycleBin_Enabled']
    } else {
        $recycleBinSetting = $strings['DomainRecycleBin_Disabled']
    }
    [PSCustomObject]@{
        Name          = $strings['DomainRecycleBin_Descriptor']
        ActualValue   = $value
        ExpectedValue = $true
    }
}

function Set-MDIAdRecycleBin {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $adGroupParams = @{
        Identity = ($settings.SensitiveGroups["Domain Admins"] -f $($myDomain.DomainSid) )
    }; if (-not [string]::IsNullOrEmpty($Server)) { $adGroupParams.Add("Server", $Server) }
    if (($myDomain.DomainFunctionality) -gt 3 -and ($myDomain.ForestFunctionality) -gt 3) {
        try {
            if ($env:username -in @( (Get-AdGroupMember @adGroupParams  | ForEach-Object { $_.SamAccountName } ) )) {
                $enableAdRecycleBinParams = @{
                    Identity = "CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,$($myDomain.DistinguishedName)"
                    Scope    = "ForestOrConfigurationSet"
                    Target   = $env:USERDNSDOMAIN
                }; if (-not [string]::IsNullOrEmpty($Server)) { $enableAdRecycleBinParams.Add("Server", $Server) }
                $null = Enable-ADOptionalFeature @enableAdRecycleBinParams
                Write-Verbose -Message $strings["DomainRecycleBin_EnableSuccess"]
            } else {
                throw
            }
        } catch {
            Write-Warning -Message $strings['DomainRecycleBin_EnableFailed']
        }
    } else {
        Write-Warning -Message $strings['DomainRecycleBin_ForestDomainFail']
    }
}

function Test-MDIAdRecycleBin {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    Write-Verbose -Message $strings['DomainRecycleBin_Validation']
    $recycleBinParams = @{
        myDomain = $myDomain
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $recycleBinParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $recycleBinParams.Add("Domain", $myDomain.DNSRoot) }
    $status = [bool](Get-MDIAdRecycleBin @recycleBinParams | Where-Object { $_.ActualValue -eq $_.ExpectedValue })
    if ($status) {
        $recycleBinSetting = $strings['DomainRecycleBin_Enabled']
    } else {
        $recycleBinSetting = $strings['DomainRecycleBin_Disabled']
    }
    Write-Verbose (Get-MDIValidationMessage $status)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $status
                Details = $recycleBinSetting
            })
    } else {
        $status
    }
}

#endregion

#region RemoteSAM helper functions
function Get-MDIRemoteSAM {
    [CmdletBinding()]
    param(
        [string] $Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiDsaParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
    $identitySid = (Get-MDIDSA @mdiDsaParams -myDomain $myDomain).objectSid.value
    $settings.RemoteSAM.RegistrySet.GetEnumerator() | ForEach-Object {
        $name = ($_.Name -split '\\')[-1]
        $path = 'HKLM:\{0}' -f ($_.Name -replace $name)
        $value = Get-ItemProperty -Path $path -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name
        $expected = $_.Value -f $identitySid
        [PSCustomObject]@{
            Path          = $path
            Name          = $name
            ActualValue   = $value
            ExpectedValue = $expected
        }
    }
}

function Set-MDIRemoteSAM {
    [CmdletBinding()]
    param(
        [string] $Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiDsaParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
    $identitySid = (Get-MDIDSA @mdiDsaParams -myDomain $myDomain).objectSid.value
    $settings.RemoteSAM.RegistrySet.GetEnumerator() | ForEach-Object {
        $name = ($_.Name -split '\\')[-1]
        $path = 'HKLM:\{0}' -f ($_.Name -replace $name)
        $value = $_.Value -f $identitySid
        Set-ItemProperty -Path $path -Name $name -Value $value -ErrorAction Stop
    }
}

function Test-MDIRemoteSAM {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    Write-Verbose -Message $strings['RemoteSAM_Validate']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiRemoteSamParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiRemoteSamParams.Add("Server", $Server) }
    $remoteSAMSettings = Get-MDIRemoteSAM @mdiRemoteSamParams -myDomain $myDomain
    $status = @($remoteSAMSettings | Where-Object { $_.ActualValue -match $_.ExpectedValue }).Count -eq $settings.RemoteSAM.RegistrySet.Count
    Write-Verbose (Get-MDIValidationMessage $status)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $status
                Details = $remoteSAMSettings
            })
    } else {
        $status
    }
}

function Get-MDIRemoteSAMGPO {
    [CmdletBinding()]
    param(
        [string] $GpoNamePrefix,
        [string] $Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiDsaParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
    $identitySid = (Get-MDIDSA @mdiDsaParams -myDomain $myDomain).objectSid.value
    $gpoName = Get-MDIGPOName -Name $settings.RemoteSAM.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($gpo) {
        $gpoReportParams = @{
            Guid       = $gpo.Id
            ReportType = "Xml"
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $gpoReportParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoReportParams.Add("Domain", $myDomain.DNSRoot) }
        $report = [xml](Get-GPOReport @gpoReportParams)

        $options = $report.GPO.Computer.ExtensionData.Extension.SecurityOptions | Where-Object { $_.KeyName -Match 'RestrictRemoteSAM' }
        $RegistryValue = foreach ($opt in $options) {
            $valueName = ($opt.KeyName -split '\\')[-1]
            $path = $opt.KeyName -replace '(.*)\\(\w+)', '$1'
            [PSCustomObject]@{
                KeyName       = $path
                ValueName     = $valueName
                Value         = $opt.Display.DisplayString
                ExpectedValue = (($settings.RemoteSAM.RegistrySet.GetEnumerator() |
                            Where-Object { ('MACHINE\{0}' -f $_.Name) -eq (Join-Path -Path $path -ChildPath $valueName) }).Value -f $identitySid)
            }
        }
        $gpo | Select-Object -Property *, @{N = 'RegistryValue'; E = { $RegistryValue } }
    }
}

function Set-MDIRemoteSAMGPO {
    [CmdletBinding()]
    param(
        [string] $Identity,
        [switch] $SkipGpoLink,
        [switch] $CreateGpoDisabled,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiDsaParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
    $identitySid = (Get-MDIDSA @mdiDsaParams -myDomain $myDomain).objectSid.value
    $gpoName = Get-MDIGPOName -Name $settings.RemoteSAM.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($null -eq $gpo) {
        $mdiGpoParams.Add("CreateGpoDisabled", $CreateGpoDisabled)
        $gpo = New-MDIGPO @mdiGpoParams -myDomain $myDomain
    }

    $filePath = '{0}\Machine\Microsoft\Windows NT\SecEdit' -f $gpo.gPCFileSysPath
    try {
        New-Item -Path $filePath -ItemType Directory -Force | Out-Null
    } catch {}

    $fileContent = @'
[Unicode]
Unicode=yes
[Version]
signature="$CHICAGO$"
Revision=1
[Registry Values]
'@


    $settings.RemoteSAM.GpoRegSet.GetEnumerator() | ForEach-Object {
        $value = $_.Value -f $identitySid
        $fileContent += '{2}MACHINE\{0}={1}' -f $_.Name, $Value, [System.Environment]::NewLine
    }
    [System.Io.File]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'GptTmpl.inf'), $fileContent, (New-Object System.Text.UnicodeEncoding))
    if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled }
    $gpo.MakeAclConsistent()
    $mdiGpoMachineExtensionParams = @{
        Guid      = $gpo.Id.Guid
        Extension = @($settings.gpoExtensions['Security'], $settings.gpoExtensions['Computer Restricted Groups'])
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoMachineExtensionParams.Add("Server", $Server) }
    $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams -myDomain $myDomain

    $settings.RemoteSAM.DenyGPPermissions.GetEnumerator() | ForEach-Object {
        $adObjectParams = @{
            LDAPFilter = "(objectSid=$($_.Name -f $($myDomain.DomainSid)))"
            Properties = "samaccountname"
        }; if (-not [string]::IsNullOrEmpty($Server)) { $adObjectParams.Add("Server", $Server) }
        $identitySamAccountName = (Get-ADObject @adObjectParams).samaccountname
        $mdiApplyParams = @{
            Guid           = $gpo.Id.Guid
            Identity       = $identitySamAccountName
            PermissionType = "Deny"
        }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiApplyParams.Add("Server", $Server) }
        $gpoAclUpdate = Set-MDIGpoApplyPermission @mdiApplyParams -myDomain $myDomain
        if (-not $gpoAclUpdate) {
            Write-Warning $strings['GPO_UnableToSetPermissions']
        }
    }

    if ($null -ne $gpoUpdated) {
        if (-not $SkipGpoLink) {
            if (-not $SkipGpoLink) {
                Write-Warning -Message ($strings['GPO_ManualLinkRequired'] -f $GPO.DisplayName)
            }
        }
    } else {
        Write-Warning $strings['GPO_UnableToSetExtension']
    }
}

function Test-MDIRemoteSAMGPO {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $Identity,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.RemoteSAM.GpoName -GpoNamePrefix $GpoNamePrefix
    Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName)
    $mdiGpoParams = @{
        Identity      = $Identity
        GpoNamePrefix = $GpoNamePrefix
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    $state = $false
    $gpo = Get-MDIRemoteSAMGPO @mdiGpoParams -myDomain $myDomain

    if ($gpo) {
        $gpSetOk = @($gpo.RegistryValue | Where-Object {
                ([string]::Compare($_.Value, $_.ExpectedValue) -eq 0)
            }).Count -eq $settings.RemoteSAM.RegistrySet.Count
        if ($gpSetOk) {
            $mdiGpoCheckParams = @{
                GPO                = $gpo
                ManualLinkRequired = $true
            }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoCheckParams.Add("Server", $Server) }
            $state = Test-MDIGPOEnabledAndLink @mdiGpoCheckParams -myDomain $myDomain
        } else {
            Write-Verbose -Message $strings['GPO_SettingsMismatch']
        }
    }
    Write-Verbose -Message (Get-MDIValidationMessage $state)
    if ($Detailed) {
        $RegistryValue = $gpo.RegistryValue
        [PSCustomObject]([ordered]@{
                Status  = $state
                Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, @{N = 'RegistryValue'; E = { [string]($gpo.RegistryValue -join ',') } } }
                else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] }
            })
    } else {
        $state
    }
}

#endregion

#region EntraConnect Auditing helper functions
function Add-MDIServiceLogonRight {
    [CmdletBinding()]
    param(
        [string] $Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $identitySamAccountName = (Get-MDIDSA -Identity $Identity -myDomain $myDomain).sAMAccountName
    $secPol = Get-MDIServiceLogonRight
    $filePath = '{0}\secpol.inf' -f $env:temp
    $dbPath = '{0}\secpol.db' -f $env:temp
    ($secPol) -replace '^SeServiceLogonRight .+', "`$0,$identitySamAccountName" | Set-Content "$filePath" -Force
    $null = & "$($env:SystemRoot)\system32\secedit.exe" @('/import', '/cfg', "$filePath", '/db', "$dbPath", '/areas', 'USER_RIGHTS', '/overwrite', '/quiet')
    $null = & "$($env:SystemRoot)\system32\secedit.exe" @('/configure', '/cfg', "$filePath", '/db', "$dbPath", '/areas', 'USER_RIGHTS', '/overwrite', '/quiet')
    Remove-Item $filePath -ErrorAction SilentlyContinue
    Remove-Item $dbPath -ErrorAction SilentlyContinue
}

function Get-MDIServiceLogonRight {
    [CmdletBinding()]
    param()
    $filePath = '{0}\secpol.inf' -f $env:temp
    $null = & "$($env:SystemRoot)\system32\secedit.exe" @('/export', '/cfg', "$filePath")
    $content = Get-Content $filePath
    Remove-Item $filePath -ErrorAction SilentlyContinue
    return $content
}

function Get-MDIEntraConnectAuditing {
    [CmdletBinding()]
    param()
    $relevantGUIDs = @($settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv) | Select-Object -ExpandProperty 'Subcategory GUID' -Unique
    $auditResult = Get-MDIAdvAuditPolicy | Where-Object { $_.'Subcategory GUID' -in $relevantGUIDs }
    $secPol = ((Get-MDIServiceLogonRight) -match 'SeServiceLogonRight')
    return [PSCustomObject](@{
            AuditSettings = $auditResult
            GptSettings   = $secPol
        })
}

function Test-MDIEntraConnectAuditing {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    Write-Verbose -Message $strings['AdvancedPolicyEntra_Validate']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiDsaParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
    $identitySid = (Get-MDIDSA @mdiDsaParams -myDomain $myDomain).objectSid.value
    $existingEntraConnectSettings = Get-MDIEntraConnectAuditing
    $result = Test-MDIAdvAuditPolicy -ExpectedAuditing @($settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv) -Detailed:$Detailed
    if ($Detailed) {
        Write-Verbose -Message (Get-MDIValidationMessage $result.Status)
    } else {
        Write-Verbose -Message (Get-MDIValidationMessage $result)
    }
    $result | Add-Member -MemberType NoteProperty -Name GptSettings -Value $existingEntraConnectSettings.GptSettings
    $result.Status = $result.status -and ($($result.GptSettings) -match $identitySid)
    return $result
}

function Set-MDIEntraConnectAuditing {
    [CmdletBinding()]
    param(
        [string] $Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    Write-Verbose -Message $strings['AdvancedPolicyEntra_Set']
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    Add-MDIServiceLogonRight -Identity $Identity -myDomain $myDomain
    $settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv | ForEach-Object {
        $param = @{
            SubcategoryGUID  = $_.'Subcategory GUID'
            InclusionSetting = $_.'Inclusion Setting'
        }
        Set-MDIAdvAuditPolicy @param
    }
}

function Get-MDIEntraConnectAuditingGPO {
    [CmdletBinding()]
    param(
        [string] $Identity,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiDsaParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
    $identitySid = (Get-MDIDSA @mdiDsaParams -myDomain $myDomain).objectSid.value
    $gpoName = Get-MDIGPOName -Name $settings.EntraConnectAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($gpo) {
        $gpoReportParams = @{
            Guid       = $gpo.Id
            ReportType = "Xml"
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $gpoReportParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoReportParams.Add("Domain", $myDomain.DNSRoot) }
        $report = [xml](Get-GPOReport @gpoReportParams)
        $currentSettings = $report.GPO.Computer.ExtensionData.Extension.AuditSetting
        $expectedSettings = $settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv
        $AuditSettings = foreach ($audit in $expectedSettings) {
            [PSCustomObject]@{
                PolicyTarget    = $audit.'Policy Target'
                SubcategoryName = $audit.'Subcategory'
                SubcategoryGuid = $audit.'Subcategory GUID'
                SettingValue    = $audit.'Setting Value'
                ExpectedValue   = ($currentSettings | Where-Object { -not [string]::IsNullOrEmpty($_) } | Where-Object {
                    ($_.SubcategoryGuid).ToUpper() -eq ($audit.'Subcategory GUID').ToUpper() -and
                        $_.PolicyTarget -eq $audit.'Policy Target' }).SettingValue
            }
        }
        $currentSettings = ($report.GPO.Computer.ExtensionData.Extension.UserRightsAssignment | Where-Object { $_.name -eq 'SeServiceLogonRight' }).member.sid.'#text' | sort
        $expectedSettings = (($settings.EntraConnectAuditing.GptSet['SeServiceLogonRight'] -split ',').trim('*') -f $identitySid) -split ' ' | sort
        $GptSettings = [PSCustomObject]@{
            UserRightsAssignment = 'Logon as a Service'
            SettingValue         = [string]$expectedSettings -join ','
            ExpectedValue        = [string]$(foreach ($gpt in $expectedSettings) { $currentSettings | Where-Object { -not [string]::IsNullOrEmpty($_) } | Where-Object { $_ -eq $gpt } }) -join ','
        }
    }
    $gpo | Select-Object -Property *, @{N = 'AuditSettings'; E = { $AuditSettings } }, @{N = 'GptSettings'; E = { $GptSettings | select SettingValue, ExpectedValue } }
}

function Test-MDIEntraConnectAuditingGPO {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $Identity,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.EntraConnectAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName)
    $mdiGpoParams = @{
        Identity      = $Identity
        GpoNamePrefix = $GpoNamePrefix
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $state = $false
    $gpo = Get-MDIEntraConnectAuditingGPO @mdiGpoParams -myDomain $myDomain

    if ($gpo) {
        $expectedSettings = @($settings.EntraConnectAuditing.AuditSet | ConvertFrom-Csv)
        $gpSetOk = ((@(($gpo.GptSettings | Where-Object { $_.SettingValue -match $_.ExpectedValue })).count -eq 1)) -and (@($gpo.AuditSettings | Where-Object { $_.SettingValue -match $_.ExpectedValue }).Count -eq $expectedSettings.Count)
        if ($gpSetOk) {
            $mdiGpoCheckParams = @{
                GPO                = $gpo
                ManualLinkRequired = $true
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoCheckParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoCheckParams.Add("Domain", $myDomain.DNSRoot) }
            $state = Test-MDIGPOEnabledAndLink @mdiGpoCheckParams -myDomain $myDomain
        } else {
            Write-Verbose -Message $strings['GPO_SettingsMismatch']
        }
    }
    Write-Verbose -Message (Get-MDIValidationMessage $state)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $state
                Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, @{N = "AuditSettings"; E = { [string]($gpo.AuditSettings -join ',') } }, @{N = "GptSettings"; E = { [string]($gpo.GptSettings -join ',') } } }
                else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] }
            })
    } else {
        $state
    }
}

function Set-MDIEntraConnectAuditingGPO {
    [CmdletBinding()]
    param(
        [switch] $SkipGpoLink,
        [string] $Identity,
        [switch] $CreateGpoDisabled,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiDsaParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
    $identitySid = (Get-MDIDSA @mdiDsaParams -myDomain $myDomain).objectSid.value
    if ($null -eq $identitySid) {
        throw
    }
    $gpoName = Get-MDIGPOName -Name $settings.EntraConnectAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($null -eq $gpo) {
        $mdiGpoParams.Add("CreateGpoDisabled", $CreateGpoDisabled)
        $gpo = New-MDIGPO @mdiGpoParams -myDomain $myDomain
    }

    $filePath = '{0}\Machine\Microsoft\Windows NT\SecEdit' -f $gpo.gPCFileSysPath
    try {
        New-Item -Path $filePath -ItemType Directory -Force | Out-Null
    } catch {}

    $fileContent = @'
[Unicode]
Unicode=yes
[Version]
signature="$CHICAGO$"
Revision=1
[Privilege Rights]
'@


    $settings.EntraConnectAuditing.GptSet.GetEnumerator() | ForEach-Object {
        $fileContent += '{2}{0}={1}' -f $_.Name, $($_.Value -f $identitySid), [System.Environment]::NewLine
    }
    [System.Io.File]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'GptTmpl.inf'), $fileContent, (New-Object System.Text.UTF8Encoding))

    $auditFilePath = '{0}\Machine\Microsoft\Windows NT\Audit' -f $gpo.gPCFileSysPath
    try {
        New-Item -Path $auditFilePath -ItemType Directory -Force | Out-Null
    } catch {}
    [System.io.file]::WriteAllLines((Join-Path -Path $auditFilePath -ChildPath 'audit.csv'), $settings.EntraConnectAuditing.AuditSet, (New-Object System.Text.ASCIIEncoding))

    if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled }
    $gpo.MakeAclConsistent()
    $stringBuilderGpc = [System.Text.StringBuilder]::new()
    [void]$stringBuilderGpc.Append("{$($settings.gpoExtensions['Security'])}")
    [void]$stringBuilderGpc.Append("{$($settings.gpoExtensions['Computer Restricted Groups'])}")
    $stringBuilderAudit = [System.Text.StringBuilder]::new()
    [void]$stringBuilderAudit.Append("{$($settings.gpoExtensions['Audit Policy Configuration'])}")
    [void]$stringBuilderAudit.Append("{$($settings.gpoExtensions['Audit Configuration Extension'])}")
    $mdiGpoMachineExtensionParams = @{
        Guid         = $gpo.Id.Guid
        RawExtension = '[{0}][{1}]' -f $stringBuilderGpc.ToString(), $stringBuilderAudit.ToString()
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoMachineExtensionParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoMachineExtensionParams.Add("Domain", $myDomain.DNSRoot) }
    $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams -myDomain $myDomain

    if ($null -ne $gpoUpdated) {
        if (-not $SkipGpoLink) {
            Write-Warning -Message ($strings['GPO_ManualLinkRequired'] -f $GPO.DisplayName)
        }
    } else {
        Write-Warning $strings['GPO_UnableToSetExtension']
    }
}

#endregion

#region NTLM Auditing helper functions

function Get-MDINTLMAuditing {
    [CmdletBinding()]
    param()
    $settings.NTLMAuditing.RegistrySet.GetEnumerator() | ForEach-Object {
        $name = ($_.Name -split '\\')[-1]
        $path = 'HKLM:\{0}' -f ($_.Name -replace $name)
        $value = Get-ItemProperty -Path $path -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name
        $expected = $_.Value
        [PSCustomObject]@{
            Path          = $path
            Name          = $name
            ActualValue   = $value
            ExpectedValue = $expected
        }
    }
}

function Test-MDINTLMAuditing {
    [CmdletBinding()]
    param(
        [switch] $Detailed
    )
    Write-Verbose -Message $strings['NTLM_ValidateAuditing']
    $ntlmAuditing = Get-MDINTLMAuditing
    $status = @($ntlmAuditing | Where-Object { $_.ActualValue -match $_.ExpectedValue }).Count -eq $settings.NTLMAuditing.RegistrySet.Count
    Write-Verbose (Get-MDIValidationMessage $status)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $status
                Details = $ntlmAuditing
            })
    } else {
        $status
    }
}

function Set-MDINTLMAuditing {
    [CmdletBinding()]
    param()
    $settings.NTLMAuditing.RegistrySet.GetEnumerator() | ForEach-Object {
        $name = ($_.Name -split '\\')[-1]
        $path = 'HKLM:\{0}' -f ($_.Name -replace $name)
        $value = ($_.Value -split '\|')[0]
        Set-ItemProperty -Path $path -Name $name -Value $value -ErrorAction Stop
    }
}

function Get-MDINTLMAuditingGPO {
    [CmdletBinding()]
    param(
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.NTLMAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($gpo) {
        $gpoReportParams = @{
            Guid       = $gpo.Id
            ReportType = "Xml"
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $gpoReportParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoReportParams.Add("Domain", $myDomain.DNSRoot) }
        $report = [xml](Get-GPOReport @gpoReportParams)

        $options = $report.GPO.Computer.ExtensionData.Extension.SecurityOptions | Where-Object { $_.KeyName -Match 'AuditReceivingNTLMTraffic|RestrictSendingNTLMTraffic|AuditNTLMInDomain' }
        $RegistryValue = foreach ($opt in $options) {
            $valueName = ($opt.KeyName -split '\\')[-1]
            $path = $opt.KeyName -replace '(.*)\\(\w+)', '$1'
            [PSCustomObject]@{
                KeyName       = $path
                valueName     = $valueName
                Value         = $opt.SettingNumber
                valueDisplay  = $opt.Display.DisplayString
                ExpectedValue = ($settings.NTLMAuditing.RegistrySet.GetEnumerator() |
                        Where-Object { ('MACHINE\{0}' -f $_.Name) -eq (Join-Path -Path $path -ChildPath $valueName) }).Value
            }
        }
        $gpo | Select-Object -Property *, @{N = 'RegistryValue'; E = { $RegistryValue } }
    }
}

function Test-MDINTLMAuditingGPO {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.NTLMAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName)
    $mdiGpoParams = @{
        GpoNamePrefix = $GpoNamePrefix
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $state = $false
    $gpo = Get-MDINTLMAuditingGPO @mdiGpoParams -myDomain $myDomain

    if ($gpo) {
        $gpSetOk = @($gpo.RegistryValue | Where-Object {
                $_.Value -match $_.ExpectedValue
            }).Count -eq $settings.NTLMAuditing.RegistrySet.Count
        if ($gpSetOk) {
            $mdiGpoCheckParams = @{
                GPO = $gpo
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoCheckParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoCheckParams.Add("Domain", $myDomain.DNSRoot) }
            $state = Test-MDIGPOEnabledAndLink @mdiGpoCheckParams -myDomain $myDomain
        } else {
            Write-Verbose -Message $strings['GPO_SettingsMismatch']
        }
    }
    Write-Verbose -Message (Get-MDIValidationMessage $state)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $state
                Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, RegistryValue }
                else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] }
            })
    } else {
        $state
    }
}

function Set-MDINTLMAuditingGPO {
    [CmdletBinding()]
    param(
        [switch] $SkipGpoLink,
        [switch] $CreateGpoDisabled,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.NTLMAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($null -eq $gpo) {
        $mdiGpoParams.Add("CreateGpoDisabled", $CreateGpoDisabled)
        $gpo = New-MDIGPO @mdiGpoParams -myDomain $myDomain
    }

    $filePath = '{0}\Machine\Microsoft\Windows NT\SecEdit' -f $gpo.gPCFileSysPath
    try {
        New-Item -Path $filePath -ItemType Directory -Force | Out-Null
    } catch {}

    $fileContent = @'
[Unicode]
Unicode=yes
[Version]
signature="$CHICAGO$"
Revision=1
[Registry Values]
'@


    $settings.NTLMAuditing.RegistrySet.GetEnumerator() | ForEach-Object {
        $value = ($_.Value -split '\|')[0]
        $fileContent += '{2}MACHINE\{0}=4,{1}' -f $_.Name, $Value, [System.Environment]::NewLine
    }
    [System.Io.File]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'GptTmpl.inf'), $fileContent, (New-Object System.Text.UnicodeEncoding))
    if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled }
    $gpo.MakeAclConsistent()
    $mdiGpoMachineExtensionParams = @{
        Guid      = $gpo.Id.Guid
        Extension = @($settings.gpoExtensions['Security'], $settings.gpoExtensions['Computer Restricted Groups'])
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoMachineExtensionParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoMachineExtensionParams.Add("Domain", $myDomain.DNSRoot) }
    $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams -myDomain $myDomain

    if ($null -ne $gpoUpdated) {
        if (-not $SkipGpoLink) {
            $gpLinkParams = @{
                Guid        = $gpo.Id.Guid
                LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes
                Enforced    = [Microsoft.GroupPolicy.EnforceLink]::Yes
                Target      = 'OU=Domain Controllers,{0}' -f $myDomain.DistinguishedName
            }
            if (-not [string]::IsNullOrEmpty($Server)) {
                Start-Sleep -Milliseconds 500
                $gpLinkParams.Add("Server", $Server)
            }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpLinkParams.Add("Domain", $myDomain.DNSRoot) }
            Set-MDIGPOLink @gpLinkParams -myDomain $myDomain
        }
    } else {
        Write-Warning $strings['GPO_UnableToSetExtension']
    }
}

#endregion

#region Advanced Auditing Policy helper functions

function Get-MDIAdvAuditPolicySetting {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Guid
    )
    $TypeDefinition = @'
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
 
namespace Audit
{
    public class AuditPol
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.U1)]
        public static extern bool AuditQuerySystemPolicy(
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1), In]
            Guid[] pSubCategoryGuids,
            uint dwPolicyCount,
            out IntPtr ppAuditPolicy);
 
        public static IEnumerable<AUDIT_POLICY_INFORMATION> AuditQuerySystemPolicy([In] Guid[] pSubCategoryGuids)
        {
            IntPtr ppAuditPolicy;
            if (!AuditQuerySystemPolicy(pSubCategoryGuids, (uint) pSubCategoryGuids.Length, out ppAuditPolicy))
                return new AUDIT_POLICY_INFORMATION[0];
 
            return ToIEnum<AUDIT_POLICY_INFORMATION>(ppAuditPolicy, pSubCategoryGuids.Length);
        }
 
        public static IEnumerable<T> ToIEnum<T>(IntPtr ptr, int count, int prefixBytes = 0)
        {
            if (count != 0 && !(ptr == IntPtr.Zero))
            {
                int stSize = Marshal.SizeOf(typeof(T));
                for (int i = 0; i < count; ++i)
                    yield return ToStructure<T>(new IntPtr(ptr.ToInt64() + prefixBytes + i * stSize));
            }
        }
 
        public static T ToStructure<T>(IntPtr ptr, long allocatedBytes = -1)
        {
            Type type = typeof(T).IsEnum ? Enum.GetUnderlyingType(typeof(T)) : typeof(T);
            if (allocatedBytes < 0L || allocatedBytes >= (long) Marshal.SizeOf(type))
            {
                return (T) Marshal.PtrToStructure(ptr, type);
            }
 
            throw new InsufficientMemoryException();
        }
 
        public struct AUDIT_POLICY_INFORMATION
        {
            public Guid AuditSubCategoryGuid;
            public AuditCondition AuditingInformation;
            public Guid AuditCategoryGuid;
        }
 
        public enum AuditCondition : uint
        {
            POLICY_AUDIT_EVENT_UNCHANGED = 0,
            POLICY_AUDIT_EVENT_SUCCESS = 1,
            POLICY_AUDIT_EVENT_FAILURE = 2,
            POLICY_AUDIT_EVENT_NONE = 4,
            PER_USER_POLICY_UNCHANGED = 0,
            PER_USER_AUDIT_SUCCESS_INCLUDE = POLICY_AUDIT_EVENT_SUCCESS, // 0x00000001
            PER_USER_AUDIT_SUCCESS_EXCLUDE = POLICY_AUDIT_EVENT_FAILURE, // 0x00000002
            PER_USER_AUDIT_FAILURE_INCLUDE = POLICY_AUDIT_EVENT_NONE, // 0x00000004
            PER_USER_AUDIT_FAILURE_EXCLUDE = 8,
            PER_USER_AUDIT_NONE = 16, // 0x00000010
        }
 
        public static int GetPolicy(String uid)
        {
            var guid = new Guid(uid);
            var result = AuditQuerySystemPolicy(new[] {guid});
            foreach (var info in result)
            {
                return (int) info.AuditingInformation;
            }
            return -1;
        }
    }
}
'@

    try {
        Add-Type -TypeDefinition $TypeDefinition -Language CSharp
    } catch {
        $result = -1
    } finally {
        $result = [Audit.AuditPol]::GetPolicy($Guid)
    }
    $result
}

function Get-MDIAdvAuditPolicy {
    [CmdletBinding()]
    param()
    & "$($env:SystemRoot)\system32\auditpol.exe" @('/get', '/category:*', '/r') | ConvertFrom-Csv |
        Select-Object *, @{N = 'Setting Value'; E = {
                $setting = Get-MDIAdvAuditPolicySetting(([regex]::Matches((($_[0] -join '')).split(';')[3], '(?<=\{).+?(?=\})').Value))
                $setting
            }
        }
}

function Test-MDIAdvAuditPolicy {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object[]] $ExpectedAuditing,
        [switch] $Detailed
    )
    $AppliedAuditing = Get-MDIAdvAuditPolicy
    $status = @(foreach ($applied in $AppliedAuditing) {
            $ExpectedAuditing | Where-Object {
                (($applied -join '').trim('@').trim('{').trim('}').split(';')[3].split('=')[1]) -eq (($_ -join '').trim('@').trim('{').trim('}').split(';')[3].split('=')[1]) -and
                (($applied -join '').trim('@').trim('{').trim('}').split(';')[6].split('=')[1]) -eq (($_ -join '').trim('@').trim('{').trim('}').split(';')[6].split('=')[1])
            }
        }).Count -ge $ExpectedAuditing.Count
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $status
                Details = $AppliedAuditing
            })
    } else {
        $status
    }
}

function Set-MDIAdvAuditPolicy {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] $SubcategoryGUID,
        [string] $InclusionSetting
    )
    if ($SubcategoryGUID -notmatch '^{.*}$') { $SubcategoryGUID = "{$SubcategoryGUID}" }
    $success = if ($InclusionSetting -match 'Success') { 'enable' } else { 'disable' }
    $failure = if ($InclusionSetting -match 'Failure') { 'enable' } else { 'disable' }
    $null = & "$($env:SystemRoot)\system32\auditpol.exe" @('/set', "/subcategory:$SubcategoryGUID", "/success:$success", "/failure:$failure")
}

#endregion

#region Advanced Auditing Policy for DCs Settings

function Get-MDIAdvancedAuditPolicyDCsGPO {
    [CmdletBinding()]
    param(
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain

    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyDCs.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($gpo) {
        $gpoReportParams = @{
            Guid       = $gpo.Id
            ReportType = "Xml"
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $gpoReportParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoReportParams.Add("Domain", $myDomain.DNSRoot) }
        $report = [xml](Get-GPOReport @gpoReportParams)
        $currentSettings = $report.GPO.Computer.ExtensionData.Extension.AuditSetting
        $expectedSettings = $settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv
        $AuditSettings = foreach ($audit in $expectedSettings) {
            [PSCustomObject]@{
                PolicyTarget    = $audit.'Policy Target'
                SubcategoryName = $audit.'Subcategory'
                SubcategoryGuid = $audit.'Subcategory GUID'
                SettingValue    = $audit.'Setting Value'
                ExpectedValue   = ($currentSettings | Where-Object { -not [string]::IsNullOrEmpty($_) } | Where-Object {
                    ($_.SubcategoryGuid).ToUpper() -eq ($audit.'Subcategory GUID').ToUpper() -and
                        $_.PolicyTarget -eq $audit.'Policy Target' }).SettingValue
            }
        }
        $gpo | Select-Object -Property *, @{N = 'AuditSettings'; E = { $AuditSettings } }
    }
}

function Test-MDIAdvancedAuditPolicyDCsGPO {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyDCs.GpoName -GpoNamePrefix $GpoNamePrefix
    Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName)
    $state = $false
    $mdiGpoParams = @{
        GpoNamePrefix = $GpoNamePrefix
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIAdvancedAuditPolicyDCsGPO @mdiGpoParams -myDomain $myDomain
    if ($gpo) {
        $expectedSettings = @($settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv)
        $gpSetOk = @($gpo.AuditSettings | Where-Object {
                $_.SettingValue -match $_.ExpectedValue
            }).Count -eq $expectedSettings.Count

        if ($gpSetOk) {
            $mdiGpoCheckParams = @{
                GPO = $gpo
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoCheckParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoCheckParams.Add("Domain", $myDomain.DNSRoot) }
            $state = Test-MDIGPOEnabledAndLink @mdiGpoCheckParams -myDomain $myDomain
        } else {
            Write-Verbose -Message $strings['GPO_SettingsMismatch']
        }
    }
    Write-Verbose -Message (Get-MDIValidationMessage $state)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $state
                Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, AuditSettings }
                else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] }
            })
    } else {
        $state
    }
}

function Set-MDIAdvancedAuditPolicyDCsGPO {
    [CmdletBinding()]
    param(
        [switch] $SkipGpoLink,
        [switch] $CreateGpoDisabled,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyDCs.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }

    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($null -eq $gpo) {
        $mdiGpoParams.Add("CreateGpoDisabled", $CreateGpoDisabled)
        $gpo = New-MDIGPO @mdiGpoParams -myDomain $myDomain
    }

    $filePath = '{0}\Machine\Microsoft\Windows NT\Audit' -f $gpo.gPCFileSysPath
    try {
        New-Item -Path $filePath -ItemType Directory -Force | Out-Null
    } catch {}
    [System.io.file]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'audit.csv'), $settings.AdvancedAuditPolicyDCs.PolicySettings, (New-Object System.Text.ASCIIEncoding))

    if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled }
    $gpo.MakeAclConsistent()
    $mdiGpoMachineExtensionParams = @{
        Guid      = $gpo.Id.Guid
        Extension = @($settings.gpoExtensions['Audit Policy Configuration'], $settings.gpoExtensions['Audit Configuration Extension'])
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoMachineExtensionParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoMachineExtensionParams.Add("Domain", $myDomain.DNSRoot) }
    $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams -myDomain $myDomain

    if ($null -ne $gpoUpdated) {
        if (-not $SkipGpoLink) {
            $gpLinkParams = @{
                Guid        = $gpo.Id.Guid
                LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes
                Enforced    = [Microsoft.GroupPolicy.EnforceLink]::Yes
                Target      = 'OU=Domain Controllers,{0}' -f $myDomain.DistinguishedName
            }
            if (-not [string]::IsNullOrEmpty($Server)) {
                Start-Sleep -Milliseconds 500
                $gpLinkParams.Add("Server", $Server)
            }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpLinkParams.Add("Domain", $myDomain.DNSRoot) }
            Set-MDIGPOLink @gpLinkParams -myDomain $myDomain
        }
    } else {
        Write-Warning $strings['GPO_UnableToSetExtension']
    }
}

function Get-MDIAdvancedAuditPolicyDCs {
    [CmdletBinding()]
    param()
    $relevantGUIDs = @($settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv) | Select-Object -ExpandProperty 'Subcategory GUID' -Unique
    Get-MDIAdvAuditPolicy | Where-Object { $_.'Subcategory GUID' -in $relevantGUIDs }
}

function Test-MDIAdvancedAuditPolicyDCs {
    [CmdletBinding()]
    param(
        [switch] $Detailed
    )
    Write-Verbose -Message $strings['AdvancedPolicyDCs_Validate']
    $result = Test-MDIAdvAuditPolicy -ExpectedAuditing @($settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv) -Detailed:$Detailed
    if ($Detailed) {
        Write-Verbose -Message (Get-MDIValidationMessage $result.Status)
    } else {
        Write-Verbose -Message (Get-MDIValidationMessage $result)
    }
    $result
}

function Set-MDIAdvancedAuditPolicyDCs {
    Write-Verbose -Message $strings['AdvancedPolicyDCs_Set']
    $settings.AdvancedAuditPolicyDCs.PolicySettings | ConvertFrom-Csv | ForEach-Object {
        $param = @{
            SubcategoryGUID  = $_.'Subcategory GUID'
            InclusionSetting = $_.'Inclusion Setting'
        }
        Set-MDIAdvAuditPolicy @param
    }
}

#endregion

#region Advanced Auditing Policy for CAs Settings

function Get-MDIAdvancedAuditPolicyCAsGPO {
    [CmdletBinding()]
    param(
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyCAs.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($gpo) {
        $gpoReportParams = @{
            Guid       = $gpo.Id
            ReportType = "Xml"
        }; if (-not [string]::IsNullOrEmpty($Server)) { $gpoReportParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpoReportParams.Add("Domain", $myDomain.DNSRoot) }
        $report = [xml](Get-GPOReport @gpoReportParams)
        $currentSettings = $report.GPO.Computer.ExtensionData.Extension.AuditSetting
        $expectedSettings = $settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv
        $AuditSettings = foreach ($audit in $expectedSettings) {
            [PSCustomObject]@{
                PolicyTarget    = $audit.'Policy Target'
                SubcategoryName = $audit.'Subcategory'
                SubcategoryGuid = $audit.'Subcategory GUID'
                SettingValue    = $audit.'Setting Value'
                ExpectedValue   = ($currentSettings | Where-Object {
                        $_.SubcategoryName -eq ($audit.Subcategory) -and
                        ($_.SubcategoryGuid).ToUpper() -eq ($audit.'Subcategory GUID').ToUpper() -and
                        $_.PolicyTarget -eq $audit.'Policy Target' }).SettingValue
            }
        }
        $delegation = $settings.AdvancedAuditPolicyCAs.GPPermissions.GetEnumerator() | ForEach-Object {
            $mdiGpPermissionParams = @{
                Guid       = $gpo.Id.Guid
                TargetType = "Group"
                TargetName = Get-MDIADObjectName -SidMask $_.Key -Server $Server -myDomain $myDomain
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpPermissionParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpPermissionParams.Add("Domain", $myDomain.DNSRoot) }
            Get-GPPermission @mdiGpPermissionParams
        }
        $gpo = $gpo | Select-Object -Property *, @{N = 'AuditSettings'; E = { $AuditSettings } }, @{N = 'Delegation'; E = { $delegation } }
    }
    $gpo
}

function Test-MDIAdvancedAuditPolicyCAsGPO {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyCAs.GpoName -GpoNamePrefix $GpoNamePrefix
    Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName)
    $mdiGpoParams = @{
        GpoNamePrefix = $GpoNamePrefix
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $state = $false
    $gpo = Get-MDIAdvancedAuditPolicyCAsGPO @mdiGpoParams -myDomain $myDomain

    if ($gpo) {
        $expectedSettings = @($settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv)
        $gpSetOk = @($gpo.AuditSettings | Where-Object {
                $_.SettingValue -match $_.ExpectedValue
            }).Count -eq $expectedSettings.Count

        if ($gpSetOk) {
            $mapping = @{}; $settings.AdvancedAuditPolicyCAs.GPPermissions.GetEnumerator() | ForEach-Object {
                $mapping[(Get-MDIADObjectName -SidMask $_.Key -Server $Server -myDomain $myDomain)] = $_.Key
            }

            $gpDelegationOk = @($gpo.Delegation | Where-Object {
                    $settings.AdvancedAuditPolicyCAs.GPPermissions[$mapping[$_.Trustee.Name]] -eq $_.Permission
                }).Count -eq $settings.AdvancedAuditPolicyCAs.GPPermissions.Count

            if (-not $gpDelegationOk) {
                Write-Verbose -Message $strings['GPO_DelegationMismatch']
            } else {
                $mdiGpoCheckParams = @{
                    GPO = $gpo
                }
                if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoCheckParams.Add("Server", $Server) }
                if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoCheckParams.Add("Domain", $myDomain.DNSRoot) }
                $state = Test-MDIGPOEnabledAndLink @mdiGpoCheckParams -myDomain $myDomain
            }

        } else {
            Write-Verbose -Message $strings['GPO_SettingsMismatch']
        }
    }
    Write-Verbose -Message (Get-MDIValidationMessage $state)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $state
                Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, AuditSettings }
                else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] }
            })
    } else {
        $state
    }
}

function Set-MDIAdvancedAuditPolicyCAsGPO {
    [CmdletBinding()]
    param(
        [switch] $SkipGpoLink,
        [switch] $CreateGpoDisabled,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.AdvancedAuditPolicyCAs.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain
    if ($null -eq $gpo) {
        $mdiGpoParams.Add("CreateGpoDisabled", $CreateGpoDisabled)
        $gpo = New-MDIGPO @mdiGpoParams -myDomain $myDomain
    }
    if ($gpo) {
        $filePath = '{0}\Machine\Microsoft\Windows NT\Audit' -f $gpo.gPCFileSysPath
        try {
            Test-Path $filePath | Out-Null
        } catch {
            Start-Sleep 3
        }
        try {
            New-Item -Path $filePath -ItemType Directory -Force | Out-Null
            Start-Sleep -Milliseconds 500
        } catch {}
        [System.io.file]::WriteAllLines((Join-Path -Path $filePath -ChildPath 'audit.csv'), $settings.AdvancedAuditPolicyCAs.PolicySettings, (New-Object System.Text.ASCIIEncoding))

        if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled }
        $gpo.MakeAclConsistent()
        $mdiGpoMachineExtensionParams = @{
            Guid      = $gpo.Id.Guid
            Extension = @($settings.gpoExtensions['Audit Policy Configuration'], $settings.gpoExtensions['Audit Configuration Extension'])
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoMachineExtensionParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoMachineExtensionParams.Add("Domain", $myDomain.DNSRoot) }
        $gpoUpdated = Set-MDIGPOMachineExtension @mdiGpoMachineExtensionParams -myDomain $myDomain
        Write-Verbose -Message $strings['GPO_SetDelegation']
        $settings.AdvancedAuditPolicyCAs.GPPermissions.GetEnumerator() | ForEach-Object {
            $TargetName = Get-MDIADObjectName -SidMask $_.Key -Server $Server -myDomain $myDomain
            $PermissionLevel = $($_.Value)
            $mdiGpPermissionParams = @{
                Guid            = $gpo.Id.Guid
                TargetType      = "Group"
                TargetName      = "$TargetName"
                PermissionLevel = "$PermissionLevel"
                Replace         = $true
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpPermissionParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpPermissionParams.Add("Domain", $myDomain.DNSRoot) }
            Start-Sleep -Milliseconds 500
            Set-GPPermission @mdiGpPermissionParams | Out-Null
        }

        if ($null -ne $gpoUpdated) {
            if (-not $SkipGpoLink) {
                $gpLinkParams = @{
                    Guid        = $gpo.Id.Guid
                    LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes
                    Enforced    = [Microsoft.GroupPolicy.EnforceLink]::Yes
                    Target      = $myDomain.DistinguishedName
                }
                if (-not [string]::IsNullOrEmpty($Server)) {
                    Start-Sleep -Milliseconds 500
                    $gpLinkParams.Add("Server", $Server)
                }
                if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpLinkParams.Add("Domain", $myDomain.DNSRoot) }
                Set-MDIGPOLink @gpLinkParams -myDomain $myDomain
            }
        } else {
            Write-Warning $strings['GPO_UnableToSetExtension']
        }
    }

}

function Get-MDIAdvancedAuditPolicyCAs {
    [CmdletBinding()]
    param()
    $relevantGUIDs = @($settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv) | Select-Object -ExpandProperty 'Subcategory GUID' -Unique
    Get-MDIAdvAuditPolicy | Where-Object { $_.'Subcategory GUID' -in $relevantGUIDs }
}

function Test-MDIAdvancedAuditPolicyCAs {
    [CmdletBinding()]
    param(
        [switch] $Detailed
    )
    Write-Verbose -Message $strings['AdvancedPolicyCAs_Validate']
    if (Test-MDICAServer) {
        $result = Test-MDIAdvAuditPolicy -ExpectedAuditing @($settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv) -Detailed:$Detailed
    } else {
        Write-Verbose -Message $strings['CAAuditing_NotCAServer']
        $result = [PSCustomObject]([ordered]@{
                Status  = $true
                Details = $strings['CAAuditing_NotCAServer']
            })
    }

    if ($Detailed) {
        Write-Verbose -Message (Get-MDIValidationMessage $result.Status)
    } else {
        Write-Verbose -Message (Get-MDIValidationMessage $result)
    }
    $result
}

function Set-MDIAdvancedAuditPolicyCAs {
    Write-Verbose -Message $strings['AdvancedPolicyCAs_Set']
    $settings.AdvancedAuditPolicyCAs.PolicySettings | ConvertFrom-Csv | ForEach-Object {
        $param = @{
            SubcategoryGUID  = $_.'Subcategory GUID'
            InclusionSetting = $_.'Inclusion Setting'
        }
        Set-MDIAdvAuditPolicy @param
    }
}

#endregion

#region CA Audit configuration helper functions

function Get-MDICAAuditing {
    [CmdletBinding()]
    param()
    $certSvcConfigPath = $settings.CAAuditing.RegPathActive
    $name = ($certSvcConfigPath -split '\\')[-1]
    $activePath = 'HKLM:\{0}' -f ($certSvcConfigPath -replace $name)
    $activeValue = Get-ItemProperty -Path $activePath -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name

    $settings.CAAuditing.RegistrySet.GetEnumerator() | ForEach-Object {
        $name = ($_.Name -split '\\')[-1]
        $path = 'HKLM:\{0}' -f (($_.Name -replace $name) -f $activeValue)
        $value = Get-ItemProperty -Path $path -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name
        $expected = $_.Value
        [PSCustomObject]@{
            Path          = $path
            Name          = $name
            ActualValue   = $value
            ExpectedValue = $expected
        }
    }
}

function Test-MDICAAuditing {
    [CmdletBinding()]
    param(
        [switch] $Detailed
    )
    Write-Verbose -Message $strings['CAAuditing_Validate']
    if (Test-MDICAServer) {
        $caAuditing = Get-MDICAAuditing
        $caAuditingOk = @($caAuditing | Where-Object { $_.ActualValue -match $_.ExpectedValue }).Count -eq $settings.CAAuditing.RegistrySet.Count
    } else {
        Write-Verbose -Message $strings['CAAuditing_NotCAServer']
        $caAuditing = $strings['CAAuditing_NotCAServer']
        $caAuditingOk = $true
    }
    Write-Verbose -Message (Get-MDIValidationMessage $caAuditingOk)

    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $caAuditingOk
                Details = $caAuditing
            })
    } else {
        $caAuditingOk
    }
}

function Set-MDICAAuditing {
    [CmdletBinding()]
    param(
        [switch] $SkipServiceRestart
    )
    if (Get-Service CertSvc -ErrorAction SilentlyContinue) {
        $certSvcConfigPath = $settings.CAAuditing.RegPathActive
        $name = ($certSvcConfigPath -split '\\')[-1]
        $activePath = 'HKLM:\{0}' -f ($certSvcConfigPath -replace $name)
        $activeValue = Get-ItemProperty -Path $activePath -Name $name -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $name

        $settings.CAAuditing.RegistrySet.GetEnumerator() | ForEach-Object {
            $name = ($_.Name -split '\\')[-1]
            $path = 'HKLM:\{0}' -f (($_.Name -replace $name) -f $activeValue)
            $value = ($_.Value -split '\|')[0]
            Write-Verbose -Message ('Setting {0}{1} to {2}' -f $path, $name, $value)
            Set-ItemProperty -Path $path -Name $name -Value $value -Type DWord -ErrorAction Stop
        }
        if (-not $SkipServiceRestart) { Restart-Service -Name CertSvc -Force -Verbose:$VerbosePreference }
    } else {
        Write-Warning $strings['CAAuditing_NotCAServer']
    }
}

function Get-MDICAAuditingGPO {
    [CmdletBinding()]
    param(
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.CAAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain

    if ($gpo) {
        $params = @{
            Guid      = $gpo.Id
            Context   = 'Computer'
            Key       = 'HKEY_LOCAL_MACHINE\{0}' -f $settings.CAAuditing.GpoReg
            ValueName = ($settings.CAAuditing.GpoVal).Keys[0]
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $params.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $params.Add("Domain", $myDomain.DNSRoot) }
        $GPPrefRegistryValue = Get-GPPrefRegistryValue @params -ErrorAction SilentlyContinue
        $delegation = $settings.CAAuditing.GPPermissions.GetEnumerator() | ForEach-Object {
            $mdiGpPermissionParams = @{
                Guid       = $gpo.Id.Guid
                TargetType = "Group"
                TargetName = Get-MDIADObjectName -SidMask $_.Key -Server $Server -myDomain $myDomain
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpPermissionParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpPermissionParams.Add("Domain", $myDomain.DNSRoot) }
            Get-GPPermission @mdiGpPermissionParams
        }
        $gpo = $gpo | Select-Object -Property *, @{N = 'GPPrefRegistryValue'; E = { $GPPrefRegistryValue } }, @{N = 'Delegation'; E = { $delegation } }
    }
    $gpo
}

function Test-MDICAAuditingGPO {
    [CmdletBinding()]
    param(
        [switch] $Detailed,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $gpoName = Get-MDIGPOName -Name $settings.CAAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    Write-Verbose -Message ($strings['GPO_Validate'] -f $gpoName)
    $state = $false
    $mdiGpoParams = @{
        GpoNamePrefix = $GpoNamePrefix
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDICAAuditingGPO @mdiGpoParams -myDomain $myDomain
    $gpSetOk = @()

    if ($gpo -and $gpo.GPPrefRegistryValue) {
        $settings.CAAuditing.GpoVal.GetEnumerator() | ForEach-Object {
            $expected = [PSCustomObject]@{
                DisabledDirectly = $false
                Type             = 'DWord'
                Action           = 'Update'
                Hive             = 'LocalMachine'
                FullKeyPath      = 'HKEY_LOCAL_MACHINE\{0}' -f $settings.CAAuditing.GpoReg
                ValueName        = $_.Key
                Value            = $_.Value
            }
            $properties = $expected | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name
            $applied = $gpo.GPPrefRegistryValue | Select-Object -Property $properties
            $gpSetOk += ($null -ne (Compare-Object -ReferenceObject $applied -DifferenceObject $expected -Property $properties -IncludeEqual -ExcludeDifferent))
        }

        if (($gpSetOk -eq $false).Count -eq 0) {
            $mapping = @{}; $settings.CAAuditing.GPPermissions.GetEnumerator() | ForEach-Object {
                $mapping[(Get-MDIADObjectName -SidMask $_.Key -Server $Server -myDomain $myDomain)] = $_.Key
            }

            $gpDelegationOk = @($gpo.Delegation | Where-Object {
                    $settings.CAAuditing.GPPermissions[$mapping[$_.Trustee.Name]] -eq $_.Permission
                }).Count -eq $settings.CAAuditing.GPPermissions.Count

            if (-not $gpDelegationOk) {
                Write-Verbose -Message $strings['GPO_DelegationMismatch']
            } else {
                $mdiGpoParams.Add("GPO", $gpo)
                $mdiGpoParams.Remove("GpoNamePrefix")
                $state = Test-MDIGPOEnabledAndLink @mdiGpoParams -myDomain $myDomain
            }

        } else {
            Write-Verbose -Message $strings['GPO_SettingsMismatch']
        }
    }
    Write-Verbose -Message (Get-MDIValidationMessage $state)
    if ($Detailed) {
        [PSCustomObject]([ordered]@{
                Status  = $state
                Details = if ($gpo) { $gpo | Select-Object DisplayName, Id, GpoStatus, GPPrefRegistryValue }
                else { "'{0}' - {1}" -f $gpoName, $strings['GPO_NotFound'] }
            })
    } else {
        $state
    }
}

function Set-MDICAAuditingGPO {
    [CmdletBinding()]
    param(
        [switch] $SkipGpoLink,
        [switch] $CreateGpoDisabled,
        [string] $GpoNamePrefix,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )

    $gpoName = Get-MDIGPOName -Name $settings.CAAuditing.GpoName -GpoNamePrefix $GpoNamePrefix
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $mdiGpoParams = @{
        Name = $gpoName
    }
    if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpoParams.Add("Server", $Server) }
    if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpoParams.Add("Domain", $myDomain.DNSRoot) }
    $gpo = Get-MDIGPO @mdiGpoParams -myDomain $myDomain

    if ($null -eq $gpo) {
        $mdiGpoParams.Add("CreateGpoDisabled", $CreateGpoDisabled)
        $gpo = New-MDIGPO @mdiGpoParams -myDomain $myDomain
    }

    $settings.CAAuditing.GpoVal.GetEnumerator() | ForEach-Object {
        $params = @{
            Guid      = $gpo.Id
            Context   = 'Computer'
            Key       = 'HKEY_LOCAL_MACHINE\{0}' -f $settings.CAAuditing.GpoReg
            ValueName = $_.Name
            Order     = -1
        }
        if (-not [string]::IsNullOrEmpty($Server)) {
            Start-Sleep -Milliseconds 500
            $params.Add("Server", $Server)
        }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $params.Add("Domain", $myDomain.DNSRoot) }
        if (Get-GPPrefRegistryValue @params -ErrorAction SilentlyContinue) { $gpo = Remove-GPPrefRegistryValue @params }

        $params += @{
            Value  = [int]$_.Value
            Type   = 'DWord'
            Action = 'Update'
        }
        Set-GPPrefRegistryValue @params | Out-Null
    }

    if (-not ($CreateGpoDisabled)) { $gpo.GpoStatus = [Microsoft.GroupPolicy.GpoStatus]::UserSettingsDisabled }
    $gpo.MakeAclConsistent()
    $gpoUpdated = Set-MDIGPOMachineExtension -Server $Server -Domain $myDomain.DNSRoot -myDomain $myDomain -Guid $gpo.Id.Guid -Extension @(
        $settings.gpoExtensions['Preference CSE GUID Registry'], $settings.gpoExtensions['Preference Tool CSE GUID Registry'])

    Write-Verbose -Message $strings['GPO_SetDelegation']
    $settings.CAAuditing.GPPermissions.GetEnumerator() | ForEach-Object {
        $TargetName = Get-MDIADObjectName -SidMask $_.Key -Server $Server -myDomain $myDomain
        $PermissionLevel = $($_.Value)
        $mdiGpPermissionParams = @{
            Guid            = $gpo.Id.Guid
            TargetType      = "Group"
            TargetName      = "$TargetName"
            PermissionLevel = "$PermissionLevel"
            Replace         = $true
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $mdiGpPermissionParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiGpPermissionParams.Add("Domain", $myDomain.DNSRoot) }
        Start-Sleep -Milliseconds 500
        Set-GPPermission @mdiGpPermissionParams | Out-Null
    }

    if ($null -ne $gpoUpdated) {
        if (-not $SkipGpoLink) {
            $gpLinkParams = @{
                Guid        = $gpo.Id.Guid
                LinkEnabled = [Microsoft.GroupPolicy.EnableLink]::Yes
                Enforced    = [Microsoft.GroupPolicy.EnforceLink]::Yes
                Target      = $myDomain.DistinguishedName
            }
            if (-not [string]::IsNullOrEmpty($Server)) {
                Start-Sleep -Milliseconds 500
                $gpLinkParams.Add("Server", $Server)
            }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $gpLinkParams.Add("Domain", $myDomain.DNSRoot) }
            Set-MDIGPOLink @gpLinkParams -myDomain $myDomain
        }
    } else {
        Write-Warning $strings['GPO_UnableToSetExtension']
    }
}

#endregion

#region Domain helper functions

function Get-MDIDomain {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    if ($null -eq $myDomain) {
        $successServer = $false
        $successPdc = $false
        if ([string]::IsNullOrEmpty($Domain)) {
            $Domain = $env:USERDNSDOMAIN
        }
        if (-not ([string]::IsNullOrEmpty($Server))) {
            if (Test-MDIDC -Server $Server) {
                $successServer = $true
                $chosenDc = $Server
            }
        } else {
            $pdc = Get-ADDomainController -DomainName $Domain -Service primarydc, adws -Discover -ForceDiscover -ErrorAction SilentlyContinue
            if (-not [string]::IsNullOrEmpty(($pdc.HostName))) {
                $successPdc = $true
                $chosenDc = $pdc.HostName[0]
            }
        }
        if ((-not $successServer) -and (-not $successPdc)) {
            $possibleDC = Get-ADDomainController -DomainName $Domain -Service adws -Discover -ForceDiscover
            if (-not [string]::IsNullOrEmpty($possibleDC.HostName)) {
                $chosenDc = $possibleDC.HostName[0]
            }
        }
        try {
            if ([string]::IsNullOrEmpty($chosenDc)) { throw }
            $domainInfo = Get-ADDomain -Server $chosenDc
            $ipTest = [ipaddress]::Any
            if ($null -ne $domainInfo) {
                switch ($chosenDc) {
                    { $([system.net.ipaddress]::tryparse($chosenDc, [ref]$ipTest)) } {
                        $nameHost = (Resolve-DnsName -Name $chosenDc -ErrorAction SilentlyContinue).NameHost
                        if ($nameHost.Contains(".")) {
                            $chosenDcHostName = $nameHost
                        } else {
                            $chosenDcHostName = ('{0}.{1}' -f $nameHost, $domainInfo.DNSRoot)
                        }
                        break
                    }
                    { $(-not $chosenDc.Contains(".")) } {
                        $chosenDcHostName = ('{0}.{1}' -f $chosenDc, $domainInfo.DNSRoot)
                        break
                    }
                    { $($chosenDc.Contains(".")) } {
                        $chosenDcHostName = $chosenDc
                        break
                    }
                }
            }
            $wellKnownObjects = (Get-ADObject -Server $chosenDcHostName -filter 'ObjectClass -eq "domain"' -Properties wellKnownObjects).wellKnownObjects
            $wellKnownObjects | ForEach-Object {
                if ($_ -match '^B:32:A9D1CA15768811D1ADED00C04FD8D5CD:(.*)$') {
                    $usersContainer = $matches[1]
                }
            }
            try {
                $schemaVersion = (Get-MDIDomainSchemaVersion -Server $chosenDcHostName).schemaVersion
            } catch {
                $schemaVersion = $null
            }
            $myDomain = [PSCustomObject][ordered]@{
                ChosenDC                = $chosenDcHostName
                DeletedObjectsContainer = $domainInfo.DeletedObjectsContainer
                DistinguishedName       = $domainInfo.DistinguishedName
                DNSRoot                 = $domainInfo.DNSRoot
                DomainFunctionality     = (([adsi]"LDAP://$chosenDc/rootDSE").properties["domainFunctionality"]).value
                DomainSid               = $domainInfo.DomainSid.value
                Forest                  = $domainInfo.Forest
                ForestFunctionality     = (([adsi]"LDAP://$chosenDc/rootDSE").properties["forestFunctionality"]).value
                ForestSid               = $(try { (Get-ADDomain -server $domainInfo.Forest).DomainSID.value } catch { $null })
                NetBIOSName             = $domainInfo.NetBIOSName
                ParentDomain            = $domainInfo.ParentDomain
                PDCEmulator             = $domainInfo.PDCEmulator
                ReplicaDirectoryServers = $domainInfo.ReplicaDirectoryServers
                SchemaVersion           = $schemaVersion
                UsersContainer          = $usersContainer
            }
            if (-not ($myDomain.ChosenDC -in $myDomain.ReplicaDirectoryServers)) {
                $myDomain.ChosenDC = $myDomain.PDCEmulator
            }
        } catch {
            Write-Warning -Message $strings['DomainControllerUnavailable']
        }
    }
    return $myDomain
}

function Test-MDIDC {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $Server
    )
    & {
        $VerbosePreference = 'SilentlyContinue'
        $returnVal = $false
        try {
            $socket = New-Object -TypeName System.Net.Sockets.TcpClient
            $socket.SendTimeout = 3000
            $socket.ReceiveTimeout = 3000
            $socket.Connect($Server, 9389)
            if ($socket.Connected) {
                $returnVal = $true
            }
            $socket.Close()
        } catch {
            $returnVal = $false
        }
        return $returnVal
    }
}

function Get-MDIDomainSchemaVersion {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)] [string] $Server
    )
    $schemaVersions = @{
        13 = 'Windows 2000 Server'
        30 = 'Windows Server 2003'
        31 = 'Windows Server 2003 R2'
        44 = 'Windows Server 2008'
        47 = 'Windows Server 2008 R2'
        56 = 'Windows Server 2012'
        69 = 'Windows Server 2012 R2'
        87 = 'Windows Server 2016'
        88 = 'Windows Server 2019 / 2022'
        90 = 'Windows Server vNext'
    }
    $schemaNamingContextBind = 'LDAP://{0}/rootDSE' -f $Server
    $schema = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList (
        'LDAP://{0}' -f ([adsi]$schemaNamingContextBind).Properties['schemaNamingContext'].Value
    )
    $schemaVersion = $schema.Properties['objectVersion'].Value

    $return = @{
        schemaVersion = $schemaVersion
        details       = $schemaVersions[$schemaVersion]
    }
    $return
}

function Get-MDIADObjectName {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)] [ValidatePattern('^S-1-\d{1}(-\d+){1,2}$|^\{0\}-\d{3}$')] [string] $SidMask,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $null
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    if ($SidMask -match '^\{0\}-\d{3}$') {
        $sid = $SidMask -f $myDomain.DomainSid
        Get-ADObject -Filter { objectSid -eq $sid } -Server $Server | Select-Object -ExpandProperty Name
    } else {
        (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $SidMask
            ).Translate([System.Security.Principal.NTAccount]).Value -replace '(.*)\\(.*)', '$2';
    }
}
#endregion

#region DSA helper functions

function Get-MDIDeletedObjectsContainerPermission {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    if ($myDomain.DNSRoot -eq $ENV:USERDNSDOMAIN) {
        $deletedObjectsDN = 'CN=Deleted Objects,{0}' -f $($myDomain.DistinguishedName)
        $output = & "$($env:SystemRoot)\system32\dsacls.exe" @($deletedObjectsDN)
        ($output -join [System.Environment]::NewLine) -split '(?=Allow\s)' | Where-Object { $_ -match 'Allow' } | ForEach-Object {
            if ($_ -match 'Allow\s(?<Identity>(NT AUTHORITY\\\w+)|([^\s]+))\s+(?<Permissions>.*(?:\n\s+.*)*)') {
                [PSCustomObject]@{
                    Identity    = $Matches.Identity
                    Permissions = $Matches.Permissions -split '\s{2,}' | ForEach-Object { $_.Trim() }
                }
            }
        }
    } else {
        Write-Warning $strings['DeletedObjectsPermissions_CrossDomain']
    }

}

function Set-MDIDeletedObjectsContainerPermission {
    [CmdletBinding()]
    Param(
        [parameter(Mandatory = $True, Position = 1)]
        [string]$Identity,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow)]
        [AllowNull()]
        $myDomain
    )
    $returnVal = $false
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($myDomain.DNSRoot -eq $env:USERDNSDOMAIN) {
        if (-not $Identity.Contains('\')) {
            $mdiDsaParams = @{
                Identity = $Identity
            }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
            $mdiDsa = Get-MDIDSA @mdiDsaParams
            $Identity = '{0}\{1}' -f $myDomain.NetBIOSName, $mdiDsa.SamAccountName
        }
        if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
        try {
            $parameters = @{
                ScriptBlock  = {
                    Param ($param1, $param2)
                    $deletedObjectsDN = "\\$Server\CN=Deleted Objects,{0}" -f $param1
                    $params = @("$deletedObjectsDN", '/takeOwnership')
                    & "$($env:SystemRoot)\system32\dsacls.exe" $params
                    $params = @("$deletedObjectsDN", '/G', "$($param2):LCRP")
                    & "$($env:SystemRoot)\system32\dsacls.exe" $params
                }
                ArgumentList = $($myDomain.distinguishedName), $Identity
            }
            $command = "Invoke-command @parameters"
            $dsaclCheck = Invoke-Expression $command
            $returnVal = $true
        } catch {
            Write-Error $strings['DeletedObjectsPermissions_StatusFail']
        }
    } else {
        Write-Warning $strings['DeletedObjectsPermissions_CrossDomain']
    }
    return $returnVal
}

function Test-MDIDeletedObjectsContainerPermission {
    [CmdletBinding(DefaultParameterSetName = 'SingleIdentity')]
    param (
        [parameter(Mandatory = $True, ParameterSetName = 'SingleIdentity')]
        [string]$Identity,
        [parameter(Mandatory = $True, ParameterSetName = 'MultipleIdentities')]
        [string[]]$msDSPrincipalNamesToCheck,
        [Parameter(Mandatory = $false, ParameterSetName = 'SingleIdentity')]
        [Parameter(Mandatory = $false, ParameterSetName = 'MultipleIdentities')]
        [switch] $Detailed,
        [Parameter(Mandatory = $false, ParameterSetName = 'SingleIdentity')]
        [Parameter(Mandatory = $false, ParameterSetName = 'MultipleIdentities')]
        [AllowEmptyString()]
        [string] $Server,
        [Parameter(Mandatory = $false, ParameterSetName = 'SingleIdentity')]
        [Parameter(Mandatory = $false, ParameterSetName = 'MultipleIdentities')]
        [AllowEmptyString()]
        [string] $Domain,
        [Parameter(DontShow, ParameterSetName = 'SingleIdentity')]
        [Parameter(DontShow, ParameterSetName = 'MultipleIdentities')]
        [AllowNull()]
        [PSCustomObject] $myDomain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($myDomain.DNSRoot -eq $env:USERDNSDOMAIN) {
        if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
        if ($PSCmdlet.ParameterSetName -eq 'SingleIdentity') {
            $mdiDsaParams = @{
                Identity = $Identity
            }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
            $mdiDsa = Get-MDIDSA @mdiDsaParams
            $msDSPrincipalNamesToCheck = @()
            $msDSPrincipalNamesToCheck += $mdiDsa.'msDS-PrincipalName'
        }
        $appliedAsExpected = $false
        $expectedDsacls = @('SPECIAL ACCESS', 'LIST CONTENTS', 'READ PROPERTY')
        $appliedDsacls = Get-MDIDeletedObjectsContainerPermission -myDomain $myDomain
        if ([string]::IsNullOrEmpty($appliedDsacls)) {
            Write-Warning -Message $strings['DSA_CannotReadDeletedObjectsContainer']
        } else {
            $dsaDsacls = $appliedDsacls | Where-Object { $msDSPrincipalNamesToCheck -contains $_.Identity } | Select-Object -ExpandProperty Permissions
            if ($null -eq $dsaDsacls) {
                $dsaDsacls = $strings['DSA_DeletedObjectsPermissionNotFound']
            } else {
                $dsaDsacls = $dsaDsacls | Select-Object -Unique
                $appliedAsExpected = (Compare-Object -ReferenceObject $dsaDsacls -DifferenceObject $expectedDsacls -IncludeEqual -ExcludeDifferent).Count -eq $expectedDsacls.Count
            }
        }
        $return = [PSCustomObject][ordered]@{
            Test    = 'DeletedObjectsContainerPermission'
            Status  = $appliedAsExpected
            Details = $dsaDsacls
        }
    } else {
        Write-Warning $strings['DeletedObjectsPermissions_CrossDomain']
        $return = [PSCustomObject][ordered]@{
            Test    = 'DeletedObjectsContainerPermission'
            Status  = $false
            Details = $strings['DSA_CannotReadDeletedObjectsContainer']
        }
    }
    if ($Detailed) {
        return $return
    } else {
        return $return.status
    }
}

function Test-MDIDSA {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)] [string] $Identity,
        [switch] $Detailed,
        [Parameter(Mandatory = $false)]
        [string] $Server,
        [Parameter(Mandatory = $false)]
        [string] $Domain
    )
    $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
    if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
    $return = @()
    $mdiDsaParams = @{
        Identity = $Identity
    }; if (-not [string]::IsNullOrEmpty($Server)) { $mdiDsaParams.Add("Server", $Server) }
    $account = (Get-MDIDSA @mdiDsaParams -myDomain $myDomain)
    if ($null -eq $account) {
        $return += [PSCustomObject][ordered]@{
            Test    = 'AccountExists'
            Status  = $false
            Details = $strings['ServiceAccount_NotFound']
        }
    } else {

        Write-Verbose -Message $strings['DSA_TestGroupMembership']
        $memberOf = @{}
        $filter = '(&(objectCategory=group)(objectClass=group)(member:1.2.840.113556.1.4.1941:={0}))' -f $account.DistinguishedName
        $searcher = [adsisearcher]$filter
        'objectSid', 'distinguishedName', 'msDS-PrincipalName' | ForEach-Object { [void]($searcher.PropertiesToLoad.Add($_)) }
        $searcher.FindAll() | ForEach-Object {
            $memberOf.Add($_.Properties['distinguishedname'][0],
                [PSCustomObject]@{
                    'objectSid'          = (New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList @($_.Properties['objectSid'][0], 0)).Value
                    'msDS-PrincipalName' = $_.Properties['msDS-PrincipalName'][0]
                })
        }

        $domainSid = $myDomain.DomainSid
        $forestSid = $myDomain.ForestSid
        $sensitiveGroups = @{}
        $settings.SensitiveGroups.GetEnumerator() | ForEach-Object {
            if ($_.key -match "Enterprise Admins" -or $_.key -match "Schema Admins") {
                $sensitiveGroups.Add(($_.Value -f $forestSid), $_.Key)
            } else {
                $sensitiveGroups.Add(($_.Value -f $domainSid), $_.Key)
            }
        }

        $sensitiveGroupsMembership = @(
            $memberOf.GetEnumerator() | Where-Object {
                $sensitiveGroups.ContainsKey($_.Value.objectSid)
            } | Select-Object -ExpandProperty Name
        )
        $return += [PSCustomObject][ordered]@{
            Test    = 'SensitiveGroupsMembership'
            Status  = $sensitiveGroupsMembership.Count -eq 0
            Details = $sensitiveGroupsMembership
        }

        Write-Verbose -Message $strings['DSA_TestDelegation']
        $sidsToCheck = @($account.ObjectSid.Value)
        $sidsToCheck += ($memberOf.GetEnumerator() | Where-Object {
                $sensitiveGroupsMembership -notcontains $_.Key }).Value.Value

        $filter = '(|(objectClass=domain)(objectClass=organizationalUnit)(objectClass=group))'
        $searcher = [adsisearcher]"LDAP://$($myDomain.ChosenDC)/$($myDomain.DistinguishedName)"
        $searcher.Filter = $filter
        $searcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree
        $delegatedObjects = $searcher.FindAll() | ForEach-Object {
            $de = $_.GetDirectoryEntry()
            $permissions = $de.PsBase.ObjectSecurity.GetAccessRules($true, $false, [System.Security.Principal.SecurityIdentifier])
            if ($permissions | Where-Object { ($_.AccessControlType -eq 'Allow') -and ($sidsToCheck -contains $_.IdentityReference.Value) }) {
                $de.distinguishedName.Value
            }
        }
        $return += [PSCustomObject][ordered]@{
            Test    = 'ExplicitDelegation'
            Status  = $delegatedObjects.Count -eq 0
            Details = @($delegatedObjects | Select-Object -Unique)
        }

        Write-Verbose -Message $strings['DSA_TestDeletedObjectsAccess']
        $msDSPrincipalNamesToCheck = @($memberOf.GetEnumerator() | ForEach-Object { $_.Value.'msDS-PrincipalName' })
        $msDSPrincipalNamesToCheck += $account.'msDS-PrincipalName'
        $return += Test-MDIDeletedObjectsContainerPermission -myDomain $myDomain -msDSPrincipalNamesToCheck $msDSPrincipalNamesToCheck -Detailed

        if ($account.ObjectClass -eq 'user') {
            Write-Verbose -Message $strings['DSA_TestManager']
            $filter = '(|(managedBy={0})(manager={0}))' -f ($account.DistinguishedName -replace '\s', '\20')
            $searcher = [adsisearcher]"LDAP://$($myDomain.ChosenDC)/$($myDomain.DistinguishedName)"
            $searcher.Filter = $filter
            $searcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree
            $managerOf = $searcher.FindAll()
            $return += [PSCustomObject][ordered]@{
                Test    = 'ManagerOf'
                Status  = $managerOf.Count -eq 0
                Details = @($managerOf | ForEach-Object { $_.Properties['distinguishedname'] })
            }
        } else {
            Write-Warning $strings['DSA_SkipGmsaTests']
            Write-Verbose -Message $strings['DSA_TestPasswordRetrieval']
            try {
                if ($env:USERDNSDOMAIN -eq $myDomain.DNSRoot) {
                    $pwdCheck = Test-ADServiceAccount -Identity $($account.samaccountname)
                    $return += [PSCustomObject][ordered]@{
                        Test    = 'PasswordRetrieval'
                        Status  = $pwdCheck
                        Details = $null
                    }
                } else {
                    Write-Warning $strings['DSA_CannotTestGMSAAccount']
                    throw
                }
            } catch {
                $return += [PSCustomObject][ordered]@{
                    Test    = 'PasswordRetrieval'
                    Status  = $false
                    Details = $null
                }
            }
        }

    }
    $overallStatus = ($return.Status -eq $false).Count -eq 0
    if (-not $Detailed) { $overallStatus }
    else { $return }
    Write-Verbose -Message (Get-MDIValidationMessage $overallStatus)
}

#endregion

#region Connectivity helper functions

function Test-MDISensorApiConnection {
    [CmdletBinding(DefaultParameterSetName = 'UseCurrentConfiguration')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'BypassConfiguration')]
        [switch] $BypassConfiguration,

        [Parameter(Mandatory = $true, ParameterSetName = 'BypassConfiguration')]
        [string] $SensorApiUrl,

        [Parameter(Mandatory = $true, ParameterSetName = 'BypassConfiguration')]
        [ValidateSet('Classic', 'Unified')]
        [string] $SensorType,

        [Parameter(Mandatory = $false, ParameterSetName = 'BypassConfiguration')]
        [string] $ProxyUrl,

        [Parameter(Mandatory = $false, ParameterSetName = 'BypassConfiguration')]
        [PSCredential] $ProxyCredential
    )

    $protocol = @{
        80  = 'http'
        443 = 'https'
    }

    if ($PSCmdlet.ParameterSetName -eq 'BypassConfiguration') {
        if ($SensorType -eq 'Classic') {
            $sensorApiPath = 'tri/sensor/api/ping'
        } else {
            $sensorApiPath = 'cnc/identity/api'
        }
        $params = @{ URI = '{0}/{1}' -f $SensorApiUrl, $sensorApiPath }
        if ($ProxyUrl) { $params.Add('Proxy', $ProxyUrl) }
        if ($ProxyCredential) { $params.Add('ProxyCredential', $ProxyCredential) }
    } else {
        $sensorProcesses = Get-MDISensorProcessInformation
        $sensorConfiguration = Get-MDISensorConfiguration
        if ($sensorProcesses.SenseIdentity -eq 'Running') {
            $sensorApiPath = 'cnc/identity/api'
            $URI = '{0}{1}' -f $sensorConfiguration.geoLocationUrl, $sensorApiPath
        } else {
            $sensorApiPath = 'tri/sensor/api/ping'
            $URI = '{0}://{1}' -f $protocol[$sensorConfiguration.WorkspaceApplicationSensorApiWebClientConfigurationServiceEndpoint.Port],
            $sensorConfiguration.WorkspaceApplicationSensorApiWebClientConfigurationServiceEndpoint.Address
        }
        if ([string]::IsNullOrEmpty($sensorConfiguration)) {
            Write-Error $strings['Sensor_ErrorReadingSensorConfiguration'] -ErrorAction Stop
        } else {
            $params = @{
                URI = $URI
            }
            $params.Add("UseBasicParsing", $true)
            if ($sensorConfiguration.SensorProxyConfiguration.IsProxyEnabled) {
                $params.Add('Proxy', $sensorConfiguration.SensorProxyConfiguration.Url)

                if ($sensorConfiguration.SensorProxyConfiguration.IsAuthenticationProxyEnabled) {
                    $decryptParams = @{
                        CertificateThumbprint = $sensorConfiguration.SensorProxyConfiguration.CertificateThumbprint
                        EncryptedString       = $sensorConfiguration.SensorProxyConfiguration.EncryptedUserPasswordData
                    }
                    $passwd = Get-MDIDecryptedPassword @decryptParams
                    $proxyCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @(
                        $sensorConfiguration.SensorProxyConfiguration.UserName,
                        ($passwd | ConvertTo-SecureString -AsPlainText -Force)
                    )
                    $params.Add('ProxyCredential', $proxyCredential)
                }
            }
        }
    }
    try {
        if ($params.URI -notmatch "$sensorApiPath`$") {
            $params.URI = '{0}/{1}' -f $params.URI, $sensorApiPath
        }
        $response = Invoke-WebRequest @params
        if ($params.URI -match "(\/tri\/sensor\/api\/ping)$") {
            (200 -eq $response.StatusCode)
        } else {
            throw
        }
    } catch {
        if (($params.URI -match "(\/cnc\/identity\/api)$") -and ($_.Exception.Response.StatusCode -eq 'InternalServerError')) {
            $true
        } else {
            Write-Verbose -Message $_.Exception.Message
            $false
        }
    }
}

#endregion

#region Post deployment configuration helper functions

function Use-MDIConfigName {
    param(
        [Parameter(Mandatory)] [string[]] $Configuration,
        [Parameter(Mandatory)] [string[]] $ActionItem
    )
    $ActionItem += 'All'
    @(Compare-Object -ReferenceObject $Configuration -DifferenceObject $ActionItem -ExcludeDifferent -IncludeEqual).Count -gt 0
}

function Get-MDIConfiguration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode,
        [Parameter(Mandatory = $true)] [ValidateSet('AdfsAuditing', 'AdRecycleBin', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs',
            'CAAuditing', 'ConfigurationContainerAuditing', 'DeletedObjectsContainerPermission', 'DomainObjectAuditing', 'EntraConnectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'RemoteSAM', 'All')] [string[]] $Configuration
    )
    DynamicParam {
        $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("GpoNamePrefix", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam3 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $true
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParamIdentity = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Identity", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam4 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Domain", [string], $paramAttributesCollect)
        if ($Mode -eq 'Domain') {
            $paramDictionary.Add("GpoNamePrefix", $dynParam1)
            $paramDictionary.Add("Domain", $dynParam4)
            $paramDictionary.Add("Server", $dynParam3)
        }
        if ([bool](($($Configuration -join ',')) -match 'DeletedObjectsContainerPermission|EntraConnectAuditing|RemoteSAM|All')) {
            $paramDictionary.Add("Identity", $dynParamIdentity)
        }
        return $paramDictionary
    }
    begin {
        foreach ($key in $PSBoundParameters.Keys) {
            if ($MyInvocation.MyCommand.Parameters.$key.isDynamic) {
                Set-Variable -Name $key -Value $PSBoundParameters.$key
            }
        }
    }
    process {
        if ($Mode -eq 'Domain') {
            $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
            if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
            $mdiParams = @{
                Detailed      = $true
                GpoNamePrefix = $GpoNamePrefix
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiParams.Add("Domain", $myDomain.DNSRoot) }
            if ($null -ne $myDomain) { $mdiParams.Add("myDomain", $myDomain) }
        }
        $results = @{}
        if (Use-MDIConfigName $Configuration 'AdfsAuditing') {
            $results.Add('AdfsAuditing', (Test-MDIAdfsAuditing -Detailed -myDomain $myDomain))
        }
        if (Use-MDIConfigName $Configuration 'AdRecycleBin') {
            $adRecycleBinParams = @{
                Detailed = $true
            }; if (-not [string]::IsNullOrEmpty($Server)) { $adRecycleBinParams.Add("Server", $Server) }
            if ($null -ne $myDomain) { $adRecycleBinParams.Add("myDomain", $myDomain) }
            $results.Add('AdRecycleBin', (Test-MDIAdRecycleBin @adRecycleBinParams))
        }
        if (Use-MDIConfigName $Configuration 'AdvancedAuditPolicyCAs') {
            if ($Mode -eq 'LocalMachine') {
                $results.Add('AdvancedAuditPolicyCAs', (Test-MDIAdvancedAuditPolicyCAs -Detailed))
            } else {
                $results.Add('AdvancedAuditPolicyCAs', (Test-MDIAdvancedAuditPolicyCAsGPO @mdiParams))
            }
        }
        if (Use-MDIConfigName $Configuration 'AdvancedAuditPolicyDCs') {
            if ($Mode -eq 'LocalMachine') {
                $results.Add('AdvancedAuditPolicyDCs', (Test-MDIAdvancedAuditPolicyDCs -Detailed))
            } else {
                $results.Add('AdvancedAuditPolicyDCs', (Test-MDIAdvancedAuditPolicyDCsGPO @mdiParams))
            }
        }
        if (Use-MDIConfigName $Configuration 'CAAuditing') {
            if ($Mode -eq 'LocalMachine') {
                $results.Add('CAAuditing', (Test-MDICAAuditing -Detailed))
            } else {
                $results.Add('CAAuditing', (Test-MDICAAuditingGPO @mdiParams))
            }
        }
        if (Use-MDIConfigName $Configuration 'ConfigurationContainerAuditing') {
            $results.Add('ConfigurationContainerAuditing', (Test-MDIConfigurationContainerAuditing -Detailed -myDomain $myDomain))
        }
        if (Use-MDIConfigName $Configuration 'DeletedObjectsContainerPermission') {
            $results.Add('DeletedObjectsContainerPermission', (Test-MDIDeletedObjectsContainerPermission -Detailed -myDomain $myDomain -Identity $Identity))
        }
        if (Use-MDIConfigName $Configuration 'DomainObjectAuditing') {
            $results.Add('DomainObjectAuditing', (Test-MDIDomainObjectAuditing -Detailed -myDomain $myDomain))
        }
        if (Use-MDIConfigName $Configuration 'EntraConnectAuditing') {
            if ($Mode -eq 'LocalMachine') {
                $results.Add('EntraConnectAuditing', (Test-MDIEntraConnectAuditing -Detailed -Identity $Identity))
            } else {
                $results.Add('EntraConnectAuditing', (Test-MDIEntraConnectAuditingGPO @mdiParams -Identity $Identity))
            }
        }
        if (Use-MDIConfigName $Configuration 'NTLMAuditing') {
            if ($Mode -eq 'LocalMachine') {
                $results.Add('NTLMAuditing', (Test-MDINTLMAuditing -Detailed))
            } else {
                $results.Add('NTLMAuditing', (Test-MDINTLMAuditingGPO @mdiParams))
            }
        }
        if (Use-MDIConfigName $Configuration 'ProcessorPerformance') {
            if ($Mode -eq 'LocalMachine') {
                $results.Add('ProcessorPerformance', (Test-MDIProcessorPerformance -Detailed))
            } else {
                $results.Add('ProcessorPerformance', (Test-MDIProcessorPerformanceGPO @mdiParams))
            }
        }
        if (Use-MDIConfigName $Configuration 'RemoteSAM') {
            if ($Mode -eq 'LocalMachine') {
                $results.Add('RemoteSAM', (Test-MDIRemoteSAM -Detailed -Identity $Identity))
            } else {
                $results.Add('RemoteSAM', (Test-MDIRemoteSAMGPO @mdiParams -Identity $Identity))
            }
        }
        if ($Configuration -contains 'All') {
            $Configuration += $results.GetEnumerator() | Select-Object -ExpandProperty Name
        }
        $Configuration | Select-Object -Unique | Sort-Object -Property Configuration | Where-Object { $_ -ne 'All' } | ForEach-Object {
            [PSCustomObject]@{
                Configuration = $_
                Mode          = $Mode
                Status        = $results[$_].Status
                Details       = $results[$_].Details
            }
        }
    }

}

function Test-MDIConfiguration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode,
        [Parameter(Mandatory = $true)] [ValidateSet('AdfsAuditing', 'AdRecycleBin', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs',
            'CAAuditing', 'ConfigurationContainerAuditing', 'DeletedObjectsContainerPermission', 'DomainObjectAuditing', 'EntraConnectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'RemoteSAM', 'All')] [string[]] $Configuration
    )
    DynamicParam {
        $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("GpoNamePrefix", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam3 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $true
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParamIdentity = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Identity", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam4 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Domain", [string], $paramAttributesCollect)
        if ($Mode -eq 'Domain') {
            $paramDictionary.Add("GpoNamePrefix", $dynParam1)
            $paramDictionary.Add("Domain", $dynParam4)
            $paramDictionary.Add("Server", $dynParam3)
        }
        if ([bool](($($Configuration -join ',')) -match 'DeletedObjectsContainerPermission|EntraConnectAuditing|RemoteSAM|All')) {
            $paramDictionary.Add("Identity", $dynParamIdentity)
        }
        return $paramDictionary
    }
    begin {
        foreach ($key in $PSBoundParameters.Keys) {
            if ($MyInvocation.MyCommand.Parameters.$key.isDynamic) {
                Set-Variable -Name $key -Value $PSBoundParameters.$key
            }
        }
    }
    process {
        $results = if ($Mode -eq 'Domain') {
            $mdiParams = @{
                Configuration = $Configuration
                Mode          = "Domain"
                GpoNamePrefix = $GpoNamePrefix
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($Identity)) { $mdiParams.Add("Identity", $Identity) }
            if (-not [string]::IsNullOrEmpty($Domain)) { $mdiParams.Add("Domain", $Domain) }
            Get-MDIConfiguration @mdiParams
        } else {
            Get-MDIConfiguration -Configuration $Configuration -Mode LocalMachine
        }

        if ('All' -eq $Configuration) {
            @($results | Where-Object { $_.Status -eq $false }).Count -eq 0
        } else {
            $results.Status
        }
    }
}

function Set-MDIConfiguration {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode,
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [ValidateSet('AdfsAuditing', 'AdRecycleBin', 'AdvancedAuditPolicyCAs', 'AdvancedAuditPolicyDCs',
            'CAAuditing', 'ConfigurationContainerAuditing', 'DeletedObjectsContainerPermission', 'DomainObjectAuditing', 'EntraConnectAuditing', 'NTLMAuditing', 'ProcessorPerformance', 'RemoteSAM', 'All')] [string[]] $Configuration,
        [Parameter(Mandatory = $false)] [switch] $Force
    )
    DynamicParam {
        $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("GpoNamePrefix", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam2 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("CreateGpoDisabled", [switch], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam3 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("SkipGpoLink", [switch], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam5 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $true
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParamIdentity = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Identity", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam4 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Domain", [string], $paramAttributesCollect)
        if ($Mode -eq 'Domain') {
            $paramDictionary.Add("GpoNamePrefix", $dynParam1)
            $paramDictionary.Add("CreateGpoDisabled", $dynParam2)
            $paramDictionary.Add("SkipGpoLink", $dynParam3)
            $paramDictionary.Add("Domain", $dynParam4)
            $paramDictionary.Add("Server", $dynParam5)
        }
        if ([bool](($($Configuration -join ',')) -match 'DeletedObjectsContainerPermission|EntraConnectAuditing|RemoteSAM|All')) {
            $paramDictionary.Add("Identity", $dynParamIdentity)
        }
        return $paramDictionary
    }
    begin {
        foreach ($key in $PSBoundParameters.Keys) {
            if ($MyInvocation.MyCommand.Parameters.$key.isDynamic) {
                Set-Variable -Name $key -Value $PSBoundParameters.$key
            }
        }
    }
    Process {
        if ($Mode -eq 'Domain') {
            $myDomain = Get-MDIDomain -myDomain $myDomain -Server $Server -Domain $Domain
            if ($null -ne $myDomain) { $Server = $myDomain.ChosenDC }
        }
        foreach ($config in $Configuration) {
            $mdiParams = @{
                CreateGpoDisabled = $CreateGpoDisabled
                SkipGpoLink       = $SkipGpoLink
                GpoNamePrefix     = $GpoNamePrefix
            }
            if (-not [string]::IsNullOrEmpty($Server)) { $mdiParams.Add("Server", $Server) }
            if (-not [string]::IsNullOrEmpty($myDomain.DNSRoot)) { $mdiParams.Add("Domain", $myDomain.DNSRoot) }
            if ($null -ne $myDomain) { $mdiParams.Add("myDomain", $myDomain) }
            $adRecycleBinParams = @{

            }; if (-not [string]::IsNullOrEmpty($Server)) { $adRecycleBinParams.Add("Server", $Server) }
            Write-Verbose ($strings['Configuration_Set'] -f $config)
            if (Use-MDIConfigName $config 'AdfsAuditing') { Set-MDIAdfsAuditing -myDomain $myDomain }
            if (Use-MDIConfigName $config 'AdRecycleBin') {
                if (-not (Test-MDIAdRecycleBin @adRecycleBinParams -myDomain $myDomain)) {
                    Set-MDIAdRecycleBin
                }
            }
            if (Use-MDIConfigName $config 'AdvancedAuditPolicyCAs') {
                if ($Mode -eq 'LocalMachine') {
                    Set-MDIAdvancedAuditPolicyCAs
                } else {
                    Set-MDIAdvancedAuditPolicyCAsGPO @mdiParams
                }
            }

            if (Use-MDIConfigName $config 'AdvancedAuditPolicyDCs') {
                if ($Mode -eq 'LocalMachine') {
                    Set-MDIAdvancedAuditPolicyDCs
                } else {
                    Set-MDIAdvancedAuditPolicyDCsGPO @mdiParams
                }
            }

            if (Use-MDIConfigName $config 'CAAuditing') {
                if ($Mode -eq 'LocalMachine') {
                    Set-MDICAAuditing
                } else {
                    Set-MDICAAuditingGPO @mdiParams
                }
            }

            if (Use-MDIConfigName $config 'ConfigurationContainerAuditing') { Set-MDIConfigurationContainerAuditing -Force:$Force -myDomain $myDomain }

            if (Use-MDIConfigName $config 'DeletedObjectsContainerPermission') { Set-MDIDeletedObjectsContainerPermission -myDomain $myDomain -Identity $Identity }

            if (Use-MDIConfigName $config 'DomainObjectAuditing') { Set-MDIDomainObjectAuditing -myDomain $myDomain }

            if (Use-MDIConfigName $config 'EntraConnectAuditing') {
                if ($Mode -eq 'LocalMachine') {
                    Set-MDIEntraConnectAuditing -Identity $Identity
                } else {
                    Set-MDIEntraConnectAuditingGPO @mdiParams -Identity $Identity
                }
            }

            if (Use-MDIConfigName $config 'NTLMAuditing') {
                if ($Mode -eq 'LocalMachine') {
                    Set-MDINTLMAuditing
                } else {
                    Set-MDINTLMAuditingGPO @mdiParams
                }
            }

            if (Use-MDIConfigName $config 'ProcessorPerformance') {
                if ($Mode -eq 'LocalMachine') {
                    Set-MDIProcessorPerformance
                } else {
                    Set-MDIProcessorPerformanceGPO @mdiParams
                }
            }

            if (Use-MDIConfigName $config 'RemoteSAM') {
                Write-Warning -Message $strings['RemoteSAM_NTLMWarn']
                if ($Mode -eq 'LocalMachine') {
                    Set-MDIRemoteSAM -Identity $Identity
                } else {
                    Set-MDIRemoteSAMGPO @mdiParams -Identity $Identity
                }
            }
        }
    }
}

function New-MDIConfigurationReport {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $Path,
        [Parameter(Mandatory = $false)] [ValidateSet('Domain', 'LocalMachine')] [string] $Mode = 'Domain',
        [switch] $OpenHtmlReport
    )
    DynamicParam {
        $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("GpoNamePrefix", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam3 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $true
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParamIdentity = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Identity", [string], $paramAttributesCollect)
        $paramAttributes = New-Object -Type System.Management.Automation.ParameterAttribute
        $paramAttributes.Mandatory = $false
        $paramAttributesCollect = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
        $paramAttributesCollect.Add($paramAttributes)
        $dynParam4 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Domain", [string], $paramAttributesCollect)
        if ($Mode -eq 'Domain') {
            $paramDictionary.Add("Identity", $dynParamIdentity)
            $paramDictionary.Add("Domain", $dynParam4)
            $paramDictionary.Add("Server", $dynParam3)
        }
        return $paramDictionary
    }
    begin {
        foreach ($key in $PSBoundParameters.Keys) {
            if ($MyInvocation.MyCommand.Parameters.$key.isDynamic) {
                Set-Variable -Name $key -Value $PSBoundParameters.$key
            }
        }
    }
    process {
        if (-not(Test-Path -Path $Path)) { [void](New-Item -Path $Path -ItemType Directory -Force -ErrorAction SilentlyContinue) }
        $reportTarget = if ($Mode -eq 'Domain') { if ([string]::IsNullOrEmpty($Domain)) { $env:USERDNSDOMAIN } } else { '{0}.{1}' -f $env:COMPUTERNAME, $env:USERDNSDOMAIN }
        $getMdiConfigParams = @{
            Configuration = "All"
            Mode          = $Mode
            GpoNamePrefix = $GpoNamePrefix
        }
        if (-not [string]::IsNullOrEmpty($Server)) { $getMdiConfigParams.Add("Server", $Server) }
        if (-not [string]::IsNullOrEmpty($Identity)) { $getMdiConfigParams.Add("Identity", $Identity) }
        if (-not [string]::IsNullOrEmpty($Domain)) { $getMdiConfigParams.Add("Domain", $Domain) }
        $configurations = Get-MDIConfiguration @getMdiConfigParams

        $jsonReportFile = Resolve-MDIPath -Path (
            Join-Path -Path $Path -ChildPath ('MDI-configuration-report-{0}.json' -f $reportTarget))
        $htmlReportFile = Resolve-MDIPath -Path (
            Join-Path -Path $Path -ChildPath ('MDI-configuration-report-{0}.html' -f $reportTarget))

        $css = @'
<style>
body { font-family: Arial, sans-serif, 'Open Sans'; }
table { border-collapse: collapse; }
td, th { border: 1px solid #aeb0b5; padding: 5px; text-align: left; vertical-align: middle; }
tr:nth-child(even) { background-color: #f2f2f2; }
th { padding: 8px; text-align: left; background-color: #e4e2e0; color: #212121; }
.red {background-color: #cd2026; color: #ffffff; }
.green {background-color: #4aa564; color: #212121; }
ul { list-style: none; padding-left: 0.5em;}
</style>
'@

        $colors = @{$true = 'green'; $false = 'red' }
        $status = @{$true = $strings['DomainReport_StatusPass']; $false = $strings['DomainReport_StatusFail'] }
        $tblHeader = '<tr><th>{0}</th><th>{1}</th><th>{2}</th><th>{3}</th></tr>' -f $strings['DomainReport_Configuration'],
        $strings['DomainReport_Status'], $strings['DomainReport_GpoHeader'], $strings['DomainReport_CommandToFix']
        $tblContent = @($configurations | Sort-Object Configuration | ForEach-Object {
                $gpoPrefixIfUsed = if ([string]::IsNullOrEmpty($GpoNamePrefix)) { '' } else { " -GpoNamePrefix $GpoNamePrefix" }
                if ([string]::IsNullOrEmpty($_.Details.DisplayName)) {
                    try {
                        $gpoNameTemp = $_.Details.ToString()
                    } catch {
                        $gpoNameTemp = $null
                    }
                } else {
                    try {
                        if (-not [string]::IsNullOrEmpty($_.Details.DisplayName)) {
                            $gpoNameTemp = $_.Details.DisplayName
                        }
                    } catch {
                        $gpoNameTemp = $null
                    }
                }
                try {
                    $gpoNameTemp = $gpoNameTemp.replace("'", '')
                } catch {
                    $gpoNameTemp = $gpoNameTemp
                }
                if ($gpoPrefixIfUsed) {
                    $matchString = "^($GpoNamePrefix \- )"
                } else {
                    $matchString = "^(Microsoft Defender for Identity \- )"
                }
                $gpoNameTested = if ($gpoNameTemp -match $matchString) { try { $gpoNameTemp.replace(" - GPO not found", '') } catch { $null } } else { $null }
                "<tr><td><a href='https://aka.ms/mdi/{0}'>{0}</a></td><td class='{1}'>{2}</td><td>{5}</td><td>{3}{0}{4}</td></tr>" -f `
                    $_.Configuration, $colors[$_.Status], $status[$_.Status], 'Set-MDIConfiguration -Mode Domain -Configuration ', $gpoPrefixIfUsed, $(if ([string]::IsNullOrEmpty($gpoNameTested)) { $strings['DomainReport_GpoNotApplicable'] } else { $gpoNameTested })
            }) -join [environment]::NewLine

        $htmlContent = @'
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>{0}</head><body>
<h2>{1}</h2>
{2}
<br/><br/>
<table>
{3}
{4}
</table>
<br/>
<hr>
<ul>
<li>{5}</li>
</ul>
<hr>
<br/>{6} <a href='{7}'>{7}</a><br/>
<br/>{8}
'@
 -f $css, ($strings['DomainReport_Title'] -f $reportTarget), $strings['DomainReport_Subtitle'],
        $tblHeader, $tblContent, $strings['DomainReport_NoteMessage'], $strings['DomainReport_DetailsMessage'],
        $jsonReportFile, ($strings['DomainReport_CreatedBy'] -f "<a href='https://aka.ms/mdi/psmodule'>DefenderForIdentity</a>")

        Write-Verbose ('{0}: {1}' -f $strings['DomainReport_JsonMessage'], $jsonReportFile)
        $configurations | ConvertTo-Json -Depth 5 | Format-Json | Out-File -FilePath $jsonReportFile -Force -Encoding utf8

        Write-Verbose ('{0}: {1}' -f $strings['DomainReport_HtmlMessage'], $htmlReportFile)
        $htmlContent | Out-File -FilePath $htmlReportFile -Force -Encoding utf8

        $reportPath = (Resolve-Path -Path $htmlReportFile).Path
        if ($OpenHtmlReport) { Invoke-Item -Path $reportPath }
    }

}

#endregion
# SIG # Begin signature block
# MIIoQwYJKoZIhvcNAQcCoIIoNDCCKDACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA9z4GX2DK1oR4z
# axKkBhSeHw6zRyfsC/R3QhllNAh9gaCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0
# Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz
# NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo
# DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3
# a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF
# HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy
# 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC
# Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj
# L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp
# h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3
# cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X
# dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL
# E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi
# u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1
# sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq
# 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb
# DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/
# V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGiMwghofAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIL809sTVTtXUXqrRy/IPWjYa
# eOkgE7QOBp4rj73pV2AsMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEArmNAAeTS2H6R/9GfFBDk/CHdJnvJPji0HfSfkF1K59ocXP0yitIp7iVW
# RVAfAXgkSoxkg4vRYGxtXNSVrXXJiz0CP4kW64j79J9Ty7rKLZGtzhdlAbVHdiDO
# 4rZkU2FKzDMh2BhshFb9ZfkLYYxKbsvvjx+D7CftNEUJr8DxwJ3qAIyS/zyNIZ/m
# lcoCkfMh7g/ZOcZ80kw9oPkcdG2f4/30fw30ILq/dna/sm+RmaklY+ElDZ5XDogI
# PpkUCnRyqWeWbhZynpNOgUeW+XJ8eRmP4cL2gaeRhStus/wr7fqPqH5FJdeyuaL3
# qyoTWlIcZSAUX4mCi8K8dNQwc7PUGqGCF60wghepBgorBgEEAYI3AwMBMYIXmTCC
# F5UGCSqGSIb3DQEHAqCCF4YwgheCAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq
# hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCD+k38dYeeosOqWJCcZNtrIQ8lpg54SIG3UxMm0zXFBAwIGaC3xZOXS
# GBMyMDI1MDUyOTA4MjIxNS41NDJaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# TjoyQTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaCCEfswggcoMIIFEKADAgECAhMzAAAB+R9njXWrpPGxAAEAAAH5MA0G
# CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0
# MDcyNTE4MzEwOVoXDTI1MTAyMjE4MzEwOVowgdMxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w
# ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjJBMUEt
# MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtD1MH3yAHWHNVslC+CBT
# j/Mpd55LDPtQrhN7WeqFhReC9xKXSjobW1ZHzHU8V2BOJUiYg7fDJ2AxGVGyovUt
# gGZg2+GauFKk3ZjjsLSsqehYIsUQrgX+r/VATaW8/ONWy6lOyGZwZpxfV2EX4qAh
# 6mb2hadAuvdbRl1QK1tfBlR3fdeCBQG+ybz9JFZ45LN2ps8Nc1xr41N8Qi3KVJLY
# X0ibEbAkksR4bbszCzvY+vdSrjWyKAjR6YgYhaBaDxE2KDJ2sQRFFF/egCxKgogd
# F3VIJoCE/Wuy9MuEgypea1Hei7lFGvdLQZH5Jo2QR5uN8hiMc8Z47RRJuIWCOeyI
# J1YnRiiibpUZ72+wpv8LTov0yH6C5HR/D8+AT4vqtP57ITXsD9DPOob8tjtsefPc
# QJebUNiqyfyTL5j5/J+2d+GPCcXEYoeWZ+nrsZSfrd5DHM4ovCmD3lifgYnzjOry
# 4ghQT/cvmdHwFr6yJGphW/HG8GQd+cB4w7wGpOhHVJby44kGVK8MzY9s32Dy1THn
# Jg8p7y1sEGz/A1y84Zt6gIsITYaccHhBKp4cOVNrfoRVUx2G/0Tr7Dk3fpCU8u+5
# olqPPwKgZs57jl+lOrRVsX1AYEmAnyCyGrqRAzpGXyk1HvNIBpSNNuTBQk7FBvu+
# Ypi6A7S2V2Tj6lzYWVBvuGECAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSJ7aO6nJXJ
# I9eijzS5QkR2RlngADAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf
# BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww
# bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El
# MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAZiAJgFbkf7jf
# hx/mmZlnGZrpae+HGpxWxs8I79vUb8GQou50M1ns7iwG2CcdoXaq7VgpVkNf1uvI
# hrGYpKCBXQ+SaJ2O0BvwuJR7UsgTaKN0j/yf3fpHD0ktH+EkEuGXs9DBLyt71iut
# Vkwow9iQmSk4oIK8S8ArNGpSOzeuu9TdJjBjsasmuJ+2q5TjmrgEKyPe3TApAio8
# cdw/b1cBAmjtI7tpNYV5PyRI3K1NhuDgfEj5kynGF/uizP1NuHSxF/V1ks/2tCEo
# riicM4k1PJTTA0TCjNbkpmBcsAMlxTzBnWsqnBCt9d+Ud9Va3Iw9Bs4ccrkgBjLt
# g3vYGYar615ofYtU+dup+LuU0d2wBDEG1nhSWHaO+u2y6Si3AaNINt/pOMKU6l4A
# W0uDWUH39OHH3EqFHtTssZXaDOjtyRgbqMGmkf8KI3qIVBZJ2XQpnhEuRbh+Agpm
# Rn/a410Dk7VtPg2uC422WLC8H8IVk/FeoiSS4vFodhncFetJ0ZK36wxAa3FiPgBe
# bRWyVtZ763qDDzxDb0mB6HL9HEfTbN+4oHCkZa1HKl8B0s8RiFBMf/W7+O7EPZ+w
# MH8wdkjZ7SbsddtdRgRARqR8IFPWurQ+sn7ftEifaojzuCEahSAcq86yjwQeTPN9
# YG9b34RTurnkpD+wPGTB1WccMpsLlM0wggdxMIIFWaADAgECAhMzAAAAFcXna54C
# m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp
# Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy
# MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51
# yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY
# 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9
# cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN
# 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua
# Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74
# kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2
# K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5
# TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk
# i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q
# BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri
# Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC
# BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y
# eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA
# YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw
# MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp
# b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm
# ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM
# 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW
# OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4
# FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw
# xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX
# fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX
# VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC
# onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU
# 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG
# ahC0HVUzWLOhcGbyoYIDVjCCAj4CAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# TjoyQTFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaIjCgEBMAcGBSsOAwIaAxUAqs5WjWO7zVAKmIcdwhqgZvyp6UaggYMw
# gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF
# AAIFAOviUnkwIhgPMjAyNTA1MjkwMzI2NDlaGA8yMDI1MDUzMDAzMjY0OVowdDA6
# BgorBgEEAYRZCgQBMSwwKjAKAgUA6+JSeQIBADAHAgEAAgIVNjAHAgEAAgISNjAK
# AgUA6+Oj+QIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQAjZ1c6wtOT8SdR
# DX1JkBifGB5AO2HkUwZTQSzl95CchktxEtVA4iNmBQ2Gtec68DJ6S13XbnHm/8bh
# YiozcizqfISNJ7YGw6zimjp9ZQSkML4SMaQoYLxWX/sYwefuc1SLUuGQlMbucpI4
# s1ik74QZ/ax4oovY73Z2T3WHQaq+rPM5qTy95AkedY9hW8nPE3A7MezNshYgxbVD
# R7h1cbjg+gWYVyNsHySrCgf9PEAlBZjbnG2ix7Du+v9i7ioBpgyF0NZh4XUsVhZz
# k7scLA9l4YQpGg0atHCcch+DdR2NRmsM3J3gHYtdDCfzoH5GNPpPJIlaIPD6QaXN
# IL3mOspcMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAH5H2eNdauk8bEAAQAAAfkwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqG
# SIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgmSwepzvctJ25
# AzQi7ftF4OjCoN0ObZYDG1jL9BdnnLMwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHk
# MIG9BCA5I4zIHvCN+2T66RUOLCZrUEVdoKlKl8VeCO5SbGLYEDCBmDCBgKR+MHwx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB+R9njXWrpPGxAAEAAAH5
# MCIEIORTxGPdBgVq1cCsT8DrUAi6oFqlD2CPUB+/9ox/+gr+MA0GCSqGSIb3DQEB
# CwUABIICAD6fGwvBE6hMrbjnc2tiEtpHo/mXVcxP4O19SvXanAb2NBr37UQznQqz
# Qvp+KOiKlsRKi829Uo0tcQrQlkOC1pjqpmxDS5DWu3oLZLIUgAMHYamGAxJaui3D
# 4YufDAh6FzdP81QVpFPgzLQWVFlziKny1hhdRpABf4r4jpzQk3WJ7m0zU16uEHk2
# QXEql/FLbSqhjXOZhdhd9gdT1XA5CnncdQY54sqjHz3eqKJgkRCnJ1LS2fHb9U6J
# nnQfscMxBjhK5CQrulCVXKl+hJS3HjsSYOFMTf25t+VaOzcJaRunP+jhWJlmJMRm
# WDw/n/zFOCANehIVrIZalNcvgAY+yR7RkcWA6k2rp4lW3M1yYVvGhgOVyIXtL1cE
# /3eUrZWNsoKZfJEs3duUJSm8CltFEYCMgpJ533iITn+xtnInm1qU1LQbCfP2kAxX
# 2e4rYJ3pgvuhIeBWaTKwmxQz7Q3o2Swzz4G7f8NxzxkajLCEcRC5lbrSzOB7WN33
# PvlJSQY4BOMGDanobTyzNE9TEvCvaRQ7gS4igOf+6Vy3GJXnlIb6m/KcP0Qt0ib8
# 53fcs8ntv0uYs2fzwbVHoLy2qLuwjdwmV8bhTVomJU7u2Ejwz4lUayxxSUrNngwz
# g4sxvdJB5R7VqtkHhlZY3uhXu5H+YOHEfhK925G0e/hzaUIcQFlF
# SIG # End signature block