PureStorage.RunCommandLauncher.ps1

Import-Module PureStorage.RunCommandWrapper

$MICROSOFT_AVS_VMFS_VERSION = "1.0.207"

function Get-DefaultAzureSubscriptionId {
    # Azure Resource Manager cmdlets use this settings by default when making Azure Resource Manager requests.
    # To list all of subscription, it would be Get-AzContext -ListAvailable
    # In our case, getting default account is sufficient
    $DefaultAzContext = Get-AzContext
    if ($DefaultAzContext.Subscription) {
        return $DefaultAzContext.Subscription.Id
    }
    else {
        throw "Could not find default Azure subscription. Please make sure you are logging in to Azure using 'Connect-AzAccount'. If you have multiple subscriptions, please use 'Select-AzSubscription' to select the subscription you want to use."
    }
}

function Invoke-AvsScriptExecution {
    param (
        [string]$AVSCloudName,
        [string]$SubscriptionId,
        [string]$AVSResourceGroup,
        [string]$RunCommandId,
        [string]$RunCommandExecName,
        [int]$TimeoutInMinutes = 10,
        [string]$ProductName,
        [string]$ProductVersion,
        [hashtable]$CommandParameters,

        [Parameter(Mandatory=$true)]
        $Logger
    )

    # Convert Timeout to ISO 8601 duration format
    $Timeout = "P0Y0M0DT0H$TimeoutInMinutes`M0S"

    # Construct JSON payload for script execution
    $Payload = @{
        properties = @{
            scriptCmdletId = $RunCommandId
            timeout = $Timeout
            parameters = @()
        }
    }

    foreach ($key in $CommandParameters.Keys) {
        $value = $CommandParameters[$key]

        # Convert large numbers (above 2^53) to string to avoid 'Number ...M' issue
        if ($value -is [UInt64] -or ($value -is [int64] -and $value -ge [math]::Pow(2,53))) {
            $value = "$value"
        }

        $Payload.properties.parameters += @{
            name  = $key
            type  = "Value"
            value = $value
        }
    }
    $JsonPayload = $Payload | ConvertTo-Json -Depth 10

    # REST API call to create script execution
    $ExecutionUri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$AVSResourceGroup/providers/Microsoft.AVS/privateClouds/$AVSCloudName/scriptExecutions/$($RunCommandExecName)?api-version=2022-05-01"
    $Logger.LogDebug("Invoking script execution: $ExecutionUri`nRun Command ID: $RunCommandId`nPayload: $($JsonPayload)")

    $ExecutionResponse = Invoke-AzRestMethod -Uri $ExecutionUri -Method Put -Payload $JsonPayload
    if ($ExecutionResponse.StatusCode -lt 200 -or $ExecutionResponse.StatusCode -gt 299) {
        throw "Failed to create script execution. Status code: $($ExecutionResponse.StatusCode), Response: $($ExecutionResponse.Content)"
    }

    $ExecutionId = ($ExecutionResponse.Content | ConvertFrom-Json -Depth 10).name
    $Logger.LogDebug("Script Execution ID: $ExecutionId")

    # Poll for completion with a timeout
    $StartTime = Get-Date
    $TimeoutLimit = New-TimeSpan -Minutes $TimeoutInMinutes
    $LastStatusLogTime = $StartTime
    $Status = "Unknown"
    $ExecutionOutput = $null

    do {
        Start-Sleep -Seconds 5  # Poll every 5 seconds
        $Elapsed = (Get-Date) - $StartTime

        # Fetch script execution status and output in a single call
        $StatusResponse = Invoke-AzRestMethod -Uri $ExecutionUri -Method Get
        if ($StatusResponse.StatusCode -eq 200) {
            $ResponseData = $StatusResponse.Content | ConvertFrom-Json -Depth 10
            $Status = $ResponseData.properties.provisioningState
            $ExecutionOutput = $ResponseData.properties.output
        }

        # Log status every 60 seconds to avoid excessive logging
        if ((Get-Date) - $LastStatusLogTime -ge (New-TimeSpan -Seconds 60)) {
            $Logger.LogDebug("Current Status: $Status")
            $LastStatusLogTime = Get-Date
        }

        if ($Status -eq "Succeeded") {
            $Logger.LogDebug("Script executed successfully!")
            break
        } elseif ($Status -eq "Failed") {
            Invoke-CollectAvsScriptExecutionLog -ExecutionId $ExecutionId -SubscriptionId $SubscriptionId -AVSResourceGroup $AVSResourceGroup -AVSCloudName $AVSCloudName -Logger $Logger
            $FailureReason = $ResponseData.properties.failureReason
            throw "Script execution failed! Execution ID: $ExecutionId, URI: $ExecutionUri, Details: $FailureReason"
        }
    } while ($Elapsed -lt $TimeoutLimit)

    # If timeout is reached, terminate
    if ($Elapsed -ge $TimeoutLimit) {
        Invoke-CollectAvsScriptExecutionLog -ExecutionId $ExecutionId -SubscriptionId $SubscriptionId -AVSResourceGroup $AVSResourceGroup -AVSCloudName $AVSCloudName -Logger $Logger
        throw "Timeout reached! Script execution did not complete in $TimeoutInMinutes minutes. Execution ID: $ExecutionId, URI: $ExecutionUri, Last known status: $Status"
    }

    $Logger.LogInfo("Script Execution Completed. Execution ID: $ExecutionId, URI: $ExecutionUri")
    if ($ExecutionOutput) {
        $Logger.LogInfo("Script Output: $ExecutionOutput")
    }

    return $ExecutionOutput
}

function Invoke-CollectAvsScriptExecutionLog {
    param (
        [Parameter(Mandatory=$true)]
        [string]$ExecutionId,

        [Parameter(Mandatory=$true)]
        [string]$SubscriptionId,

        [Parameter(Mandatory=$true)]
        [string]$AVSResourceGroup,

        [Parameter(Mandatory=$true)]
        [string]$AVSCloudName,

        [Parameter(Mandatory=$true)]
        $Logger
    )

    $Url = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$AVSResourceGroup/providers/Microsoft.AVS/privateClouds/$AVSCloudName/scriptExecutions/$ExecutionId/getExecutionLogs?api-version=2025-09-01"
    $Body = @("Output", "Error", "Warning", "Information") | ConvertTo-Json

    $Logger.LogDebug("Collecting AVS script execution logs for Execution ID: $ExecutionId")
    $Response = Invoke-AzRestMethod -Uri $Url -Method POST -Payload $Body

    if ($Response.StatusCode -lt 200 -or $Response.StatusCode -gt 299) {
        $Logger.LogWarning("Failed to collect AVS script execution logs. Status code: $($Response.StatusCode), Response: $($Response.Content)")
        return
    }

    $LogData = $Response.Content | ConvertFrom-Json
    $SeverityMap = [ordered]@{
        "Output"      = $LogData.properties.output
        "Information" = $LogData.properties.information
        "Warning"     = $LogData.properties.warnings
        "Error"       = $LogData.properties.errors
    }

    foreach ($Severity in $SeverityMap.Keys) {
        $Messages = $SeverityMap[$Severity]
        if ($Messages -and $Messages.Count -gt 0) {
            $Logger.LogInfo("[RunCommand Execution log $Severity] : $($Messages -join "`n")")
        }
    }
}

function Invoke-RunScript {
    Param (
        [Parameter(Mandatory=$true)]
        [String] $RunCommandName,

        [Parameter(Mandatory=$true)]
        [ValidateSet("Microsoft.AVS.VMFS")]
        [String] $RunCommandModule,

        [Parameter(Mandatory=$true)]
        $Parameters,

        [Parameter(Mandatory=$false)]
        [String]$AVSCloudName,

        [Parameter(Mandatory=$false)]
        [String]$AVSResourceGroup,

        [Parameter(Mandatory=$false)]
        [switch]$GetNamedOutputs,

        [Parameter(Mandatory=$false)]
        [int] $TimeoutInMinutes = 10,

        [Parameter(Mandatory=$true)]
        $Logger
    )

    $SubscriptionId = Get-DefaultAzureSubscriptionId
    $Logger.LogInfo("Using Azure default subscription: $SubscriptionId")

    # Determine which RunCommand version to use
    $UseAvsClient = $env:CBS_AVS_SCRIPT_EXECUTION_VIA_AVS_CLIENT -eq "true"

    # Microsoft recommends using the stable version
    if ($RunCommandModule -eq "Microsoft.AVS.VMFS") {
        $RunCommandPackageVersion = $MICROSOFT_AVS_VMFS_VERSION
    } else {
        throw "Unknown RunCommandModule $RunCommandModule"
    }

    # Allow overriding the version via an environment variable
    $EnvVariableName = ($RunCommandModule -replace "\.", "_") + '_Version'
    $UpdatedVersion = [Environment]::GetEnvironmentVariable($EnvVariableName)
    if ($UpdatedVersion) {
        $Logger.LogWarning("Using customized AVS Run Command version $UpdatedVersion for Module ($RunCommandModule). Please consult with Pure Storage Support before using this version")
        $RunCommandPackageVersion = $UpdatedVersion
    }

    # Test RunCommand availability
    Test-RunCommandPackageAvailability -SubscriptionId $SubscriptionId -RunCommandModule $RunCommandModule -RunCommandPackageVersion $RunCommandPackageVersion -AVSCloudName $AVSCloudName -AVSResourceGroup $AVSResourceGroup -Logger $Logger

    # Construct command execution details
    $CmdletId = "/subscriptions/$SubscriptionId/resourceGroups/$AVSResourceGroup/providers/Microsoft.AVS/privateClouds/$AVSCloudName/scriptPackages/$RunCommandModule@$RunCommandPackageVersion/scriptCmdlets/$RunCommandName"
    $RandomID = [System.Guid]::NewGuid().ToString().Substring(0,8)
    $RunCmdExecutionName = "$RunCommandName-PureStorage.RunCommandWrapper-$RandomID"
    $ProductName = "PureStorage.CBS.AVS"
    $ProductVersion = (Get-Module $ProductName).Version.ToString()

    $Logger.LogInfo("Invoking RunCommand $RunCmdExecutionName with ID $CmdletId (use AvsClient: $UseAvsClient) ...")

    if ($UseAvsClient) {
        # Use the original AvsClient-based function
        Invoke-AvsScript -AVSCloudName $AVSCloudName -SubscriptionId  $SubscriptionId -AVSResourceGroup $AVSResourceGroup `
        -RunCommandId $CmdletId -RunCommandExecName $RunCmdExecutionName -TimeoutInMinutes $TimeoutInMinutes `
        -ProductName $ProductName -ProductVersion $ProductVersion `
        -CommandParameters $Parameters -ErrorVariable stopError
    } else {
        # Use the new REST API-based function
        Invoke-AvsScriptExecution -AVSCloudName $AVSCloudName -SubscriptionId  $SubscriptionId -AVSResourceGroup $AVSResourceGroup `
        -RunCommandId $CmdletId -RunCommandExecName $RunCmdExecutionName -TimeoutInMinutes $TimeoutInMinutes `
        -ProductName $ProductName -ProductVersion $ProductVersion `
        -CommandParameters $Parameters -ErrorVariable stopError -Logger $Logger
    }

    if ($stopError) {
        $Logger.LogInfo("RunCommand $RunCmdExecutionName failed with error:")
        throw $stopError
    }

    if ($GetNamedOutputs) {
        $Output = Get-RunCommandNamedOutput -RunCmdExecutionName $RunCmdExecutionName -AvsPrivateCloudName $AVSCloudName -AvsResourceGroupName $AVSResourceGroup -Logger $Logger
        Set-Variable -Name NamedOutputs -Value $Output -Scope Script
    }
}

Export-ModuleMember -Function Invoke-RunScript

# SIG # Begin signature block
# MIIpRQYJKoZIhvcNAQcCoIIpNjCCKTICAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAldRDQoLPfOjtn
# 7fGFmiaLYC1o/6ihMSmfvKvi/zPimaCCDfIwggbmMIIEzqADAgECAhB3vQ4DobcI
# +FSrBnIQ2QRHMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQK
# ExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENvZGUgU2ln
# bmluZyBSb290IFI0NTAeFw0yMDA3MjgwMDAwMDBaFw0zMDA3MjgwMDAwMDBaMFkx
# CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMS8wLQYDVQQD
# EyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25pbmcgQ0EgMjAyMDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBANZCTfnjT8Yj9GwdgaYw90g9z9DljeUg
# IpYHRDVdBs8PHXBg5iZU+lMjYAKoXwIC947Jbj2peAW9jvVPGSSZfM8RFpsfe2vS
# o3toZXer2LEsP9NyBjJcW6xQZywlTVYGNvzBYkx9fYYWlZpdVLpQ0LB/okQZ6dZu
# bD4Twp8R1F80W1FoMWMK+FvQ3rpZXzGviWg4QD4I6FNnTmO2IY7v3Y2FQVWeHLw3
# 3JWgxHGnHxulSW4KIFl+iaNYFZcAJWnf3sJqUGVOU/troZ8YHooOX1ReveBbz/IM
# BNLeCKEQJvey83ouwo6WwT/Opdr0WSiMN2WhMZYLjqR2dxVJhGaCJedDCndSsZlR
# Qv+hst2c0twY2cGGqUAdQZdihryo/6LHYxcG/WZ6NpQBIIl4H5D0e6lSTmpPVAYq
# gK+ex1BC+mUK4wH0sW6sDqjjgRmoOMieAyiGpHSnR5V+cloqexVqHMRp5rC+QBmZ
# y9J9VU4inBDgoVvDsy56i8Te8UsfjCh5MEV/bBO2PSz/LUqKKuwoDy3K1JyYikpt
# WjYsL9+6y+JBSgh3GIitNWGUEvOkcuvuNp6nUSeRPPeiGsz8h+WX4VGHaekizIPA
# tw9FbAfhQ0/UjErOz2OxtaQQevkNDCiwazT+IWgnb+z4+iaEW3VCzYkmeVmda6tj
# cWKQJQ0IIPH/AgMBAAGjggGuMIIBqjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAww
# CgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU2rONwCSQ
# o2t30wygWd0hZ2R2C3gwHwYDVR0jBBgwFoAUHwC/RoAK/Hg5t6W0Q9lWULvOljsw
# gZMGCCsGAQUFBwEBBIGGMIGDMDkGCCsGAQUFBzABhi1odHRwOi8vb2NzcC5nbG9i
# YWxzaWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUwRgYIKwYBBQUHMAKGOmh0dHA6
# Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2NvZGVzaWduaW5ncm9vdHI0
# NS5jcnQwQQYDVR0fBDowODA2oDSgMoYwaHR0cDovL2NybC5nbG9iYWxzaWduLmNv
# bS9jb2Rlc2lnbmluZ3Jvb3RyNDUuY3JsMFYGA1UdIARPME0wQQYJKwYBBAGgMgEy
# MDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9z
# aXRvcnkvMAgGBmeBDAEEATANBgkqhkiG9w0BAQsFAAOCAgEACIhyJsav+qxfBsCq
# jJDa0LLAopf/bhMyFlT9PvQwEZ+PmPmbUt3yohbu2XiVppp8YbgEtfjry/RhETP2
# ZSW3EUKL2Glux/+VtIFDqX6uv4LWTcwRo4NxahBeGQWn52x/VvSoXMNOCa1Za7j5
# fqUuuPzeDsKg+7AE1BMbxyepuaotMTvPRkyd60zsvC6c8YejfzhpX0FAZ/ZTfepB
# 7449+6nUEThG3zzr9s0ivRPN8OHm5TOgvjzkeNUbzCDyMHOwIhz2hNabXAAC4ShS
# S/8SS0Dq7rAaBgaehObn8NuERvtz2StCtslXNMcWwKbrIbmqDvf+28rrvBfLuGfr
# 4z5P26mUhmRVyQkKwNkEcUoRS1pkw7x4eK1MRyZlB5nVzTZgoTNTs/Z7KtWJQDxx
# pav4mVn945uSS90FvQsMeAYrz1PYvRKaWyeGhT+RvuB4gHNU36cdZytqtq5NiYAk
# CFJwUPMB/0SuL5rg4UkI4eFb1zjRngqKnZQnm8qjudviNmrjb7lYYuA2eDYB+sGn
# iXomU6Ncu9Ky64rLYwgv/h7zViniNZvY/+mlvW1LWSyJLC9Su7UpkNpDR7xy3bzZ
# v4DB3LCrtEsdWDY3ZOub4YUXmimi/eYI0pL/oPh84emn0TCOXyZQK8ei4pd3iu/Y
# TT4m65lAYPM8Zwy2CHIpNVOBNNwwggcEMIIE7KADAgECAgxcuW61kTkv+4t8zgQw
# DQYJKoZIhvcNAQELBQAwWTELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNp
# Z24gbnYtc2ExLzAtBgNVBAMTJkdsb2JhbFNpZ24gR0NDIFI0NSBDb2RlU2lnbmlu
# ZyBDQSAyMDIwMB4XDTI0MDMxMTE0MDQxMloXDTI3MDMxMjE0MDQxMlowcjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC1NhbnRhIENs
# YXJhMRswGQYDVQQKExJQdXJlIFN0b3JhZ2UsIEluYy4xGzAZBgNVBAMTElB1cmUg
# U3RvcmFnZSwgSW5jLjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMCQ
# rioSn48IvHpTg5dofsUYj/pNTDidwjYUrcxVu78NoyhSweG8FhcxDi/SI40+8Fcc
# l3D5ZoqpjkFnGhzSwmpxU3J4AP7+fdTZht9eWD1I5qKY07esYwdPDV4yg+csPfdG
# PqI2XjRfT5UC3YkXQeUrX8KQZldD4KqvgxzpYcuBwsgHbTb/eArpi68YgFR2jgZG
# yZigfy8RuJMrL1thcBOe/VWjUyK21wVT8cuunBYFaStLHhsRBRMDcZBDuTSGC4ev
# E6oaCqlQbdMl9YFJ64mDQsKlCxrr7rmLVtcVzKGwmjp4b2xRwE+RmTh6JtrUL9Wx
# /3a3UzgAnDNimfwp85zoL48kyLtHqQ3FI8tVKGm+aBOgBZfmURoy7fbp4zKhGgqF
# bpOmILO16i4f999YsEEJQgIF3CtyH1R60/ZZWlDmoeeEgjAGrnd14muU5Hk3Cksr
# 43uPUAg+fV78Y0fDV85ibm42ZwwPuz6MI4HhYNUlGzRwIQ31vjaGuAMWHNqFKkcO
# 0JuIeHQ/gFKPnYIxnGC9H9R4Kw/uMezqtnYJwGU2epB/ABl/w7U4NgU2ZOxWB5BF
# y4frZ3f+hNgbjFUjMaXnVFotOJxXntzjdSl4znw8DaKiC5ooChteZMITG9p078p/
# TUsOJQbUtFADSY1hsfCfB7t+gJSNt5peS9GOZIMVAgMBAAGjggGxMIIBrTAOBgNV
# HQ8BAf8EBAMCB4AwgZsGCCsGAQUFBwEBBIGOMIGLMEoGCCsGAQUFBzAChj5odHRw
# Oi8vc2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2djY3I0NWNvZGVzaWdu
# Y2EyMDIwLmNydDA9BggrBgEFBQcwAYYxaHR0cDovL29jc3AuZ2xvYmFsc2lnbi5j
# b20vZ3NnY2NyNDVjb2Rlc2lnbmNhMjAyMDBWBgNVHSAETzBNMEEGCSsGAQQBoDIB
# MjA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBv
# c2l0b3J5LzAIBgZngQwBBAEwCQYDVR0TBAIwADBFBgNVHR8EPjA8MDqgOKA2hjRo
# dHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjQ1Y29kZXNpZ25jYTIwMjAu
# Y3JsMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB8GA1UdIwQYMBaAFNqzjcAkkKNrd9MM
# oFndIWdkdgt4MB0GA1UdDgQWBBSzJ9KiDCa3UBiAajy+Iioj5kQjzDANBgkqhkiG
# 9w0BAQsFAAOCAgEAHsFQixeQEcoHurq9NWSUt4S39Q+UGP6crmVq3Wwy9g23YbdW
# g+SgMxoLUqdoDfA4k4B6Dyoo0jEQzn2kxnsnT9lNHKrcZHH88dv0hjfiH2qAiQWa
# zPjS3LhK2J6nhpyipJPpyRaSQG4x4aG0NB2D4WUfUz9CGAYsERJGww/wkTaaxMip
# ttKDTaI1C49u1igDfRzIO+Q8vuyyBFLiYTno/df97xtjNC+KxxFhDhl/4tawK6kw
# xaVzCMAfj48I67Wbo4DMH6pM1s19as7c3qp92i3MylGKsB6+u+o7UkbSdLNkS4AL
# I33CJOUc+GoK3Nt5IXXCFJTQFHBXkBdAur3gmlXEm8vlNG/1Sbxr0H7T1e7ABGH/
# 48o/+PeMLuCc72EeK5dJ4cX9NEQ3QnTsZHwGnYzjEOvOvP0s1c7yNsDbcUHoIqQv
# b5xS5aqMU5G+8sdPQ1nwpPf7gGaEEbAVW4w51Pam42qeN9HIPa+ZinXnsN02Kk1Q
# w0QwUqzaQy9W/gIquI0KOjw0LmoW9M/8S0lrjpEq2eEeUw9WQLhhUEIirFxGPtjq
# iCLiiS9CZ+kf2vWLJKUspkYv+OHT3q805Zg1dJsBFAzEYUFLb1mhmigDEO9bsMor
# jECIL2ijE5zHtbGkalrrsPWu8tiDT/B7P9GSYzKfOOy4PoOIfWSK0IxlS7Ixghqp
# MIIapQIBATBpMFkxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52
# LXNhMS8wLQYDVQQDEyZHbG9iYWxTaWduIEdDQyBSNDUgQ29kZVNpZ25pbmcgQ0Eg
# MjAyMAIMXLlutZE5L/uLfM4EMA0GCWCGSAFlAwQCAQUAoIGXMBkGCSqGSIb3DQEJ
# AzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCsG
# CisGAQQBgjcCAQwxHTAboRmAF2h0dHBzOi8vcHVyZXN0b3JhZ2UuY29tMC8GCSqG
# SIb3DQEJBDEiBCA+oigZHpDl+DoLC5L0rJZcKCSYdc2JhauEmMS9gCCq/jANBgkq
# hkiG9w0BAQEFAASCAgApq5pZ2yWGZh2JwUrCeScgfSsmDHjKIJK+ehoOk27pZLFb
# jP6qkJRjNVzyjRpaInyuXulCgUMeTUNHxnuSnehhFlr62WVRM3V8cA7LPzcqFse1
# RsjqCoLyGstHrSWzX1Rg0+ny8P0uSC62zjorxucNktikaAjv9YDRdIAHU3imWp5i
# R//n6PJFhXtMNAiEMRxl2gZc+VlPcfTT/aQG1w4Fd7ywWJmaS6popjKMYbZrNCEr
# FM0mmeJYdcXhiyF9mYWqEH6BZViM3swx4dR1lbfR7C7Zgs00Vw5BSR1kbM4rgidc
# TvDqJ6/G2KRTE/+Deil43LphqMqEpONIch99d6GqGkpsaAAZ6JDVA6EIqj9P1CJ7
# BHQzwP5sDxfqHkoeGonbMNzYYLOQMlWlON8V6wGGLRdqMVcJ/8A1JKFtR4N72t4a
# 7LKSwuIC1GA2chN9iFy9ZVFh233ji6cvy4JeGGw3IyKaPBNgeqhO1DZhKTEY4b79
# LHreYyF4QdY7ZOc7fAc5Zl1/OP97yY1WADrcIXVi/z8AnNt1R+JbRvShBHJl81nV
# E/XAS/t2wMiHzaGRHR79DKHK9AcXF8Fp/62zr/iVdKQeaDfGdJDNjBETQsGtSUXN
# feD6nlNyTaqHiZ44qtLlHXbdPDbxZRec/IM1Y8/RPbgRcsUwt6nSrJ7/rujYMKGC
# F3cwghdzBgorBgEEAYI3AwMBMYIXYzCCF18GCSqGSIb3DQEHAqCCF1AwghdMAgED
# MQ8wDQYJYIZIAWUDBAIBBQAweAYLKoZIhvcNAQkQAQSgaQRnMGUCAQEGCWCGSAGG
# /WwHATAxMA0GCWCGSAFlAwQCAQUABCA/v25kqF9ML/oArxfFdPM8dsZPLihSvJQu
# 4HXROv0SGgIRAM+Zty8to/8iWMbORHDzNbQYDzIwMjYwOTMwMTM0NTMxWqCCEzow
# ggbtMIIE1aADAgECAhAIT9wzT35FTtvDD4/5khg1MA0GCSqGSIb3DQEBCwUAMGkx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYg
# MjAyNSBDQTEwHhcNMjYwODA1MDAwMDAwWhcNMzcxMTA0MjM1OTU5WjBjMQswCQYD
# VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lD
# ZXJ0IFNIQTI1NiBSU0E0MDk2IFRpbWVzdGFtcCBSZXNwb25kZXIgMjAyNiAxMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtnum8sn+zUr41JtMZbP9OMYw
# +HwJDpG5xkIu/lqcfNYmMX81YmsUiHLbh9ykpeWBGKTLhYBrAN9Tdg/QEzG32XcO
# bmgIblnr0CoQ3WSAeDZ6nH6X6VkFyYkJw3QBJREwvm4UhLzSxmwPA7cFKRTEOMsm
# EEj6qJk/dqLEAL+oQYuOwE2UuiX1Vnul8YReIyWd4kgLn9gq6LNXM0UplkR6jL/Q
# Hxmb6fMoGBJYbnaUI7XD6cKDpekK2SVMld4iDbzeHDtOaaxldH5IxuNusQ69nd8/
# ZXEiB5Hbxj3RlK13cX1W4DlFXKdv/CEhM8Cj1vvlmvhNroyPdRGbbpBlgyf8Wdu5
# N6ByhFwURn0U6ozlPoxN22v+fviUhP+6DR547OZnpBMWDfei1f5sVGwiiW/KQTWO
# K97g+4RJpPzPNV4VYMAwO2jM2Aty2QYPVmOQTJm0msuXnJrSbl2gf9JylpkJlWXq
# k1Q4LJsxz+TELoQCZIljbgvTJgoPU2R12ydv8i1UqL/adelA0y7U9Pmmtbze9Xx3
# rtajC5SzQd1jgfwAwsa90v9YcSPdmeoyoBBA/27cCL237l5DTYYPDLQ4ON3OLTGW
# nvRb6jDrf/T75gMRfUzSLCBQfBusm9+mSWRlC/Df6S/e9Q8i13CuhzOT2Jx+V/nl
# bXM4QoBwlUAhelwwJT0CAwEAAaOCAZUwggGRMAwGA1UdEwEB/wQCMAAwHQYDVR0O
# BBYEFBTJY4owLtRK+26U8+bjQH717M3iMB8GA1UdIwQYMBaAFO9vU0rp5AZ8esri
# kFb2L9RJ7MtOMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# CDCBlQYIKwYBBQUHAQEEgYgwgYUwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
# Z2ljZXJ0LmNvbTBdBggrBgEFBQcwAoZRaHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZEc0VGltZVN0YW1waW5nUlNBNDA5NlNIQTI1NjIw
# MjVDQTEuY3J0MF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0
# LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYy
# MDI1Q0ExLmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJ
# KoZIhvcNAQELBQADggIBAI3FOmEenVIK35msCYB+fShAsWvSYvLBItoNdAgQ2jIq
# rGsVsluXMJU/+mRebBc52s6lbKAvOVPXaizmKkMLLflEEKDZQx4CkS2t8aHPjkXh
# a3hYZ010htFa3dhNgmalH5vuWvh3tTCf4frTS7gPtGc4Z/xaPhQ2AB1mR8eEe/Wb
# H0RWHvVIl6VwQ3+g5FKNfN2N/DWJkf13w2H+2GfqEfbd35Ww8CvoYBjLNIDTadcP
# WdgsjsiOaK/7EsKJgLjUNIVgvcaFOLLQ/GlrA+0ZHJoFUbOr5SJN8zykPspXIXlp
# DJY/gqFUZRROeab9GVgmhbdOJcD/63RhxPahFUGbckRONqMe6DYAv6/mOG0pWd3c
# PStsdcS7buj5DyniwRY8yooMH6ptx5vpP/pZzBPBeZD2U4IsthyxB5Jaa8qrOkB5
# z160TXiM5ADMspZ0TfD9MJoq0tFpFPssKRFhWeEDYPvcUuN7U7lvcdHl4ezQ3NT/
# 7Ffs1sR1yh/LRbdZ3B3Vc6q2WmD8mDC0p9kzl2o73iVtS946IkEj7FkRsZGww1te
# YxERROC745xrtjvcw9ZyyUjHZWGRIpJeMNsPquCDf0fkyHtB+J4AiNZqCQk23rxh
# +KbpyMTNVKItJ5l92Svl20U9NbqMBOVYl1h54NEYLJq1/xHWFKPNK903zJZA9P2D
# MIIGtDCCBJygAwIBAgIQDcesVwX/IZkuQEMiDDpJhjANBgkqhkiG9w0BAQsFADBi
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
# d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
# RzQwHhcNMjUwNTA3MDAwMDAwWhcNMzgwMTE0MjM1OTU5WjBpMQswCQYDVQQGEwJV
# UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRy
# dXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMIIC
# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtHgx0wqYQXK+PEbAHKx126NG
# aHS0URedTa2NDZS1mZaDLFTtQ2oRjzUXMmxCqvkbsDpz4aH+qbxeLho8I6jY3xL1
# IusLopuW2qftJYJaDNs1+JH7Z+QdSKWM06qchUP+AbdJgMQB3h2DZ0Mal5kYp77j
# YMVQXSZH++0trj6Ao+xh/AS7sQRuQL37QXbDhAktVJMQbzIBHYJBYgzWIjk8eDrY
# hXDEpKk7RdoX0M980EpLtlrNyHw0Xm+nt5pnYJU3Gmq6bNMI1I7Gb5IBZK4ivbVC
# iZv7PNBYqHEpNVWC2ZQ8BbfnFRQVESYOszFI2Wv82wnJRfN20VRS3hpLgIR4hjzL
# 0hpoYGk81coWJ+KdPvMvaB0WkE/2qHxJ0ucS638ZxqU14lDnki7CcoKCz6eum5A1
# 9WZQHkqUJfdkDjHkccpL6uoG8pbF0LJAQQZxst7VvwDDjAmSFTUms+wV/FbWBqi7
# fTJnjq3hj0XbQcd8hjj/q8d6ylgxCZSKi17yVp2NL+cnT6Toy+rN+nM8M7LnLqCr
# O2JP3oW//1sfuZDKiDEb1AQ8es9Xr/u6bDTnYCTKIsDq1BtmXUqEG1NqzJKS4kOm
# xkYp2WyODi7vQTCBZtVFJfVZ3j7OgWmnhFr4yUozZtqgPrHRVHhGNKlYzyjlroPx
# ul+bgIspzOwbtmsgY1MCAwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAw
# HQYDVR0OBBYEFO9vU0rp5AZ8esrikFb2L9RJ7MtOMB8GA1UdIwQYMBaAFOzX44LS
# cV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEF
# BQcDCDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
# Z2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYy
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5j
# cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB
# CwUAA4ICAQAXzvsWgBz+Bz0RdnEwvb4LyLU0pn/N0IfFiBowf0/Dm1wGc/Do7oVM
# Y2mhXZXjDNJQa8j00DNqhCT3t+s8G0iP5kvN2n7Jd2E4/iEIUBO41P5F448rSYJ5
# 9Ib61eoalhnd6ywFLerycvZTAz40y8S4F3/a+Z1jEMK/DMm/axFSgoR8n6c3nuZB
# 9BfBwAQYK9FHaoq2e26MHvVY9gCDA/JYsq7pGdogP8HRtrYfctSLANEBfHU16r3J
# 05qX3kId+ZOczgj5kjatVB+NdADVZKON/gnZruMvNYY2o1f4MXRJDMdTSlOLh0HC
# n2cQLwQCqjFbqrXuvTPSegOOzr4EWj7PtspIHBldNE2K9i697cvaiIo2p61Ed2p8
# xMJb82Yosn0z4y25xUbI7GIN/TpVfHIqQ6Ku/qjTY6hc3hsXMrS+U0yy+GWqAXam
# 4ToWd2UQ1KYT70kZjE4YtL8Pbzg0c1ugMZyZZd/BdHLiRu7hAWE6bTEm4XYRkA6T
# l4KSFLFk43esaUeqGkH/wyW4N7OigizwJWeukcyIPbAvjSabnf7+Pu0VrFgoiovR
# Diyx3zEdmcif/sYQsfch28bZeUz2rtY/9TCA6TD8dC3JE3rYkrhLULy7Dc90G6e8
# BlqmyIjlgp2+VqsS9/wQD7yFylIz0scmbKvFoW2jNrbM1pD2T7m3XDCCBY0wggR1
# oAMCAQICEA6bGI750C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4X
# DTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTAT
# BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEh
# MB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLh
# Kac9WKt2ms2uexuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+
# vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMp
# Lc7sXk7Ik/ghYZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+n
# MNlW7sp7XeOtyU9e5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1Dek
# LgV9iPWCPhCRcKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmk
# wuapoGfdpCe8oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0
# yt5LHucOY67m1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP
# 9yGyshG3u3/y1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHh
# D5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnf
# fEx1P2PsIV/EIFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId
# 5RsCAwEAAaOCATowggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LS
# cV1kTN8uZz/nupiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgP
# MA4GA1UdDwEB/wQEAwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0
# dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2Vy
# dHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNV
# HR8EPjA8MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRB
# c3N1cmVkSURSb290Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0B
# AQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlU
# Iu2kiHdtvRoU9BNKei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59PesMHqa
# i7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3A8eH
# qNJMQBk1RmppVLC4oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rbII01
# YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+2DrZ
# 8LaHlv1b0VysGMNNn3O3AamfV6peKOK5lDGCA3wwggN4AgEBMH0waTELMAkGA1UE
# BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy
# dCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENB
# MQIQCE/cM09+RU7bww+P+ZIYNTANBglghkgBZQMEAgEFAKCB0TAaBgkqhkiG9w0B
# CQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI2MDkzMDEzNDUzMVow
# KwYLKoZIhvcNAQkQAgwxHDAaMBgwFgQUUdmr2gNJc9hPQmaspIJI5rNpxDkwLwYJ
# KoZIhvcNAQkEMSIEIA16lBs9mrZWEW4LhSKw7CfipO2gSzjGvZoYdDBcg26pMDcG
# CyqGSIb3DQEJEAIvMSgwJjAkMCIEIC2gnaf0Ex+f5y22xebpyWVnVa8EPx6nQswN
# ISDhQev8MA0GCSqGSIb3DQEBAQUABIICAHLM/4aAjbxiwwS+i2DRJ7gx2gSHsYbr
# LUrJQ2WCIE1L8y0sLn7IjyWk62+wOHLqMJ9vZsBm2FiNvTUyUkA9W64DFi7GGyob
# WNvQzY69jdPhR2lsSGT4U9LBGuBDx4L3bhnzRwt5dhweAr4qKE69394mJNQSTQwy
# +kOuBjGe5y5JCh80yuU/Rvv3VWFrdndjGeqq2vf1HNIVfNbtncbEO2QNQzbPxht9
# XL7rA8Urxcfc+2C4YPQHRe2/sSGwJ4+xrLmm7xehIaJI2JtdNoNqpD5bFiLg1XO2
# 619TPigw+BuevAV/bMMl4OY5GJ3rPJNjNrM+vfyoA4ucyupzxfPAzV/9MZDU18Wa
# oLKYNjrjwFOgzVpQlga4fM2+/920vla6ZPy4GpD+6XTBy1xOO5cktt1AueKomMRX
# qdadG7ItsBhxRLK2YsBerakM8pXeNeVGjT1ulGnGh2z3NWCwh2dCbrLVpXrtMoQW
# V3Vn3Wh37XanQM7MGWRQzGl5dqjeOz3vtlY8cXM3clG/4n6bSk/PjJOKrXpKFa2q
# 80sNwEZ9UQ1U6RktkUMNYZyYU5M7LWdmqII8VPkXy21iwkAKKlk2Jqew1r7xtp24
# DeLS6ATTBSiAJs7XXLJwVXak6YC7mJezlj1GHM2bhMsuhVLE0GsMl2e+KO0b9WaT
# MTSnBAva8CB4
# SIG # End signature block