xkln.net


Running PowerShell On AWS Lambda

Posted by md on October 07, 2019

Having previously written a few Amazon Echo Skills I knew AWS Lambda could run JavaScript and Python, but I recently found out it can also run PowerShell.

Requirements

PowerShell Core

The first requirement is that only PowerShell Core 6 and later are supported. One advantage of PowerShell Core is the its ability to run on Windows, Linux, macOS, and even ARM. Microsoft have installation instructions for each OS here.

In this post I’ll be using PowerShell Core v6.2.3.

PS C:\> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      6.2.3
PSEdition                      Core
GitCommitId                    6.2.3
OS                             Microsoft Windows 10.0.18362
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Installing PowerShell Core does not upgrade or replace the existing Windows PowerShell install. Due to backwards compatibility issues it is installed alongside Windows PowerShell.

PowerShell Core can be ran with the pwsh command.

.NET Core 2.1 SDK

When we create PowerShell Lambda functions they are uploaded to AWS as a package in the form of a zip file that contains our code, any required modules, as well as the assemblies needed to run PowerShell Core.

This means we need to install the .NET Core 2.1 SDK which can also be downloaded from Microsoft. The specific version I’m using is v2.1.802 from here.

Install AWS PowerShell Modules

Finally, we need to install the AWS PowerShell and AWS Lambda PowerShell Core modules

Install-Module AWSPowerShell -Scope CurrentUser
Install-Module AWSLambdaPSCore -Scope CurrentUser
Install-Module AWSPowerShell.NetCore -Scope CurrentUser

Creating a deployment package

The AWSLambdaPSCore module provides access to a number of templates which we can use to create the boilerplate we need for the deployment package.

Available templates can be viewed with the Get-AWSPowerShellLambdaTemplate command.

PS C:\> Get-AWSPowerShellLambdaTemplate

Template                     Description
--------                     -----------
Basic                        Bare bones script
CloudFormationCustomResource PowerShell handler base for use with CloudFormation custom resource events
CodeCommitTrigger            Script to process AWS CodeCommit Triggers
DetectLabels                 Use Amazon Rekognition service to tag image files in S3 with detected labels.
KinesisStreamProcessor       Script to be process a Kinesis Stream
S3Event                      Script to process S3 events
S3EventToSNS                 Script to process SNS Records triggered by S3 events
S3EventToSNSToSQS            Script to process SQS Messages, subscribed to an SNS Topic that is triggered by S3 events
S3EventToSQS                 Script to process SQS Messages triggered by S3 events
SNSSubscription              Script to be subscribed to an SNS Topic
SNSToSQS                     Script to be subscribed to an SQS Queue, that is subscribed to an SNS Topic
SQSQueueProcessor            Script to be subscribed to an SQS Queue

Let’s create a package based on the Basic template, this is done with the New-AWSPowerShellLambda cmdlet.

PS C:\temp> New-AWSPowerShellLambda -Template Basic -ScriptName TestAWSLambdaFunction
WARNING: This script requires the AWSPowerShell.NetCore module which is not installed locally.
WARNING: To use the AWS CmdLets execute "Install-Module AWSPowerShell.NetCore" 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.
Created new AWS Lambda PowerShell script TestAWSLambdaFunction.ps1 from template Basic at C:\temp\TestAWSLambdaFunction

This command will create a directory under the current root with the name provided to the ScriptName paramaeter.

PS C:\temp> dir .\TestAWSLambdaFunction\


    Directory: C:\temp\TestAWSLambdaFunction

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----         7/10/2019  5:34 PM            457 readme.txt
-a----         7/10/2019  5:34 PM            852 TestAWSLambdaFunction.ps1

The contents of the default TestAWSLambdaFunction.ps1 file are as follows.

# PowerShell script file to be executed as a AWS Lambda function. 
# 
# When executing in Lambda the following variables will be predefined.
#   $LambdaInput - A PSObject that contains the Lambda function input data.#   $LambdaContext - An Amazon.Lambda.Core.ILambdaContext object that contains information about the currently running Lambda environment.
#
# The last item in the PowerShell pipeline will be returned as the result of the Lambda function.#
# To include PowerShell modules with your Lambda function, like the AWSPowerShell.NetCore module, add a "#Requires" statement 
# indicating the module and version.

#Requires -Modules @{ModuleName='AWSPowerShell.NetCore';ModuleVersion='3.3.335.0'}

# Uncomment to send the input event to CloudWatch Logs
# Write-Host (ConvertTo-Json -InputObject $LambdaInput -Compress -Depth 5)

Two key lines are highlighted.

  1. We will have an input object called $LambdaInput
  2. The last object we output will be the returned value

With those in mind, let’s simply add Write-Output $LambdaInput | select * to the bottom of the file. This will make our function echo back the $LambdaInput object so we can see what data AWS Lambda has.

With those in mind, let’s modify the file to include two actions at the bottom, the first, to append a timestamp to the input object, and the second to return the modified input object.

$LambdaInput | Add-Member -MemberType NoteProperty -Name Timestamp -Value (Get-Date)
Write-Output $LambdaInput | select *

While this doesn’t accomplish much, it serves as a nice quick test.

Publishing the package to AWS Lambda

There are two options for publishing our function. We can either simply create a deployment zip file, which can be uploaded manually or using other tooling you may already have in place (terraform, etc).

The package can also be pushed directly up to AWS. Both options are covered below.

Creating a deployment zip

While the AWS Lambda PowerShell module allows for deployment directly to an AWS region, we may instead wish to simply generate the package zip file in case we have other deployment tooling available.

This section can be skipped if deploying directly to AWS Lambda.

PS C:\temp\TestAWSLambdaFunction> New-AWSPowerShellLambdaPackage -ScriptPath .\TestAWSLambdaFunction.ps1 -OutputPackage TestAWSLambdaFunction.zip
Staging deployment at C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction
Configuring PowerShell to version 6.1.1
Generating C# project C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\TestAWSLambdaFunction.csproj used to create Lambda function bundle.
Generating C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\Bootstrap.cs to load PowerShell script and required modules in Lambda environment.
Generating aws-lambda-tools-defaults.json config file with default values used when publishing project.
Copying PowerShell script to staging directory
Resolved full output package path as C:\temp\TestAWSLambdaFunction\TestAWSLambdaFunction.zip
Creating deployment package at C:\temp\TestAWSLambdaFunction\TestAWSLambdaFunction.zip
Restoring .NET Lambda deployment tool
Initiate packaging
When deploying this package to AWS Lambda you will need to specify the function handler. The handler for this package is: TestAWSLambdaFunction::TestAWSLambdaFunction.Bootstrap::ExecuteFunction. To request Lambda to invoke a specific PowerShell function in your script specify the name of the PowerShell function in the environment variable AWS_POWERSHELL_FUNCTION_HANDLER when publishing the package.

LambdaHandler                                                           PathToPackage                                           PowerShellFunctionHandlerEnvVar
-------------                                                           -------------                                           -------------------------------
TestAWSLambdaFunction::TestAWSLambdaFunction.Bootstrap::ExecuteFunction C:\temp\TestAWSLambdaFunction\TestAWSLambdaFunction.zip AWS_POWERSHELL_FUNCTION_HANDLER

Deploying directly to AWS Lambda

Configuring an IAM role for Lambda execution

Create a new AWS IAM Role called lambda_basic_execution and assign it the AWSLambdaBasicExecutionRole policy from the existing default AWS IAM policies

Setting up AWS Authentication and Default Region

First we need to configure credentials the AWS Lambda PowerShell module can use to authenticate to AWS. This includes setting up an IAM user which is a little out of scope for this post, this will provide you with an API Access Key and an API SecretKey Key.

Initialize-AWSDefaultConfiguration -Region ap-southeast-2 -AccessKey "1234" -SecretKey "5678"

This should create a default AWS credential

PS C:\temp\TestAWSLambdaFunction> Get-AWSCredential -ListProfileDetail

ProfileName StoreTypeName         ProfileLocation
----------- -------------         ---------------
default     NetSDKCredentialsFile

Publishing to AWS

Publishing can be done using the Publish-AWSPowerShellLambda cmdlet.

PS C:\temp\TestAWSLambdaFunction> Publish-AWSPowerShellLambda -ScriptPath .\TestAWSLambdaFunction.ps1 -Name TestAWSLambdaFunction -Region ap-southeast-2 -IAMRoleArn lambda_basic_execution

Staging deployment at C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction
Configuring PowerShell to version 6.1.1
Generating C# project C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\TestAWSLambdaFunction.csproj used to create Lambda function bundle.
Generating C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\Bootstrap.cs to load PowerShell script and required modules in Lambda environment.
Generating aws-lambda-tools-defaults.json config file with default values used when publishing project.
Copying PowerShell script to staging directory
Deploying to AWS Lambda
Restoring .NET Lambda deployment tool
Initiate deployment
Amazon Lambda Tools for .NET Core applications (3.3.1)
Project Home: https://github.com/aws/aws-extensions-for-dotnet-cli, https://github.com/aws/aws-lambda-dotnet

Executing publish command
... invoking 'dotnet publish', working folder 'C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\bin\Release\netcoreapp2.1\publish'
... Disabling compilation context to reduce package size. If compilation context is needed pass in the "/p:PreserveCompilationContext=false" switch.
... publish: Microsoft (R) Build Engine version 16.2.32702+c4012a063 for .NET Core
... publish: Copyright (C) Microsoft Corporation. All rights reserved.
... publish:   Restore completed in 1.2 sec for C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\TestAWSLambdaFunction.csproj.
... publish:   TestAWSLambdaFunction -> C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\bin\Release\netcoreapp2.1\rhel.7.2-x64\TestAWSLambdaFunction.dll
... publish:   TestAWSLambdaFunction -> C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\bin\Release\netcoreapp2.1\publish\
Zipping publish folder C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\bin\Release\netcoreapp2.1\publish to C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\bin\Release\netcoreapp2.1\TestAWSLambdaFunction.zip
... zipping: Amazon.Lambda.Core.dll
... zipping: Amazon.Lambda.PowerShellHost.dll
<--output truncated -->
... zipping: Modules/AWSPowerShell.NetCore/3.3.335.0/CHANGELOG.md
... zipping: Modules/AWSPowerShell.NetCore/3.3.335.0/PSGetModuleInfo.xml
Created publish archive (C:\Users\md\AppData\Local\Temp\TestAWSLambdaFunction\bin\Release\netcoreapp2.1\TestAWSLambdaFunction.zip).
Creating new Lambda function TestAWSLambdaFunction
New Lambda function created

We can confirm deployment by checking the AWS Console.

Powershell Lambda Function

Publishing changes

The same command can be ran to update the function. The final line of the output will indicate a code update rather than function creation.

Updating code for existing function TestAWSLambdaFunction

Running a test

Once live, we can run a Test function in the AWS console to see whether our function behaves as expected. The simple ‘Hello World’ test template will suffice.

Powershell Lambda Test

Looks good, our input is echoed back to us, along with a timestamp.

Errors

Here’s a brief summary of some of the errors I got while writing this and how they were corrected.

.NET SDK Missing, incompatible version, or incorrect architecture installed

The installed .NET Core SDK does not meet the minimum requirement to build the PowerShell Lambda package bundle. Download the .NET Core 2.1 SDK from https://www.microsoft.com/net/download
At C:\Users\md\Documents\PowerShell\Modules\AWSLambdaPSCore\1.2.0.0\Private\_DeploymentFunctions.ps1:547 char:9
+         throw 'The installed .NET Core SDK does not meet the minimum  ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OperationStopped: (The installed .NET \u2026ft.com/net/download:String) [], RuntimeException
+ FullyQualifiedErrorId : The installed .NET Core SDK does not meet the minimum requirement to build the PowerShell Lambda package bundle. Download the .NET Core 2.1 SDK from https://www.microsoft.com/net/download

To validate what .NET SDKs you have installed, you can use dotnet --info, it should look as follows

PS C:\temp\TestAWSLambdaFunction> dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   2.1.802 Commit:    177d0b2525

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.18362
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.1.802\

Host (useful for support):
  Version: 2.1.13
  Commit:  1a165a1588

.NET Core SDKs installed:
  1.0.1 [C:\Program Files\dotnet\sdk]
  1.0.4 [C:\Program Files\dotnet\sdk]
  2.1.802 [C:\Program Files\dotnet\sdk]
.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 1.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 1.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 1.1.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 1.1.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

If you get an error like the one above, you’ve probably installed the incorrect .NET SDK architecture for your system, or you’ve entirely skipped the SDK install (make sure you install the SDK, not the runtime).

Authentication not configured correctly

Error retrieving configuration for function TestAWSLambdaFunction: The operation was canceled.
Error publishing PowerShell Lambda Function: -1

The error above indicates an issue with local AWS credentials created during Initialize-AWSDefaultConfiguration configuration.

AWS Account does not have correct permissions

Error retrieving configuration for function TestAWSLambdaFunction: User: arn:aws:iam::151292711111:user/deployment is not authorized to perform: lambda:GetFunctionConfiguration on resource: arn:aws:lambda:ap-southeast-2:151292711111:function:TestAWSLambdaFunction
Unknown error executing command: One or more errors occurred. (User: arn:aws:iam::151292711111:user/deployment is not authorized to perform: iam:ListRoles on resource: arn:aws:iam::151292711111:role/)
Unknown error executing command: One or more errors occurred. (User: arn:aws:iam::151292711111:user/deployment is not authorized to perform: iam:GetRole on resource: role lambda_basic_execution)
Error creating Lambda function: User: arn:aws:iam::151292711111:user/deployment is not authorized to perform: iam:PassRole on resource: arn:aws:iam::151292711111:role/lambda_basic_execution

Check that your deployment account has access to Lambda and IAM. I configured both IAMFullAccessIAMFullAccess and AWSLambdaFullAccess policies.

Error creating Lambda function: 1 validation error detected: Value 'arn:aws:iam::151292711111:user/lambda_basic_execution' at 'role' failed to satisfy constraint: Member must satisfy regular expression pattern: arn:(aws[a-zA-Z-]*)?:iam::\d{12}:role/?[a-zA-Z_0-9+=,.@\-_/]+

You created an IAM User instead of an IAM Role. Don’t worry, you’re not the only one.