public/Convert-SentinelARArmToYaml.ps1

<#
.SYNOPSIS
Converts an Azure Sentinel Analytics Rule ARM template to YAML
 
.DESCRIPTION
Converts an Azure Sentinel Analytics Rule ARM template to YAML.
The ARM template can be provided as a file or as a string.
The YAML file can be saved to the same directory as the ARM template file.
 
.PARAMETER Filename
The path to the Analytics Rule ARM template file
 
.PARAMETER Data
The ARM template data as a string
 
.PARAMETER OutFile
The path to the output YAML file
 
.PARAMETER UseOriginalFilename
If set, the output file will be saved with the original filename of the ARM template file
The extension will be replaced with .yaml
 
.PARAMETER UseDisplayNameAsFilename
If set, the output file will be saved with the display name of the Analytics Rule as filename
The extension will be replaced with .yaml
 
.PARAMETER UseIdAsFilename
If set, the output file will be saved with the id of the Analytics Rule as filename
The extension will be replaced with .yaml
 
.EXAMPLE
Convert-SentinelARArmToYaml -Filename "C:\Temp\MyRule.json" -OutFile "C:\Temp\MyRule.yaml"
Will convert a the file with a single ART to a single YAML-file
 
.EXAMPLE
Convert-SentinelARArmToYaml -Filename "C:\Temp\MyRule.json" -UseOriginalFilename
Will convert a the file with a single ART to a single YAML-file, with the same basename as the supplied JSON (ARM).
 
.EXAMPLE
Get-Content -Path "C:\Temp\MyRule.json" -Raw | Convert-SentinelARArmToYaml -OutFile "C:\Temp\MyRule.yaml"
Will convert JSON ARM-text in the pipeline containg a single ART to a single YAML-file, saved in the supplied filename.
 
.EXAMPLE
Convert-SentinelARArmToYaml -Filename "C:\Temp\MultipleRules.json" -OutFile "C:\Temp\MultipleRules.yaml"
Will create multiple files, one per alert in the file: MultipleRules.yaml, MultipleRules_1.yaml, MultipleRules_2.yaml etc.
 
.EXAMPLE
Convert-SentinelARArmToYaml -Filename "C:\Temp\MultipleRules.json" -UseOriginalFilename
Will create multiple files in the same directory, one per alert in the file names as: MultipleRules.yaml, MultipleRules_1.yaml and MultipleRules_2.yaml.
 
.EXAMPLE
Convert-SentinelARArmToYaml -Filename "C:\Temp\Multiple.json" -UseDisplayNameAsFilename
Will create multiple files in the same directory, one per alert in the file names as: Displaynameofalert1.yaml, Displaynameofalert2.yaml, Displayname3.yaml
 
.EXAMPLE
Convert-SentinelARArmToYaml -Filename "C:\Temp\MyRule.json" -UseIdAsFilename
Will create multiple files in the same directory, one per alert in the file, with the names: 734075d4-1974-4318-b262-5268e36e4f35.yaml, 734075d4-1974-4318-b262-5268e36e4f34.yaml etc.
 
.EXAMPLE
Get-Content -Path "C:\Temp\Multiple.json" -Raw | Convert-SentinelARArmToYaml -OutFile "C:\Temp\MyRule.yaml"
Will create multiple files in the supplied directory, with the prefix mentioned in OutFile, one per alert in the file, with the names: MyRule.yaml, MyRule_1.yaml etc.
 
.EXAMPLE
Get-Content -Path "C:\Temp\Multiple.json" -Raw | Convert-SentinelARArmToYaml -Directory "C:\Temp\" -UseDisplayNameAsFilename
Will create multiple files in the supplied directory, one per alert in the file, with the names: Displaynameofalert1.yaml, Displaynameofalert2.yaml, Displayname3.yaml
 
.EXAMPLE
Get-Content -Path "C:\Temp\Multiple.json" -Raw | Convert-SentinelARArmToYaml -Directory "C:\Temp\" -UseIdAsFilename
Will create multiple files in the supplied directory, one per alert in the file, with the names: 734075d4-1974-4318-b262-5268e36e4f35.yaml, 734075d4-1974-4318-b262-5268e36e4f34.yaml etc.
 
.NOTES
  Author: Fabian Bader (https://cloudbrothers.info/)
#>


function Convert-SentinelARArmToYaml {
    [CmdletBinding(DefaultParameterSetName = 'StdOut')]
    param (
        [Parameter(Mandatory,
            Position = 0,
            ParameterSetName = 'Path')]
        [Parameter(
            Position = 0,
            ParameterSetName = 'UseOriginalFilename')]
        [Parameter(
            Position = 0,
            ParameterSetName = 'UseDisplayNameAsFilename')]
        [Parameter(
            Position = 0,
            ParameterSetName = 'UseIdAsFilename')]
        [Parameter(
            Position = 0,
            ParameterSetName = 'StdOut')]
        [string]$Filename,

        [Alias('Yaml')]
        [Parameter(Mandatory,
            ValueFromPipeline,
            ParameterSetName = 'Pipeline',
            Position = 0)]
        [array]$Data,

        [Parameter(ParameterSetName = 'Path')]
        [Parameter(ParameterSetName = 'Pipeline')]
        [string]$OutFile,

        [Parameter(ParameterSetName = 'UseOriginalFilename')]
        [switch]$UseOriginalFilename,

        [Parameter(ParameterSetName = 'Pipeline')]
        [Parameter(ParameterSetName = 'UseDisplayNameAsFilename')]
        [switch]$UseDisplayNameAsFilename,

        [Parameter(ParameterSetName = 'Pipeline')]
        [Parameter(ParameterSetName = 'UseIdAsFilename')]
        [switch]$UseIdAsFilename,

        [Parameter(ParameterSetName = 'Pipeline')]
        [string]$Directory = $PWD,

        [Parameter(
            ParameterSetName = 'Path')]
        [Parameter(
            ParameterSetName = 'UseOriginalFilename')]
        [Parameter(
            ParameterSetName = 'UseDisplayNameAsFilename')]
        [Parameter(
            ParameterSetName = 'UseIdAsFilename')]
        [Parameter(
            ParameterSetName = 'StdOut')]
        [Parameter(
            ParameterSetName = 'Pipeline')]
        [switch]$Force = $false
    )

    process {

        #region common

        if ($PsCmdlet.ParameterSetName -ne "Pipeline" ) {
            try {
                if (-not (Test-Path $Filename)) {
                    Write-Error -Exception
                }
            } catch {
                throw "File not found"
            }
        }


        # Mapping of Arm property names to YAML when different
        $ValueNameMappingArm2Yaml = [ordered]@{
            "displayName"           = "name"
            "alertRuleTemplateName" = "id"
            "templateVersion"       = "version"
            "techniques"            = "relevantTechniques"
        }

        # Mapping of Arm operator names to YAML when different
        $CompareOperatorArm2Yaml = @{
            "Equals"             = "eq"
            "GreaterThan"        = "gt"
            "GreaterThanOrEqual" = "ge"
            "LessThan"           = "lt"
            "LessThanOrEqual"    = "le"
        }

        # Default sort order, naming is before key rename
        $DefaultSortOrderInYAML = @(
            "id"
            "alertRuleTemplateName", # id
            "displayName", # name
            "version",
            "templateVersion", #version
            "kind",
            "description",
            "severity",
            "requiredDataConnectors",
            "queryFrequency",
            "queryPeriod",
            "triggerOperator",
            "triggerThreshold",
            "tactics",
            "techniques", #relevantTechniques
            "query"
        )

        # Use pipeline data and create a variable containing all parsed strings
        if ($PsCmdlet.ParameterSetName -eq "Pipeline") {
            $FullARM += $Data
        }

        # Use parsed pipeline data if no file was specified (default)
        try {
            if ($PsCmdlet.ParameterSetName -eq "Pipeline") {
                if ($PSVersionTable.PSEdition -ne "Core") {
                    $AnalyticsRuleTemplate = $FullARM | ConvertFrom-Json -Verbose
                } else {
                    $AnalyticsRuleTemplate = $FullARM | ConvertFrom-Json -Depth 99
                }
            } else {
                Write-Verbose "Read file `"$Filename`""

                if ($PSVersionTable.PSEdition -ne "Core") {
                    $AnalyticsRuleTemplate = Get-Content $Filename | ConvertFrom-Json -Verbose
                } else {
                    $AnalyticsRuleTemplate = Get-Content $Filename | ConvertFrom-Json -Depth 99 -Verbose
                }
            }
        } catch {
            throw "Could not convert source file. JSON might be corrupted"
        }

        try {
            if ( (-not $AnalyticsRuleTemplate.resources) ) {
                throw "This template contains no Analytics Rules or resources"
            }
        } catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }

        #endregion common

        #region ART
        $resourceCounter = 0

        foreach ($resource in ( $AnalyticsRuleTemplate.resources | Where-Object { $_.type -eq "Microsoft.OperationalInsights/workspaces/providers/alertRules" } ) ) {
            if ( $resource.kind -notin @("Scheduled", "NRT") ) {
                Write-Warning "Analytics Rule $($resource.properties.displayName) is using an unsupported type `"$($resource.kind)`". Only type `"Scheduled`", `"NRT`" are supported."
                Continue
            } elseif ($resource.kind -eq "NRT") {
                # List of values to always remove for a NRT
                $RemoveArmValues = @(
                    "enabled",
                    "startTimeUtc",
                    "queryFrequency",
                    "queryPeriod",
                    "triggerOperator",
                    "triggerThreshold"
                )
            } else {
                # List of values to always remove
                $RemoveArmValues = @(
                    "enabled"
                )
            }

            # Get the id of the analytic rule
            if ($resource.id -match "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") {
                $Id = $Matches[0]
            } else {
                Write-Warning "Error reading current Id. Generating new Id."
                $Id = (New-Guid).Guid
            }

            Write-Verbose "Convert Analytics Rule: $($resource.properties.displayName) ($($Id)) to YAML file"

            #region Set output filename to defined value if not specified by user
            if ($PsCmdlet.ParameterSetName -in ("UseOriginalFilename", "UseDisplayNameAsFilename", "UseIdAsFilename") ) {
                $FileObject = Get-ChildItem $Filename
                if ($UseOriginalFilename) {
                    # Use original filename as new filename
                    if ($resourceCounter -eq 0) {
                        $NewFileName = $FileObject.Name -replace $FileObject.Extension, ".yaml"
                    } else {
                        $NewFileName = $FileObject.BaseName + "_$resourceCounter" + ".yaml"
                    }
                }
                if ($UseDisplayNameAsFilename) {
                    # Use the display name of the Analytics Rule as filename
                    $NewFileName = $resource.properties.displayName -Replace '[^0-9A-Z]', ' '
                    # Convert To CamelCase
                    $NewFileName = ((Get-Culture).TextInfo.ToTitleCase($NewFileName) -Replace ' ') + '.yaml'
                }
                if ($UseIdAsFilename) {
                    # Use id as of the Analytics Rule filename
                    $NewFileName = $Id + '.yaml'
                }

                $OutFilePath = Join-Path $FileObject.Directory $NewFileName
            } elseif ( $PsCmdlet.ParameterSetName -in ("Pipeline", "Path") -and $OutFile ) {
                $DirectoryName = [System.IO.Path]::GetDirectoryName($OutFile)
                $FileExtension = [System.IO.Path]::GetExtension($OutFile)
                $FileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($OutFile)
                if ($resourceCounter -gt 0) {
                    $NewFileName = "$($FileNameWithoutExtension)_$($resourceCounter)$($FileExtension)"
                    $OutFilePath = Join-Path $DirectoryName $NewFileName
                } else {
                    $OutFilePath = Join-Path $DirectoryName ([System.IO.Path]::GetFileName($OutFile))
                }
            } elseif ($PsCmdlet.ParameterSetName -in ("Pipeline") -and ($UseDisplayNameAsFilename -or $UseIdAsFilename)) {
                if ($UseDisplayNameAsFilename) {
                    # Use the display name of the Analytics Rule as filename
                    $NewFileName = $resource.properties.displayName -Replace '[^0-9A-Z]', ' '
                    # Convert To CamelCase
                    $NewFileName = ((Get-Culture).TextInfo.ToTitleCase($NewFileName) -Replace ' ') + '.yaml'
                }
                if ($UseIdAsFilename) {
                    # Use id as of the Analytics Rule filename
                    $NewFileName = $Id + '.yaml'
                }
                $OutFilePath = Join-Path -Path $Directory -ChildPath $NewFileName
            }
            #endregion

            # Get the properties of the analytic rule
            $AnalyticsRule = $resource | Select-Object -ExpandProperty properties
            # Add the id and kind from the ARM template
            $AnalyticsRule = $AnalyticsRule | Add-Member -MemberType NoteProperty -Name "id" -Value $Id -PassThru -Force
            $AnalyticsRule = $AnalyticsRule | Add-Member -MemberType NoteProperty -Name "kind" -Value $resource.kind -PassThru -Force
            # Add version if not present
            if ( [string]::IsNullOrWhiteSpace($resource.properties.templateVersion) ) {
                $AnalyticsRule = $AnalyticsRule | Add-Member -MemberType NoteProperty -Name "version" -Value "1.0.0" -PassThru -Force
            }
            # Remove values that are not needed
            foreach ($RemoveArmValue in $RemoveArmValues) {
                $AnalyticsRule.PSObject.Properties.Remove($RemoveArmValue) | Out-Null
            }

            $JSON = $AnalyticsRule | ConvertTo-Json -Depth 100
            # Use ISO8601 format for timespan values
            $JSON = $JSON -replace '"PT([0-9]+)M"', '"$1m"' -replace '"PT([0-9]+)H"', '"$1h"' -replace '"P([0-9]+)D"', '"$1d"'

            # Convert the compare operators to the names used in the YAML
            foreach ($Arm2Yaml in $CompareOperatorArm2Yaml.Keys) {
                $JSON = $JSON -replace "`"$Arm2Yaml`"", "`"$($CompareOperatorArm2Yaml[$Arm2Yaml])`""
            }

            # Convert the JSON to a PowerShell object
            $AnalyticsRule = $JSON | ConvertFrom-Json

            # Use custom sort order of YAML
            $ErrorActionPreference = "SilentlyContinue"
            $AnalyticsRuleKeys = $AnalyticsRule.PSObject.Properties.Name | Sort-Object { $i = $DefaultSortOrderInYAML.IndexOf($_) ; if ( $i -eq -1 ) { 100 } else { $i } }
            $ErrorActionPreference = "Continue"
            # Create ordered hashtable
            $AnalyticsRuleCleaned = [ordered]@{}
            foreach ($PropertyName in $AnalyticsRuleKeys) {
                # Remove empty properties
                if ( -not [string]::IsNullOrWhiteSpace($AnalyticsRule.$PropertyName) -or ( $AnalyticsRule.$PropertyName -is [array] -and ($AnalyticsRule.$PropertyName.Count -gt 0) ) ) {
                    # Change the name of the value if needed
                    $KeyName = $ValueNameMappingArm2Yaml[$PropertyName]
                    # If the name is not in the mapping, use the original name
                    if ([string]::IsNullOrWhiteSpace($KeyName)) {
                        $KeyName = $PropertyName
                    }
                    if ( -not $AnalyticsRuleCleaned.Contains($KeyName) ) {
                        $AnalyticsRuleCleaned.Add($KeyName, $AnalyticsRule.$PropertyName)
                    }
                }
            }

            # Convert the PowerShell object to YAML
            $AnalyticsRuleYAML = $AnalyticsRuleCleaned | ConvertTo-Yaml

            #endregion ART

            # Write the YAML to a file or return the YAML
            if ($OutFilePath) {
                $AnalyticsRuleYAML | Out-File $OutFilePath -NoClobber:(-not $Force) -Encoding utf8
                Write-Verbose "Output written to file: `"$OutFilePath`""
            } else {
                $AnalyticsRuleYAML
            }

            $resourceCounter++
        }
    }
}


# SIG # Begin signature block
# MIIn4QYJKoZIhvcNAQcCoIIn0jCCJ84CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU/hwhfIFX9UlAGYafRIuSLaK4
# b4mggiEJMIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjANBgkqhkiG9w0B
# AQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1OTU5WjBiMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz
# 7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS
# 5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7
# bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfI
# SKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jH
# trHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14
# Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2
# h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt
# 6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPR
# iQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ER
# ElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4K
# Jpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/BAUwAwEB/zAd
# BgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgwFoAUReuir/SS
# y4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUFBwEBBG0wazAk
# BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAC
# hjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS
# b290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0
# LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0gBAowCDAGBgRV
# HSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW1/e/Vwe9mqyh
# hyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH38nLeJLxSA8hO
# 0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMTdydE1Od/6Fmo
# 8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY9HdaXFSMb++h
# UD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyerbHbObyMt9H5x
# aiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmUMIIGrjCCBJag
# AwIBAgIQBzY3tyRUfNhHrP0oZipeWzANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQG
# EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
# cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjIw
# MzIzMDAwMDAwWhcNMzcwMzIyMjM1OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UE
# ChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQg
# UlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEAxoY1BkmzwT1ySVFVxyUDxPKRN6mXUaHW0oPRnkyibaCw
# zIP5WvYRoUQVQl+kiPNo+n3znIkLf50fng8zH1ATCyZzlm34V6gCff1DtITaEfFz
# sbPuK4CEiiIY3+vaPcQXf6sZKz5C3GeO6lE98NZW1OcoLevTsbV15x8GZY2UKdPZ
# 7Gnf2ZCHRgB720RBidx8ald68Dd5n12sy+iEZLRS8nZH92GDGd1ftFQLIWhuNyG7
# QKxfst5Kfc71ORJn7w6lY2zkpsUdzTYNXNXmG6jBZHRAp8ByxbpOH7G1WE15/teP
# c5OsLDnipUjW8LAxE6lXKZYnLvWHpo9OdhVVJnCYJn+gGkcgQ+NDY4B7dW4nJZCY
# OjgRs/b2nuY7W+yB3iIU2YIqx5K/oN7jPqJz+ucfWmyU8lKVEStYdEAoq3NDzt9K
# oRxrOMUp88qqlnNCaJ+2RrOdOqPVA+C/8KI8ykLcGEh/FDTP0kyr75s9/g64ZCr6
# dSgkQe1CvwWcZklSUPRR8zZJTYsg0ixXNXkrqPNFYLwjjVj33GHek/45wPmyMKVM
# 1+mYSlg+0wOI/rOP015LdhJRk8mMDDtbiiKowSYI+RQQEgN9XyO7ZONj4KbhPvbC
# dLI/Hgl27KtdRnXiYKNYCQEoAA6EVO7O6V3IXjASvUaetdN2udIOa5kM0jO0zbEC
# AwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLoW2W1N
# hS9zKXaaL3WMaiCPnshvMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAI
# BgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQB9WY7Ak7Zv
# mKlEIgF+ZtbYIULhsBguEE0TzzBTzr8Y+8dQXeJLKftwig2qKWn8acHPHQfpPmDI
# 2AvlXFvXbYf6hCAlNDFnzbYSlm/EUExiHQwIgqgWvalWzxVzjQEiJc6VaT9Hd/ty
# dBTX/6tPiix6q4XNQ1/tYLaqT5Fmniye4Iqs5f2MvGQmh2ySvZ180HAKfO+ovHVP
# ulr3qRCyXen/KFSJ8NWKcXZl2szwcqMj+sAngkSumScbqyQeJsG33irr9p6xeZmB
# o1aGqwpFyd/EjaDnmPv7pp1yr8THwcFqcdnGE4AJxLafzYeHJLtPo0m5d2aR8XKc
# 6UsCUqc3fpNTrDsdCEkPlM05et3/JWOZJyw9P2un8WbDQc1PtkCbISFA0LcTJM3c
# HXg65J6t5TRxktcma+Q4c6umAU+9Pzt4rUyt+8SVe+0KXzM5h0F4ejjpnOHdI/0d
# KNPH+ejxmF/7K9h+8kaddSweJywm228Vex4Ziza4k9Tm8heZWcpw8De/mADfIBZP
# J/tgZxahZrrdVcA6KYawmKAr7ZVBtzrVFZgxtGIJDwq9gdkT/r+k0fNX2bwE+oLe
# Mt8EifAAzV3C+dAjfwAL5HYCJtnwZXZCpimHCUcr5n8apIUP/JiW9lVUKx+A+sDy
# Divl1vupL0QVSucTDh3bNzgaoSv27dZ8/DCCBrAwggSYoAMCAQICEAitQLJg0pxM
# n17Nqb2TrtkwDQYJKoZIhvcNAQEMBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoT
# DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UE
# AxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIxMDQyOTAwMDAwMFoXDTM2
# MDQyODIzNTk1OVowaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBS
# U0E0MDk2IFNIQTM4NCAyMDIxIENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
# AgoCggIBANW0L0LQKK14t13VOVkbsYhC9TOM6z2Bl3DFu8SFJjCfpI5o2Fz16zQk
# B+FLT9N4Q/QX1x7a+dLVZxpSTw6hV/yImcGRzIEDPk1wJGSzjeIIfTR9TIBXEmtD
# mpnyxTsf8u/LR1oTpkyzASAl8xDTi7L7CPCK4J0JwGWn+piASTWHPVEZ6JAheEUu
# oZ8s4RjCGszF7pNJcEIyj/vG6hzzZWiRok1MghFIUmjeEL0UV13oGBNlxX+yT4Us
# SKRWhDXW+S6cqgAV0Tf+GgaUwnzI6hsy5srC9KejAw50pa85tqtgEuPo1rn3MeHc
# reQYoNjBI0dHs6EPbqOrbZgGgxu3amct0r1EGpIQgY+wOwnXx5syWsL/amBUi0nB
# k+3htFzgb+sm+YzVsvk4EObqzpH1vtP7b5NhNFy8k0UogzYqZihfsHPOiyYlBrKD
# 1Fz2FRlM7WLgXjPy6OjsCqewAyuRsjZ5vvetCB51pmXMu+NIUPN3kRr+21CiRshh
# WJj1fAIWPIMorTmG7NS3DVPQ+EfmdTCN7DCTdhSmW0tddGFNPxKRdt6/WMtyEClB
# 8NXFbSZ2aBFBE1ia3CYrAfSJTVnbeM+BSj5AR1/JgVBzhRAjIVlgimRUwcwhGug4
# GXxmHM14OEUwmU//Y09Mu6oNCFNBfFg9R7P6tuyMMgkCzGw8DFYRAgMBAAGjggFZ
# MIIBVTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRoN+Drtjv4XxGG+/5h
# ewiIZfROQjAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8B
# Af8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYIKwYBBQUHAQEEazBpMCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKG
# NWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290
# RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMBwGA1UdIAQVMBMwBwYFZ4EMAQMw
# CAYGZ4EMAQQBMA0GCSqGSIb3DQEBDAUAA4ICAQA6I0Q9jQh27o+8OpnTVuACGqX4
# SDTzLLbmdGb3lHKxAMqvbDAnExKekESfS/2eo3wm1Te8Ol1IbZXVP0n0J7sWgUVQ
# /Zy9toXgdn43ccsi91qqkM/1k2rj6yDR1VB5iJqKisG2vaFIGH7c2IAaERkYzWGZ
# gVb2yeN258TkG19D+D6U/3Y5PZ7Umc9K3SjrXyahlVhI1Rr+1yc//ZDRdobdHLBg
# XPMNqO7giaG9OeE4Ttpuuzad++UhU1rDyulq8aI+20O4M8hPOBSSmfXdzlRt2V0C
# FB9AM3wD4pWywiF1c1LLRtjENByipUuNzW92NyyFPxrOJukYvpAHsEN/lYgggnDw
# zMrv/Sk1XB+JOFX3N4qLCaHLC+kxGv8uGVw5ceG+nKcKBtYmZ7eS5k5f3nqsSc8u
# pHSSrds8pJyGH+PBVhsrI/+PteqIe3Br5qC6/To/RabE6BaRUotBwEiES5ZNq0RA
# 443wFSjO7fEYVgcqLxDEDAhkPDOPriiMPMuPiAsNvzv0zh57ju+168u38HcT5uco
# P6wSrqUvImxB+YJcFWbMbA7KxYbD9iYzDAdLoNMHAmpqQDBISzSoUSC7rRuFCOJZ
# DW3KBVAr6kocnqX9oKcfBnTn8tZSkP2vhUgh+Vc7tJwD7YZF9LRhbr9o4iZghurI
# r6n+lB3nYxs6hlZ4TjCCBsIwggSqoAMCAQICEAVEr/OUnQg5pr/bP1/lYRYwDQYJ
# KoZIhvcNAQELBQAwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2
# IFRpbWVTdGFtcGluZyBDQTAeFw0yMzA3MTQwMDAwMDBaFw0zNDEwMTMyMzU5NTla
# MEgxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEgMB4GA1UE
# AxMXRGlnaUNlcnQgVGltZXN0YW1wIDIwMjMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
# DwAwggIKAoICAQCjU0WHHYOOW6w+VLMj4M+f1+XS512hDgncL0ijl3o7Kpxn3GIV
# WMGpkxGnzaqyat0QKYoeYmNp01icNXG/OpfrlFCPHCDqx5o7L5Zm42nnaf5bw9Yr
# IBzBl5S0pVCB8s/LB6YwaMqDQtr8fwkklKSCGtpqutg7yl3eGRiF+0XqDWFsnf5x
# XsQGmjzwxS55DxtmUuPI1j5f2kPThPXQx/ZILV5FdZZ1/t0QoRuDwbjmUpW1R9d4
# KTlr4HhZl+NEK0rVlc7vCBfqgmRN/yPjyobutKQhZHDr1eWg2mOzLukF7qr2JPUd
# vJscsrdf3/Dudn0xmWVHVZ1KJC+sK5e+n+T9e3M+Mu5SNPvUu+vUoCw0m+PebmQZ
# BzcBkQ8ctVHNqkxmg4hoYru8QRt4GW3k2Q/gWEH72LEs4VGvtK0VBhTqYggT02ke
# fGRNnQ/fztFejKqrUBXJs8q818Q7aESjpTtC/XN97t0K/3k0EH6mXApYTAA+hWl1
# x4Nk1nXNjxJ2VqUk+tfEayG66B80mC866msBsPf7Kobse1I4qZgJoXGybHGvPrhv
# ltXhEBP+YUcKjP7wtsfVx95sJPC/QoLKoHE9nJKTBLRpcCcNT7e1NtHJXwikcKPs
# CvERLmTgyyIryvEoEyFJUX4GZtM7vvrrkTjYUQfKlLfiUKHzOtOKg8tAewIDAQAB
# o4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/
# BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcB
# MB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQWBBSltu8T
# 5+/N0GSh1VapZTGj3tXjSTBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsMy5k
# aWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGltZVN0
# YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcwAYYYaHR0
# cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8vY2FjZXJ0
# cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2VGlt
# ZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCBGtbeoKm1mBe8cI1P
# ijxonNgl/8ss5M3qXSKS7IwiAqm4z4Co2efjxe0mgopxLxjdTrbebNfhYJwr7e09
# SI64a7p8Xb3CYTdoSXej65CqEtcnhfOOHpLawkA4n13IoC4leCWdKgV6hCmYtld5
# j9smViuw86e9NwzYmHZPVrlSwradOKmB521BXIxp0bkrxMZ7z5z6eOKTGnaiaXXT
# UOREEr4gDZ6pRND45Ul3CFohxbTPmJUaVLq5vMFpGbrPFvKDNzRusEEm3d5al08z
# jdSNd311RaGlWCZqA0Xe2VC1UIyvVr1MxeFGxSjTredDAHDezJieGYkD6tSRN+9N
# UvPJYCHEVkft2hFLjDLDiOZY4rbbPvlfsELWj+MXkdGqwFXjhr+sJyxB0JozSqg2
# 1Llyln6XeThIX8rC3D0y33XWNmdaifj2p8flTzU8AL2+nCpseQHc2kTmOt44Owde
# OVj0fHMxVaCAEcsUDH6uvP6k63llqmjWIso765qCNVcoFstp8jKastLYOrixRoZr
# uhf9xHdsFWyuq69zOuhJRrfVf8y2OMDY7Bz1tqG4QyzfTkx9HmhwwHcK1ALgXGC7
# KP845VJa1qwXIiNO9OzTF/tQa/8Hdx9xl0RBybhG02wyfFgvZ0dl5Rtztpn5aywG
# Ru9BHvDwX+Db2a2QgESvgBBBijCCB0gwggUwoAMCAQICEAqCMJBHqzYjysMfsj2s
# 65owDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lD
# ZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2ln
# bmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENBMTAeFw0yMjA1MTgwMDAwMDBaFw0y
# NTA1MTcyMzU5NTlaME0xCzAJBgNVBAYTAkRFMRAwDgYDVQQHEwdIYW1idXJnMRUw
# EwYDVQQKEwxGYWJpYW4gQmFkZXIxFTATBgNVBAMTDEZhYmlhbiBCYWRlcjCCAiIw
# DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMEjxUm2ziBQmPNov//zacCKh7+x
# iQg6o7vgiYO0wKIw1dgg0s+9qjdj2I1eWp34f43iVRrQOwawnilpC81R/T+KVudo
# 7hIcEXu327+7Pj5KU4SwXcEF4fyRxF6SL/Yy5Dhh92Ma8aCEuW3Z8yn7slK/Kify
# flOmvUoJDpBTTOqRhxRmNk7EQPMDI03aYWBkyn+fOYR+Jg+/xqtXW+9iEA6aKCl2
# D2aG0PiwLi7Rf0YE1R9WLYP+tZndvKVd+UMmbowHHKGCY4A1s3nMezu3HWLsAJbD
# 34ei/mBVTsfS8rmlRUIOsOt3tQzWBILiJj5erVrQDDh5oF8XeGuMPx9Lb1JKozh+
# SuHVOAlpnCEp5HYdrHRzcMfII32Ht08KVsZRTlNRxGnuhwDRerVbSMlYUEcEuydQ
# zSTtSiDhmQwYSoYj5Ja9e1jJ0eXmBAjnXGnJBw0/cmSd0B5E9BjGtyp2UkeekYTm
# 8+zR/JZSUUmf9sswzjwIYhTqyo2T2GC/EeduPGZMTsW0yVqU4Vud3SjzOm/TaLao
# iVua7uHjlatMo0jHbTy7bUALuhxLTSiNfdAIr+Vzz0GDMiNTx1S2ObFe6Zt+oSKW
# ORFttx5ybN9NuAIheORTdZsR5VJJu3PgmLDQG58kSZSHFMhddtWpBTVbhkFMGyEJ
# 7ZkKGv5MXf76WNqRAgMBAAGjggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG
# +/5hewiIZfROQjAdBgNVHQ4EFgQU9QqUwn2Wwx5W7kpA5piphcoCDjkwDgYDVR0P
# AQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BR
# oE+GTWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENv
# ZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8v
# Y3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT
# QTQwOTZTSEEzODQyMDIxQ0ExLmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcG
# CCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUF
# BwEBBIGHMIGEMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwG
# A1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAAnByFUoYIEa6FQ8Yvkg3yTq
# lTNmfdk9uWL/MclhjFJteGxhDgYJ3x4iFCZivQaZtJP4Z2BKVeSJZfP0X9tJfsr5
# cE49ZuLLdka+HPOEUhpq1vN5pGMTIYOWC7S4mXWnzQnJLvftunYGed+yu2A4RriH
# loO9PFXIQJwxG/ApYqljGC8OieL2oEPdROXpj6auxYQ7NqjTBJZ641lC40JGERpQ
# 38NtgHKbLQqxAZHPtfFmZegQ9jsNQ9cITFej9eO9mYE3KOj3KfASoa6qKFF6tgg8
# GFyW/4hTcEZ7uwrO/LvnZS7vrFzcbGxrj0/SIPuTrwtRYhzHjmuGkvB2UqU27uMP
# HHJS/RHV6pYFVxQtj5Q4q/9lvVY/bOrYkqd4XZR+3SDuRREIfCS63TxcCl2fHa4L
# z9ujH9dYwr8pDPkEJvY4Qf1YQJ8IrJIFKHSJH6MdEYybQkJDU9aF6iVRK8AapfyF
# UFDUAke0O3wPFNo1PBQU5GK8lEAEGiBKsWr93PhqjPeE4dwuRb6zruAlNvPx2bk4
# 2WQgT+7NhFv/+G0VEEp1weJmrM1Efv/Efm1/vsnx/zrtR0C+WhqPati9+qGlRY2j
# NvNsNacxQBFH9I4KVJAXiThVHhb4q2FsqnsfqCsMvFc4LPgSnexTkoS2u8AmotX4
# 32L6TuxsW6XWCampzTJgMYIGQjCCBj4CAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUG
# A1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQg
# RzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAKgjCQR6s2
# I8rDH7I9rOuaMAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAA
# MBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgor
# BgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBSUiQ5PluctwdAywcVEZQzh44FbsTAN
# BgkqhkiG9w0BAQEFAASCAgAYbT5MEohgQO1WdmM2DIVvPbnstNbEq0w7+eXbTTMs
# /msC7tA+88neN6eA8kci5z8bzfo32Yk3+myUrrTq8n4jCx53+I3jHjL6f/n2YVvX
# Tb06S3PWN40zVs94aaK18FOxkSsOqAjRaodS2AvDdllA72jx5WMurrgcUnoKJCEZ
# CqABblmAn04/ojg7JVxoEI4wbu+dmgkQ4fnK4t8i1j5ysGSkY5CvYMt8pJOwshsA
# qxgmKh9CxWEzF1dTc1H+tg5+3/+LRLisfJ3uW8wp9O+9QBEKvg23LgQFzdR7QnT2
# fGlB7LttZBISpdp6PFNoMkeBKdxWt4ljRrJELbAJFFW2kZINKtRHvbMB8kPYKt3X
# ijofSrOnapWfvMLkx0VMrQx+FnOjFh3l+rWoX0HdIDVXwhB34zVYa/wVJf4xOlzt
# nEtv33WiPjcaRokaNB50wxhqZLlXJQUjLWiDtNTqClKs4aW6xqxt1w3DtSCJEX+8
# Ker5qhFQuXP2rCAn+UP5zYOd0t7jod4Vhdi3gEvk65yrIER+oPOrS+cg3lZLDmig
# BPE6QIqAJAf4WfHcQJFb3DaduNDEgSlg6qM9CsUrgd1B6c7FtW8I9WirpowlVgdb
# 9HSpWc5gpkbLiN4gCSnpJm/DYn6DxWPSQJC4CwFzLiKKGepi7EXbDl0bYAwJdHJJ
# KaGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAVEr/OUnQg5
# pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcN
# AQcBMBwGCSqGSIb3DQEJBTEPFw0yMzExMTcyMTU3NTJaMC8GCSqGSIb3DQEJBDEi
# BCBCtiPWe+FE0SUW2fr0DyfILUNPAkm9IeP36dMihqaSYjANBgkqhkiG9w0BAQEF
# AASCAgBMFwzB2dI+5n7UyD5/Hi/j2AJLqfgekp+0zfreyQrOUPztfPyZb11Vo1qe
# e6sE/o5ECbDPv84Y40Qho00Un5yxtjsQq19S3k02VvHAzPQjyKcSME4tSM5FjCiv
# q3CGr3hgc8d/cP5epPZmci4ztqPddbLr2HkNzTAUcbC9X9+gVmeoez1XxeveS27T
# mC//B59JNdzSFvteFHVt9KwV0Hz6xHVenupjojnfDriF3OenHVGjSiLDIbP76IY7
# xHy3uIHzVdsOXMCcgwWiRcvPnjyCZCgWk4LU2s3TCgrFZR4gj4SajMvU7mC91fkK
# GII+yfLO2UwRAFFmqrY6dTYH2lQOR4UTAphac8uZiqMKsx/rbHwzKHC7QhOM7CQ3
# ItMmUXAwRlkEjuZZuwds2nFznU2heBpHw06UgLyrlyOcdg0+8VetIHtSF3kZlc9h
# PbfH5GzLhihwsjx6nHHhpwoCNPVSYi8xt3D8oSLCBcUy8/1ere6KUpb3o8LJBhQh
# glV6+lHMwxbVaK+dr1G7YQBe3O827B2DEfbgSo2/SUMp9C5k3P9Vja6bvjuZW9Qw
# 2wHXnFzCHmeSbfx4x8vzCV5TLRnQ7IxgKMoHq8QfIpdPLl+wa/yUdsU2+J8qdxR6
# gicaHeSRcCVtpfkglYgURBYMBwL0hiMOG75ZeqJWUdFAVh724g==
# SIG # End signature block