public/Get-IvantiWCRegistry.ps1

<#
.SYNOPSIS
    Reads and parses Ivanti Workspace Control registry sets from a Building Block XML file or a
    standalone .reg file.
.DESCRIPTION
    Accepts either:
    - An Ivanti Workspace Control Building Block XML file: extracts every <registry type="registry">
      node, decodes the hex-encoded registryfile, and parses it. Name and description are taken
      from the <name> and <description> XML elements.
    - A standalone .reg file: parses it directly. Name and description are read from the
      ;<PFNAME> and ;<PFDESC> comment tags if present.
 
    Each value entry is returned as a structured object with its registry hive, key, value name,
    value data, value type, and an optional per-value description captured from the ;<PF>...</PF>
    comment tag that may follow the value line.
 
    Supports all standard .reg value types:
    - REG_SZ (quoted string or hex(1):)
    - REG_EXPAND_SZ (hex(2):)
    - REG_BINARY (hex: / hex(3):)
    - REG_DWORD (dword: / hex(4):)
    - REG_MULTI_SZ (hex(7):)
    - REG_QWORD (qword: / hex(b):)
 
.PARAMETER Path
    Path to an Ivanti Workspace Control Building Block XML file or a standalone .reg file.
 
.PARAMETER ExportFor
    Target export format. 'AppVentiX' adds the AppVentiXParams property to each output object.
 
.EXAMPLE
    # From a Building Block XML
    Get-IvantiWCRegistry -Path 'C:\temp\LAB-BB.xml'
 
.EXAMPLE
    # From a standalone .reg file, exported for AppVentiX
    Get-IvantiWCRegistry -Path 'C:\temp\registry.reg' -ExportFor AppVentiX
 
.LINK
    Import-IvantiWCRegistry
 
.NOTES
    Function : Get-IvantiWCRegistry
    Author : John Billekens
    Copyright : (c) John Billekens Consultancy & AppVentiX
    Version : 2026.0309.1200
#>

function Get-IvantiWCRegistry {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateScript({ Test-Path $_ -PathType Leaf })]
        [string]$Path,

        [ValidateSet('AppVentiX', 'WEM')]
        [string]$ExportFor = 'AppVentiX'
    )

    #region Internal helpers

    function Convert-HexToString {
        param([string]$HexString)
        if ([string]::IsNullOrEmpty($HexString)) { return '' }
        $bytes = for ($i = 0; $i -lt $HexString.Length; $i += 2) {
            [System.Convert]::ToByte($HexString.Substring($i, 2), 16)
        }
        return [System.Text.Encoding]::GetEncoding(1252).GetString($bytes)
    }

    function Convert-RegistryHexToValue {
        param([string]$hexString, [string]$type)
        $hexData = $hexString -replace '^hex\(\w+\):', '' -replace '[,\s]', ''
        if ([string]::IsNullOrEmpty($hexData)) { return $null }
        try {
            $bytes = for ($i = 0; $i -lt $hexData.Length; $i += 2) {
                [System.Convert]::ToByte($hexData.Substring($i, 2), 16)
            }
            switch ($type) {
                'REG_SZ' { return [System.Text.Encoding]::Unicode.GetString($bytes).TrimEnd([char]0) }
                'REG_EXPAND_SZ' { return [System.Text.Encoding]::Unicode.GetString($bytes).TrimEnd([char]0) }
                'REG_BINARY' { return [System.BitConverter]::ToString($bytes) }
                'REG_DWORD' { if ($bytes.Count -eq 4) { return [System.BitConverter]::ToUInt32($bytes, 0) } return $null }
                'REG_MULTI_SZ' {
                    $fullString = [System.Text.Encoding]::Unicode.GetString($bytes)
                    return ($fullString -split '\0' | Where-Object { $_ })
                }
                'REG_QWORD' { if ($bytes.Count -eq 8) { return [System.BitConverter]::ToUInt64($bytes, 0) } return $null }
                default { return [System.BitConverter]::ToString($bytes) }
            }
        } catch {
            Write-Warning "Could not decode hex value for type $type. Error: $_"
            return $hexString
        }
    }

    function ConvertTo-RegistryEntries {
        param(
            [string]$Name,
            [string]$Description,
            [string]$RegContent,
            [string]$SourceFile,
            [bool]$Enabled = $true,
            [bool]$RunOnce = $false,
            [string]$ExportFor = 'WEM',
            [PSCustomObject[]]$Assignments = @()
        )

        $lines = $RegContent.Split([string[]]@("`r`n", "`n", "`r"), [StringSplitOptions]::None)

        # If name/description not provided, fall back to PFNAME/PFDESC tags in the content
        $resolvedName = $Name
        $resolvedDescription = $Description
        if ([string]::IsNullOrEmpty($resolvedName) -or [string]::IsNullOrEmpty($resolvedDescription)) {
            foreach ($line in $lines) {
                if ([string]::IsNullOrEmpty($resolvedName) -and $line -match ';<PFNAME>(.+)</PFNAME>') {
                    $resolvedName = $Matches[1].Trim()
                }
                if ([string]::IsNullOrEmpty($resolvedDescription) -and $line -match ';<PFDESC>(.+)</PFDESC>') {
                    $resolvedDescription = $Matches[1].Trim()
                }
                if (-not [string]::IsNullOrEmpty($resolvedName) -and -not [string]::IsNullOrEmpty($resolvedDescription)) {
                    break
                }
            }
        }

        Write-Verbose "Registry set: '$resolvedName' | Description: '$resolvedDescription' | Enabled: '$Enabled' | RunOnce: '$RunOnce'"

        $entries = [System.Collections.Generic.List[PSCustomObject]]::new()
        $currentHive = ''
        $currentKey = ''

        for ($i = 0; $i -lt $lines.Length; $i++) {
            $line = $lines[$i]

            # Section header: [HKEY_...] or ![HKEY_...] (delete entire key)
            if ($line -match '^(!?)\[((HKEY_[^\\]+)(\\(.+))?)\]$') {
                $isKeyDeletion = $Matches[1] -eq '!'
                $sectionHive = $Matches[3]
                $sectionKey = if ($Matches[5]) { $Matches[5] } else { '' }

                if ($isKeyDeletion) {
                    # Emit a key-deletion entry; do not update current section context
                    Write-Verbose " Delete key: $sectionHive\$sectionKey"
                    $entries.Add([PSCustomObject]([ordered]@{
                                RegistryHive      = $sectionHive
                                RegistryKey       = $sectionKey
                                RegistryKeyPath   = if ([string]::IsNullOrEmpty($sectionKey)) { $sectionHive } else { "$sectionHive\$sectionKey" }
                                RegistryValueName = $null
                                Value             = $null
                                ValueType         = $null
                                Description       = ''
                                Delete            = $true
                            }))
                } else {
                    $currentHive = $sectionHive
                    $currentKey = $sectionKey
                    Write-Verbose " Section: $currentHive\$currentKey"
                }
                continue
            }

            # Skip comments, blank lines, and the editor header
            if ([string]::IsNullOrWhiteSpace($line) -or
                $line.StartsWith(';') -or
                $line.StartsWith('Windows Registry Editor')) {
                continue
            }

            # Skip structural placeholder default values (@="") on keys with no meaningful path
            # These are Ivanti reg file artifacts — empty-string default values on intermediate keys
            if ($line -match '^@=""$' -and [string]::IsNullOrEmpty($currentKey)) {
                continue
            }

            # Deletion marker on value: !"ValueName"=...
            $isDeletion = $false
            $parsedLine = $line
            if ($parsedLine.TrimStart().StartsWith('!"')) {
                $isDeletion = $true
                $parsedLine = $parsedLine.TrimStart().Substring(1)
            }

            $valueName = $null
            $rawValue = $null

            if ($parsedLine -match '^"([^"]*)"=(.+)') {
                $valueName = $Matches[1]
                $rawValue = $Matches[2].Trim()
            } elseif ($parsedLine -match '^@=(.+)') {
                $valueName = ''
                $rawValue = $Matches[1].Trim()
            } else {
                continue
            }

            # Skip empty-string default values on intermediate keys (structural artifacts)
            if ($valueName -eq '' -and $rawValue -eq '""') {
                continue
            }

            # Peek at next line for optional ;<PF>...</PF> per-value description
            $valueDescription = ''
            if (($i + 1) -lt $lines.Length -and $lines[$i + 1] -match ';<PF>(.+)</PF>') {
                $valueDescription = $Matches[1].Trim()
            }

            # Decode value data and determine type
            $registryValue = $null
            $registryValueType = 'Unknown'

            if ($rawValue.StartsWith('dword:')) {
                $registryValueType = 'REG_DWORD'
                try { $registryValue = [uint64]("0x" + $rawValue.Substring(6)) }
                catch { $registryValue = $rawValue; $registryValueType = 'Unknown' }
            } elseif ($rawValue.StartsWith('qword:')) {
                $registryValueType = 'REG_QWORD'
                try { $registryValue = [uint64]("0x" + $rawValue.Substring(6)) }
                catch { $registryValue = $rawValue; $registryValueType = 'Unknown' }
            } elseif ($rawValue.StartsWith('"') -and $rawValue.EndsWith('"')) {
                $registryValueType = 'REG_SZ'
                $registryValue = $rawValue.Substring(1, $rawValue.Length - 2)
            } elseif ($rawValue.StartsWith('hex(1):')) {
                $registryValueType = 'REG_SZ'
                $registryValue = Convert-RegistryHexToValue -hexString $rawValue -type 'REG_SZ'
            } elseif ($rawValue.StartsWith('hex(2):')) {
                $registryValueType = 'REG_EXPAND_SZ'
                $registryValue = Convert-RegistryHexToValue -hexString $rawValue -type 'REG_EXPAND_SZ'
            } elseif ($rawValue.StartsWith('hex(3):') -or $rawValue.StartsWith('hex:')) {
                $registryValueType = 'REG_BINARY'
                $registryValue = Convert-RegistryHexToValue -hexString $rawValue -type 'REG_BINARY'
            } elseif ($rawValue.StartsWith('hex(4):')) {
                $registryValueType = 'REG_DWORD'
                $registryValue = Convert-RegistryHexToValue -hexString $rawValue -type 'REG_DWORD'
            } elseif ($rawValue.StartsWith('hex(7):')) {
                $registryValueType = 'REG_MULTI_SZ'
                $registryValue = Convert-RegistryHexToValue -hexString $rawValue -type 'REG_MULTI_SZ'
            } elseif ($rawValue.StartsWith('hex(b):')) {
                $registryValueType = 'REG_QWORD'
                $registryValue = Convert-RegistryHexToValue -hexString $rawValue -type 'REG_QWORD'
            } else {
                $registryValueType = 'REG_SZ'
                $registryValue = $rawValue
            }

            $entries.Add(
                [PSCustomObject](
                    [PSCustomObject]@{
                        RegistryHive      = $currentHive
                        RegistryKey       = $currentKey
                        RegistryKeyPath   = if ([string]::IsNullOrEmpty($currentKey)) { $currentHive } else { "$currentHive\$currentKey" }
                        RegistryValueName = $valueName
                        Value             = $registryValue
                        ValueType         = $registryValueType
                        Description       = $valueDescription
                        Delete            = $isDeletion
                    }
                )
            )
        }

        Write-Verbose " Parsed $($entries.Count) registry value(s)"

        $output = [ordered]@{
            Name        = $resolvedName
            Description = $resolvedDescription
            Enabled     = $Enabled
            RunOnce     = $RunOnce
            Entries     = $entries.ToArray()
            SourceFile  = $SourceFile
        }

        if ($ExportFor -eq 'AppVentiX') {
            $registryEntries = @(
                foreach ($entry in $entries) {
                    $deleteKey = $false
                    if ($entry.Delete -eq $true -and [string]::IsNullOrEmpty($entry.RegistryValueName)) {
                        $deleteKey = $true
                    }
                    $valueType = $entry.ValueType
                    if ([string]::IsNullOrEmpty(($valueType))) {
                        $valueType = 'REG_SZ'
                    }

                    [PSCustomObject]@{
                        RootKey   = $entry.RegistryHive
                        KeyPath   = $entry.RegistryKey
                        ValueName = $entry.RegistryValueName
                        ValueData = $entry.Value
                        ValueType = $valueType
                        Action    = if ($entry.Delete) { 'Remove' } else { 'Set' }
                        DeleteKey = $deleteKey
                    }

                }
            )
            $output.AppVentiXAssignments = @($Assignments | Select-Object Sid, Name, Type, DomainFQDN)
            $output.AppVentiXParams = (
                [ordered]@{
                    FriendlyName    = $resolvedName
                    Description     = $resolvedDescription
                    RegistryEntries = @($registryEntries)
                }
            )
        } elseif ($ExportFor -eq 'WEM') {
            Write-Warning "WEM export format is not implemented yet for registry sets!"
        }

        [PSCustomObject]$output
    }

    #endregion

    $absolutePath = (Resolve-Path -LiteralPath $Path).Path
    $extension = [System.IO.Path]::GetExtension($absolutePath).ToLower()

    if ($extension -eq '.xml') {
        # Building Block XML — extract all <registry type="registry"> nodes
        Write-Verbose "Loading Building Block XML: $absolutePath"
        try {
            $xmlContent = New-Object System.Xml.XmlDocument
            $xmlContent.Load($absolutePath)
        } catch {
            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    [System.Xml.XmlException]::new("Failed to parse XML file '$absolutePath': $($_.Exception.Message)"),
                    'XmlParseError',
                    [System.Management.Automation.ErrorCategory]::InvalidData,
                    $absolutePath
                )
            )
        }

        $registryNodes = @($xmlContent.GetElementsByTagName('registry') | Where-Object { $_.type -eq 'registry' })
        Write-Verbose "Found $($registryNodes.Count) registry set node(s)"

        if ($registryNodes.Count -eq 0) {
            Write-Warning "No <registry type='registry'> nodes found in '$absolutePath'"
            return
        }

        foreach ($node in $registryNodes) {
            $nodeName = $node.name
            $nodeDescription = $node.description
            $decodedContent = (Convert-HexToString -HexString $node.registryfile).TrimStart([char]0xFEFF)
            if ($node.enabled -ieq "yes") {
                $enabled = $true
            } else {
                $enabled = $false
            }
            if ($node.runonce -ieq "yes") {
                $runOnce = $true
            } else {
                $runOnce = $false
            }
            Write-Verbose "Processing registry set: '$nodeName'"
            $Assignments = @(ConvertFrom-IvantiAccessControl -AccessControl $node.accesscontrol -IWCComponentName $nodeName -IWCComponent "Policy")

            ConvertTo-RegistryEntries -Name $nodeName -Description $nodeDescription -RunOnce $runOnce -RegContent $decodedContent -SourceFile $absolutePath -ExportFor $ExportFor -Assignments $Assignments
        }

    } else {
        # Standalone .reg file
        Write-Verbose "Loading .reg file: $absolutePath"
        try {
            $rawContent = Get-Content -LiteralPath $absolutePath -Raw -ErrorAction Stop
        } catch {
            $PSCmdlet.ThrowTerminatingError(
                [System.Management.Automation.ErrorRecord]::new(
                    [System.IO.IOException]::new("Failed to read file '$absolutePath': $($_.Exception.Message)"),
                    'RegFileReadError',
                    [System.Management.Automation.ErrorCategory]::ReadError,
                    $absolutePath
                )
            )
        }
        ConvertTo-RegistryEntries -Name '' -Description '' -RegContent $rawContent -SourceFile $absolutePath -ExportFor $ExportFor
    }
}

# SIG # Begin signature block
# MIInigYJKoZIhvcNAQcCoIInezCCJ3cCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBGX6RZyGnwFjsk
# bR0B0rKoCqvDIobKWgof6hAOYr46HqCCIR0wggZFMIIELaADAgECAhAIMk+dt9qR
# b2Pk8qM8Xl1RMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQK
# ExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2Rl
# IFNpZ25pbmcgMjAyMSBDQTAeFw0yNDA0MDQxNDA0MjRaFw0yNzA0MDQxNDA0MjNa
# MGsxCzAJBgNVBAYTAk5MMRIwEAYDVQQHDAlTY2hpam5kZWwxIzAhBgNVBAoMGkpv
# aG4gQmlsbGVrZW5zIENvbnN1bHRhbmN5MSMwIQYDVQQDDBpKb2huIEJpbGxla2Vu
# cyBDb25zdWx0YW5jeTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAMsl
# ntDbSQwHZXwFhmibivbnd0Qfn6sqe/6fos3pKzKxEsR907RkDMet2x6RRg3eJkiI
# r3TFPwqBooyXXgK3zxxpyhGOcuIqyM9J28DVf4kUyZHsjGO/8HFjrr3K1hABNUsz
# P0o7H3o6J31eqV1UmCXYhQlNoW9FOmRC1amlquBmh7w4EKYEytqdmdOBavAD5Xq4
# vLPxNP6kyA+B2YTtk/xM27TghtbwFGKnu9Vwnm7dFcpLxans4ONt2OxDQOMA5Nwg
# cUv/YTpjhq9qoz6ivG55NRJGNvUXsM3w2o7dR6Xh4MuEGrTSrOWGg2A5EcLH1XqQ
# tkF5cZnAPM8W/9HUp8ggornWnFVQ9/6Mga+ermy5wy5XrmQpN+x3u6tit7xlHk1H
# c+4XY4a4ie3BPXG2PhJhmZAn4ebNSBwNHh8z7WTT9X9OFERepGSytZVeEP7hgypt
# SLcuhpwWeR4QdBb7dV++4p3PsAUQVHFpwkSbrRTv4EiJ0Lcz9P1HPGFoHiFAQQID
# AQABo4IBeDCCAXQwDAYDVR0TAQH/BAIwADA9BgNVHR8ENjA0MDKgMKAuhixodHRw
# Oi8vY2NzY2EyMDIxLmNybC5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNybDBzBggrBgEF
# BQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0dHA6Ly9jY3NjYTIwMjEub2NzcC1jZXJ0
# dW0uY29tMDUGCCsGAQUFBzAChilodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv
# Y2NzY2EyMDIxLmNlcjAfBgNVHSMEGDAWgBTddF1MANt7n6B0yrFu9zzAMsBwzTAd
# BgNVHQ4EFgQUO6KtBpOBgmrlANVAnyiQC6W6lJwwSwYDVR0gBEQwQjAIBgZngQwB
# BAEwNgYLKoRoAYb2dwIFAQQwJzAlBggrBgEFBQcCARYZaHR0cHM6Ly93d3cuY2Vy
# dHVtLnBsL0NQUzATBgNVHSUEDDAKBggrBgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4Aw
# DQYJKoZIhvcNAQELBQADggIBAEQsN8wgPMdWVkwHPPTN+jKpdns5AKVFjcn00psf
# 2NGVVgWWNQBIQc9lEuTBWb54IK6Ga3hxQRZfnPNo5HGl73YLmFgdFQrFzZ1lnaMd
# Icyh8LTWv6+XNWfoyCM9wCp4zMIDPOs8LKSMQqA/wRgqiACWnOS4a6fyd5GUIAm4
# CuaptpFYr90l4Dn/wAdXOdY32UhgzmSuxpUbhD8gVJUaBNVmQaRqeU8y49MxiVrU
# KJXde1BCrtR9awXbqembc7Nqvmi60tYKlD27hlpKtj6eGPjkht0hHEsgzU0Fxw7Z
# JghYG2wXfpF2ziN893ak9Mi/1dmCNmorGOnybKYfT6ff6YTCDDNkod4egcMZdOSv
# +/Qv+HAeIgEvrxE9QsGlzTwbRtbm6gwYYcVBs/SsVUdBn/TSB35MMxRhHE5iC3aU
# TkDbceo/XP3uFhVL4g2JZHpFfCSu2TQrrzRn2sn07jfMvzeHArCOJgBW1gPqR3Wr
# J4hUxL06Rbg1gs9tU5HGGz9KNQMfQFQ70Wz7UIhezGcFcRfkIfSkMmQYYpsc7rfz
# j+z0ThfDVzzJr2dMOFsMlfj1T6l22GBq9XQx0A4lcc5Fl9pRxbOuHHWFqIBD/BCE
# hwniOCySzqENd2N+oz8znKooSISStnkNaYXt6xblJF2dx9Dn89FK7d1IquNxOwt0
# tI5dMIIGgjCCBGqgAwIBAgIQNsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0BAQwF
# ADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcT
# C0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAs
# BgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcN
# MjEwMzIyMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjBXMQswCQYDVQQGEwJHQjEYMBYG
# A1UEChMPU2VjdGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBU
# aW1lIFN0YW1waW5nIFJvb3QgUjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAiJ3YuUVnnR3d6LkmgZpUVMB8SQWbzFoVD9mUEES0QUCBdxSZqdTkdizI
# CFNeINCSJS+lV1ipnW5ihkQyC0cRLWXUJzodqpnMRs46npiJPHrfLBOifjfhpdXJ
# 2aHHsPHggGsCi7uE0awqKggE/LkYw3sqaBia67h/3awoqNvGqiFRJ+OTWYmUCO2G
# AXsePHi+/JUNAax3kpqstbl3vcTdOGhtKShvZIvjwulRH87rbukNyHGWX5tNK/WA
# BKf+Gnoi4cmisS7oSimgHUI0Wn/4elNd40BFdSZ1EwpuddZ+Wr7+Dfo0lcHflm/F
# DDrOJ3rWqauUP8hsokDoI7D/yUVI9DAE/WK3Jl3C4LKwIpn1mNzMyptRwsXKrop0
# 6m7NUNHdlTDEMovXAIDGAvYynPt5lutv8lZeI5w3MOlCybAZDpK3Dy1MKo+6aEtE
# 9vtiTMzz/o2dYfdP0KWZwZIXbYsTIlg1YIetCpi5s14qiXOpRsKqFKqav9R1R5vj
# 3NgevsAsvxsAnI8Oa5s2oy25qhsoBIGo/zi6GpxFj+mOdh35Xn91y72J4RGOJEoq
# zEIbW3q0b2iPuWLA911cRxgY5SJYubvjay3nSMbBPPFsyl6mY4/WYucmyS9lo3l7
# jk27MAe145GWxK4O3m3gEFEIkv7kRmefDR7Oe2T1HxAnICQvr9sCAwEAAaOCARYw
# ggESMB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBT2
# d2rdP/0BE/8WoWyCAi/QCj0UJTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw
# AwEB/zATBgNVHSUEDDAKBggrBgEFBQcDCDARBgNVHSAECjAIMAYGBFUdIAAwUAYD
# VR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVz
# dFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMDUGCCsGAQUFBwEBBCkwJzAl
# BggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0B
# AQwFAAOCAgEADr5lQe1oRLjlocXUEYfktzsljOt+2sgXke3Y8UPEooU5y39rAARa
# AdAxUeiX1ktLJ3+lgxtoLQhn5cFb3GF2SSZRX8ptQ6IvuD3wz/LNHKpQ5nX8hjsD
# LRhsyeIiJsms9yAWnvdYOdEMq1W61KE9JlBkB20XBee6JaXx4UBErc+YuoSb1SxV
# f7nkNtUjPfcxuFtrQdRMRi/fInV/AobE8Gw/8yBMQKKaHt5eia8ybT8Y/Ffa6HAJ
# yz9gvEOcF1VWXG8OMeM7Vy7Bs6mSIkYeYtddU1ux1dQLbEGur18ut97wgGwDiGin
# CwKPyFO7ApcmVJOtlw9FVJxw/mL1TbyBns4zOgkaXFnnfzg4qbSvnrwyj1NiurMp
# 4pmAWjR+Pb/SIduPnmFzbSN/G8reZCL4fvGlvPFk4Uab/JVCSmj59+/mB2Gn6G/U
# YOy8k60mKcmaAZsEVkhOFuoj4we8CYyaR9vd9PGZKSinaZIkvVjbH/3nlLb0a7SB
# IkiRzfPfS9T+JesylbHa1LtRV9U/7m0q7Ma2CQ/t392ioOssXW7oKLdOmMBl14su
# VFBmbzrt5V5cQPnwtd3UOTpS9oCG+ZZheiIvPgkDmA8FzPsnfXW5qHELB43ET7HH
# FHeRPRYrMBKjkb8/IN7Po0d0hQoF4TeMM+zYAJzoKQnVKOLg8pZVPT8wgganMIIE
# j6ADAgECAhEAkKwIciD9xafEa1zHDfc9BjANBgkqhkiG9w0BAQwFADBXMQswCQYD
# VQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0
# aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIFJvb3QgUjQ2MB4XDTI2MDMyNTAwMDAw
# MFoXDTQxMDMyNDIzNTk1OVowVTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3Rp
# Z28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFtcGlu
# ZyBDQSBSNDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCu5EqiAa2C
# HGL5Zi1bmgPM8NUXwYZJ+BtQqHps43GLTC+sjVLypsBh+8uv+TLkgtVGD//vSmA0
# qrzELf9YRCh2MTAA/aGaQZKGg0BRCmziR3pbCnvgWjtGXBDUyn3j3K2lZAO8KxgF
# tlxwOYEAkL+CCqK4v9zzTl8ZwzDpPMiDIFa5THk8an1ieF5I09cXNrPQw+1ER1li
# ThaG0z6FrOpqwxZWmPRZQBw2E32878UB1bL0Zp91vuWZgsMpNNiPCoBj0/1F+LE8
# +NRokfqacFI0F2tftrRB2W7HQClLR9zjxFbWb5be2rceIfNyHUUfKGIvMI2NzoxS
# lxXnFqUG887D8W1Cj8DFok688JKxWvHR/9aQykSbd+9Vutj36ij2sgq/125wTpUZ
# /AgC0ph50bRs7gFrUyaXE9wSsOqMvCCC+sEm7vd/BemSG0TSHNXSmyCba+FCzeke
# WX03TRIcF3Laqd0Rw24OH7jpei4zaGhcI7nfdhBA4c8RScxNY6jeHLHHmSMMTk9W
# qn7H4dLhUBP5YEwbgbN4uv1i9ltTnHli8t1xHV0StX9BFgrnmunTX19kUXY1H5OR
# JbRZyZDdvm1oZyteDj0SnMozr+YSmdIleDUTXdfoY7b2taz8s2+QbOxLxcahEIYG
# Wzqu6h955tKwcANHcZ4gTmAhT3btuOiQsQIDAQABo4IBbjCCAWowHwYDVR0jBBgw
# FoAU9ndq3T/9ARP/FqFsggIv0Ao9FCUwHQYDVR0OBBYEFDp0pQxnxkJQwv21/Me7
# KTSC9Hq5MA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1Ud
# JQQMMAoGCCsGAQUFBwMIMCMGA1UdIAQcMBowCAYGZ4EMAQQCMA4GDCsGAQQBsjEB
# AgEDCDBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLnNlY3RpZ28uY29tL1Nl
# Y3RpZ29QdWJsaWNUaW1lU3RhbXBpbmdSb290UjQ2LmNybDB8BggrBgEFBQcBAQRw
# MG4wRwYIKwYBBQUHMAKGO2h0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1
# YmxpY1RpbWVTdGFtcGluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8v
# b2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMt5SR2bxngNm+N8o
# c6Gq76Gx1c235fkX7jw8Ho9MAkJGADerHE7dhsBXttqmzgr/7ZZahZSykGRPhPY1
# crj028kB8KzO0dKC2qQBAwtfgqMLKkkX/6bYq2uT33eD6ByAp2/XKD0LcmZh0kKe
# cvSBr6ln9ajX6u1dnx2fA7xEKy1M3qBhfQSUWLtjs2nFt0ELVLptzTlX9ID0cL+i
# OPfdboZ3CelT+JXKVKR2Sge0d4YiFAtPZkfSo8z1Z1x7y/Z9mwMIlBAnyuWXs4Ys
# NuxdrYIt/QxE31PDOJ9DesS4Bc7H9OTORlEV/AvfiF/VepKZpira1MzLYuCw+uoL
# Zn/pkpvd+CvNTS+mEHjBJNa6WK1j8qXFu+jIq+sG9QILHiyB6p/xpHrkJu8zkw39
# 3+VqF9eKlTY2VjRxdycZLrVemZ4Yp3wi33b+W58CllH3HqjmowlZ7SOrgmx8YwYO
# kgrHsXOQHyBp6O4FRb8In0+FzjT7ElGie9V7CfhL3IlVFZ4zjuKsZtH1iU3fGu4z
# /JnOGT6sCb0BbTqe/uhvpFCQBdH5xPGIA/LrbQUXjU2tWJgHhTIqnN/HvHyOHi5t
# M4zP3nhgh2rJ6Kqq2xsHBeNYs/R18xQ8DeIg+c90Eoaeh0YlN1KU8AyYol3K9M+q
# Y5ez8syd/7ZlrRnoVewgH3P1pcswgga5MIIEoaADAgECAhEAmaOACiZVO2Wr3G6E
# prPqOTANBgkqhkiG9w0BAQwFADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVu
# aXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZp
# Y2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y
# ayBDQSAyMB4XDTIxMDUxOTA1MzIxOFoXDTM2MDUxODA1MzIxOFowVjELMAkGA1UE
# BhMCUEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UE
# AxMbQ2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEAnSPPBDAjO8FGLOczcz5jXXp1ur5cTbq96y34vuTmflN4
# mSAfgLKTvggv24/rWiVGzGxT9YEASVMw1Aj8ewTS4IndU8s7VS5+djSoMcbvIKck
# 6+hI1shsylP4JyLvmxwLHtSworV9wmjhNd627h27a8RdrT1PH9ud0IF+njvMk2xq
# bNTIPsnWtw3E7DmDoUmDQiYi/ucJ42fcHqBkbbxYDB7SYOouu9Tj1yHIohzuC8KN
# qfcYf7Z4/iZgkBJ+UFNDcc6zokZ2uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD2bSl
# bfsq7BiqljjaCun+RJgTgFRCtsuAEw0pG9+FA+yQN9n/kZtMLK+Wo837Q4QOZgYq
# VWQ4x6cM7/G0yswg1ElLlJj6NYKLw9EcBXE7TF3HybZtYvj9lDV2nT8mFSkcSkAE
# xzd4prHwYjUXTeZIlVXqj+eaYqoMTpMrfh5MCAOIG5knN4Q/JHuurfTI5XDYO962
# WZayx7ACFf5ydJpoEowSP07YaBiQ8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq1UiY
# PIAHlezf4muJqxqIns/kqld6JVX8cixbd6PzkDpwZo4SlADaCi2JSplKShBSND36
# E/ENVv8urPS0yOnpG4tIoBGxVCARPCg1BnyMJ4rBJAcOSnAWd18Jx5n858JSqPEC
# AwEAAaOCAVUwggFRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA23uf
# oHTKsW73PMAywHDNMB8GA1UdIwQYMBaAFLahVDkCw6A/joq8+tT4HKbROg79MA4G
# A1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzAwBgNVHR8EKTAnMCWg
# I6Ahhh9odHRwOi8vY3JsLmNlcnR1bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUFBwEB
# BGAwXjAoBggrBgEFBQcwAYYcaHR0cDovL3N1YmNhLm9jc3AtY2VydHVtLmNvbTAy
# BggrBgEFBQcwAoYmaHR0cDovL3JlcG9zaXRvcnkuY2VydHVtLnBsL2N0bmNhMi5j
# ZXIwOQYDVR0gBDIwMDAuBgRVHSAAMCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93d3cu
# Y2VydHVtLnBsL0NQUzANBgkqhkiG9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q7EP8
# 9DttyZqGYn2XRDhJkL6P+/T0IPZyxfxiXumYlARMgwRzLRUStJl490L94C9LGF3v
# jzzH8Jq3iR74BRlkO18J3zIdmCKQa5LyZ48IfICJTZVJeChDUyuQy6rGDxLUUAsO
# 0eqeLNhLVsgw6/zOfImNlARKn1FP7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXdd3x2
# mbJCKKtl2s42g9KUJHEIiLni9ByoqIUul4GblLQigO0ugh7bWRLDm0CdY9rNLqyA
# 3ahe8WlxVWkxyrQLjH8ItI17RdySaYayX3PhRSC4Am1/7mATwZWwSD+B7eMcZNhp
# n8zJ+6MTyE6YoEBSRVrs0zFFIHUR08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vUBfSo
# uCReZwSLo8WdrDlPXtR0gicDnytO7eZ5827NS2x7gCBibESYkOh1/w1tVxTpV2Na
# 3PR7nxYVlPu1JPoRZCbH86gc96UTvuWiOruWmyOEMLOGGniR+x+zPF/2DaGgK2W1
# eEJfo2qyrBNPvF7wuAyQfiFXLwvWHamoYtPZo0LHuH8X3n9C+xN4YaNjt2ywzOr+
# tKyEVAotnyU9vyEVOaIYMk3IeBrmFnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602NCmv
# O1nm+/80nLy5r0AZvCQxaQ4wggbiMIIEyqADAgECAhEA507yVbBQT/rbpt/3/Iuj
# FTANBgkqhkiG9w0BAQwFADBVMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGln
# byBMaW1pdGVkMSwwKgYDVQQDEyNTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5n
# IENBIFI0MTAeFw0yNjAzMjUwMDAwMDBaFw0zNzA2MjQyMzU5NTlaMHIxCzAJBgNV
# BAYTAkdCMRcwFQYDVQQIEw5HcmVhdGVyIExvbmRvbjEYMBYGA1UEChMPU2VjdGln
# byBMaW1pdGVkMTAwLgYDVQQDEydTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5n
# IFNpZ25lciBSMzcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCy/8Nt
# S9xQ2UUtBRF32bj7VK3n4m50Uqjk/zTciSziYV40H1LKah0/oEklYG42E4VCP3Dv
# sBUB6DmpCkDZ0jCnZBPIEevaH15ZJOQwFWP2ZXr5YjlJpb68Nlbs+ElNvKx32/1Y
# Hde3qqUSLybjulxPLz6T85+HOIqK7M1Bep8LspyhEP/q6nw5kGxTSrGvufmeH+JF
# 8CnVBcVMFA40FlIYh0cDJVFhhfTfdWgLy/vWuLMQoKkf3s/FvByf16r0rtbyHm/i
# emwxSioJL9zyZDDKUNAbHXl0dhXo2VxUV2NcPXWXuoKsjL+6cfk6Vm2DHnxAlFdF
# saBDIF1JOkSnC6PeLlBznZn2buF3vIIYJcq6N/zeFRCk4/HXDz7zgRsRRMdUB+rh
# yk5FoZaBjw0nLq3GZ3fClLUx5es5pUAxzNODMBn7JkFYip2BAGBPER5eV0ROhk6t
# GTG+fUiMiV+vgjg1YnP5FvnYWyEtWeQD/B2hp3vz0RvtdkM0p3igyadzrfpOBq5p
# pVk/YsuhTQkP99ivneHAGfi5e7lmxJ+meoBPrRLuzMmb81rzzbESjJHMsn5RVtc6
# Ucs7rcMqQC13PUIO7BbGBETV2ufCmV6lPTp3P7XJOvmnUCRTPbVvMTpxP/z+SOHg
# 4/OCBhiqs4FA9+4oQvlkk9w32NGASli9GWrm5wIDAQABo4IBjjCCAYowHwYDVR0j
# BBgwFoAUOnSlDGfGQlDC/bX8x7spNIL0erkwHQYDVR0OBBYEFGEQ6XoSr1HEhdTy
# z6R0D1DNIK/4MA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB
# /wQMMAoGCCsGAQUFBwMIMEoGA1UdIARDMEEwCAYGZ4EMAQQCMDUGDCsGAQQBsjEB
# AgEDCDAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28uY29tL0NQUzBKBgNV
# HR8EQzBBMD+gPaA7hjlodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJs
# aWNUaW1lU3RhbXBpbmdDQVI0MS5jcmwwegYIKwYBBQUHAQEEbjBsMEUGCCsGAQUF
# BzAChjlodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3Rh
# bXBpbmdDQVI0MS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28u
# Y29tMA0GCSqGSIb3DQEBDAUAA4ICAQAD6j2N0azN+hl6k6bKB5/U6VuSOs93ZBb3
# Pczy9VtBIKu4947Z5GwL0aFngIxl+GSuLFrJgPruBCRvKJEJsm7kv+LQ1COVCEG9
# tZ+IRtr4ocUoa53lgdFaENlS0N4wgkZkbQEPv+x+1lSjYh+T4JeL9mUznT7Erc6S
# p5dWLka5sMP/m3GZi6oJPdPcsCKWagH7m2H2xDGIyHJC5PdH9phvi/KmhkktiSVT
# NNqVeV5bWdX2zhRE6UTfz0IcMoCL996lFIydXxOCE4MNDHDM0as4lnTiT/KHMccO
# 6l8c9TnUVgmpci9ar1IABZ2U1XUkYjGGSn9MC3EHDP9V39VuBVvZ33/BEV/EWSRr
# f07T7jFplKX+gQr/UOqPGMlE7ZJ72UaUkNJy7bVl3bcLKzdpjIHzLkf/4MVa1V7w
# 8wqCv5W4gOnRGTlud5UMARbRM8BPxR/CXYXoMmIOD8pmTk2axgRL4LG8XtuchISd
# CHRmtacAmLGq5XSYSVTHTXADlO48iDKh3HM2r98LSF6f0sG12d8V9Jn7C3wDUieO
# xuKj4MdWrW+hiJU2kF87v6eH00HgCFFc2V0+CvfOCMn7juzS41jLaINcBlKWQ/fK
# b/uDLfWOW73z1I2lFY7Xj8tQ1XYtK5eREjWItM8jpl1cbQOc88btR+0XS2TmboE/
# 141+va2PWzGCBcMwggW/AgEBMGowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFz
# c2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2ln
# bmluZyAyMDIxIENBAhAIMk+dt9qRb2Pk8qM8Xl1RMA0GCWCGSAFlAwQCAQUAoIGE
# MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQB
# gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE
# MSIEIJSyO2MLVEWyRTzTOk4SYbygcke5DgEym9hHaB2+fZNjMA0GCSqGSIb3DQEB
# AQUABIIBgGshmM7VnsP7O17nskw/163pdO82pJF0vB4R6MfQ4+Av9E54FU19/S14
# vPxZfSBpMazo2/uvxviGHy3wi0zz7zI9vWnKtpnJSZIBjuzL4J+UDxD1tfyzxwqB
# HNfCZaMls8R2NtL0hFtKozDEHjfVyRRkUobsaLe61CVft4QJjJEHhoNL3FBsTl96
# sNvsxiCeGvUsd6ZmXH6eFtbw1Qk0U6jihHvxLJ5c9N4APZmjfbZi3GdA4C/5UWwl
# bqks/2A9+P9Kzq7x/eOabNz6cCwzTz80Z9LcWE8H/ZxdsU7VNnGiZ16ZMXSfvRRg
# 721se2gejAQmm58pDIgtbXjyhhi/M7YvQb8Jlm68S8cCjf/Cji2YF7MznQY09jtq
# zF25X+iBZSJfiO7ZNaymY03i1ZjuMQVjDQ7E+A/R/3kHQbdc5QWv+Lv8hgWbokA8
# TStzyDPXryNoa4YK+ZDfJHKYhgtQJ/CmXqyK6X9tpb5M9EDQBKVLPS5yGeXzTBYe
# xLo+0R9d76GCAyMwggMfBgkqhkiG9w0BCQYxggMQMIIDDAIBATBqMFUxCzAJBgNV
# BAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3Rp
# Z28gUHVibGljIFRpbWUgU3RhbXBpbmcgQ0EgUjQxAhEA507yVbBQT/rbpt/3/Iuj
# FTANBglghkgBZQMEAgIFAKB5MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJ
# KoZIhvcNAQkFMQ8XDTI2MDcwNzE1Mjc0MlowPwYJKoZIhvcNAQkEMTIEMInkNN5u
# OikRaw+plqVVRa2WB2Q6sFx6EvB5Niv3hK84J3lZXHtj5uN58SnzTDD2DzANBgkq
# hkiG9w0BAQEFAASCAgCOrWvjhDA29y1wnDgLctWc/ZR367bNgC52T95ThM7MWCft
# VhO3N9aLEqY66KbsCrVAfdVLcSg1CreNb68HDzrtCs78FxHJ7IZsgksFMx/H8jXd
# ZLIEjQeeLo5d4CuuJ/Bs8eG5rHMXM3lRunRsldf59si8TEIJNmjg9MXLQlNLIbam
# Ca2RWXN04kVB8+mwtplJ2npIPul+C9WIOvioP+QlrlzJY4HuUN9sWv1L54kppf4+
# IYB5Tm1ZlYwcdAd/aP1tGvHZZo/U0nV9DqylgLPc7t4Z+JG2StAiIFvLDCjQdv29
# iXBkVCVjXnZuRRvh/NRESNtAI1tWiYPId5VrB/jTi/UOsiQ+mnGeAnZ7Wk2kcIOG
# ejZNOUC9STbEenAUtqAMGJ8Pa25qJQ07sqC11jBYtvoDqiA6ZN5lR2AVJtKOAln0
# teinazbq6EHllInApDZNgTaP1wYpukhufkgjquVDQ07tZytQKqYsu/q2MXcwKEak
# 0heT75k3gwTmMmlmV6D6L1wA+qvkZrneCtSI1ebtY3lx63A3imho1GrLTQ4asHXu
# HJMlVe4uP2wnezS6lLsWt0qalgRPh7FJaSzgo2gfRQ7zLcxaSuNG8S47MH4ObDLr
# 33Z6J6hSdHn99mQnbD3QqqlV3h9r539aUWxlUpi33EH895ebdDk+v1Q8y0DKXw==
# SIG # End signature block