modules/AzStack.Insights/AzStack.Insights.Telemetry.psm1

<################################################################
# #
# Copyright (C) Microsoft Corporation. All rights reserved. #
# #
################################################################>


#################################################################
# #
# STARTUP ACTIONS ON IMPORT #
# #
#################################################################

Import-Module $PSScriptRoot\..\AzStack.Common\AzStack.Common.psm1
Import-LocalizedData -BindingVariable 'msg' -BaseDirectory "$PSScriptRoot\locale" -UICulture $PSUICulture

# import the telemetry module which contains the telemetry events
# if we cannot find the module, we will not be able to send telemetry
if (Test-Path -Path "$($PSScriptRoot)\..\AzStack.Telemetry" -PathType Container) {
    try {
        # Try to import the module
        Import-Module "$($PSScriptRoot)\..\AzStack.Telemetry" -Force -DisableNameChecking -ErrorAction Stop
        $script:TelemetryEnabled = $true
    }
    catch {
        # If the import fails, write an error message and set telemetry to false
        Write-Warning "Failed to import AzStack.Telemetry module due to $($_.Exception.Message) Telemetry will not be enabled."
        $script:TelemetryEnabled = $false
    }
}
else {
    $script:TelemetryEnabled = $false
}

$script:InsightLoggerInstance = $null

#################################################################
# #
# ENUMS AND CLASSES #
# #
#################################################################

#################################################################
# #
# FUNCTIONS #
# #
#################################################################

function Send-InsightTelemetry {
    <#
    .SYNOPSIS
        Sends telemetry data for an AzStack Insight.
    .PARAMETER Insight
        The Insight object containing telemetry data.
    .PARAMETER Version
        The version of the insights module. Defaults to the current CSSTools module version.
    .DESCRIPTION
        This function sends telemetry data for an AzStack Insight using the CSSToolsEventWriter.
        It supports both Rule and Analyzer insight types.
        For Rule insights, telemetry is only sent for non-INFO and non-SUCCESS statuses to reduce event volume.
        For Analyzer insights, a summary of rule statuses is included in the telemetry event.
    .EXAMPLE
        PS> Send-InsightTelemetry -Insight $insightObject
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Insight,

        [Parameter(Mandatory = $false)]
        [string]$Version = $Global:CSSTools_AzsSupport.ModuleVersion
    )

    # check if telemetry is enabled, if not, return without sending telemetry or logging any events
    if (-NOT ($script:TelemetryEnabled)){
        return
    }

    switch ($Insight.InsightType) {
        'Rule' {
            # To reduce the total number of events sent, we only send telemetry for non INFO and non SUCCESS rules
            if ($Insight.Status -eq "INFO" -or $Insight.Status -eq "SUCCESS") {
                return
            }

            $insightTelemetryEvent = [Microsoft.AzureStack.CSSTools.Telemetry.Events.InsightRuleTelemetryEvent]::new(
                $Insight.Name,
                $Insight.Id,
                $Insight.CorrelationId,
                $Insight.Status,
                $Insight.ErrorCode,
                $Insight.Duration.Seconds,
                $Version,
                $Insight.ScriptStackTrace
            )
        }
        'Analyzer' {
            # Summarize the rules
            $ruleCount = $Insight.Rules.Count
            $failedRuleCount = ($Insight.Rules | Where-Object { $_.Status -eq "FAILURE" }).Count
            $warningRuleCount = ($Insight.Rules | Where-Object { $_.Status -eq "WARNING" }).Count
            $successRuleCount = ($Insight.Rules | Where-Object { $_.Status -eq "SUCCESS" }).Count
            $infoRuleCount = ($Insight.Rules | Where-Object { $_.Status -eq "INFO" }).Count
            $unknownRuleCount = ($Insight.Rules | Where-Object { $_.Status -eq "UNKNOWN" }).Count

            $insightTelemetryEvent = [Microsoft.AzureStack.CSSTools.Telemetry.Events.InsightAnalyzerTelemetryEvent]::new(
                $Insight.Name,
                $Insight.Id,
                $Insight.CorrelationId,
                $Insight.Status,
                $Insight.Duration.Seconds,
                $Version,
                $Insight.ScriptStackTrace,
                $ruleCount,
                $failedRuleCount,
                $warningRuleCount,
                $successRuleCount,
                $infoRuleCount,
                $unknownRuleCount
            )
        }
    }

    [Microsoft.AzureStack.CSSTools.Telemetry.Events.CSSToolsEventWriter]::EmitTelemetryEvent($insightTelemetryEvent)
}

function Write-InsightLog {
    <#
    .SYNOPSIS
        Writes a log message to the AzStack Insights log.
    .PARAMETER Message
        The log message to write.
    .PARAMETER Level
        The log level. Valid values are 'Informational', 'Warning', 'Error', 'Exception'. Default is 'Informational'.
    .PARAMETER FilePath
        The file path to write the log to. Default is 'C:\Temp\Azs.Support\AzStack.Insights.log'.
    .PARAMETER InstanceGuid
        The instance GUID for the logger. Default is a new GUID.
    .DESCRIPTION
        This function writes a log message to the AzStack Insights log using the CSSToolsObservabilityLogger.
        If the environment variable 'AZSTACK_INSIGHT_TRACE_ENABLED' is set, the log will be written to the specified file path.
        Otherwise, it will only log to ETW.
    .EXAMPLE
        PS> Write-InsightLog -Message "This is an informational message." -Level "Informational"
    .EXAMPLE
        PS> Write-InsightLog -Message "This is a warning message." -Level "Warning" -FilePath "C:\Logs\AzStack.Insights.log"
    .EXAMPLE
        PS> Write-InsightLog -Message "This is an error message." -Level "Error" -InstanceGuid "12345678-1234-1234-1234-1234567890ab"
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string]$Message,

        [Parameter(Mandatory = $false)]
        [ValidateSet('Informational', 'Warning', 'Error', 'Exception')]
        [string]$Level = 'Informational',

        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        $Exception,

        [Parameter(Mandatory = $false)]
        [string]$FilePath = (Join-Path -Path $env:SystemDrive -ChildPath "Temp\Azs.Support\AzStack_InsightTraceLog.log"),

        [Parameter(Mandatory = $false)]
        [Guid]$InstanceGuid = [guid]::NewGuid()
    )

    $loggerParams = @{
        InstanceGuid = $InstanceGuid
    }

    if ($Exception) {
        # Handle both ErrorRecord and System.Exception types
        if ($Exception -is [System.Management.Automation.ErrorRecord]) {
            $actualException = $Exception.Exception
        }
        elseif ($Exception -is [System.Exception]) {
            $actualException = $Exception
        }
        else {
            throw "Exception parameter must be of type [System.Exception] or [ErrorRecord]. Received type: $($Exception.GetType().FullName)"
        }
        
        # Validate the exception has a message
        if ([string]::IsNullOrWhiteSpace($actualException.Message)) {
            throw "The Exception must contain a valid error message. The Message property is null or empty."
        }
        
        $Message = $actualException.Message
        $formattedException = Get-FormattedException -Exception $actualException
        $Level = 'Exception'
    }
    else {
        $formattedException = $null
        if ($Level -ieq 'Exception') {
            throw "Level 'Exception' requires an Exception parameter to be provided."
        }
    }

    # If FilePath is not provided, then it will be an empty string and the logger will not log to a file.
    # used for debugging purposes to troubleshoot issues and send data to log file rather than into ETW
    # [System.Environment]::SetEnvironmentVariable("AZSTACK_INSIGHT_TRACE_ENABLED", $true, [System.EnvironmentVariableTarget]::User)
    if ([System.Environment]::GetEnvironmentVariable('AZSTACK_INSIGHT_TRACE_ENABLED')) {
        [void]$loggerParams.Add('FilePath', $FilePath)
    }

    if ($script:TelemetryEnabled) {
        # Initialize the logger instance if not already done
        if ($null -eq $script:InsightLoggerInstance ){

            # Create a new logger instance with the provided parameters
            $script:InsightLoggerInstance = Initialize-Logger @loggerParams
        }

        # Write the message using the logger instance
        switch ($Level) {
            'Informational' { $script:InsightLoggerInstance.LogInformational($Message) }
            'Warning'       { $script:InsightLoggerInstance.LogWarning($Message) }
            'Error'         { $script:InsightLoggerInstance.LogError($Message) }
            'Exception'     { $script:InsightLoggerInstance.LogException($Message, $formattedException) }
        }
    }
}

function Enable-InsightLog {
    <#
    .SYNOPSIS
        Enables trace logging to file for AzStack Insights.
    #>

    [CmdletBinding()]
    param ()

    try {
        [System.Environment]::SetEnvironmentVariable('AZSTACK_INSIGHT_TRACE_ENABLED', $true)
    }
    catch {
        $failureMessage = $msg.TraceLogDisableFailure -f "Enable", $_.Exception.Message
        Write-Error -Message $failureMessage
    }
}

function Disable-InsightLog {
    <#
    .SYNOPSIS
        Disables trace logging to file for AzStack Insights.
    #>

    [CmdletBinding()]
    param ()

    try {
        [System.Environment]::SetEnvironmentVariable('AZSTACK_INSIGHT_TRACE_ENABLED', $false)
    }
    catch {
        $failureMessage = $msg.TraceLogDisableFailure -f "Disable", $_.Exception.Message
        Write-Error -Message $failureMessage
    }
}

Export-ModuleMember -Function Send-InsightTelemetry
Export-ModuleMember -Function Write-InsightLog
Export-ModuleMember -Function Enable-InsightLog
Export-ModuleMember -Function Disable-InsightLog

# SIG # Begin signature block
# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAY641bticrF+Nu
# Sq9qbSp4a5cxsHIX26AWOFxXAn7ZwKCCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z
# 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy
# 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi
# 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ
# hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ
# 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe
# UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk
# tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj
# Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS
# DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns
# WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO
# lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71
# 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9
# nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk
# C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm
# M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn
# lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo
# STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFso9NPNAnqHEj0q6duO7+B9
# 30vgPTiJwTi1OWQdl4A5MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAoE6sDSw/MYOPP+qvvoLGwVBr+vWYtB8fo+tzPAjZxqx15Zl1fS0AnN8R
# nMhnKrvEv7GxNtKIyPnyhskMnVx0jSrH7KYd3C3puu9jJ6OXRxIaLFg16nuZrzhX
# 0oMKpTTKnkb74OyiY58ETOpfsCg6cpJZ378c8deON0ruuD/W+bnrvw6lb9f0ibn2
# AG3Kwxf+kz57mXfzm4nLwOuaFyb0A/BxtfgOqU39UvF8JtfzdvnSdHuDfK/WWKEK
# Q/HcoiuLQjzYtqtG0IKJpYsz8sR2nzJQY+at9EHNFjCsi70hU7PwG/JmyyLNhD8Q
# hH3b4MvytQFk/R2cYzD63hkOzgf97aGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC
# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAK/6O5Kk7a44jps/+3gLgekNt66+KN5SFYg5lxHUJqZAIGaZR6fKkw
# GBMyMDI2MDIyNDE4NDY0OC43NjFaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHtMIIHIDCCBQigAwIBAgITMwAAAgh4nVhdksfZUgABAAACCDANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNTAxMzAxOTQy
# NTNaFw0yNjA0MjIxOTQyNTNaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTAwMC0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQC1y3AI5lIz3Ip1nK5BMUUbGRsjSnCz/VGs33zvY0Ne
# shsPgfld3/Z3/3dS8WKBLlDlosmXJOZlFSiNXUd6DTJxA9ik/ZbCdWJ78LKjbN3t
# FkX2c6RRpRMpA8sq/oBbRryP3c8Q/gxpJAKHHz8cuSn7ewfCLznNmxqliTk3Q5LH
# qz2PjeYKD/dbKMBT2TAAWAvum4z/HXIJ6tFdGoNV4WURZswCSt6ROwaqQ1oAYGvE
# ndH+DXZq1+bHsgvcPNCdTSIpWobQiJS/UKLiR02KNCqB4I9yajFTSlnMIEMz/Ni5
# 38oGI64phcvNpUe2+qaKWHZ8d4T1KghvRmSSF4YF5DNEJbxaCUwsy7nULmsFnTaO
# jVOoTFWWfWXvBuOKkBcQKWGKvrki976j4x+5ezAP36fq3u6dHRJTLZAu4dEuOooU
# 3+kMZr+RBYWjTHQCKV+yZ1ST0eGkbHXoA2lyyRDlNjBQcoeZIxWCZts/d3+nf1ji
# SLN6f6wdHaUz0ADwOTQ/aEo1IC85eFePvyIKaxFJkGU2Mqa6Xzq3qCq5tokIHtjh
# ogsrEgfDKTeFXTtdhl1IPtLcCfMcWOGGAXosVUU7G948F6W96424f2VHD8L3FoyA
# I9+r4zyIQUmqiESzuQWeWpTTjFYwCmgXaGOuSDV8cNOVQB6IPzPneZhVTjwxbAZl
# aQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFKMx4vfOqcUTgYOVB9f18/mhegFNMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBRszKJKwAfswqdaQPFiaYB/ZNAYWDa040X
# TcQsCaCua5nsG1IslYaSpH7miTLr6eQEqXczZoqeOa/xvDnMGifGNda0CHbQwtpn
# IhsutrKO2jhjEaGwlJgOMql21r7Ik6XnBza0e3hBOu4UBkMl/LEX+AURt7i7+RTN
# sGN0cXPwPSbTFE+9z7WagGbY9pwUo/NxkGJseqGCQ/9K2VMU74bw5e7+8IGUhM2x
# spJPqnSeHPhYmcB0WclOxcVIfj/ZuQvworPbTEEYDVCzSN37c0yChPMY7FJ+HGFB
# NJxwd5lKIr7GYfq8a0gOiC2ljGYlc4rt4cCed1XKg83f0l9aUVimWBYXtfNebhpf
# r6Lc3jD8NgsrDhzt0WgnIdnTZCi7jxjsIBilH99pY5/h6bQcLKK/E6KCP9E1YN78
# fLaOXkXMyO6xLrvQZ+uCSi1hdTufFC7oSB/CU5RbfIVHXG0j1o2n1tne4eCbNfKq
# UPTE31tNbWBR23Yiy0r3kQmHeYE1GLbL4pwknqaip1BRn6WIUMJtgncawEN33f8A
# YGZ4a3NnHopzGVV6neffGVag4Tduy+oy1YF+shChoXdMqfhPWFpHe3uJGT4GJEiN
# s4+28a/wHUuF+aRaR0cN5P7XlOwU1360iUCJtQdvKQaNAwGI29KOwS3QGriR9F2j
# OGPUAlpeEzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ
# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkEwMDAtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCN
# kvu0NKcSjdYKyrhJZcsyXOUTNKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7UgyXTAiGA8yMDI2MDIyNDE0MjEx
# N1oYDzIwMjYwMjI1MTQyMTE3WjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDtSDJd
# AgEAMAoCAQACAhK6AgH/MAcCAQACAhIZMAoCBQDtSYPdAgEAMDYGCisGAQQBhFkK
# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ
# KoZIhvcNAQELBQADggEBALvlg9Erpb2MD8uPQ12v2ulD9gsaPuIXsthkYrclQply
# wC3/B2lAapnj0nVFFNgkpE+ZPEdkcdn7e4/7+UMvnQnM8sedIlt//dnHGp5+h4WA
# d4RdEXIlwOBjs53NNaiJmL9xZlisVpCgaYG0Gm7NQMxCqnL6lp1wPVXBLidXxul8
# 7KMYbGrdvA6HVI6cMSSq2Dk49yekVdd2SHP4zOQJG4NkbVA1LQLWhJJwiLlejWcB
# TCVrrMtuNocy5ihpgrB8mKcFyACxaQczPpPjEpbhKqD8JSH3i1v74I6sJtoLJBs4
# orDXeiMiAhVAMoiD1JRC43qvBQbCo4LYpI1vzol96LoxggQNMIIECQIBATCBkzB8
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAgh4nVhdksfZUgABAAAC
# CDANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
# MC8GCSqGSIb3DQEJBDEiBCAbUNXxTYpmLKrsXRGagtVO6pdQyKliUGUtZrYhc8oT
# XTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EII//jm8JHa2W1O9778t9+Ft2
# Z5NmKqttPk6Q+9RRpmepMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
# IDIwMTACEzMAAAIIeJ1YXZLH2VIAAQAAAggwIgQgPE7svvn7UHwCeySLlhcBIseB
# N5VF+/d/+FyRRFlcdbswDQYJKoZIhvcNAQELBQAEggIAMZMGP0Ed/wQc7orDvTM9
# TnahRyl/bQTYo0kLrQ3myXwcHaPRRfgpZHvnXZZXk9W+P+7cLm8mp7RodypXzlRY
# SwsNTSgvJU9imzxDUM5xy3z9Tb8nl1eMsum3N6eyQgnOiCg4bQesZo0UvSlmPwCC
# UfUoLtAiFtTmoCwXZmQeLi+T1n4THCm/HwHhQK7pDIHPtnA/dsKgKXusYDXNa8W4
# yWZgF5PPktfq3J+MQasFoGOLaCUi8k45VJTibJ43oS39X00AJkPjU2f7Gd+h/PeD
# bkzeF4jture6wl1nKQu79a3ixMXuhazD1xaGMQMR2uG6NtYTgEn6nK+JTyQkZqEW
# p6qkg7ydcYvdJRPbZx7JhAYWK+njYVAekNv0ese0/6nEgO9fI7Q89Cuk76klSSUK
# aL7TeQEWPWuVztZpxTWlX92+oPchuxJbww3GclLyuNEOQGkFmt1y3pk01Q2ilrhG
# gT7J8fnUCUn/mLlry78Y6Sa1PSqkKMV91gUR+xvLCQMoEL5Lr31DnxrFx0IUfZDM
# cuFqddO/6eGz7x7Q2rHHaobdZDScowgUvmxO9hfQCMDNvR3zrhEfjqZ0U9P2o2+s
# ZK9CG7hG4DiB/c/hwi5bxUHqNAe+3LW8QC7yM3J50IBOwIQdI31fiUEYkyiwWtrb
# +wNOx8TZuifT3rSmKBlY8Ic=
# SIG # End signature block