SimplePSLogger.AzLogAnalytics/SimplePSLogger.AzLogAnalytics.psm1

<#
    Source - https://docs.microsoft.com/en-us/azure/azure-monitor/platform/data-collector-api#powershell-sample
#>

Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource) {
    $xHeaders = "x-ms-date:" + $date
    $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource

    $bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
    $keyBytes = [Convert]::FromBase64String($sharedKey)

    $sha256 = New-Object System.Security.Cryptography.HMACSHA256
    $sha256.Key = $keyBytes
    $calculatedHash = $sha256.ComputeHash($bytesToHash)
    $encodedHash = [Convert]::ToBase64String($calculatedHash)
    $authorization = 'SharedKey {0}:{1}' -f $customerId, $encodedHash
    return $authorization
}

<#
     
#>

Function Send-LogAnalyticsData($customerId, $sharedKey, $body, $logType) {
    $method = "POST"
    $contentType = "application/json"
    $resource = "/api/logs"
    $rfc1123date = [DateTime]::UtcNow.ToString("r")
    $contentLength = $body.Length
    $signature = Build-Signature `
        -customerId $customerId `
        -sharedKey $sharedKey `
        -date $rfc1123date `
        -contentLength $contentLength `
        -method $method `
        -contentType $contentType `
        -resource $resource
    
    if (-Not  $logType) {
        $logType = "SimplePSLogger"
    }
    $headers = @{
        "Authorization" = $signature;
        "Log-Type"      = $logType;
        "x-ms-date"     = $rfc1123date;
    }
    $endpoint = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"
    try {
        $params = @{
            "ContentType" = "application/json"
            "Header"      = $headers
            "Body"        = $body
            "Method"      = "POST"
            "URI"         = $endpoint
        }

        # TODO : add status code check and resiliency
        $response = Invoke-RestMethod @params
    }
    catch {
        throw
    }
}

<#
.SYNOPSIS
    AzLogAnalytics logging provider for SimplePSLogger logger
    This provider send log to Azure Log Analytics https://dev.loganalytics.io/documentation/Overview
    Use - https://docs.microsoft.com/en-us/azure/azure-monitor/platform/data-collector-api
    https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-store-custom-rest-api
    Limits - https://docs.microsoft.com/en-us/azure/azure-monitor/platform/data-collector-api#data-limits
.DESCRIPTION
    TODO: add more details
    More details
 
 
.PARAMETER Name
    SimplePSLogger action execution id which is used to identify executing action
.PARAMETER Level
    Log level : allowed values are - "verbose", "debug", "information", "warning", "error", "critical", "none"
    TODO : add more infor for each level
.PARAMETER Message
    Log message :
        String - plain text string
        OtherTypes - json serialized string
 
.PARAMETER Config
    Required configuration provided by user
 
.EXAMPLE
    TODO : Add examples
 
.NOTES
    Author: Ganesh Raskar
     
#>

Function New-AzLogAnalytics-Logger {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Name of the SimplePSLogger instance")]
        [string]
        $Name,
        [Parameter(Mandatory = $false, HelpMessage = "Log level, default value is information")]
        [string]
        $Level,
        [Parameter(Mandatory = $true, HelpMessage = "Log message")]
        [string]
        $Message,
        [Parameter(Mandatory = $false, HelpMessage = "Configuration object")]
        [object]
        $Config,
        # Flush logs to make sure all logs gets logged
        [Parameter(Mandatory = $false)]
        [Switch]
        $Flush
    )

    # TODO : SimpleLogger will pass on current provider name(my name :P)
    #$CurrentProviderName = "AzLogAnalytics"
    #$bufferedFile = Join-Path $([system.io.path]::GetTempPath()) -ChildPath "$Name.$CurrentProviderName.log"

    # TODO : refactor?
    if (-Not $Config) {
        Write-Warning "Configuration not provided for 'AzLogAnalytics' provider"
    }
    if (-Not $Config['WorkspaceId']) {
        Write-Warning "Azure Log Analytics WorkspaceId not provided for 'AzLogAnalytics' provider"
    }

    if (-Not $Config['WorkspaceKey']) {
        Write-Warning "Azure Log Analytics WorkspaceKey(secret) not provided for 'AzLogAnalytics' provider"
    }
    if (-Not $Config['LogType']) {
        $Config['LogType'] = "SimplePSLogger"
    }
    if (-Not $Config["BufferSize"]) {
        $Config["BufferSize"] = 20
    }

    $logMessage = "$((Get-Date).ToUniversalTime().ToString("yyyy/MM/ddHH:mm:ss:fffftt"))`t$Name`t$($Level)`t$($Message.Trim())"

    $bufferFileName = Join-Path $([system.io.path]::GetTempPath()) -ChildPath "$Name-az-loganalytics.log"
    
    if (-Not $(Test-Path -Path $bufferFileName)) {
        New-Item $bufferFileName -ItemType file
    }

    $currentBufferSize = Get-Content $bufferFileName | Measure-Object â€“Line
    $LogsToFlush = New-Object Collections.Generic.List[Object]

    if ($currentBufferSize.Lines -ge $Config["BufferSize"] -or $Flush) {
        # in case, someone modify file and added line
        if (-Not $Flush) {
            Add-Content -Path $bufferFileName -Value $logMessage
        }

        $content = Get-Content -Path $bufferFileName

        foreach ($line in $content) {
            # Help : do we need to replace `t with 'space char'? What if message contains `t char?
            $log = $line.Split("`t")
            if ($log.Count -gt 4 -or $log.Count -lt 4) {
                Write-Information "Malformed log entry found. Logging entire content as log message"
                $lineItem = New-Object -TypeName psobject
                $lineItem | Add-Member -MemberType NoteProperty -Name Timestamp -Value $((Get-Date).ToUniversalTime().ToString("yyyy/MM/dd HH:mm:ss:ffff tt"))
                $lineItem | Add-Member -MemberType NoteProperty -Name LoggerName -Value $Name
                $lineItem | Add-Member -MemberType NoteProperty -Name Level -Value "information"
                $lineItem | Add-Member -MemberType NoteProperty -Name Message -Value $line
                $LogsToFlush.Add($lineItem)
            }
            else {
                $lineItem = New-Object -TypeName psobject
                $lineItem | Add-Member -MemberType NoteProperty -Name Timestamp -Value $log[0].Trim()
                $lineItem | Add-Member -MemberType NoteProperty -Name LoggerName -Value $log[1].Trim()
                $lineItem | Add-Member -MemberType NoteProperty -Name Level -Value $log[2].Trim()
                $lineItem | Add-Member -MemberType NoteProperty -Name Message -Value $log[3]
                $LogsToFlush.Add($lineItem)
            }
        }
        $payLoad = $LogsToFlush | ConvertTo-Json -Depth 20 -Compress -AsArray
        #Flush to LAWS
        try {
            Send-LogAnalyticsData -customerId $Config['WorkspaceId'] -sharedKey $Config['WorkspaceKey'] -body $payLoad -logType $Config['LogType']    
            Remove-Item -Path $bufferFileName -Force
        }
        catch {
            throw
        }
        finally {
            # TODO : retains logs and try agian
        }
    }
    else {
        Add-Content -Path $bufferFileName -Value $logMessage
    }
}

# TODO : is there any way to ignore other functions export?
Export-ModuleMember -Function New-AzLogAnalytics-Logger, Send-LogAnalyticsData, Build-Signature

# SIG # Begin signature block
# MIIYYQYJKoZIhvcNAQcCoIIYUjCCGE4CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUyr106LKmyowO8RXzcG1C7Q6R
# TP6gghKzMIIFcDCCA1igAwIBAgIQZDtcNubuNpVEfXOVHbciTzANBgkqhkiG9w0B
# AQsFADBQMSUwIwYDVQQDDBxHYW5lc2ggUmFza2FyIFNpbXBsZVBTTG9nZ2VyMScw
# JQYJKoZIhvcNAQkBFhhnYW5lc2hyYXNrYXJAb3V0bG9vay5jb20wHhcNMjAwNzA0
# MTgzMjE0WhcNMjEwNzA0MTg1MjE0WjBQMSUwIwYDVQQDDBxHYW5lc2ggUmFza2Fy
# IFNpbXBsZVBTTG9nZ2VyMScwJQYJKoZIhvcNAQkBFhhnYW5lc2hyYXNrYXJAb3V0
# bG9vay5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCywhH9ot8P
# 9i7rClMbjRwIH1DeVotpNCscFvcSqEbMo3AtJ0WFOb13Xt/g1d0axBa3DvriedVR
# KF2AD0U8jGIsToPBnIdBKVXQ72CN+i1BHrqu9DOI7+csE0hVFAqYCv7qIXgAGued
# FQOe1oD5q3H3GlzLDfwhk/cdLCt+WEgzWdtTca8kirNoRftSNrl7R6MEEZIRY9d4
# /J1RhjKoDfyWn2+8KcAtAkLOND5yjRPzY+jM2IEe4uELQbzbdYASmJYZq6h8Dy/6
# w/A3LRpi06kzceTao8xyUax3K11Sw/QkljLr2BKpwO8JNMMUTLNx5mAZBRyfvFvG
# dZDjnnIrK2vr/qBB+cGb5S7son0N90Xc0yXyDWqAEQqMT4C0jX8FDGv//ZafbsHj
# wSEV//L8E6GBP76QxfX+TTZIik0wy52Gf79ok1v3s7PgE2SyaE+iY0klYzo0bBKK
# B2MPTQ2dmRy9peTzeC+KP7DWxm/9fF7OjsXY2FdWnIQL/qW6YEN5g+c+mTV/8OqB
# w3xiK5WUHsd39GAGKNaHfKdUQ0nPrzDdsxGlwBlqmX+kPj7b+hZR+NBiwQLyPEk+
# qK3YKNF182GLjUj0uH5FsGrT6d+UteyB2OibxH3I2rN/KKHqQFKbC8Lbw4SbY154
# pLDc5Tfn+4GcHbpnN8pjEIsOF6Efa3/yxQIDAQABo0YwRDAOBgNVHQ8BAf8EBAMC
# B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFJuHGRIu8rLatkA/b+kP
# R1QXu0RlMA0GCSqGSIb3DQEBCwUAA4ICAQAjr84JfMOG6r3jzwLl6z0nKmL5faE+
# 02U2QIcOHOlHnXOtMBmLm4IX91gOG93rBCB94GRVOUFMhAx1xkLa6moHIimfyaEU
# 2UoLKVNtqrMymq6ZlQ/Yg8tPMEZfDN9wq/3EewJu+OvQvkMPRuC4LTEZs2GIHQbJ
# urAzp7YT7agchAo/lm65Alg4e8QNALPcx3hkwuZtYs/FI6PEqlzpFF5RFy8Prys8
# gO4yAR3BHrz5Ri5wj9QD54kLdCvw50jJwWu+d4no2kMcKa90zjie4RBS7v4LwLdS
# Yg1RMKT9yE53ch7lef/0Oj/m09mnIp+KVhtb0pe3ZOCDVvl7MCq7JWj/IGa1SMOP
# 1BPxaP4FBr3vB6eE4WVyOETtT3heJScETmMXlDl7MQgc8KXz2B6iOPYMkblt3d7W
# jtwIRrkujL468qzmxrlPTcEPKLqpfHaYGD3lHfwQm59KBEAqiqf/Rrya3uQmkG+a
# pNoG9sWRF/M0BsOnRj31gpJqysvrHRlIf2XQebQqWY3ztVDsht+GQ8kEZnDic6j7
# oP6eSdjVG/Itv7w8ZHbqeRbXmE5mbzw1kTl3ZCPsPgmIU5r/PMcsH6H+aWJNwann
# 5f+MZfM1Yozmot2z8Z65AnWB+0EfLv97Y70s1TWGaUsFk1e36f2sh/bLlSIFjcaG
# 8DViFGT0BzLX4jCCBmowggVSoAMCAQICEAMBmgI6/1ixa9bV6uYX8GYwDQYJKoZI
# hvcNAQEFBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
# MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNz
# dXJlZCBJRCBDQS0xMB4XDTE0MTAyMjAwMDAwMFoXDTI0MTAyMjAwMDAwMFowRzEL
# MAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSUwIwYDVQQDExxEaWdpQ2Vy
# dCBUaW1lc3RhbXAgUmVzcG9uZGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
# CgKCAQEAo2Rd/Hyz4II14OD2xirmSXU7zG7gU6mfH2RZ5nxrf2uMnVX4kuOe1Vpj
# WwJJUNmDzm9m7t3LhelfpfnUh3SIRDsZyeX1kZ/GFDmsJOqoSyyRicxeKPRktlC3
# 9RKzc5YKZ6O+YZ+u8/0SeHUOplsU/UUjjoZEVX0YhgWMVYd5SEb3yg6Np95OX+Ko
# ti1ZAmGIYXIYaLm4fO7m5zQvMXeBMB+7NgGN7yfj95rwTDFkjePr+hmHqH7P7IwM
# Nlt6wXq4eMfJBi5GEMiN6ARg27xzdPpO2P6qQPGyznBGg+naQKFZOtkVCVeZVjCT
# 88lhzNAIzGvsYkKRrALA76TwiRGPdwIDAQABo4IDNTCCAzEwDgYDVR0PAQH/BAQD
# AgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwggG/BgNV
# HSAEggG2MIIBsjCCAaEGCWCGSAGG/WwHATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBz
# Oi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwggFkBggrBgEFBQcCAjCCAVYeggFSAEEA
# bgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEA
# dABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMA
# ZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMA
# IABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEA
# ZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEA
# YgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEA
# dABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4w
# CwYJYIZIAYb9bAMVMB8GA1UdIwQYMBaAFBUAEisTmLKZB+0e36K+Vw0rZwLNMB0G
# A1UdDgQWBBRhWk0ktkkynUoqeRqDS/QeicHKfTB9BgNVHR8EdjB0MDigNqA0hjJo
# dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNy
# bDA4oDagNIYyaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEQ0EtMS5jcmwwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3J0MA0GCSqGSIb3DQEB
# BQUAA4IBAQCdJX4bM02yJoFcm4bOIyAPgIfliP//sdRqLDHtOhcZcRfNqRu8WhY5
# AJ3jbITkWkD73gYBjDf6m7GdJH7+IKRXrVu3mrBgJuppVyFdNC8fcbCDlBkFazWQ
# EKB7l8f2P+fiEUGmvWLZ8Cc9OB0obzpSCfDscGLTYkuw4HOmksDTjjHYL+NtFxMG
# 7uQDthSr849Dp3GdId0UyhVdkkHa+Q+B0Zl0DSbEDn8btfWg8cZ3BigV6diT5VUW
# 8LsKqxzbXEgnZsijiwoc5ZXarsQuWaBh3drzbaJh6YoLbewSGL33VVRAA5Ira8JR
# wgpIr7DUbuD0FAo6G+OPPcqvao173NhEMIIGzTCCBbWgAwIBAgIQBv35A5YDreoA
# Cus/J7u6GzANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM
# RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQD
# ExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcN
# MjExMTEwMDAwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg
# SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2Vy
# dCBBc3N1cmVkIElEIENBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDogi2Z+crCQpWlgHNAcNKeVlRcqcTSQQaPyTP8TUWRXIGf7Syc+BZZ3561JBXC
# mLm0d0ncicQK2q/LXmvtrbBxMevPOkAMRk2T7It6NggDqww0/hhJgv7HxzFIgHwe
# og+SDlDJxofrNj/YMMP/pvf7os1vcyP+rFYFkPAyIRaJxnCI+QWXfaPHQ90C6Ds9
# 7bFBo+0/vtuVSMTuHrPyvAwrmdDGXRJCgeGDboJzPyZLFJCuWWYKxI2+0s4Grq2E
# b0iEm09AufFM8q+Y+/bOQF1c9qjxL6/siSLyaxhlscFzrdfx2M8eCnRcQrhofrfV
# dwonVnwPYqQ/MhRglf0HBKIJAgMBAAGjggN6MIIDdjAOBgNVHQ8BAf8EBAMCAYYw
# OwYDVR0lBDQwMgYIKwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUH
# AwQGCCsGAQUFBwMIMIIB0gYDVR0gBIIByTCCAcUwggG0BgpghkgBhv1sAAEEMIIB
# pDA6BggrBgEFBQcCARYuaHR0cDovL3d3dy5kaWdpY2VydC5jb20vc3NsLWNwcy1y
# ZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMA
# ZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8A
# bgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAA
# dABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAA
# dABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0A
# ZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQA
# eQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgA
# ZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjALBglghkgBhv1s
# AxUwEgYDVR0TAQH/BAgwBgEB/wIBADB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUH
# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDov
# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNy
# dDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDQuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDAdBgNVHQ4EFgQU
# FQASKxOYspkH7R7for5XDStnAs0wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6ch
# nfNtyA8wDQYJKoZIhvcNAQEFBQADggEBAEZQPsm3KCSnOB22WymvUs9S6TFHq1Zc
# e9UNC0Gz7+x1H3Q48rJcYaKclcNQ5IK5I9G6OoZyrTh4rHVdFxc0ckeFlFbR67s2
# hHfMJKXzBBlVqefj56tizfuLLZDCwNK1lL1eT7EF0g49GqkUW6aGMWKoqDPkmzmn
# xPXOHXh2lCVz5Cqrz5x2S+1fwksW5EtwTACJHvzFebxMElf+X+EevAJdqP77BzhP
# DcZdkbkPZ0XN1oPt55INjbFpjE/7WeAjD9KqrgB87pxCDs+R1ye3Fu4Pw718CqDu
# LAhVhSK46xgaTfwqIa1JMYNHlXdx3LEbS0scEJx3FMGdTy9alQgpECYxggUYMIIF
# FAIBATBkMFAxJTAjBgNVBAMMHEdhbmVzaCBSYXNrYXIgU2ltcGxlUFNMb2dnZXIx
# JzAlBgkqhkiG9w0BCQEWGGdhbmVzaHJhc2thckBvdXRsb29rLmNvbQIQZDtcNubu
# NpVEfXOVHbciTzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKA
# ADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYK
# KwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUEpsHcw+szaSq9QU4qzI9mYXIe2Aw
# DQYJKoZIhvcNAQEBBQAEggIAYUBo0nsZxCOLA/Bb8gdRdi0GMgabQhsEcaZ9503s
# E7aK0brE3cef4hOs8lBx9vfoHuIdWe6kk6Hmln03U1vcDcyxmmTMoeSZrE5GDYrI
# EXMCexq8X7Rv5P5zyye7PfFIWacx9j151tyDAvEoZ5+oF4hgeKXD4UXNL70TQ0c3
# UkJFSXvGe2CHHxq6v99u49vxglckEx7ZYW3GyPRtJw+Jp/k9nK80VEFdv6gTrK+i
# NGIufz4YWycnzQ0tE23umBSNHfrhBddpHJLoZLXQMOTYfi0JTyyY4tIz2YcKf3WV
# zP7BMXa8V/bMOQ2e4CdTv3CHgTgeoRX5B1OYNzZwWoaP/ClNHsJMsI7I8LOtI8C/
# Qj0ct0R3VcBlvMAfarGwil8owAXZT1pybQVdcqgFzl9mbVPcZ4h7OqEImgdpr0Ou
# 6ts+4kdyNOpfmDiS7Zp8DuuRYySRSEhVM3EbomM2GaoJVPHK+DsHjXk6qxSAQf8I
# +NL0/ivQXVmmCa3t5gt/7913mXPeTkNNsinVkU4rCAjhKsJgcHNmJv6IYEm5PU0d
# rLcM4daIQvRHJpuYR6dA+u2azR3YI1oSQgEcKfexZoZaKmtpnOpFLD9lbTsADFVk
# 4HBVTlVIfX+Gi7avFqohNut/rujveVSJ+dbJp2gCm01jfI8NWpi/8nUdue9QAxM3
# gbahggIPMIICCwYJKoZIhvcNAQkGMYIB/DCCAfgCAQEwdjBiMQswCQYDVQQGEwJV
# UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
# Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTECEAMBmgI6/1ix
# a9bV6uYX8GYwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw
# HAYJKoZIhvcNAQkFMQ8XDTIwMDcwNDIwNDgwMVowIwYJKoZIhvcNAQkEMRYEFFEr
# bXXs7q0PAjzQMFyG/cXfWTvFMA0GCSqGSIb3DQEBAQUABIIBADo6N15clH7miNIk
# LPvsdsPSS7WNPq/o4ERiID1dk+3ZxNTG0EpneloUsNo3RP4LA8GNtx1/VFWZhlEK
# fp7dWNLD3PutUDtqa288HdhrMCz5hcGFWUNVuYVgVFlxlTNJOa9nnJikb7pj+CcJ
# YFEhQSBgHBEn9vix3T1nD96tbH/eI7N3+8eCNVu9afjx4lGeANy/+tgjM+jVD/sW
# 2wfHFnwoZ9CCcoiaeVpaLiQZPArv91htC/MGNkO00dtaO7Awq5zShlmIPd92laCN
# u94Y5NEIJkcHHHcb+0akXKfrqxBMf/ypxB5RsahHhvMJR9tbBDhHOyUYEXl2iTrK
# MS3+ng0=
# SIG # End signature block