Private/_DeploymentFunctions.ps1

function _deployProject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$FunctionName,

        [Parameter(Mandatory = $false)]
        [string]$FunctionHandler,

        [Parameter(Mandatory = $false)]
        [string]$PowerShellFunctionHandler,

        [Parameter(Mandatory = $false)]
        [string]$ProfileName,

        [Parameter(Mandatory = $false)]
        [string]$Region,

        [Parameter(Mandatory = $false)]
        [string]$FunctionRole,

        [Parameter(Mandatory = $false)]
        [int]$FunctionMemory,

        [Parameter(Mandatory = $false)]
        [int]$FunctionTimeout,

        [Parameter(Mandatory = $false)]
        [string[]]$FunctionLayer,        

        [Parameter(Mandatory = $false)]
        [Boolean]$PublishNewVersion,

        [Parameter(Mandatory = $false)]
        [Hashtable]$EnvironmentVariables,

        [Parameter(Mandatory = $false)]
        [string]$KmsKeyArn,

        [Parameter(Mandatory = $false)]
        [string[]]$FunctionSubnets,

        [Parameter(Mandatory = $false)]
        [string[]]$FunctionSecurityGroups,

        [Parameter(Mandatory = $false)]
        [string]$DeadLetterQueueArn,

        [Parameter(Mandatory = $false)]
        [string]$TracingMode,

        [Parameter(Mandatory = $false)]
        [string]$S3Bucket,

        [Parameter(Mandatory = $false)]
        [string]$S3KeyPrefix,

        [Parameter(Mandatory = $false)]
        [Hashtable]$Tags,

        [Parameter(Mandatory = $false)]
        [Boolean]$DisableInteractive,

        [Parameter(Mandatory = $false)]
        [string]$BuildDirectory
    )

    _validateDotnetInstall

    if ($BuildDirectory)
    {
        Push-Location $BuildDirectory
    }

    try
    {
        $arguments = '"{0}"' -f $FunctionName
        $arguments += " --configuration Release --framework $AwsPowerShellTargetFramework --function-runtime $AwsPowerShellLambdaRuntime"

        $arguments += ' '
        $arguments += _setupAWSCredentialsCliArguments -ProfileName $ProfileName
        $arguments += ' '
        $arguments += _setupAWSRegionCliArguments -Region $Region

        if (($FunctionHandler))
        {
            $arguments += " --function-handler $FunctionHandler"
        }

        if (($FunctionRole))
        {
            $arguments += " --function-role $FunctionRole"
        }

        if (($FunctionMemory))
        {
            $arguments += " --function-memory-size $FunctionMemory"
        }

        if (($FunctionTimeout))
        {
            $arguments += " --function-timeout $FunctionTimeout"
        }

        $formattedLayers = _formatArray($FunctionLayer)
        if(($formattedLayers))
        {
            $arguments += " --function-layers $formattedLayers"
        }

        if (($PublishNewVersion))
        {
            $arguments += ' --function-publish true'
        }

        if ($PowerShellFunctionHandler)
        {
            $arguments += ' --append-environment-variables "{0}={1}"' -f $AwsPowerShellFunctionEnvName, $PowerShellFunctionHandler
            Write-Host "Setting the $AwsPowerShellFunctionEnvName environment variable to $PowerShellFunctionHandler to identify the PowerShell function to call"
        }

        $formattedEnvironmentVariables = _formatHashTable($EnvironmentVariables)
        if (($formattedEnvironmentVariables))
        {
            $arguments += ' --environment-variables "{0}"' -f $formattedEnvironmentVariables
        }

        if (($KmsKeyArn))
        {
            $arguments += " --kms-key $KmsKeyArn"
        }

        $formattedSubnets = _formatArray($FunctionSubnets)
        if (($formattedSubnets))
        {
            $arguments += " --function-subnets $formattedSubnets"
        }

        $formattedSecurityGroups = _formatArray($FunctionSecurityGroups)
        if (($formattedSecurityGroups))
        {
            $arguments += " --function-security-groups $formattedSecurityGroups"
        }

        if (($DeadLetterQueueArn))
        {
            $arguments += " --dead-letter-target-arn $DeadLetterQueueArn"
        }

        if (($TracingMode))
        {
            $arguments += " --tracing-mode $TracingMode"
        }

        if (($S3Bucket))
        {
            $arguments += " --s3-bucket $S3Bucket"
        }

        if (($S3KeyPrefix))
        {
            $arguments += " --s3-prefix $S3KeyPrefix"
        }

        $formattedTags = _formatHashTable($Tags)
        if (($formattedTags))
        {
            $arguments += ' --tags "{0}"' -f $formattedTags
        }

        if (($DisableInteractive))
        {
            $arguments += ' --disable-interactive true'
        }

        $amazonLambdaToolsPath = _configureAmazonLambdaTools

        $env:AWS_EXECUTION_ENV="AWSLambdaPSCore"
        try
        {
            if ($DisableInteractive)
            {
                Invoke-Expression "$amazonLambdaToolsPath deploy-function $arguments" | Foreach-Object {Write-Verbose -Message "$_`r"}
            }
            else
            {
                Write-Host 'Initiate deployment'
                Invoke-Expression "$amazonLambdaToolsPath deploy-function $arguments"
            }
        }
        finally
        {
            Remove-Item Env:\AWS_EXECUTION_ENV
        }

        if ($LASTEXITCODE -ne 0)
        {
            $msg = @"
Error publishing PowerShell Lambda Function: $LastExitCode
CALLSTACK:$(Get-PSCallStack | Out-String)
"@

            throw $msg
        }
    }
    finally
    {
        Pop-Location
    }
}


function _packageProject
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$OutputPackage,

        [Parameter(Mandatory = $false)]
        [string]$BuildDirectory
    )

    _validateDotnetInstall

    if (($BuildDirectory))
    {
        Push-Location $BuildDirectory
    }
    try
    {
        $arguments = $Name
        $arguments += " --configuration Release --framework $AwsPowerShellTargetFramework --function-runtime $AwsPowerShellLambdaRuntime"

        if (($OutputPackage))
        {
            $arguments += " --output-package `"$OutputPackage`""
        }
        $amazonLambdaToolsPath = _configureAmazonLambdaTools

        Write-Host 'Initiate packaging'

        # All output from the function deployment is sent to the verbose stream to allow user controlled access
        # to this level of detail
        Write-Verbose -Message "$amazonLambdaToolsPath package $arguments"
        Invoke-Expression "$amazonLambdaToolsPath package $arguments" | Foreach-Object {Write-Verbose -Message "$_`r"}
        if ($LASTEXITCODE -ne 0)
        {
            $msg = @"
Error publishing PowerShell Lambda Function: $LastExitCode
CALLSTACK:$(Get-PSCallStack | Out-String)
"@

            throw $msg
        }
    }
    finally
    {
        Pop-Location
    }
}

function _setupAWSCredentialsCliArguments
{
    param
    (
        [string]$ProfileName
    )

    if ($ProfileName)
    {
        return "--profile $ProfileName"
    }

    # Look to see if the AWS module is loaded and that it was used to configure credentials for the shell.
    # If it has then pass those credentials into the Lambda dotnet CLI tool.
    if (Get-Command 'Get-AWSCredentials' -ErrorAction SilentlyContinue)
    {
        $shellCredentials = Get-AWSCredentials
        if ($shellCredentials)
        {
            $realCreds = $shellCredentials.GetCredentials()
            Write-Verbose -Message 'Using aws credentials configured for the hosting shell'
            $arguments = '--aws-access-key-id {0} --aws-secret-key {1}' -f $realCreds.AccessKey, $realCreds.SecretKey

            if ($realCreds.UseToken)
            {
                Write-Verbose -Message 'Using session token'
                $arguments += ' --aws-session-token {0}' -f $realCreds.Token
            }

            return $arguments
        }
    }

    return [String]::Empty
}

function _setupAWSRegionCliArguments
{
    param
    (
        [string]$Region
    )

    if ($Region)
    {
        return "--region $Region"
    }

    if (Get-Command 'Get-DefaultAWSRegion' -ErrorAction SilentlyContinue)
    {
        $shellRegion = Get-DefaultAWSRegion
        if ($shellRegion)
        {
            Write-Verbose -Message ('Using region {0} configured for the hosting shell' -f $shellRegion.Region)
            return '--region {0}' -f $shellRegion.Region
        }
    }

    return [String]::Empty
}

function _configureAmazonLambdaTools
{
    Write-Host 'Restoring .NET Lambda deployment tool'

    # see if tool is already installed
    $amazonLambdaToolsInstalled = & dotnet tool list -g | Select-String -Pattern Amazon.Lambda.Tools -SimpleMatch -Quiet

    # When "-Verbose" switch was used this output was not hidden.
    # Using stream redirection to force hide all output from the dotnet cli call
    if (-not $amazonLambdaToolsInstalled)
    {
        Write-Verbose -Message 'Installing .NET Global Tool Amazon.Lambda.Tools'
        & dotnet tool install -g Amazon.Lambda.Tools *>&1 | Out-Null
    }
    else
    {
        Write-Verbose -Message 'Updating .NET Global Tool Amazon.Lambda.Tools'

        # When "-Verbose" switch was used this output was not hidden.
        # Using stream redirection to force hide all output from the dotnet cli call
        & dotnet tool update -g Amazon.Lambda.Tools *>&1 | Out-Null
    }

    if ($LASTEXITCODE -ne 0) {
        $msg = @"
Error configuring .NET CLI AWS Lambda deployment tools: $LastExitCode
CALLSTACK:$(Get-PSCallStack | Out-String)
"@

        throw $msg
    }

    $toolsFolder = Join-Path -Path '~' -ChildPath '.dotnet' -AdditionalChildPath 'tools'

    $amazonLambdaToolsPath = Join-Path -Path $toolsFolder -ChildPath 'dotnet-lambda.exe'
    Write-Verbose -Message 'Looking for windows excutable for dotnet-lambda.exe'
    if (!(Test-Path -Path $amazonLambdaToolsPath))
    {
        Write-Verbose -Message 'Did not find windows executable, assuming on non windows platform and using dotnet-lambda'
        $amazonLambdaToolsPath = Join-Path -Path $toolsFolder -ChildPath 'dotnet-lambda'
    }

    return $amazonLambdaToolsPath
}

function _formatHashTable
{
    param
    (
        [Parameter(Mandatory = $false)]
        [Hashtable]$Table
    )

    if (!($Table) -or $Table.Count -eq 0)
    {
        return $null
    }

    $sb = [System.Text.StringBuilder]::new()

    $Table.Keys | ForEach-Object {
        if ($sb.Length -ne 0)
        {
            $sb.Append(";") | Out-Null
        }

        $sb.AppendFormat('{0}={1}', $_, $Table[$_]) | Out-Null
    }

    return $sb.ToString()
}

function _formatArray
{
    param
    (
        [Parameter(Mandatory = $false)]
        [string[]]$Items
    )

    if (!($Items) -or $Items.Count -eq 0)
    {
        return $null
    }

    $sb = [System.Text.StringBuilder]::new()

    $items | ForEach-Object {
        if ($sb.Length -ne 0)
        {
            $sb.Append(",") | Out-Null
        }
        $sb.Append($_) | Out-Null
    }

    return $sb.ToString()
}

function _prepareDependentPowerShellModules
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$Script,

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

        [Parameter(Mandatory = $true)]
        [bool]$ClearExisting,

        [Parameter()]
        [string[]]$ModuleRepository
    )

    $SavedModulesDirectory = Join-Path -Path $ProjectDirectory -ChildPath $ProjectModuleDirectory
    if ($ClearExisting -and (Test-Path -Path $SavedModulesDirectory))
    {
        Remove-Item -Path $SavedModulesDirectory -Recurse -Force
    }

    if (!(Test-Path -Path $SavedModulesDirectory))
    {
        New-Item -ItemType directory -Path $SavedModulesDirectory | Out-Null
    }

    ## Use the FullName property of the $Script fileinfo object, as [System.Management.Automation.Language.Parser]::ParseFile() does not succeed with PSPath values like `Microsoft.PowerShell.Core\FileSystem::\\someserver\somepath\Get-Something.ps1`.
    ## $Script will have a PSPath value like this when the given file is at a UNC path.
    $strScriptFullname = (Get-Item -Path $Script).FullName
    ## variable in which to place any ParseFile() errors, so as to be able to check for them
    $arrErrorFromParseFile = @()
    $ast = [System.Management.Automation.Language.Parser]::ParseFile($strScriptFullname, [ref]$null, [ref]$arrErrorFromParseFile)
    if (($arrErrorFromParseFile | Measure-Object).Count -gt 0) {
        ## Write a warning (not terminating for now)
        Write-Warning "Received error trying to parse given script file '$Script'. Resulting Lambda package might not contain required PowerShell modules needed for success"
    } ## end if
    if ($ast.ScriptRequirements.RequiredModules)
    {
        $ast.ScriptRequirements.RequiredModules | ForEach-Object -Process {

            if ($_.Name -ieq 'AWSPowerShell')
            {
                Write-Warning 'This script requires the AWSPowerShell module which is not supported. Please change the #Requires statement to use the service specifc modules like AWS.Tools.S3 which is compatible with PowerShell 6.0 and above.'

                Write-Warning 'To use the AWS CmdLets install the AWS.Tools.* module for the services needed and then update the #Requires statement to the version installed. If you are not going to use the AWS CmdLets then remove the #Requires statement from the script.'

                throw 'The AWSPowerShell Module is not supported. Change the #Requires statement to reference the service specific modules like AWS.Tools.S3 module instead.'
            }

            $localModule = _findLocalModule -Name $_.Name -Version $_.Version
            if ($localModule)
            {
                Write-Host ('Copying local module {0}({1}) from {2}' -f $localModule.Name, $localModule.Version, $localModule.ModuleBase)
                $copyPath = Join-Path -Path $SavedModulesDirectory -ChildPath $localModule.Name -AdditionalChildPath $localModule.Version.ToString()
                if (!(Test-Path -Path $copyPath))
                {
                    New-Item -ItemType directory -Path $copyPath | Out-Null
                }
                Copy-Item -Path (Join-Path -Path $localModule.ModuleBase -ChildPath '*') -Destination $copyPath -Recurse
            }
            else
            {
                $splat = @{
                    Name = $_.Name
                    Path = $SavedModulesDirectory
                    ErrorAction = 'Stop'
                }

                if ($_.Version)
                {
                    $splat.Add('RequiredVersion',$_.Version)
                }

                if ($ModuleRepository)
                {
                    $splat.Add('Repository',$ModuleRepository)
                }

                # in the Save-Module call, replace -RequiredVersion with @splat
                Write-Host ('Saving module {0}' -f $_.Name)
                Save-Module @splat
            }
        }
    }
    ## Add verbosity that no RequiredModules found
    else {Write-Verbose "No RequiredModules found for script '$Script'"}
}

function _findLocalModule
{
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter()]
        [Version]$Version
    )

    $loadedModule = Get-Module -Name $Name
    if ($loadedModule -and ($Version -eq $null -or $Version -eq $loadedModule.Version))
    {
        $message = 'Found imported module {0} ({1}) to save with package bundle.' -f $loadedModule.Name, $loadedModule.Version.ToString()
        Write-Verbose -Message $message
        return $loadedModule
    }

    $availableModules = Get-Module -ListAvailable -Name $Name | Sort-Object -Property Version -Descending

    # Select-Object added to ensure multiple installed copies of a specified version won't break staging folder
    # names. Before: ModuleName\System.Obejct[]\. After: Module\Version\
    $availableModules | ForEach-Object -Process {
        if ($null -eq $Version -or $_.Version -eq $Version)
        {
            $message = 'Found installed module {0} ({1}) to save with package bundle.' -f $_.Name, $_.Version.ToString()
            Write-Verbose -Message $message
            return $_
        }
    } | Select-Object -First 1

    return $null
}

function _validateDotnetInstall
{
    $application = Get-Command -Name dotnet
    if (!($application))
    {
        throw '.NET Core 3.1 SDK was not found which is required to build the PowerShell Lambda package bundle. Download the .NET Core 3.1 SDK from https://www.microsoft.com/net/download'
    }

    $minVersion = [System.Version]::Parse('3.1.100')
    $foundMin = $false

    $installedSDKs = & dotnet --list-sdks
    foreach ($sdk in $installedSDKs) {
        $foundVersion = $sdk.split(' ')[0]
        $version = [System.Version]::new()
        if ([System.Version]::TryParse($foundVersion, [ref]$version))
        {
            if ($minVersion -le $foundVersion)
            {
                $foundMin = $true
            }
        }
    }

    if (!($foundMin))
    {
        throw 'The installed .NET Core SDK does not meet the minimum requirement to build the PowerShell Lambda package bundle. Download the .NET Core 3.1 SDK from https://www.microsoft.com/net/download'
    }
}

function _createStagingDirectory
{
    param
    (
        [Parameter(Mandatory = $true)]
        [String]$Name,

        [Parameter(Mandatory = $false)]
        [String]$StagingDirectory
    )

    if ($StagingDirectory)
    {
        $NewStagingDirectory = Join-Path -Path $StagingDirectory -ChildPath $Name
    }
    else
    {
        $NewStagingDirectory = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $Name
    }

    if (Test-Path -Path $NewStagingDirectory)
    {
        Write-Verbose -Message 'Removing previous staging directory'
        Remove-Item -Path $NewStagingDirectory -Recurse -Force
    }

    Write-Host "Staging deployment at $NewStagingDirectory"
    New-Item -ItemType Directory -Path $NewStagingDirectory -Force | Out-Null

    return $NewStagingDirectory
}
# SIG # Begin signature block
# MIIaqQYJKoZIhvcNAQcCoIIamjCCGpYCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB2elZ7W+qLY99s
# jDhVGA2h/EkjCCsyeapKpIMCLdagmaCCCoYwggUwMIIEGKADAgECAhAECRgbX9W7
# ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEwMjIxMjAwMDBa
# Fw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lD
# ZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7RZmxOttE9X/l
# qJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p0WfTxvspJ8fT
# eyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj6YgsIJWuHEqH
# CN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grkV7tKtel05iv+
# bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHyDxL0xY4PwaLo
# LFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMBAAGjggHNMIIB
# yTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAK
# BggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHow
# eDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG/WwA
# AgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAK
# BghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHwYDVR0j
# BBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQELBQADggEBAD7s
# DVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q3yBVN7Dh9tGS
# dQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/kLEbBw6RFfu6
# r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dcIFzZcbEMj7uo
# +MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6dGRrsutmQ9qz
# sIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT+hKUGIUukpHq
# aGxEMrJmoecYpJpkUe8wggVOMIIENqADAgECAhALTIJyAKtH3xTtbI8ZUVgmMA0G
# CSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0
# IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwHhcNMjAwNjIyMDAwMDAw
# WhcNMjEwNjMwMTIwMDAwWjCBijELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxIjAgBgNVBAoTGUFtYXpvbiBXZWIgU2Vy
# dmljZXMsIEluYy4xDDAKBgNVBAsTA0FXUzEiMCAGA1UEAxMZQW1hem9uIFdlYiBT
# ZXJ2aWNlcywgSW5jLjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALmr
# sFGrSta/FARlw23GEH+EpVCu0ejJBCgyuE2cX1ArId8rh8M6Q9/R8mlash12LDk6
# Zhfl0418bvsGqxp4V7x1PBwM9LqHwv+v9SRNJkIIRE9XQW5XLubMLDSZbqz4ysK4
# BeNXx8fg3DPIhzRYnNVAsINj43T95kW21Mje7pe8nABgUF+ihOyarccQ/+eUYHbf
# vNKEn7jVwVElzKc0zlYB2xwn6NC75FunB9ah9bK1eiKyDIVq0lQfW07yW4ReAIci
# 7Lmk/NLK6p+WX18tevZyOZvTp2JWCMrjQpi4Z6zNcgPVlQH/Fw9pOH88AoRNspJq
# M4cTQ9nZuVO1YP37uh8CAwEAAaOCAcUwggHBMB8GA1UdIwQYMBaAFFrEuXsqCqOl
# 6nEDwGD5LfZldQ5YMB0GA1UdDgQWBBRslc5x8VXQyhHcfVS3bCh5Tu1ZcTAOBgNV
# HQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwdwYDVR0fBHAwbjA1oDOg
# MYYvaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5j
# cmwwNaAzoDGGL2h0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQt
# Y3MtZzEuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAMBMCowKAYIKwYBBQUHAgEW
# HGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQQBMIGEBggrBgEF
# BQcBAQR4MHYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBO
# BggrBgEFBQcwAoZCaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0
# U0hBMkFzc3VyZWRJRENvZGVTaWduaW5nQ0EuY3J0MAwGA1UdEwEB/wQCMAAwDQYJ
# KoZIhvcNAQELBQADggEBAIyDXLu8ZDZqNX5ET8VHvAu/9V6yXI+HNMeUOJO4/az7
# 5HmJmja6SpmfLZC3g+WbNgF4roHwMNsIdb7dbdTGedxef49HJe5Ut5iV5vQ8DuKn
# PA7ezZV93Y5XDEiboX3sys5/k+7B1ZcP1jkObnfzQs7QXLAa3C/+kPtNmsXmTFOg
# DzRBmkr1Z/LXGTxgoWNQVZKNm2HA6ePRLPGBIXw7DUTnHtr9+4Fqxadck6fn5izz
# PUMOliRngw8XKTIRgBODRInHJZN9GRZI11emCP25LdHwLySxdHBTKsaslToKRAnd
# hrQhoc1FDAV6wKBOQoEKRZd75GIijtMCFaih+sVRCNAxgg95MIIPdQIBATCBhjBy
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
# d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQg
# SUQgQ29kZSBTaWduaW5nIENBAhALTIJyAKtH3xTtbI8ZUVgmMA0GCWCGSAFlAwQC
# AQUAoHwwEAYKKwYBBAGCNwIBDDECMAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcC
# AQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIE
# IEdk8p3eDkFtSmaskdy1E340c1w24zgERC6PcXyDd55aMA0GCSqGSIb3DQEBAQUA
# BIIBAE5xiEsQ7u2iSl9T97DqY3lbO6blHaO+4qvh2m5bMyptNUbYleOqxcUOSx5m
# wAjeFchXMzPDz3KtWW1S9WkKxGRxAe25Wrxn4mLzYFCQ6RaGpIrlF/W8jg6wh4Ul
# S9A428Aa7Kn+AMpozQayqMWOr1l9zpYKS9ukFNvA3N9996LnVXNJ+EROt/28X2a9
# 6KKMLigYez2b+ciKzViUfvboYf5YKDuOnsUPWAr4g9J/BGvjQ4i74AH80EGtYMWY
# NkUu0xoWM7uV5zva7y9WLZ2ZWTaP0hG1RNOiJppx4xD/TGUqyhcKN1MqMcsGOwry
# yGQCai/BhhSSTit/LCq95Nr5pgWhgg1FMIINQQYKKwYBBAGCNwMDATGCDTEwgg0t
# BgkqhkiG9w0BBwKggg0eMIINGgIBAzEPMA0GCWCGSAFlAwQCAQUAMHgGCyqGSIb3
# DQEJEAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgmCiB
# jva03xxvdhyTXsFvkrWsHFyYypQ1nCN6X+hcB4gCEQDTqqHf7pviu5m4wa/twUiP
# GA8yMDIxMDUwNjIyNDMyMVqgggo3MIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHO
# FADw3TANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGln
# aUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhE
# aWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEw
# MTAwMDAwMFoXDTMxMDEwNjAwMDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoT
# DkRpZ2lDZXJ0LCBJbmMuMSAwHgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAy
# MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpj
# a1HXOhFCvQp1dU2UtAxQtSYQ/h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9
# NmqB+in43Stwhd4CGPN4bbx9+cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaP
# xqPhLxs6t2HWc+xObTOKfF1FLUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsO
# jizVI9r0TXhG4wODMSlKXAwxikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7Ed
# MMKbaYK02/xWVLwfoYervnpbCiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmE
# vQECAwEAAaOCAbgwggG0MA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYG
# A1UdJQEB/wQMMAoGCCsGAQUFBwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkw
# JwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSME
# GDAWgBT0tuEgHf4prtLkYaWyoiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgt
# HUQ23eNqerwwcQYDVR0fBGowaDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQu
# Y29tL3NoYTItYXNzdXJlZC10cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2lj
# ZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYI
# KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZD
# aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJ
# RFRpbWVzdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7zn
# GucgDo5nRv1CclF0CiNHo6uS0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJm
# Te1ppA/2uHDPYuj1UUp4eTZ6J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3
# Y18jUmmDgvoaU+2QzI2hF3MN9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74
# wOFcz8QRcucbZEnYIpp1FUL1LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPW
# H1ub9y4bTxMd90oNcX6Xt/Q/hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHL
# KNpbh6aKLzCCBTEwggQZoAMCAQICEAqhJdbWMht+QeQF2jaXwhUwDQYJKoZIhvcN
# AQELBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG
# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJl
# ZCBJRCBSb290IENBMB4XDTE2MDEwNzEyMDAwMFoXDTMxMDEwNzEyMDAwMFowcjEL
# MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
# LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElE
# IFRpbWVzdGFtcGluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
# AL3QMu5LzY9/3am6gpnFOVQoV7YjSsQOB0UzURB90Pl9TWh+57ag9I2ziOSXv2Mh
# kJi/E7xX08PhfgjWahQAOPcuHjvuzKb2Mln+X2U/4Jvr40ZHBhpVfgsnfsCi9aDg
# 3iI/Dv9+lfvzo7oiPhisEeTwmQNtO4V8CdPuXciaC1TjqAlxa+DPIhAPdc9xck4K
# rd9AOly3UeGheRTGTSQjMF287DxgaqwvB8z98OpH2YhQXv1mblZhJymJhFHmgudG
# UP2UKiyn5HU+upgPhH+fMRTWrdXyZMt7HgXQhBlyF/EXBu89zdZN7wZC/aJTKk+F
# HcQdPK/P2qwQ9d2srOlW/5MCAwEAAaOCAc4wggHKMB0GA1UdDgQWBBT0tuEgHf4p
# rtLkYaWyoiWyyBc1bjAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAS
# BgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggr
# BgEFBQcDCDB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw
# LmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6
# oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElE
# Um9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0QXNzdXJlZElEUm9vdENBLmNybDBQBgNVHSAESTBHMDgGCmCGSAGG/WwAAgQw
# KjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzALBglg
# hkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggEBAHGVEulRh1Zpze/d2nyqY3qzeM8G
# N0CE70uEv8rPAwL9xafDDiBCLK938ysfDCFaKrcFNB1qrpn4J6JmvwmqYN92pDqT
# D/iy0dh8GWLoXoIlHsS6HHssIeLWWywUNUMEaLLbdQLgcseY1jxk5R9IEBhfiThh
# TWJGJIdjjJFSLK8pieV4H9YLFKWA1xJHcLN11ZOFk362kmf7U2GJqPVrlsD0WGkN
# fMgBsbkodbeZY4UijGHKeZR+WfyMD+NvtQEmtmyl7odRIeRYYJu6DC0rbaLEfrvE
# JStHAgh8Sa4TtuF8QkIoxhhWz0E0tmZdtnR79VYzIi8iNrJLokqV2PWmjlIxggJN
# MIICSQIBATCBhjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBT
# SEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBAhANQkrgvjqI/2BAIc4UAPDd
# MA0GCWCGSAFlAwQCAQUAoIGYMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAc
# BgkqhkiG9w0BCQUxDxcNMjEwNTA2MjI0MzIxWjArBgsqhkiG9w0BCRACDDEcMBow
# GDAWBBTh14Ko4ZG+72vKFpG1qrSUpiSb8zAvBgkqhkiG9w0BCQQxIgQgSZyRmw17
# C0UH2Ncv/Kn2dktYVT6kc9i4w8tYYlFQfk8wDQYJKoZIhvcNAQEBBQAEggEATasF
# dvDQQIdlTQc3A9JwZYiYeQO2sHVCZplgvyworb4YuPq6ePIfevYqO1KIMhwRpucU
# i+XxnynjIhdVNOk+CRQgPaaC+GAZX8a34AC6DvOH0TjasoHd97yR3Bveh8rw7m4k
# 6M9VzHqheyfF8rIMElydp4AFPFxgfBOdvzhDcxUkiCPpsFGNhRo2vOiR8TyeTjkH
# XVivqD+6vP/fozQ8wdR0rGojgU7VtSbizUwKrRe/1ViEq4idHyDONlOFCfEJhoTz
# +12B9a1j/aMSFjxIkxPy5kv/kj5mtmQviqeDvGl7mUPYK7b/X9Y5w1Qa+TUfo0tg
# 308XVD419ik0+DuvGg==
# SIG # End signature block