AWS Developer Blog

Announcing Lambda Support for PowerShell Core

Today we are excited to release support for PowerShell Core 6.0 with AWS Lambda. This new feature enables you to execute PowerShell scripts or functions in response to any Lambda event, such as an Amazon S3 event or Amazon CloudWatch scheduled event.

Setting up a development environment

Before we get started developing PowerShell based Lambda functions, let’s set up our development environment.

First, we need to set up the correct version of PowerShell. AWS Lambda support for PowerShell is based on the cross-platform PowerShell Core 6.0 release. This means you can develop your Lambda functions for PowerShell on Windows, Linux, or Mac. If you don’t have this version of PowerShell installed, you can find instructions here.

If you are using Visual Studio Code on Windows as your IDE, you need to ensure it’s configured for PowerShell Core 6.0. To learn how to configure Visual Studio Code for PowerShell Core, follow the instructions here.

Next, we need to install the .NET Core 2.1 SDK. Because PowerShell Core is built on top of .NET Core, the Lambda support for PowerShell uses the same .NET Core 2.1 Lambda runtime for both .NET Core and PowerShell based Lambda functions. The .NET Core 2.1 SDK is used by the new PowerShell publishing cmdlets for Lambda to create the Lambda deployment package. You can find the .NET Core 2.1 SDK here. Be sure to install the SDK, not the runtime installation.

The last component we need for the development environment is the new AWSLambdaPSCore module that you can install from the PowerShell Gallery. The following is an example of installing the module from a PowerShell Core shell.

Install-Module AWSLambdaPSCore -Scope CurrentUser

This new module has the following new cmdlets to help you author and publish PowerShell based Lambda functions.

Cmdlet name Description
Get-AWSPowerShellLambdaTemplate Returns a list of getting started templates.
New-AWSPowerShellLambda Used to create an initial PowerShell script that is based on a template.
Publish-AWSPowerShellLambda Publishes a given PowerShell script to Lambda.
New-AWSPowerShellLambdaPackage Creates the Lambda deployment package that can be used in a CI/CD system for deployment.

Example use case

To demonstrate how we can use PowerShell based Lambda, we will use it to execute a PowerShell script that ensures that the Remote Desktop (RDP) port is not left open on any of our EC2 security groups. We will publish our script to Lambda and configure a CloudWatch scheduled event to periodically run our script.

Note: If you are walking through this example and don’t want to remove any RDP ports, then for testing purposes, change references to port 3389 to something else.

Creating a PowerShell based Lambda script

To help get started writing a PowerShell based Lambda function, you can use the New-AWSPowerShellLambda cmdlet to create a starter script that is based on a template. If we execute the Get-AWSPowerShellLambdaTemplate, we can see a list of available templates.

PS C:\> Get-AWSPowerShellLambdaTemplate

Template               Description
--------               -----------
Basic                  Bare bones script
CodeCommitTrigger      Script to process AWS CodeCommit Triggers
DetectLabels           Use Amazon Rekognition service to tag image files in Amazon S3 with detected labels.
KinesisStreamProcessor Script to process an Amazon Kinesis stream
S3Event                Script to process S3 events
SNSSubscription        Script to be subscribed to an Amazon SNS topic
SQSQueueProcessor      Script to be subscribed to an Amazon SQS queue

For this walkthrough, let’s start with a Basic template. To create a script named RDPLockDown by using the Basic template, execute the following command.

New-AWSPowerShellLambda -ScriptName RDPLockDown -Template Basic

Let’s look at the new RDPLockDown.ps1 script.

There are a few things to take note of in this starter script.

Lambda input object

Lambda functions are typically invoked with an input object that gives context about why the functions are being called. For example, if the Lambda function is being invoked in response to an object being uploaded to Amazon S3, the Lambda event object will identity the object that was uploaded. For PowerShell based Lambda, the Lambda input object can be accessed by the $LambdaInput variable, which is a PSObject.

Returning data

Some Lambda invocations are meant to return data back to their caller. For example if an invocation was in response to a web request coming from Amazon API Gateway, then our Lambda function needs to return back the response. For PowerShell based Lambda, the last object added to the PowerShell pipeline is the return data from the Lambda invocation. If the object is a string, the data is returned as is. Otherwise, the object is converted to JSON using the ConvertTo-Json cmdlet.

Including additional modules

The #Requires statement indicates additional modules that are required to execute a PowerShell script. In our starter script you can see that the AWSPowerShell.NetCore module is required. This gives us access to Amazon EC2 to check for open RDP ports. Later, when we publish our script to Lambda, all modules indicated in a #Requires statement will be packaged up with the script as part of our deployment package. The modules will be imported into the PowerShell environment in Lambda before executing the PowerShell script.

When using the AWS PowerShell module, be sure to use AWSPowerShell.NetCore, which supports PowerShell Core, and not the AWSPowerShell module, which supports Windows PowerShell only. Also be sure to use version 3.3.270.0 or later of AWSPowerShell.NetCore, which optimized the cmdlet import process. If you use an older version, you will experience longer cold starts.

Logging

Lambda uses the Amazon CloudWatch Logs service to record logging messages. In PowerShell based Lambda, this means the write cmdlets, such as Write-Host, Write-Output, and Write-Information, are written to CloudWatch Logs.

Writing our script

For our use case of locking down the RDP port, let’s use the following script. This script searches through all of our EC2 security groups to see if there are any ingress rules to allow traffic for port 3389, the RDP port. If an ingress rule is found, the rule is deleted. Notice we have several calls to Write-Host to record in our logs the security group we cleared the rules for.

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

$rulesRemoved = 0

Get-EC2SecurityGroup | ForEach-Object -Process {

    $securityGroupId = $_.GroupId
    $_.IpPermission | ForEach-Object -Process {

        if($_.ToPort -eq 3389) {
            Write-Host "Found open RDP port for $securityGroupId"
            Revoke-EC2SecurityGroupIngress -GroupId $securityGroupId -IpPermission $_
            Write-Host "Removed open RDP port for $securityGroupId"
            $rulesRemoved++
        }
    }
}

Write-Host "Scan complete and removed $rulesRemoved EC2 security group ingress rules"

Publishing to Lambda

To publish our PowerShell script as a Lambda function, we need to use the Publish-AWSPowerShellLambda cmdlet. This cmdlet creates our deployment package that contains our PowerShell script and the AWSPowerShell.NetCore module, because it’s declared with the #Requires statement. The deployment package will also include the required dependencies to bootstrap PowerShell in Lambda.

Publish-AWSPowerShellLambda has many parameters that can be set to configure our PowerShell based Lambda function, and I encourage you to execute Get-Help Publish-AWSPowerShellLambda to see the full list. The most important parameters to set are the ScriptPath which points to the PowerShell script to publish, and Name, which is the name of the Lambda function. The Publish-AWSPowerShellLambda cmdlet uses our AWS Lambda dotnet CLI extension to perform the deployment. This is why the 2.1 .NET Core SDK is required. This tool also prompts for any missing required parameters. For example, for new Lambda functions, an AWS Identity and Access Management (IAM) role is required. If one isn’t specified as a parameter to Publish-AWSPowerShellLambda, then when it executes the Lambda dotnet CLI extension, the tool will allow you to select an existing role or create a role. Be sure to select a role that gives access to manage EC2 security groups.

To publish our new PowerShell based Lambda function, let’s execute the following command.

Publish-AWSPowerShellLambda -ScriptPath .\RDPLockDown.ps1 -Name  RDPLockDown -Region us-east-1

On my system I have a default profile that will be used for obtaining AWS credentials. If I wanted a different profile, I could use the -Profile parameter. If you have configured your PowerShell environment with AWS credentials and region with the Set-AWSCredentials and Set-DefaultAWSRegion cmdlets, the Publish-AWSPowerShellLambda cmdlet will pick up those settings.

Because an IAM role was not set for the new Lambda function, we were prompted to select or create a role during deployment.

Testing our Lambda function

Let’s log on to the Lambda console and select our RDPLockDown Lambda function. To test the Lambda function, choose Test. You will be prompted for an input event object, which in our case isn’t used, so you can just leave it at the default and choose Create. Once the input event is created, the Lambda console invokes the Lambda function. The console will report back the tail of the log sent to CloudWatch Logs, which in my case showed it removed one security group rule.

Configuring the event source

We have seen our PowerShell based Lambda function work when we manually invoked it through the console. Let’s set up the CloudWatch schedule event to trigger the Lambda function. To do that, go back to the configuration section in the Lambda console and select CloudWatch Events in the configure triggers section.

This opens a new Configure trigger panel to set up the schedule event. We’ll need to set the following fields.

Rule Create a new rule.
Rule Name A unique name to identify this rule.
Rule Type Set to Scheduled expression.
Schedule expression The expression that identifies the frequency to run the script. This can be expressed in either a Cron expression or a Rate expression. For details about how to specify the expression, see the CloudWatch Logs Developer Guide. To see our new PowerShell based Lambda function in action, let’s set this value to rate(5 minutes) to have it run every 5 minutes.

With these fields set, go ahead and choose Add, and then choose Save for the Lambda function configuration change.

Now that our PowerShell based Lambda function is deployed and our trigger is set up to invoke our Lambda function every 5 minutes, we can monitor the invocations from the Monitoring tab in the Lambda console.

If you choose View logs in CloudWatch, you can see the log streams that have been created for the function. Choose the most recent stream to see our function’s logging.

Closing

This post showed an example with the new PowerShell support for Lambda, where we can execute PowerShell scripts to manage an AWS account without managing any servers to execute the script.

When you’re finished seeing this PowerShell based Lambda function in action, you should delete the Lambda function. You can do this in the Lambda console by choosing Delete Function under the Action button.

We hope PowerShell developers are excited about the possibilities of using this new feature. We’re certainly excited to see what the PowerShell community does with it. We would love to hear from the community on our Lambda .NET GitHub repository, where these new tools and libraries are maintained.