Common.ps1

#requires -Version 2.0

function OpenPolicyFile
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $Path
    )

    $policyFile = New-Object TJX.PolFileEditor.PolFile
    $policyFile.FileName = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path)

    if (Test-Path -LiteralPath $policyFile.FileName)
    {
        try
        {
            $policyFile.LoadFile()
        }
        catch
        {
            $errorRecord = $_
            $message = "Error loading policy file at path '$Path': $($errorRecord.Exception.Message)"
            $exception = New-Object System.Exception($message, $errorRecord.Exception)
            throw $exception
        }
    }

    return $policyFile
}

function PolEntryToPsObject
{
    param (
        [TJX.PolFileEditor.PolEntry] $PolEntry
    )

    $type = PolEntryTypeToRegistryValueKind $PolEntry.Type
    $data = GetEntryData -Entry $PolEntry -Type $type

    return New-Object psobject -Property @{
        Key       = $PolEntry.KeyName
        ValueName = $PolEntry.ValueName
        Type      = $type
        Data      = $data
    }
}

function GetEntryData
{
    param (
        [TJX.PolFileEditor.PolEntry] $Entry,
        [Microsoft.Win32.RegistryValueKind] $Type
    )

    switch ($type)
    {
        ([Microsoft.Win32.RegistryValueKind]::Binary)
        {
            return $Entry.BinaryValue
        }

        ([Microsoft.Win32.RegistryValueKind]::DWord)
        {
            return $Entry.DWORDValue
        }

        ([Microsoft.Win32.RegistryValueKind]::ExpandString)
        {
            return $Entry.StringValue
        }

        ([Microsoft.Win32.RegistryValueKind]::MultiString)
        {
            return $Entry.MultiStringValue
        }

        ([Microsoft.Win32.RegistryValueKind]::QWord)
        {
            return $Entry.QWORDValue
        }

        ([Microsoft.Win32.RegistryValueKind]::String)
        {
            return $Entry.StringValue
        }
    }

}

function SavePolicyFile
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true)]
        [TJX.PolFileEditor.PolFile] $PolicyFile,

        [switch] $UpdateGptIni
    )

    if ($PSCmdlet.ShouldProcess($PolicyFile.FileName, 'Save new settings'))
    {
        $parentPath = Split-Path $PolicyFile.FileName -Parent
        if (-not (Test-Path -LiteralPath $parentPath -PathType Container))
        {
            try
            {
                $null = New-Item -Path $parentPath -ItemType Directory -ErrorAction Stop -Confirm:$false -WhatIf:$false
            }
            catch
            {
                $errorRecord = $_
                $message = "Error creating parent folder of path '$Path': $($errorRecord.Exception.Message)"
                $exception = New-Object System.Exception($message, $errorRecord.Exception)
                throw $exception
            }
        }

        try
        {
            $PolicyFile.SaveFile()
        }
        catch
        {
            $errorRecord = $_
            $message = "Error saving policy file to path '$($PolicyFile.FileName)': $($errorRecord.Exception.Message)"
            $exception = New-Object System.Exception($message, $errorRecord.Exception)
            throw $exception
        }
    }

    if ($UpdateGptIni)
    {
        if ($policyFile.FileName -match '^(.*)\\+([^\\]+)\\+[^\\]+$' -and
            $Matches[2] -eq 'User' -or $Matches[2] -eq 'Machine')
        {
            $iniPath = Join-Path $Matches[1] GPT.ini

            if (Test-Path -LiteralPath $iniPath -PathType Leaf)
            {
                if ($PSCmdlet.ShouldProcess($iniPath, 'Increment version number in INI file'))
                {
                    IncrementGptIniVersion -Path $iniPath -PolicyType $Matches[2] -Confirm:$false -WhatIf:$false
                }
            }
            else
            {
                if ($PSCmdlet.ShouldProcess($iniPath, 'Create new gpt.ini file'))
                {
                    NewGptIni -Path $iniPath -PolicyType $Matches[2]
                }
            }
        }
    }
}

function NewGptIni
{
    param (
        [string] $Path,
        [string[]] $PolicyType
    )

    $parent = Split-Path $Path -Parent

    if (-not (Test-Path $parent -PathType Container))
    {
        $null = New-Item -Path $parent -ItemType Directory -ErrorAction Stop
    }

    $version = GetNewVersionNumber -Version 0 -PolicyType $PolicyType

    Set-Content -Path $Path -Encoding Ascii -Value @"
[General]
gPCMachineExtensionNames=[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]
Version=$version
gPCUserExtensionNames=[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]
"@

}

function IncrementGptIniVersion
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [string] $Path,
        [string[]] $PolicyType
    )

    $foundVersionLine = $false
    $section = ''

    $newContents = @(
        foreach ($line in Get-Content $Path)
        {
            # This might not be the most unreadable regex ever, but it's trying hard to be!
            # It's looking for section lines: [SectionName]
            if ($line -match '^\s*\[([^\]]+)\]\s*$')
            {
                if ($section -eq 'General')
                {
                    if (-not $foundVersionLine)
                    {
                        $foundVersionLine = $true
                        $newVersion = GetNewVersionNumber -Version 0 -PolicyType $PolicyType

                        "Version=$newVersion"
                    }

                    if (-not $foundMachineExtensionLine)
                    {
                        $foundMachineExtensionLine = $true
                        'gPCMachineExtensionNames=[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]'
                    }

                    if (-not $foundUserExtensionLine)
                    {
                        $foundUserExtensionLine = $true
                        'gPCUserExtensionNames=[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F73-3407-48AE-BA88-E8213C6761F1}]'
                    }
                }

                $section = $matches[1]
            }
            elseif ($section -eq 'General' -and
                    $line -match '^\s*Version\s*=\s*(\d+)\s*$' -and
                    $null -ne ($version = $matches[1] -as [uint32]))
            {
                $foundVersionLine = $true
                $newVersion = GetNewVersionNumber -Version $version -PolicyType $PolicyType
                $line = "Version=$newVersion"
            }
            elseif ($section -eq 'General' -and $line -match '^\s*gPC(Machine|User)ExtensionNames\s*=')
            {
                if ($matches[1] -eq 'Machine')
                {
                    $foundMachineExtensionLine = $true
                }
                else
                {
                    $foundUserExtensionLine = $true
                }

                $line = EnsureAdminTemplateCseGuidsArePresent $line
            }

            $line
        }

        if ($section -eq 'General')
        {
            if (-not $foundVersionLine)
            {
                $foundVersionLine = $true
                $newVersion = GetNewVersionNumber -Version 0 -PolicyType $PolicyType

                "Version=$newVersion"
            }

            if (-not $foundMachineExtensionLine)
            {
                $foundMachineExtensionLine = $true
                'gPCMachineExtensionNames=[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]'
            }

            if (-not $foundUserExtensionLine)
            {
                $foundUserExtensionLine = $true
                'gPCUserExtensionNames=[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F73-3407-48AE-BA88-E8213C6761F1}]'
            }
        }
    )

    if ($PSCmdlet.ShouldProcess($Path, 'Increment Version number'))
    {
        Set-Content -Path $Path -Value $newContents -Encoding Ascii -Confirm:$false -WhatIf:$false
    }
}

function EnsureAdminTemplateCseGuidsArePresent
{
    param ([string] $Line)

    # These lines contain lists of GUIDs in "registry" format (with the curly braces), separated by nothing, wrapped in a pair of square brackets. Ex:
    # gPCMachineExtensionNames=[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F72-3407-48AE-BA88-E8213C6761F1}]

    # Per Darren Mar-Elia, these GUIDs must be in alphabetical order, or GP processing will have problems.

    if ($Line -notmatch '\s*(gPC(?:Machine|User)ExtensionNames)\s*=\s*\[([^\]]*)\]\s*$')
    {
        throw "Malformed gpt.ini line: $Line"
    }

    $valueName = $matches[1]
    $guidStrings = @($matches[2] -split '(?<=})(?={)')

    $guidList = @(
        $guidStrings
        '{35378EAC-683F-11D2-A89A-00C04FBBCFA2}'
        '{D02B1F72-3407-48AE-BA88-E8213C6761F1}'
    )

    $newGuidString = ($guidList | Sort-Object -Unique) -join ''

    return "$valueName=[$newGuidString]"
}

function GetNewVersionNumber
{
    param (
        [UInt32] $Version,
        [string[]] $PolicyType
    )

    # User version is the high 16 bits, Machine version is the low 16 bits.
    # Reference: http://blogs.technet.com/b/grouppolicy/archive/2007/12/14/understanding-the-gpo-version-number.aspx

    $pair = UInt32ToUInt16Pair -UInt32 $version

    if ($PolicyType -contains 'User')
    {
        $pair.HighPart++
    }

    if ($PolicyType -contains 'Machine')
    {
        $pair.LowPart++
    }

    return UInt16PairToUInt32 -UInt16Pair $pair
}

function UInt32ToUInt16Pair
{
    param ([UInt32] $UInt32)

    # Deliberately avoiding bitwise shift operators here, for PowerShell v2 compatibility.

    $lowPart  = $UInt32 -band 0xFFFF
    $highPart = ($UInt32 - $lowPart) / 0x10000

    return New-Object psobject -Property @{
        LowPart  = [UInt16] $lowPart
        HighPart = [UInt16] $highPart
    }
}

function UInt16PairToUInt32
{
    param ([object] $UInt16Pair)

    # Deliberately avoiding bitwise shift operators here, for PowerShell v2 compatibility.

    return ([UInt32] $UInt16Pair.HighPart) * 0x10000 + $UInt16Pair.LowPart
}

function PolEntryTypeToRegistryValueKind
{
    param ([TJX.PolFileEditor.PolEntryType] $PolEntryType)

    switch ($PolEntryType)
    {
        ([TJX.PolFileEditor.PolEntryType]::REG_NONE)
        {
            return [Microsoft.Win32.RegistryValueKind]::None
        }

        ([TJX.PolFileEditor.PolEntryType]::REG_DWORD)
        {
            return [Microsoft.Win32.RegistryValueKind]::DWord
        }

        ([TJX.PolFileEditor.PolEntryType]::REG_DWORD_BIG_ENDIAN)
        {
            return [Microsoft.Win32.RegistryValueKind]::DWord
        }

        ([TJX.PolFileEditor.PolEntryType]::REG_BINARY)
        {
            return [Microsoft.Win32.RegistryValueKind]::Binary
        }

        ([TJX.PolFileEditor.PolEntryType]::REG_EXPAND_SZ)
        {
            return [Microsoft.Win32.RegistryValueKind]::ExpandString
        }

        ([TJX.PolFileEditor.PolEntryType]::REG_MULTI_SZ)
        {
            return [Microsoft.Win32.RegistryValueKind]::MultiString
        }

        ([TJX.PolFileEditor.PolEntryType]::REG_QWORD)
        {
            return [Microsoft.Win32.RegistryValueKind]::QWord
        }

        ([TJX.PolFileEditor.PolEntryType]::REG_SZ)
        {
            return [Microsoft.Win32.RegistryValueKind]::String
        }
    }
}

function GetPolFilePath
{
    param (
        [Parameter(Mandatory = $true, ParameterSetName = 'PolicyType')]
        [string] $PolicyType,

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

    if ($PolicyType)
    {
        switch ($PolicyType)
        {
            'Machine'
            {
                return Join-Path $env:SystemRoot System32\GroupPolicy\Machine\registry.pol
            }

            'User'
            {
                return Join-Path $env:SystemRoot System32\GroupPolicy\User\registry.pol
            }

            'Administrators'
            {
                # BUILTIN\Administrators well-known SID
                return Join-Path $env:SystemRoot System32\GroupPolicyUsers\S-1-5-32-544\User\registry.pol
            }

            'NonAdministrators'
            {
                # BUILTIN\Users well-known SID
                return Join-Path $env:SystemRoot System32\GroupPolicyUsers\S-1-5-32-545\User\registry.pol
            }
        }
    }
    else
    {
        try
        {
            $sid = $Account -as [System.Security.Principal.SecurityIdentifier]

            if ($null -eq $sid)
            {
                $sid = GetSidForAccount $Account
            }

            return Join-Path $env:SystemRoot "System32\GroupPolicyUsers\$($sid.Value)\User\registry.pol"
        }
        catch
        {
            throw
        }
    }
}

function GetSidForAccount($Account)
{
    $acc = $Account
    if ($acc -notlike '*\*') { $acc = "$env:COMPUTERNAME\$acc" }

    try
    {
        $ntAccount = [System.Security.Principal.NTAccount]$acc
        return $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
    }
    catch
    {
        $message = "Could not translate account '$acc' to a security identifier."
        $exception = New-Object System.Exception($message, $_.Exception)
        $errorRecord = New-Object System.Management.Automation.ErrorRecord(
            $exception,
            'CouldNotGetSidForAccount',
            [System.Management.Automation.ErrorCategory]::ObjectNotFound,
            $Acc
        )

        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }
}

function DataIsEqual
{
    param (
        [object] $First,
        [object] $Second,
        [Microsoft.Win32.RegistryValueKind] $Type
    )

    if ($Type -eq [Microsoft.Win32.RegistryValueKind]::String -or
        $Type -eq [Microsoft.Win32.RegistryValueKind]::ExpandString -or
        $Type -eq [Microsoft.Win32.RegistryValueKind]::DWord -or
        $Type -eq [Microsoft.Win32.RegistryValueKind]::QWord)
    {
        return @($First)[0] -ceq @($Second)[0]
    }

    # If we get here, $Type is either MultiString or Binary, both of which need to compare arrays.
    # The PolicyFileEditor module never returns type Unknown or None.

    $First = @($First)
    $Second = @($Second)

    if ($First.Count -ne $Second.Count) { return $false }

    $count = $First.Count
    for ($i = 0; $i -lt $count; $i++)
    {
        if ($First[$i] -cne $Second[$i]) { return $false }
    }

    return $true
}

function ParseKeyValueName
{
    param ([string] $KeyValueName)

    if ($KeyValueName.EndsWith('\'))
    {
        $key       = $KeyValueName -replace '\\$'
        $valueName = ''
    }
    else
    {
        $key       = Split-Path $KeyValueName -Parent
        $valueName = Split-Path $KeyValueName -Leaf
    }

    return $key, $valueName
}

function GetTargetResourceCommon
{
    param (
        [string] $Path,
        [string] $KeyValueName
    )

    $configuration = @{
        PolicyType   = $PolicyType
        KeyValueName = $KeyValueName
        Ensure       = 'Absent'
        Data         = $null
        Type         = [Microsoft.Win32.RegistryValueKind]::Unknown
    }

    if (Test-Path -LiteralPath $path -PathType Leaf)
    {
        $key, $valueName = ParseKeyValueName $KeyValueName
        $entry = Get-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName

        if ($entry)
        {
            $configuration['Ensure'] = 'Present'
            $configuration['Type']   = $entry.Type
            $configuration['Data']   = $entry.Data
        }
    }

    return $configuration
}

function SetTargetResourceCommon
{
    param (
        [string] $Path,
        [string] $KeyValueName,
        [string] $Ensure,
        [string[]] $Data,
        [Microsoft.Win32.RegistryValueKind] $Type
    )

    if ($null -eq $Data) { $Data = @() }

    try
    {
        Assert-ValidDataAndType -Data $Data -Type $Type
    }
    catch
    {
        Write-Error -ErrorRecord $_
        return
    }

    $key, $valueName = ParseKeyValueName $KeyValueName

    if ($Ensure -eq 'Present')
    {
        Set-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName -Data $Data -Type $Type
    }
    else
    {
        Remove-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName
    }
}

function TestTargetResourceCommon
{
    [OutputType([bool])]
    param (
        [string] $Path,
        [string] $KeyValueName,
        [string] $Ensure,
        [string[]] $Data,
        [Microsoft.Win32.RegistryValueKind] $Type
    )

    if ($null -eq $Data) { $Data = @() }

    try
    {
        Assert-ValidDataAndType -Data $Data -Type $Type
    }
    catch
    {
        Write-Error -ErrorRecord $_
        return $false
    }

    $key, $valueName = ParseKeyValueName $KeyValueName

    $fileExists = Test-Path -LiteralPath $Path -PathType Leaf

    if ($Ensure -eq 'Present')
    {
        if (-not $fileExists) { return $false }
        $entry = Get-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName

        return $null -ne $entry -and $Type -eq $entry.Type -and (DataIsEqual $entry.Data $Data -Type $Type)
    }
    else # Ensure is 'Absent'
    {
        if (-not $fileExists) { return $true }
        $entry = Get-PolicyFileEntry -Path $Path -Key $key -ValueName $valueName

        return $null -eq $entry
    }

}

function Assert-ValidDataAndType
{
    param (
        [string[]] $Data,
        [Microsoft.Win32.RegistryValueKind] $Type
    )

    if ($Type -ne [Microsoft.Win32.RegistryValueKind]::MultiString -and
        $Type -ne [Microsoft.Win32.RegistryValueKind]::Binary -and
        $Data.Count -gt 1)
    {
        throw 'Do not pass arrays with multiple values to the -Data parameter when -Type is not set to either Binary or MultiString.'
    }

}

# SIG # Begin signature block
# MIIhfgYJKoZIhvcNAQcCoIIhbzCCIWsCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUfLft9jkOkXxurCsmGLRsFhFY
# 4eOgghywMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
# Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg
# +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT
# XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5
# a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g
# 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1
# roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
# GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3
# cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr
# EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+
# fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q
# Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu
# 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw
# 8jCCBQswggPzoAMCAQICEAOiV15N2F/TLPzy+oVrWjMwDQYJKoZIhvcNAQEFBQAw
# bzELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTEuMCwGA1UEAxMlRGlnaUNlcnQgQXNzdXJlZCBJRCBD
# b2RlIFNpZ25pbmcgQ0EtMTAeFw0xNDA1MDUwMDAwMDBaFw0xNTA1MTMxMjAwMDBa
# MGExCzAJBgNVBAYTAkNBMQswCQYDVQQIEwJPTjERMA8GA1UEBxMIQnJhbXB0b24x
# GDAWBgNVBAoTD0RhdmlkIExlZSBXeWF0dDEYMBYGA1UEAxMPRGF2aWQgTGVlIFd5
# YXR0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvcX51YAyViQE16mg
# +IVQCQ0O8QC/wXBzTMPirnoGK9TThmxQIYgtcekZ5Xa/dWpW0xKKjaS6dRwYYXET
# pzozoMWZbFDVrgKaqtuZNu9TD6rqK/QKf4iL/eikr0NIUL4CoSEQDeGLXDw7ntzZ
# XKM86RuPw6MlDapfFQQFIMjsT7YaoqQNTOxhbiFoHVHqP7xL3JTS7TApa/RnNYyl
# O7SQ7TSNsekiXGwUNxPqt6UGuOP0nyR+GtNiBcPfeUi+XaqjjBmpqgDbkEIMLDuf
# fDO54VKvDLl8D2TxTFOcKZv61IcToOs+8z1sWTpMWI2MBuLhRR3A6iIhvilTYRBI
# iX5FZQIDAQABo4IBrzCCAaswHwYDVR0jBBgwFoAUe2jOKarAF75JeuHlP9an90WP
# NTIwHQYDVR0OBBYEFDS4+PmyUp+SmK2GR+NCMiLd+DpvMA4GA1UdDwEB/wQEAwIH
# gDATBgNVHSUEDDAKBggrBgEFBQcDAzBtBgNVHR8EZjBkMDCgLqAshipodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vYXNzdXJlZC1jcy1nMS5jcmwwMKAuoCyGKmh0dHA6
# Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9hc3N1cmVkLWNzLWcxLmNybDBCBgNVHSAEOzA5
# MDcGCWCGSAGG/WwDATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2Vy
# dC5jb20vQ1BTMIGCBggrBgEFBQcBAQR2MHQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBMBggrBgEFBQcwAoZAaHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ29kZVNpZ25pbmdDQS0xLmNydDAM
# BgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBBQUAA4IBAQBbzAp8wys0A5LcuENslW0E
# oz7rc0A8h+XgjJWdJOFRohE1mZRFpdkVxM0SRqw7IzlSFtTMCsVVPNwU6O7y9rCY
# x5agx3CJBkJVDR/Y7DcOQTmmHy1zpcrKAgTznZuKUQZLpoYz/bA+Uh+bvXB9woCA
# IRbchos1oxC+7/gjuxBMKh4NM+9NIvWs6qpnH5JeBidQDQXp3flPkla+MKrPTL/T
# /amgna5E+9WHWnXbMFCpZ5n1bI1OvgNVZlYC/JTa4fjPEk8d16jYVP4GlRz/QUYI
# y6IAGc/z6xpkdtpXWVCbW0dCd5ybfUYTaeCJumGpS/HSJ7JcTZj694QDOKNvhfrm
# MIIGajCCBVKgAwIBAgIQAwGaAjr/WLFr1tXq5hfwZjANBgkqhkiG9w0BAQUFADBi
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
# d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENB
# LTEwHhcNMTQxMDIyMDAwMDAwWhcNMjQxMDIyMDAwMDAwWjBHMQswCQYDVQQGEwJV
# UzERMA8GA1UEChMIRGlnaUNlcnQxJTAjBgNVBAMTHERpZ2lDZXJ0IFRpbWVzdGFt
# cCBSZXNwb25kZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCjZF38
# fLPggjXg4PbGKuZJdTvMbuBTqZ8fZFnmfGt/a4ydVfiS457VWmNbAklQ2YPOb2bu
# 3cuF6V+l+dSHdIhEOxnJ5fWRn8YUOawk6qhLLJGJzF4o9GS2ULf1ErNzlgpno75h
# n67z/RJ4dQ6mWxT9RSOOhkRVfRiGBYxVh3lIRvfKDo2n3k5f4qi2LVkCYYhhchho
# ubh87ubnNC8xd4EwH7s2AY3vJ+P3mvBMMWSN4+v6GYeofs/sjAw2W3rBerh4x8kG
# LkYQyI3oBGDbvHN0+k7Y/qpA8bLOcEaD6dpAoVk62RUJV5lWMJPzyWHM0AjMa+xi
# QpGsAsDvpPCJEY93AgMBAAGjggM1MIIDMTAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0T
# AQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDCCAb8GA1UdIASCAbYwggGy
# MIIBoQYJYIZIAYb9bAcBMIIBkjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGln
# aWNlcnQuY29tL0NQUzCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMA
# ZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8A
# bgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAA
# dABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAA
# dABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0A
# ZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQA
# eQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgA
# ZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjALBglghkgBhv1s
# AxUwHwYDVR0jBBgwFoAUFQASKxOYspkH7R7for5XDStnAs0wHQYDVR0OBBYEFGFa
# TSS2STKdSip5GoNL9B6Jwcp9MH0GA1UdHwR2MHQwOKA2oDSGMmh0dHA6Ly9jcmwz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3JsMDigNqA0hjJo
# dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNy
# bDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj
# ZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcnQwDQYJKoZIhvcNAQEFBQADggEBAJ0l
# fhszTbImgVybhs4jIA+Ah+WI//+x1GosMe06FxlxF82pG7xaFjkAneNshORaQPve
# BgGMN/qbsZ0kfv4gpFetW7easGAm6mlXIV00Lx9xsIOUGQVrNZAQoHuXx/Y/5+IR
# Qaa9YtnwJz04HShvOlIJ8OxwYtNiS7Dgc6aSwNOOMdgv420XEwbu5AO2FKvzj0On
# cZ0h3RTKFV2SQdr5D4HRmXQNJsQOfxu19aDxxncGKBXp2JPlVRbwuwqrHNtcSCdm
# yKOLChzlldquxC5ZoGHd2vNtomHpigtt7BIYvfdVVEADkitrwlHCCkivsNRu4PQU
# Cjob4489yq9qjXvc2EQwggajMIIFi6ADAgECAhAPqEkGFdcAoL4hdv3F7G29MA0G
# CSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0
# IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMTAyMTExMjAwMDBaFw0yNjAyMTAxMjAw
# MDBaMG8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNV
# BAsTEHd3dy5kaWdpY2VydC5jb20xLjAsBgNVBAMTJURpZ2lDZXJ0IEFzc3VyZWQg
# SUQgQ29kZSBTaWduaW5nIENBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
# AoIBAQCcfPmgjwrKiUtTmjzsGSJ/DMv3SETQPyJumk/6zt/G0ySR/6hSk+dy+PFG
# hpTFqxf0eH/Ler6QJhx8Uy/lg+e7agUozKAXEUsYIPO3vfLcy7iGQEUfT/k5mNM7
# 629ppFwBLrFm6aa43Abero1i/kQngqkDw/7mJguTSXHlOG1O/oBcZ3e11W9mZJRr
# u4hJaNjR9H4hwebFHsnglrgJlflLnq7MMb1qWkKnxAVHfWAr2aFdvftWk+8b/HL5
# 3z4y/d0qLDJG2l5jvNC4y0wQNfxQX6xDRHz+hERQtIwqPXQM9HqLckvgVrUTtmPp
# P05JI+cGFvAlqwH4KEHmx9RkO12rAgMBAAGjggNDMIIDPzAOBgNVHQ8BAf8EBAMC
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwggHDBgNVHSAEggG6MIIBtjCCAbIGCGCG
# SAGG/WwDMIIBpDA6BggrBgEFBQcCARYuaHR0cDovL3d3dy5kaWdpY2VydC5jb20v
# c3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBu
# AHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0
# AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBl
# ACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAg
# AGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBn
# AHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBi
# AGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0
# AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjAS
# BgNVHRMBAf8ECDAGAQH/AgEAMHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MIGB
# BgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNl
# cnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2Vy
# dC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMB0GA1UdDgQWBBR7aM4p
# qsAXvkl64eU/1qf3RY81MjAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823I
# DzANBgkqhkiG9w0BAQUFAAOCAQEAe3IdZP+IyDrBt+nnqcSHu9uUkteQWTP6K4fe
# qFuAJT8Tj5uDG3xDxOaM3zk+wxXssNo7ISV7JMFyXbhHkYETRvqcP2pRON60Jcvw
# q9/FKAFUeRBGJNE4DyahYZBNur0o5j/xxKqb9to1U0/J8j3TbNwj7aqgTWcJ8zqA
# PTz7NkyQ53ak3fI6v1Y1L6JMZejg1NrRx8iRai0jTzc7GZQY1NWcEDzVsRwZ/4/I
# a5ue+K6cmZZ40c2cURVbQiZyWo0KSiOSQOiG3iLCkzrUm2im3yl/Brk8Dr2fxIac
# gkdCcTKGCZlyCXlLnXFp9UH/fzl3ZPGEjb6LHrJ9aKOlkLEM/zCCBs0wggW1oAMC
# AQICEAb9+QOWA63qAArrPye7uhswDQYJKoZIhvcNAQEFBQAwZTELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTA2
# MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowYjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8G
# A1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMIIBIjANBgkqhkiG9w0BAQEF
# AAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKVpYBzQHDSnlZUXKnE0kEGj8kz/E1FkVyB
# n+0snPgWWd+etSQVwpi5tHdJ3InECtqvy15r7a2wcTHrzzpADEZNk+yLejYIA6sM
# NP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/2DDD/6b3+6LNb3Mj/qxWBZDwMiEWicZw
# iPkFl32jx0PdAug7Pe2xQaPtP77blUjE7h6z8rwMK5nQxl0SQoHhg26Ccz8mSxSQ
# rllmCsSNvtLOBq6thG9IhJtPQLnxTPKvmPv2zkBdXPao8S+v7Iki8msYZbHBc63X
# 8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIUYJX9BwSiCQIDAQABo4IDejCCA3YwDgYD
# VR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYB
# BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCDCCAdIGA1UdIASCAckwggHFMIIBtAYK
# YIZIAYb9bAABBDCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQu
# Y29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCCAVYeggFS
# AEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBj
# AGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBu
# AGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQ
# AFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAg
# AEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABp
# AGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwBy
# AGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBl
# AC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB/wQIMAYBAf8CAQAweQYIKwYBBQUHAQEE
# bTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYB
# BQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0
# dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j
# cmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e36K+Vw0rZwLNMB8GA1UdIwQYMBaAFEXr
# oq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUAA4IBAQBGUD7Jtygkpzgd
# tlspr1LPUukxR6tWXHvVDQtBs+/sdR90OPKyXGGinJXDUOSCuSPRujqGcq04eKx1
# XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann4+erYs37iy2QwsDStZS9Xk+xBdIOPRqp
# FFumhjFiqKgz5Js5p8T1zh14dpQlc+Qqq8+cdkvtX8JLFuRLcEwAiR78xXm8TBJX
# /l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD7eeSDY2xaYxP+1ngIw/Sqq4AfO6cQg7P
# kdcntxbuD8O9fAqg7iwIVYUiuOsYGk38KiGtSTGDR5V3cdyxG0tLHBCcdxTBnU8v
# WpUIKRAmMYIEODCCBDQCAQEwgYMwbzELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp
# Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEuMCwGA1UEAxMl
# RGlnaUNlcnQgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EtMQIQA6JXXk3YX9Ms
# /PL6hWtaMzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZ
# BgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYB
# BAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUJOIPTVUErGuPyPz19laT4mmn4M0wDQYJ
# KoZIhvcNAQEBBQAEggEALsWEQQ1f8NorCMwwP42Fcyz5ksd8Dxtm8KTnXlLhaG2R
# j0kA/Sm26VscjxOWTb8hTDuOKZsQCJ3vmlu4rXlj4ctCZ+OOwcffY8Em6pJyMhz+
# utYZybEWcKlj+BYFe7cDZuQs+P2MqrgznYl1aeI69JB9Y6vFuaeMKol5VGPvV6N6
# t0wiUxOta6BnG4UmZ7WFUj0mV20nHEfK2TnVE9u+/b/qlKTyRHC4/7JexGNSM1Yz
# pM9aqQktI8uyN82Rm7FRYDkHio+XAOlKqR2yMM82EhchhJm9pqtS549S6S21MKMm
# qa0ih66yrwB7BvnQhRQh7U4ccRjcKSeXom1+LYkDaKGCAg8wggILBgkqhkiG9w0B
# CQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr1tXq5hfwZjAJBgUrDgMCGgUA
# oF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTUw
# NDAxMDMyMTQ3WjAjBgkqhkiG9w0BCQQxFgQU865RMqqmk+VgTnTM8g7E4Jmkr8ow
# DQYJKoZIhvcNAQEBBQAEggEAGauw3gBXJ48H/t8mfrW0VJ57QQxwa1BQ4JF0ZP6+
# Gs1hvOoR2vSECXxeRMzOqZloI9+gtnF6fMc6YkpEax/2q3t8z6o7CAGvfp8INc1q
# Cs8YyI35G68AbSDUPSWWuwKKJ13aAS01AFtJhcNY1Q87edFXVsri4ki3wBORBmj1
# Q9HkmXSVqw28MBX5A4VNs6s7UKrPQ0QGMeIMrRjHDUSTx98mzBF/522k/CfGK2hd
# 5fz9AbQMB5FNBTRMKaCyLkVAMuuWIrpo9Zuj7cyXL19uxLqP4C6ATjTcmMhjp2t8
# 1RiIWfBvaL/lsV4Fc70V/HCICWUic/X5e0uxJyn3r4bacQ==
# SIG # End signature block