Run PowerShell scripts in AWS lambda using a container image
AWS does not provide a container image for PowerShell lambdas, 823. This project provides a starting point to run PowerShell scripts from a container image.
- Lambda's deployed via zip have a limitation of 50MB compressed and 250MB expanded. Container images can be as large as 10GB!
- Some powershell modules have an install process that relies on having write access to powershell shared objects, PSWSMan for example. Once you deploy your script to AWS, you only have write access /tmp. Such a module could be installed in a container image prior to execution.
- Generic linux utilities and other arbitrary files are simpler to include into a container image as part of a build process compared to using lambda layers or bundling into a required module.
- The image is based on mcr.microsoft.com/powershell, python3 is installed and a small python program implements the runtime interface.
- The python program encodes the input event and context properties and executes a powershell init script.
- The init.ps1 script decodes the input and context and sets variables that would normally be available in a zip deployment, i.e.
$LambdaInput
and$LambdaContext
. This init script is also a nice place to include some generic functions or dot source other scripts not packages as modules. As an example, one function is included in init.ps1 that can write an AWS Secrets Manager binary secret value to a file,Get-SECSecretBinary
. - init.ps1 calls your script and does whatever you want.
- Finally the python program then collects stdout and stderr and returns an object.
A zipped powershell lambda will return the last object output. If you wrote a script with @(1,2,3)
, you would get 3
. This program will return all the stdout and stderr by default. Lambda responses are limited to 6MB. If you have a script with a lot of output, this could be a problem.
There are 5 different modes of output to choose from by setting the environment variable OUTPUT
.
Default
, object with stdout and stderr{"stdout": "", "stderr": ""}
NONE
, empty stringSTDOUT
, just standard out as a stringSTDERR
, just standard error as a stringLAST_LINE_JSON
, your object built from the last line of stdout
LAST_LINE_JSON
is very useful in getting data back from powershell as an arbitrary object. All you have to do is ensure the last line contains a string that can be parsed as JSON, i.e. @(1,2,3) | ConvertTo-Json -Compress
results in [1,2,3]
.
Script return codes are not evaluated to determine if your script succeeded or failed.
By default no errors will be thrown if your script fails, you will simply see the stdout and stderr. You can change this behavior by setting the environment variable FAIL_IF_STDERR
to 1. Any output in stderr will result in an exception being raised with the content of stderr. You can get output into stderr by calling throw
or Write-Error
in your PowerShell script.
The list of resources below are not available in $LambdaContext
as would be when using a zipped PowerShell lambda:
- RemainingTime
- Identity
- ClientContext
- Logger
or lack thereof
Running scripts this way is slow. You can speed things up increasing memory but don't expect sub-second response times. Even a basic script with 4GB of memory allocated will take a couple seconds to return and consume ~150MB. The motivation behind this project is largely to handle cron triggered tasks that start and complete within 15 minutes. AWS limits lambda's maximum execution time to 15 minutes.
Name | Required | Acceptable values | Value if not specified | Description |
---|---|---|---|---|
PWSH_SCRIPT | No | any string | /script/example_script.ps1 | Path to script that will be executed, see example_script.ps1 |
FAIL_IF_STDERR | No | 1, 0 | 0 | Cause the lambda function to fail if there is any output in STDERR |
OUTPUT | No | Default, NONE, STDOUT, STDERR, LAST_LINE_JSON | Default (both stdout and stderr) | Specify if you want no output, just stdout, just stderr, or to parse the last line of your script output as a JSON object |
Prerequisites
- Ability to build linux container images: docker, podman, rancher desktop, etc.
- AWS CLI
- Access to lambda and ECR
- A lambda execution role (or the ability to create one), AWS docs
Download, clone, or fork this repo.
git clone https://github.com/nickadam/powershell-lambda.git
cd powershell-lambda
Add your script and whatever.
Modify the Dockerfile or init.ps1 to suite your needs, Dockerfile reference:
- Install linux utilities using
apt-get
- Install powershell modules using
Install-Module -Name MyModule -Scope AllUsers -Force
-Scope AllUsers
is very important since you lambda will execute as any user
- Install additional AWS modules using
Install-AWSToolsModule
, S3 and SecretsManager are installed by default - Set desired default environment variables
- COPY your script, or just replace example_script.ps1 in the Dockerfile
- Add functions or whatever to init.ps1
Build, tag, and push your image, AWS docs.
docker build -t myimage:latest .
docker tag myimage:latest <accountid>.dkr.ecr.<region>.amazonaws.com/myimage:latest
docker push <accountid>.dkr.ecr.<region>.amazonaws.com/myimage:latest
Create lambda function
aws lambda create-function --region <region> --function-name MyFunction --package-type Image --code ImageUri=<accountid>.dkr.ecr.<region>.amazonaws.com/myimage:latest --role <execution role arn>
Run the function
aws lambda invoke --function-name MyFunction --payload '{ "key": "value" }' response.json
If you make changes - build, tag, push AND update the function. Updating the container image latest tag does not update the function.
aws lambda update-function-code --region <region> --function-name MyFunction <accountid>.dkr.ecr.<region>.amazonaws.com/myimage:latest
Download RIE
mkdir -p ~/.aws-lambda-rie && curl -Lo ~/.aws-lambda-rie/aws-lambda-rie \
https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie \
&& chmod +x ~/.aws-lambda-rie/aws-lambda-rie
Run the container
docker run --rm \
-e AWS_LAMBDA_RUNTIME_API=/aws-lambda/aws-lambda-rie \
-v ~/.aws-lambda-rie:/aws-lambda \
-p 9000:8080 \
--entrypoint /aws-lambda/aws-lambda-rie \
python3 -m awslambdaric execpwsh.handler
Make a request
curl -s -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{ "key": "value" }'