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 |