GitHubGraphQl.ps1

function Invoke-GHGraphQl
{
    <#
    .SYNOPSIS
        A wrapper around Invoke-WebRequest that understands the GitHub GraphQL API.
 
    .DESCRIPTION
        A very heavy wrapper around Invoke-WebRequest that understands the GitHub QraphQL API.
        It also understands how to parse and handle errors from the GraphQL calls.
 
        The Git repo for this module can be found here: http://aka.ms/PowerShellForGitHub
 
    .PARAMETER Description
        A friendly description of the operation being performed for logging.
 
    .PARAMETER Body
        This parameter forms the body of the request. It will be automatically
        encoded to UTF8 and sent as Content Type: "application/json; charset=UTF-8"
 
    .PARAMETER AccessToken
        If provided, this will be used as the AccessToken for authentication with the
        GraphQL Api as opposed to requesting a new one.
 
    .PARAMETER TelemetryEventName
        If provided, the successful execution of this GraphQL command will be logged to telemetry
        using this event name.
 
    .PARAMETER TelemetryProperties
        If provided, the successful execution of this GraphQL command will be logged to telemetry
        with these additional properties. This will be silently ignored if TelemetryEventName
        is not provided as well.
 
    .PARAMETER TelemetryExceptionBucket
        If provided, any exception that occurs will be logged to telemetry using this bucket.
        It's possible that users will wish to log exceptions but not success (by providing
        TelemetryEventName) if this is being executed as part of a larger scenario. If this
        isn't provided, but TelemetryEventName *is* provided, then TelemetryEventName will be
        used as the exception bucket value in the event of an exception. If neither is specified,
        no bucket value will be used.
 
    .OUTPUTS
        PSCustomObject
 
    .EXAMPLE
        Invoke-GHGraphQl
 
    .NOTES
        This wraps Invoke-WebRequest as opposed to Invoke-RestMethod because we want access
        to the headers that are returned in the response, and Invoke-RestMethod drops those headers.
#>

    [CmdletBinding()]
    [OutputType([System.Management.Automation.ErrorRecord])]
    param(
        [string] $Description,

        [Parameter(Mandatory)]
        [string] $Body,

        [string] $AccessToken,

        [string] $TelemetryEventName = $null,

        [hashtable] $TelemetryProperties = @{},

        [string] $TelemetryExceptionBucket = $null
    )

    Invoke-UpdateCheck

    # Telemetry-related
    $stopwatch = New-Object -TypeName System.Diagnostics.Stopwatch
    $localTelemetryProperties = @{}
    $TelemetryProperties.Keys | ForEach-Object { $localTelemetryProperties[$_] = $TelemetryProperties[$_] }
    $errorBucket = $TelemetryExceptionBucket
    if ([String]::IsNullOrEmpty($errorBucket))
    {
        $errorBucket = $TelemetryEventName
    }

    $stopwatch.Start()

    $hostName = $(Get-GitHubConfiguration -Name 'ApiHostName')

    if ($hostName -eq 'github.com')
    {
        $url = "https://api.$hostName/graphql"
    }
    else
    {
        $url = "https://$hostName/api/v3/graphql"
    }

    $headers = @{
        'User-Agent' = 'PowerShellForGitHub'
    }

    $AccessToken = Get-AccessToken -AccessToken $AccessToken
    if (-not [String]::IsNullOrEmpty($AccessToken))
    {
        $headers['Authorization'] = "token $AccessToken"
    }

    $timeOut = Get-GitHubConfiguration -Name WebRequestTimeoutSec
    $method = 'Post'

    Write-Log -Message $Description -Level Debug
    Write-Log -Message "Accessing [$method] $url [Timeout = $timeOut]" -Level Debug

    if (Get-GitHubConfiguration -Name LogRequestBody)
    {
        Write-Log -Message $Body -Level Debug
    }

    $bodyAsBytes = [System.Text.Encoding]::UTF8.GetBytes($Body)

    # Disable Progress Bar in function scope during Invoke-WebRequest
    $ProgressPreference = 'SilentlyContinue'

    # Save Current Security Protocol
    $originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol

    # Enforce TLS v1.2 Security Protocol
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    $invokeWebRequestParms = @{
        Uri = $url
        Method = $method
        Headers = $headers
        Body = $bodyAsBytes
        UseDefaultCredentials = $true
        UseBasicParsing = $true
        TimeoutSec = $timeOut
        Verbose = $false
    }

    try
    {
        $result = Invoke-WebRequest @invokeWebRequestParms
    }
    catch
    {
        $ex = $_.Exception

        <#
            PowerShell 5 Invoke-WebRequest returns a 'System.Net.WebException' object on error.
            PowerShell 6+ Invoke-WebRequest returns a 'Microsoft.PowerShell.Commands.HttpResponseException' or
            a 'System.Net.Http.HttpRequestException' object on error.
        #>


        if ($ex.PSTypeNames[0] -eq 'System.Net.Http.HttpRequestException')
        {
            Write-Debug -Message "Processing PowerShell Core 'System.Net.Http.HttpRequestException'"

            $newErrorRecordParms = @{
                ErrorMessage = $ex.Message
                ErrorId = $_.FullyQualifiedErrorId
                ErrorCategory = $_.CategoryInfo.Category
                TargetObject = $_.TargetObject
            }
            $errorRecord = New-ErrorRecord @newErrorRecordParms

            Write-Log -Exception $errorRecord -Level Error
            Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties

            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
        elseif ($ex.PSTypeNames[0] -eq 'Microsoft.PowerShell.Commands.HttpResponseException' -or
            $ex.PSTypeNames[0] -eq 'System.Net.WebException')
        {
            Write-Debug -Message "Processing '$($ex.PSTypeNames[0])'"

            $errorMessage = @()
            $errorMessage += $ex.Message

            $errorDetailsMessage = $_.ErrorDetails.Message

            if (-not [string]::IsNullOrEmpty($errorDetailsMessage))
            {
                Write-Debug -Message "Processing Error Details message '$errorDetailsMessage'"

                try
                {
                    Write-Debug  -Message 'Checking Error Details message for JSON content'

                    $errorDetailsMessageJson = $errorDetailsMessage | ConvertFrom-Json
                }
                catch [System.ArgumentException]
                {
                    # Will be thrown if $errorDetailsMessage isn't JSON content
                    Write-Debug -Message 'No Error Details Message JSON content Found'

                    $errorDetailsMessageJson = $false
                }

                if ($errorDetailsMessageJson)
                {
                    Write-Debug -Message 'Adding Error Details Message JSON content to output'
                    Write-Debug -Message "Error Details Message: $($errorDetailsMessageJson.message)"
                    Write-Debug -Message "Error Details Documentation URL: $($errorDetailsMessageJson.documentation_url)"

                    $errorMessage += ($errorDetailsMessageJson.message.Trim() +
                        ' | ' + $errorDetailsMessageJson.documentation_url.Trim())

                    if ($errorDetailsMessageJson.details)
                    {
                        $errorMessage += $errorDetailsMessageJson.details | Format-Table | Out-String
                    }
                }
                else
                {
                    # In this case, it's probably not a normal message from the API
                    Write-Debug -Message 'Adding Error Details Message String to output'

                    $errorMessage += $_.ErrorDetails.Message | Out-String
                }
            }

            if (-not [System.String]::IsNullOrEmpty($ex.Response))
            {
                Write-Debug -Message "Processing '$($ex.Response.PSTypeNames[0])' Object"

                <#
                    PowerShell 5.x returns a 'System.Net.HttpWebResponse' exception response object and
                    PowerShell 6+ returns a 'System.Net.Http.HttpResponseMessage' exception response object.
                #>


                $requestId = ''

                if ($ex.Response.PSTypeNames[0] -eq 'System.Net.HttpWebResponse')
                {
                    if (($ex.Response.Headers.Count -gt 0) -and
                        (-not [System.String]::IsNullOrEmpty($ex.Response.Headers['X-GitHub-Request-Id'])))
                    {
                        $requestId = $ex.Response.Headers['X-GitHub-Request-Id']
                    }
                }
                elseif ($ex.Response.PSTypeNames[0] -eq 'System.Net.Http.HttpResponseMessage')
                {
                    $requestId = ($ex.Response.Headers | Where-Object -Property Key -eq 'X-GitHub-Request-Id').Value
                }

                if (-not [System.String]::IsNullOrEmpty($requestId))
                {
                    Write-Debug -Message "GitHub RequestID '$requestId' in response header"

                    $localTelemetryProperties['RequestId'] = $requestId
                    $requestIdMessage += "RequestId: $requestId"
                    $errorMessage += $requestIdMessage

                    Write-Log -Message $requestIdMessage -Level Debug
                }
            }

            $newErrorRecordParms = @{
                ErrorMessage = $errorMessage -join [Environment]::NewLine
                ErrorId = $_.FullyQualifiedErrorId
                ErrorCategory = $_.CategoryInfo.Category
                TargetObject = $Body
            }
            $errorRecord = New-ErrorRecord @newErrorRecordParms

            Write-Log -Exception $errorRecord -Level Error
            Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties

            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
        else
        {
            Write-Debug -Message "Processing Other Exception '$($ex.PSTypeNames[0])'"

            $newErrorRecordParms = @{
                ErrorMessage = $ex.Message
                ErrorId = $_.FullyQualifiedErrorId
                ErrorCategory = $_.CategoryInfo.Category
                TargetObject = $body
            }
            $errorRecord = New-ErrorRecord @newErrorRecordParms

            Write-Log -Exception $errorRecord -Level Error
            Set-TelemetryException -Exception $ex -ErrorBucket $errorBucket -Properties $localTelemetryProperties

            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }
    finally
    {
        # Restore original security protocol
        [Net.ServicePointManager]::SecurityProtocol = $originalSecurityProtocol
    }

    # Record the telemetry for this event.
    $stopwatch.Stop()
    if (-not [String]::IsNullOrEmpty($TelemetryEventName))
    {
        $telemetryMetrics = @{ 'Duration' = $stopwatch.Elapsed.TotalSeconds }
        Set-TelemetryEvent -EventName $TelemetryEventName -Properties $localTelemetryProperties -Metrics $telemetryMetrics -Verbose:$false
    }

    Write-Debug -Message "GraphQl result: '$($result.Content)'"

    $graphQlResult = $result.Content | ConvertFrom-Json

    if ($graphQlResult.errors)
    {
        Write-Debug -Message "GraphQl Error: $($graphQLResult.errors | Out-String)"

        if (-not [System.String]::IsNullOrEmpty($graphQlResult.errors[0].type))
        {
            $errorId = $graphQlResult.errors[0].type
            switch ($errorId)
            {
                'NOT_FOUND'
                {
                    Write-Debug -Message "GraphQl Error Type: $errorId"

                    $errorCategory = [System.Management.Automation.ErrorCategory]::ObjectNotFound
                }

                Default
                {
                    Write-Debug -Message "GraphQL Unknown Error Type: $errorId"

                    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
                }
            }
        }
        else
        {
            Write-Debug -Message "GraphQl Unspecified Error"

            $errorId = 'UnspecifiedError'
            $errorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified
        }

        $errorMessage = @()
        $errorMessage += "GraphQl Error: $($graphQlResult.errors[0].message)"

        if ($result.Headers.Count -gt 0 -and
            -not [System.String]::IsNullOrEmpty($result.Headers['X-GitHub-Request-Id']))
        {
            $requestId = $result.Headers['X-GitHub-Request-Id']

            $requestIdMessage += "RequestId: $requestId"
            $errorMessage += $requestIdMessage

            Write-Log -Message $requestIdMessage -Level Debug
        }

        $newErrorRecordParms = @{
            ErrorMessage = $errorMessage -join [Environment]::NewLine
            ErrorId = $errorId
            ErrorCategory = $errorCategory
            TargetObject = $Body
        }
        $errorRecord = New-ErrorRecord @newErrorRecordParms

        Write-Log -Exception $errrorRecord -Level Error

        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }
    else
    {
        return $graphQlResult
    }
}

# SIG # Begin signature block
# MIIoLwYJKoZIhvcNAQcCoIIoIDCCKBwCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC688O6zLVc7MWr
# huAvhEHauvyblYaP20jJpF6yXY4Q7aCCDXYwggX0MIID3KADAgECAhMzAAADTrU8
# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU
# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1
# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm
# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa
# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq
# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk
# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31
# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2
# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d
# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM
# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh
# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX
# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir
# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8
# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A
# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H
# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGg8wghoLAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB
# BQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIPysgsN0nT+UwASUcY0bxFR/
# UO3Z2aq6BfWA52OX1BPQMEQGCisGAQQBgjcCAQwxNjA0oBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQuY29tIDANBgkqhkiG9w0B
# AQEFAASCAQCX8iFnFA3bVeA1OE161ATZ1kxPrBQSYoESThI3ahwduuAJCo306mR6
# icrR7rk9YEPRMfG3SQER4/qYuX+5sKeDjDN8k+zrtV2slDse6mLSxpt3G1oF5AbS
# Ebs0MmCwodplBJeu5/i5TGrz+MDDShawUHifVnq0EGv4hTYv2SzmmwccO2asEfYw
# IedUgnzzYsj/nqlmiuSCQN3RWIH9OHB4KORDWZwF2OYPO8rPhGrtmaCDgxb0uMIk
# JjObFPAylBBkjIBHh9WSYxI9gl3M8A99nxg7Y6TRgPtzgUKJZXfHaEQmh90AEAdq
# 1MFHb0f5yWfnNSSDa9VrcUpBm/Z+kh+soYIXlzCCF5MGCisGAQQBgjcDAwExgheD
# MIIXfwYJKoZIhvcNAQcCoIIXcDCCF2wCAQMxDzANBglghkgBZQMEAgEFADCCAVIG
# CyqGSIb3DQEJEAEEoIIBQQSCAT0wggE5AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZI
# AWUDBAIBBQAEIBCmITcwXqY2yd3vhHr87pbuHTSWH0OPvuAkURMYGc77AgZlVsmd
# 5EkYEzIwMjMxMTIxMTczNTI5LjkyMlowBIACAfSggdGkgc4wgcsxCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBB
# bWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo4OTAw
# LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZaCCEe0wggcgMIIFCKADAgECAhMzAAAB0x0ymhc7QDBzAAEAAAHTMA0GCSqGSIb3
# DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTIzMDUyNTE5
# MTIyNFoXDTI0MDIwMTE5MTIyNFowgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlv
# bnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo4OTAwLTA1RTAtRDk0NzElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBALSOq5M3iDXFuFcJzwxX5067xSpzcpttpa2Lm92w
# BYzUPh9VKL7g1aAa0/8FVFitWPahWeczLR5rOJ1A4ni5SxwExs8dozFo2mBtEb0U
# RBEWdwBSm1acj5U+Xnc8Pow8vTLPxwcLZkPfB4XjD64wMAacvfoGSbSys41e+cz1
# 42+cbl2OikSqIeh1ZJq5HJ7i5+0FHaxPAYdWbEq7QZLh87zs2BsnhUbMgJHJlfD3
# 5G+9cwb+OEzXUfwBYrMqmfSgwabUxIx428tRZvfUdJl6TH80ES1e+Z2jvk5XTfQ0
# eAheKHFgR5KBQjF9sjk6aAyr9UMJCnav9/L/k1VrcqMJCg2qaYQzqisAnZcqNiEQ
# nOinidYJwn3vRTqtekE8rhcY0oEWGEtrvhMz/KxMUisRc4kbV9S5d9x1ZvQTHQUB
# 5NOvqCaYKqt4k16M0d98b9UR4Xss29Sq5gVGd2IJSGDLrbitbqm1ydBOJF8TRAv+
# AsXjWQDa9kxjNxzXoSJhdBAFoXdcC0x26HV2lepM89AQ7cyzn/kH8q2OFKykxw9S
# 9G9vfkhY36r4v7MTCKmGacIYVO7I4ypzlATSu4Y3czHRW/rH+Fw6ZpfGsdAak0oj
# k+fv1iTz0ByWpTaZcfPVkdan4oFzcPpU/svfYmXDGEnHdqxrTznG/Rc8PnwxFbVZ
# oa9pAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQU0scghrgUAPj3jPfmG/MKabTjXmIw
# HwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKg
# UIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAw
# XjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# ZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQw
# DAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E
# BAMCB4AwDQYJKoZIhvcNAQELBQADggIBAEBiWFihRD7hppDngwU18ToTLy/ita/4
# u0NFKMwzZf2Di5qcD1xTtWK12kg9X/MTq/gASF79WeDZQBHmqPZJXezP58Oo3pUt
# ZRmwpHRBHYlhcqcU9FWPXp7NnI/vN3kfwiy+xwRyid5f5pEcXTEYYzi0MutLzi+P
# pGbRuChYtdacxNnmQ/ijCcaabQuyYie67QYqsNmeR5NWZ+TyBNPLx3XLc/YhhzZQ
# jiIlhcK5JooK4V47TCrKxym+EZBKejVcAUrehrJu4PWZKhDFP2rvv4sAYZBuJKga
# WBONBBrJixBo9wbVDhA3A40aqQBIJlNvMmWeaQeCRaUpItO6U5qKVYhjiFLURn7D
# 6xfQEn0twzXjaHnU6Vcsyg8unMcBvrHbaKloAnkp/e7IVo4pbDiGe7TNaz48o93X
# 3ad14raiBZ9oV1+cS+RYMMfZ2gv5kDlAF3xeeCz+Z3cGueWXYGRn+CJkT98rKiWu
# JHdpMBYLEUJcoiX8KW7ZtueP2p9VgukBVARw9oJ9MB/s5kGVeaW4RO+rVj9I2HEL
# ownVAsKeRdIj/+JdimZEpPvzdApGCaj/jO2Pe4v1nvFtsbEhKD4/QdNFfXnLhNF4
# Fs7ZEU3IKPzyA45GT6zBPWRopdR8YHjOODle6XFJvLe4s3FB5sTpMTdwArT5+djl
# SkdoR2XDh7uKMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+
# F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU
# 88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqY
# O7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzp
# cGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0Xn
# Rm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1
# zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZN
# N3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLR
# vWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTY
# uVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUX
# k8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB
# 2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKR
# PEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0g
# BFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5t
# aWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQM
# MAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE
# AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ
# W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv
# bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa
# BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqG
# SIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOX
# PTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6c
# qYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/z
# jj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz
# /AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyR
# gNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdU
# bZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo
# 3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4K
# u+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10Cga
# iQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9
# vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGC
# A1AwggI4AgEBMIH5oYHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMV
# AFLHbdwxw0HUhDCz8tiRFdrsjkmwoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDpBzbCMCIYDzIwMjMxMTIxMTQw
# MTM4WhgPMjAyMzExMjIxNDAxMzhaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOkH
# NsICAQAwCgIBAAICM24CAf8wBwIBAAICE18wCgIFAOkIiEICAQAwNgYKKwYBBAGE
# WQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDAN
# BgkqhkiG9w0BAQsFAAOCAQEAhh8qHPXyfBA/fDQqYwfXMs5u8vK7A+ah9Orxpil3
# xeF7wtEAeXH74A6P/23Tle5Pq1eLIS9LS/woWjxKVVDbcWHUV4GfyY2CsuxRdJiQ
# USx9FVPF/H4W5b8DVp+8JLY2RK2gf6kP0asBjd8mYbgkvX3pGJ8jQ/rEv023hVB5
# Kzy6x0izInWgCBHXUQNQK6BvOLGD9W9f1PBycjJMgSAh+T8OSzUzIAKO8bvbAXOF
# XDtEEHZ9aOzgQeQ/JW0v8xPy64q+s9RdFr3+L1QFUHl8LEV7YDqoUsUiuJq0DN22
# QvIK+ZFRoulL2hCfT5bnnoFiULlVQeh6zCVFoOjgueZc1DGCBA0wggQJAgEBMIGT
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB0x0ymhc7QDBzAAEA
# AAHTMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQ
# AQQwLwYJKoZIhvcNAQkEMSIEIFS1y29YCkKbk84KL+tPfg4tX/pOpxWkTgS5kYW6
# NSIIMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgkmb06sTg7k9YDUpoVrO2
# v24/3qtCASf62Aa1jfE6qvUwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMAITMwAAAdMdMpoXO0AwcwABAAAB0zAiBCB+iVUI/BfxhXhrIOe3rc3L
# 70Z6u1rjspim+/HFUirIfTANBgkqhkiG9w0BAQsFAASCAgBf6/2RhT51i2VO0dU6
# 78A87fWYPL1G2uw00hMDaZCtnysgjqhoh790OpBF91rAwEwsZdJUWo/dQPTg8v0Y
# ojelbuUIchYoRldOffWXTRAFEENWbSRIHFaf3QH/1mQc1m7CJ0qG2aJDkZzp4zLz
# 8VqcDIEOq+tR1GbaiVdr0+fW8ojSrrus8gfACZwlj2ZO+g8fWgkigvmKP1zhX0CD
# SuuQ4MksXA+dJ3Ff3IBwVVsk8upG+I5PtUKXd4o65ktnoitAEeuXDhYSfBUPAnTv
# QNRHR4CSiHnmO5a3tcTLm1iPs6dxC0SnsfWTFivZ1fFA6r02tm8+UPfOQWtWl9F+
# HjFJoUeYvemANaytTQroSDPef22Xc0STjDI/cf3rM0eaWyuzrlo8Cs6LOFR6pENp
# K01BXzloPqrnfK9ObVDwvZ2ZaRt1wDEGiLDqwF6tMbOyhiBYDXv15wT6N55OAMKl
# jlqiZM3ZGAA6V+9mNYu/vg28CWFeyoIxgHhddjOx/4gifXAYWdr7WMMYTroq8MtC
# +CUg1qzXFZaAmXTSxho94B+a8p2Gjznby+Qrub3FnSPSMNSGMFt05osJXaljHWPe
# 516wwOvgcA8Y0FZ6dRm5iY7VHkSu2lnJiluh6s4T2DcfE+HTlkFiOVw1CE8pfpJM
# +5JSC2PLMa1ldMVzvS9Lz9ruTA==
# SIG # End signature block