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 6 SDK was not found which is required to build the PowerShell Lambda package bundle. Download the .NET 6 SDK from https://www.microsoft.com/net/download'
    }

    $minVersion = [System.Version]::Parse('6.0.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
# MIIejwYJKoZIhvcNAQcCoIIegDCCHnwCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCMQX3sSwv5CPxW
# uw+I16PyLqUeyDTqVpIntiLDtGJSh6CCDb4wggawMIIEmKADAgECAhAIrUCyYNKc
# 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/wHbqgtd3jGCECcwghAj
# AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw
# PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2
# IFNIQTM4NCAyMDIxIENBMQIQC0Hs5RgA15tEJDCDDBNAzTANBglghkgBZQMEAgEF
# AKB8MBAGCisGAQQBgjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEE
# MBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCCc
# Kd3CuuSvmcMlLcpA2Yfp+UEOEz3Q+a2Y4sWK7Ds9gzANBgkqhkiG9w0BAQEFAASC
# AYAUHdjnIXbHSRywGr/bxMm13aJJktwwICKojSUMmeO5ZFN0yw+FmcTYHpB3wLrU
# gUYmDTheZOYQFVr3YBv6U80Q3cd+vj+au0kCwe5gm77V1HYcwqGZDcuOWaYI55yj
# JLKT+NfobBH11LeRshjGNENwfk/TCAWuOgSwyoz+srGHqRqXxmq1lO8Zq7LJ02uN
# NHLhkBVZuEHeqN0agRPRJwC19sDWzxa9mR1qu60BQSOp2F6aIg8w6Ej0y65ZwlXa
# OYpjKLPYaSRcVWIc0cwTn/5mOBXgdgHzimtj+SAGNU4EVm81TbFGpismpw142CfR
# Iuj5UGztbmjC30YNaBlWMEqL+pCzwIm9MNy1loW6VEFdpsdUsuHQR/o2Hc/Fzd64
# NRfSWz6nSIiOEjzcU15MStTA0jdqhbcsNVYsvqWdvuKSuDP4QQjDAJOobYWF/g+5
# HdJMHz2FFsQiHcmYqPRKFUdkGaMA/u0I7TnGxAfw6EUNqC7w2oMdM/d7NyIn/con
# ZT+hgg19MIINeQYKKwYBBAGCNwMDATGCDWkwgg1lBgkqhkiG9w0BBwKggg1WMIIN
# UgIBAzEPMA0GCWCGSAFlAwQCAQUAMHcGCyqGSIb3DQEJEAEEoGgEZjBkAgEBBglg
# hkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgpnyCj00mpYPeBJ75XdTic6UZk/Ja
# cnuhFYGEXG4ePvkCEHqkK1pTpnYlNJ4KJV7PECQYDzIwMjIwMjI0MDA0MDQxWqCC
# CjcwggT+MIID5qADAgECAhANQkrgvjqI/2BAIc4UAPDdMA0GCSqGSIb3DQEBCwUA
# MHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT
# EHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJl
# ZCBJRCBUaW1lc3RhbXBpbmcgQ0EwHhcNMjEwMTAxMDAwMDAwWhcNMzEwMTA2MDAw
# MDAwWjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xIDAe
# BgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIxMIIBIjANBgkqhkiG9w0BAQEF
# AAOCAQ8AMIIBCgKCAQEAwuZhhGfFivUNCKRFymNrUdc6EUK9CnV1TZS0DFC1JhD+
# HchvkWsMlucaXEjvROW/m2HNFZFiWrj/ZwucY/02aoH6KfjdK3CF3gIY83htvH35
# x20JPb5qdofpir34hF0edsnkxnZ2OlPR0dNaNo/Go+EvGzq3YdZz7E5tM4p8XUUt
# S7FQ5kE6N1aG3JMjjfdQJehk5t3Tjy9XtYcg6w6OLNUj2vRNeEbjA4MxKUpcDDGK
# SoyIxfcwWvkUrxVfbENJCf0mI1P2jWPoGqtbsR0wwptpgrTb/FZUvB+hh6u+elsK
# IC9LCcmVp42y+tZji06lchzun3oBc/gZ1v4NSYS9AQIDAQABo4IBuDCCAbQwDgYD
# VR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH
# AwgwQQYDVR0gBDowODA2BglghkgBhv1sBwEwKTAnBggrBgEFBQcCARYbaHR0cDov
# L3d3dy5kaWdpY2VydC5jb20vQ1BTMB8GA1UdIwQYMBaAFPS24SAd/imu0uRhpbKi
# JbLIFzVuMB0GA1UdDgQWBBQ2RIaOpLqwZr68KC0dRDbd42p6vDBxBgNVHR8EajBo
# MDKgMKAuhixodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLXRz
# LmNybDAyoDCgLoYsaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJl
# ZC10cy5jcmwwgYUGCCsGAQUFBwEBBHkwdzAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tME8GCCsGAQUFBzAChkNodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEVGltZXN0YW1waW5nQ0EuY3J0
# MA0GCSqGSIb3DQEBCwUAA4IBAQBIHNy16ZojvOca5yAOjmdG/UJyUXQKI0ejq5LS
# JcRwWb4UoOUngaVNFBUZB3nw0QTDhtk7vf5EAmZN7WmkD/a4cM9i6PVRSnh5Nnon
# t/PnUp+Tp+1DnnvntN1BIon7h6JGA0789P63ZHdjXyNSaYOC+hpT7ZDMjaEXcw30
# 82U5cEvznNZ6e9oMvD0y0BvL9WH8dQgAdryBDvjA4VzPxBFy5xtkSdgimnUVQvUt
# MjiB2vRgorq0Uvtc4GEkJU+y38kpqHNDUdq9Y9YfW5v3LhtPEx33Sg1xfpe39D+E
# 68Hjo0mh+s6nv1bPull2YYlffqe0jmd4+TaY4cso2luHpoovMIIFMTCCBBmgAwIB
# AgIQCqEl1tYyG35B5AXaNpfCFTANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJV
# UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
# Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTYw
# MTA3MTIwMDAwWhcNMzEwMTA3MTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UE
# ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYD
# VQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvdAy7kvNj3/dqbqCmcU5VChX
# tiNKxA4HRTNREH3Q+X1NaH7ntqD0jbOI5Je/YyGQmL8TvFfTw+F+CNZqFAA49y4e
# O+7MpvYyWf5fZT/gm+vjRkcGGlV+Cyd+wKL1oODeIj8O/36V+/OjuiI+GKwR5PCZ
# A207hXwJ0+5dyJoLVOOoCXFr4M8iEA91z3FyTgqt30A6XLdR4aF5FMZNJCMwXbzs
# PGBqrC8HzP3w6kfZiFBe/WZuVmEnKYmEUeaC50ZQ/ZQqLKfkdT66mA+Ef58xFNat
# 1fJky3seBdCEGXIX8RcG7z3N1k3vBkL9olMqT4UdxB08r8/arBD13ays6Vb/kwID
# AQABo4IBzjCCAcowHQYDVR0OBBYEFPS24SAd/imu0uRhpbKiJbLIFzVuMB8GA1Ud
# IwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMBIGA1UdEwEB/wQIMAYBAf8CAQAw
# DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHkGCCsGAQUFBwEB
# BG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsG
# AQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1
# cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5k
# aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRo
# dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0Eu
# Y3JsMFAGA1UdIARJMEcwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRw
# czovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAsGCWCGSAGG/WwHATANBgkqhkiG9w0B
# AQsFAAOCAQEAcZUS6VGHVmnN793afKpjerN4zwY3QITvS4S/ys8DAv3Fp8MOIEIs
# r3fzKx8MIVoqtwU0HWqumfgnoma/Capg33akOpMP+LLR2HwZYuhegiUexLoceywh
# 4tZbLBQ1QwRostt1AuByx5jWPGTlH0gQGF+JOGFNYkYkh2OMkVIsrymJ5Xgf1gsU
# pYDXEkdws3XVk4WTfraSZ/tTYYmo9WuWwPRYaQ18yAGxuSh1t5ljhSKMYcp5lH5Z
# /IwP42+1ASa2bKXuh1Eh5Fhgm7oMLSttosR+u8QlK0cCCHxJrhO24XxCQijGGFbP
# QTS2Zl22dHv1VjMiLyI2skuiSpXY9aaOUjGCAoYwggKCAgEBMIGGMHIxCzAJBgNV
# BAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdp
# Y2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1l
# c3RhbXBpbmcgQ0ECEA1CSuC+Ooj/YEAhzhQA8N0wDQYJYIZIAWUDBAIBBQCggdEw
# GgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yMjAy
# MjQwMDQwNDFaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFOHXgqjhkb7va8oWkbWq
# tJSmJJvzMC8GCSqGSIb3DQEJBDEiBCAUtTVgT7Z0tsfPShEXyDnxFky3y0gvboS6
# kjC1fu4f/jA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCCzEJAGvArZgweRVyngRANB
# XIPjKSthTyaWTI01cez1qTANBgkqhkiG9w0BAQEFAASCAQAlyWRKFAOMx/Iq4Si3
# NWBe9ouMAs4b95df7o9VS1rqtMVmrOp+aRnd1vOPcxfAAp4tAk+rGQTyVWDrkKnb
# 0A8WhRXT4kw1nw4o1Nabu9mQOj0anjLRBqz1jacN6SkbaL3ZknG60Ut2Np0qe4Hc
# elRVkm2NXyLL/pEnDAYz4h1jRgTfYdMSOacAR9XMI5csdZFaZM4dOo7xCtPgAZYd
# HBoB8YiCuF22bXjAZcfOUC2ghYm1wNPasTkXd2F7++hQu2Tj1WIl17DOU7kahMvI
# 779EQjt5yqzYE5OfSsPITKcHT7NbkwVdxQNn8AC+8Zn+ZYyWwZhMERWJnm6bYik5
# D6dk
# SIG # End signature block