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]$AWSAccessKeyId,        

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

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

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

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

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

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

        [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 -AWSAccessKeyId $AWSAccessKeyId -AWSSecretKey $AWSSecretKey -AWSSessionToken $AWSSessionToken
        $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 (($FunctionArchitecture))
        {
            $arguments += " --function-architecture $FunctionArchitecture"
        }        

        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,

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

    _validateDotnetInstall

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

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

        if($FunctionArchitecture)
        {
            $arguments += " --function-architecture $FunctionArchitecture"
        }
        $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,
        [string]$AWSAccessKeyId,
        [string]$AWSSecretKey,
        [string]$AWSSessionToken
    )

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

    if ($AWSAccessKeyId -or $AWSSecretKey -or $AWSSessionToken)
    {
        if(!($AWSAccessKeyId))
        {
            throw "The AWSAccessKeyId parameter is required when AWSSecretKey or AWSSessionToken are set."
        }

        if(!($AWSSecretKey))
        {
            throw "The AWSSecretKey parameter is required when AWSAccessKeyId is set."
        }
        
        $arguments = "--aws-access-key-id $AWSAccessKeyId --aws-secret-key $AWSSecretKey"

        if($AWSSessionToken)
        {
            $arguments += " --aws-session-token $AWSSessionToken"
        }

        return $arguments
    }

    # 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
# MIIekAYJKoZIhvcNAQcCoIIegTCCHn0CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCgNPPPza/jXT9X
# jsCx+TSGu1LOqVIVGePI9dW5e+LPyaCCDb4wggawMIIEmKADAgECAhAIrUCyYNKc
# TJ9ezam9k67ZMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0z
# NjA0MjgyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcg
# UlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
# ggIKAoICAQDVtC9C0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0
# JAfhS0/TeEP0F9ce2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJr
# Q5qZ8sU7H/Lvy0daE6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhF
# LqGfLOEYwhrMxe6TSXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+F
# LEikVoQ11vkunKoAFdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh
# 3K3kGKDYwSNHR7OhD26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJ
# wZPt4bRc4G/rJvmM1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQay
# g9Rc9hUZTO1i4F4z8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbI
# YViY9XwCFjyDKK05huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchAp
# QfDVxW0mdmgRQRNYmtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRro
# OBl8ZhzNeDhFMJlP/2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IB
# WTCCAVUwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+
# YXsIiGX0TkIwHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0P
# AQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAk
# BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAC
# hjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v
# dEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5j
# b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAED
# MAgGBmeBDAEEATANBgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql
# +Eg08yy25nRm95RysQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFF
# UP2cvbaF4HZ+N3HLIvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1h
# mYFW9snjdufE5BtfQ/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3Ryw
# YFzzDaju4ImhvTnhOE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5Ubdld
# AhQfQDN8A+KVssIhdXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw
# 8MzK7/0pNVwfiThV9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnP
# LqR0kq3bPKSchh/jwVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatE
# QOON8BUozu3xGFYHKi8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bn
# KD+sEq6lLyJsQfmCXBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQji
# WQ1tygVQK+pKHJ6l/aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbq
# yK+p/pQd52MbOoZWeE4wggcGMIIE7qADAgECAhALQezlGADXm0QkMIMME0DNMA0G
# CSqGSIb3DQEBCwUAMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcg
# UlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwHhcNMjEwNjIxMDAwMDAwWhcNMjIwNjI5
# MjM1OTU5WjCBijELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1NlYXR0bGUxIjAgBgNVBAoTGUFtYXpvbiBXZWIgU2VydmljZXMsIElu
# Yy4xDDAKBgNVBAsTA0FXUzEiMCAGA1UEAxMZQW1hem9uIFdlYiBTZXJ2aWNlcywg
# SW5jLjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAMkg9KCA2iKT2gg1
# LZCv49o+ng0urkg5D6NjrtuvjNzzoZa/OhRaG9Lk/7oInsyP1EQHz+t3TbDC3oQL
# FQlbnybnMzwA5QhCXHtgoHMKiJBTp1JXDIktrGH3viO9tJAeETndAnNIjap5ekjn
# FY9mVG34XEFlhFT+5y7VplH4tX0I3Uda2dDCxnftEgJuVu3ari2RZm08AZ7msisA
# 9lNL7EKBBvs8YlzXQjJUwOye0KmW9yDFI2d21io5WzdJZvUPOWRsdatCDR2T3abr
# ohuO5UzqS4nlutGJ5hJ8stRV2wBAomoVaWqZJuqaMTiuigzOV79VB4Cp7LSHo4+W
# FDEHFWvOd7ZhWYQKqinQfCtGJE2B4HtktyVb75fvokeDaDUr8K0qTZP+TBgcyaQQ
# CSOq2Y3hlJgUUGufqnKN3ov8oT6aRwfMmNjDSFcggAdbEKkgWYI3eTMLlhz6vE7C
# K4glARf+X/TXM4osMtukwEz+S6H6JcbI4jr+ko1OBAFpjc0WCQIDAQABo4ICBjCC
# AgIwHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYEFBHT
# u3xRzhBn50nr95B39HbOObU1MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggr
# BgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdpY2Vy
# dC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQy
# MDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD
# ZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmww
# PgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5k
# aWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT
# QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEB
# CwUAA4ICAQCI//HXDFd66gGWU+LvPhUbNZvLy4nf5iVsfdWmNCuHcYhH5mn1rzj4
# CflUA96R03Tf7SJ5C0sAuA/5iOV2x607/BWOaHsyxN/pzsNW+3gJjfPsyXHec1TE
# MmTZ6A+aBaFZokdijE6eWxqjG8AmQoWUNCTso5bg6+vy/UW74Q8JRXUlmDWbQnlz
# oLe4ACo6PPbHxQBGpBG+lsGhWKz3H1HUDx3kTDHGh/j6rCPW/sbG5kZxbhdLzpgr
# Omu0N0+iNl2HT1Ug/lJdb/l3k8ngniMLT2Er5DcBVDo3h5aj7ODt/VNCGwL2ycAC
# XDAuxLz7cRwG2GD6ibnzcMSk31bhtaXxG02V8jcIpkwn7ikAF5PORUuEMoDlf8rZ
# s+p/0EVDVQ8HBEg5xvbq55Q8IWqqGniIKrAHyHO0t826Y9Rxkar2UKVMlRgT07kG
# Wt5kWyVdtUIB9YTyABIMDjPdNhixlKUWBi3dr/+5dQ2HtrJyoZFP2SdW3XgQS1lR
# KjE/27OzjGW1Dw0CPfvWbidhFjd+4YPZ+NQ08PMS0NpNW8vjI9BtGUVX2xlrqgT+
# ulqzWOyM0Ez877QqTmIFQQBf9JEuSgFg6BCmyqzWySco1bPjB3vmFYIeJF4PbEtr
# r6Za25gE3WTkclS7ldM0feUqP4HlE2AJzgfne7QlVchP/wHbqgtd3jGCECgwghAk
# AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw
# PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2
# IFNIQTM4NCAyMDIxIENBMQIQC0Hs5RgA15tEJDCDDBNAzTANBglghkgBZQMEAgEF
# AKB8MBAGCisGAQQBgjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEE
# MBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCCQ
# RphSs1h8J6uORJSILH5gkw+KvXyai77ng8JTCJVD9jANBgkqhkiG9w0BAQEFAASC
# AYAehj4aC1UC/3eTCBsfCeegFwKHH3rQsQCUJFLzFLiL/PIkzW8px31vfKVWcQij
# cgeueWl8MeYTmZiQlltbcRovYFrLlN0eiDEraOFrHnMm3lOxXeqqi8DP7aoGSdW/
# OgDQvtIbpwCbY+zKytwYO2ySY6oM4ra25Jwh264kbyGhVZBCahm+lYkhGp3o8TJm
# sKeJ39/SyTbLckh0PSPP/GV+0I55EXsV2L8ennj3LJO7RPJbxRq4smu9LonLOdC4
# 9TzJkirAZnHip0++LE6kghh6tdY73R+6qo35xN6WkKNZeBFDSmgYKHg/MCdvUaVh
# oWXp1sI/grG6J8+RUA/+q5wv8DzwFSoza2Y7Pm8Bshh7Ka82OPHwM8OlcjG+exI9
# gQAxZkcnZ7g6gnc8zovAnY+C1ejrOQmQAZ36hHFW0I/WhkkCTp5HnV7ACHuizcVD
# iurQu1e0uR5cuMcLhp7L/eRNm7xT0OKjlE+9flhChace094X047aOlGavDi7+W4z
# u9ehgg1+MIINegYKKwYBBAGCNwMDATGCDWowgg1mBgkqhkiG9w0BBwKggg1XMIIN
# UwIBAzEPMA0GCWCGSAFlAwQCAQUAMHgGCyqGSIb3DQEJEAEEoGkEZzBlAgEBBglg
# hkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgALr/pz97LbVlTFPnG9b+yqYsRed2
# XZ0zsegXhB3C+cwCEQDOrWlflDxZF+6UKdBCt66aGA8yMDIxMTAxNTE5MDkyMFqg
# ggo3MIIE/jCCA+agAwIBAgIQDUJK4L46iP9gQCHOFADw3TANBgkqhkiG9w0BAQsF
# ADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
# ExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3Vy
# ZWQgSUQgVGltZXN0YW1waW5nIENBMB4XDTIxMDEwMTAwMDAwMFoXDTMxMDEwNjAw
# MDAwMFowSDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSAw
# HgYDVQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMTCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAMLmYYRnxYr1DQikRcpja1HXOhFCvQp1dU2UtAxQtSYQ
# /h3Ib5FrDJbnGlxI70Tlv5thzRWRYlq4/2cLnGP9NmqB+in43Stwhd4CGPN4bbx9
# +cdtCT2+anaH6Yq9+IRdHnbJ5MZ2djpT0dHTWjaPxqPhLxs6t2HWc+xObTOKfF1F
# LUuxUOZBOjdWhtyTI433UCXoZObd048vV7WHIOsOjizVI9r0TXhG4wODMSlKXAwx
# ikqMiMX3MFr5FK8VX2xDSQn9JiNT9o1j6BqrW7EdMMKbaYK02/xWVLwfoYervnpb
# CiAvSwnJlaeNsvrWY4tOpXIc7p96AXP4Gdb+DUmEvQECAwEAAaOCAbgwggG0MA4G
# A1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMEEGA1UdIAQ6MDgwNgYJYIZIAYb9bAcBMCkwJwYIKwYBBQUHAgEWG2h0dHA6
# Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAfBgNVHSMEGDAWgBT0tuEgHf4prtLkYaWy
# oiWyyBc1bjAdBgNVHQ4EFgQUNkSGjqS6sGa+vCgtHUQ23eNqerwwcQYDVR0fBGow
# aDAyoDCgLoYsaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC10
# cy5jcmwwMqAwoC6GLGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3Vy
# ZWQtdHMuY3JsMIGFBggrBgEFBQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBPBggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NBLmNy
# dDANBgkqhkiG9w0BAQsFAAOCAQEASBzctemaI7znGucgDo5nRv1CclF0CiNHo6uS
# 0iXEcFm+FKDlJ4GlTRQVGQd58NEEw4bZO73+RAJmTe1ppA/2uHDPYuj1UUp4eTZ6
# J7fz51Kfk6ftQ55757TdQSKJ+4eiRgNO/PT+t2R3Y18jUmmDgvoaU+2QzI2hF3MN
# 9PNlOXBL85zWenvaDLw9MtAby/Vh/HUIAHa8gQ74wOFcz8QRcucbZEnYIpp1FUL1
# LTI4gdr0YKK6tFL7XOBhJCVPst/JKahzQ1HavWPWH1ub9y4bTxMd90oNcX6Xt/Q/
# hOvB46NJofrOp79Wz7pZdmGJX36ntI5nePk2mOHLKNpbh6aKLzCCBTEwggQZoAMC
# AQICEAqhJdbWMht+QeQF2jaXwhUwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTE2
# MDEwNzEyMDAwMFoXDTMxMDEwNzEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8G
# A1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTCC
# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3QMu5LzY9/3am6gpnFOVQo
# V7YjSsQOB0UzURB90Pl9TWh+57ag9I2ziOSXv2MhkJi/E7xX08PhfgjWahQAOPcu
# HjvuzKb2Mln+X2U/4Jvr40ZHBhpVfgsnfsCi9aDg3iI/Dv9+lfvzo7oiPhisEeTw
# mQNtO4V8CdPuXciaC1TjqAlxa+DPIhAPdc9xck4Krd9AOly3UeGheRTGTSQjMF28
# 7DxgaqwvB8z98OpH2YhQXv1mblZhJymJhFHmgudGUP2UKiyn5HU+upgPhH+fMRTW
# rdXyZMt7HgXQhBlyF/EXBu89zdZN7wZC/aJTKk+FHcQdPK/P2qwQ9d2srOlW/5MC
# AwEAAaOCAc4wggHKMB0GA1UdDgQWBBT0tuEgHf4prtLkYaWyoiWyyBc1bjAfBgNV
# HSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzASBgNVHRMBAf8ECDAGAQH/AgEA
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB5BggrBgEFBQcB
# AQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggr
# BgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNz
# dXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQu
# ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDBQBgNVHSAESTBHMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0
# cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzALBglghkgBhv1sBwEwDQYJKoZIhvcN
# AQELBQADggEBAHGVEulRh1Zpze/d2nyqY3qzeM8GN0CE70uEv8rPAwL9xafDDiBC
# LK938ysfDCFaKrcFNB1qrpn4J6JmvwmqYN92pDqTD/iy0dh8GWLoXoIlHsS6HHss
# IeLWWywUNUMEaLLbdQLgcseY1jxk5R9IEBhfiThhTWJGJIdjjJFSLK8pieV4H9YL
# FKWA1xJHcLN11ZOFk362kmf7U2GJqPVrlsD0WGkNfMgBsbkodbeZY4UijGHKeZR+
# WfyMD+NvtQEmtmyl7odRIeRYYJu6DC0rbaLEfrvEJStHAgh8Sa4TtuF8QkIoxhhW
# z0E0tmZdtnR79VYzIi8iNrJLokqV2PWmjlIxggKGMIICggIBATCBhjByMQswCQYD
# VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGln
# aWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGlt
# ZXN0YW1waW5nIENBAhANQkrgvjqI/2BAIc4UAPDdMA0GCWCGSAFlAwQCAQUAoIHR
# MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjEx
# MDE1MTkwOTIwWjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBTh14Ko4ZG+72vKFpG1
# qrSUpiSb8zAvBgkqhkiG9w0BCQQxIgQgtDzPr/TWBEZuYn/k2ECAdkkrwVZFIaeD
# 1JuVXAu+wGYwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgsxCQBrwK2YMHkVcp4EQD
# QVyD4ykrYU8mlkyNNXHs9akwDQYJKoZIhvcNAQEBBQAEggEAek1kZqzzmUFUcYML
# CPueuxXlw5l36cxyDpk8nb81KRQdar5eU4i1IdcnKlzjl3hvQoebSex/0jAy6unW
# 9Z25pBlkSXvmDk2dAi5HLiCsgHmyb/2uJzXd7nDxi+b3YrJ+i7tboEDScioW3WBe
# Z5EoXZwoTL/m0m+4s+cgNQB9BksEejxkhDF+RHHYxzQISABK7U8/CXPPdEgHZvJl
# luktecg/WYQysO8TvdSy1OE/5zILouvWnczz13PxaslwQufQIcFP1mHtENT7dlxE
# /pIwG/LR+xNYWgolkdj6Yo27luIayFDYANkbWIYEOpqbgOU9QuEtTbw8NpsHcTOq
# iDdv6w==
# SIG # End signature block