AWS Developer Tools Blog
Creating a PowerShell REST API
With the recent AWS Lambda support for PowerShell, it’s now easy to make web APIs with Amazon API Gateway that execute your PowerShell scripts.
In the previous blog post, we showed how to deploy PowerShell-based Lambda functions with AWS CloudFormation. We are going to reuse that technique in this post because using AWS CloudFormation is one of the easiest ways to define our web API.
Use case
To demonstrate making a PowerShell web API, we will create a web API calculator. The PowerShell script will look at the resource path of the URI to determine the operation and the operands, perform the calculation, and return the result.
The following are examples of the types of request we will handle and what is returned.
- /add/100/50 -> 150
- /sub/100/50 -> 50
- /mul/100/50 -> 5000
- /div/100/50 -> 2
The script
The API Gateway request can either be transformed into a domain-specific object by using Swagger or it can be a proxy, where the entire web request is passed into the Lambda function. In this post, we use the proxy setting. For information about how the incoming proxy request looks in its JSON format, click here. In PowerShell, the JSON request is converted to a PSObject using the ConvertFrom-Json cmdlet.
Here is the PowerShell script we will run for the request. In the script we grab the URI resource path from the predefined $LambdaInput variable that contains the event object. Using the resource path, we grab the operator and operands and do the calculation.
if ($LambdaInput) {
$path = $LambdaInput.path
}
else {
# Test value when running locally.
$path = "/add/10/20"
}
if($path.StartsWith("/")) {
$path = $path.Substring(1)
}
$tokens = $path -split "/"
switch($tokens[0])
{
'add' {$result = [double]$tokens[1] + [double]$tokens[2]}
'sub' {$result = [double]$tokens[1] - [double]$tokens[2]}
'mul' {$result = [double]$tokens[1] * [double]$tokens[2]}
'div' {$result = [double]$tokens[1] / [double]$tokens[2]}
}
@{
'statusCode' = 200;
'body' = $result;
'headers' = @{'Content-Type' = 'text/plain'}
}
Returning data
Unlike in the previous blog posts, this is the first time we need to return data back from the Lambda function. In this case we need to return the response back for API Gateway, which must be in the format defined here.
In PowerShell, every time you execute a command that returns an object but don’t assign the result to a variable, it’s added to the PowerShell pipeline. This is how you pipe the results of one command into another command. In the case of Lambda, the last object added to the pipeline is used to return back to the caller. In our script we run the following command at the end.
@{
'statusCode' = 200;
'body' = $result;
'headers' = @{'Content-Type' = 'text/plain'}
}
This creates a PSObject and, because it isn’t assigned to a variable, it’s added to the pipeline and will be the last object. Because it’s not a string, the object is automatically converted to JSON using the ConvertTo-JSON cmdlet.
AWS CloudFormation template
When we deploy we want to create both our Lambda function and our REST API. This is where AWS CloudFormation works great. In the template below, we use the AWS::Serverless::Function meta resource we used in the previous post, which can create both the Lambda function and event sources.
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Transform" : "AWS::Serverless-2016-10-31",
"Resources" : {
"Get" : {
"Type" : "AWS::Serverless::Function",
"Properties": {
"Handler": "PSWebCalculator::PSWebCalculator.Bootstrap::ExecuteFunction",
"Runtime": "dotnetcore2.1",
"CodeUri": "PSWebCalculator.zip",
"MemorySize": 512,
"Timeout": 30,
"Role": null,
"Policies": [ "AWSLambdaBasicExecutionRole" ],
"Events": {
"CalculaterApi": {
"Type": "Api",
"Properties": {
"Path": "/{proxy+}",
"Method": "GET"
}
}
}
}
}
},
"Outputs" : {
"ApiURL" : {
"Description" : "API endpoint URL for Prod environment",
"Value" : { "Fn::Sub" : "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" }
}
}
}
The Events section in the AWS::Serverless::Function resource adds one REST API that accepts an HTTP GET method. The path is configured to be a proxy, which means all resource paths starting with “/” will be sent into this Lambda function.
Deployment
As we did in the previous blog post, we’ll use the New-AWSPowerShellLambdaPackage cmdlet from the AWSLambdaPSCore module to create the Lambda deployment package, and then use the AWS CLI to deploy the AWS CloudFormation template.
Here is the command to create the Lambda deployment bundle.
New-AWSPowerShellLambdaPackage -ScriptPath PSWebCalculater.ps1 -OutputPackage PSWebCalculater.zip
This command creates the PSWebCalculater.zip file and tells us that the function handler is PSWebCalculater::PSWebCalculater.Bootstrap::ExecuteFunction. In the AWS CloudFormation template above, the CodeUri property is set to PSWebCalculater.zip and the handler field is set to PSWebCalculater:: PSWebCalculater.Bootstrap::ExecuteFunction.
Next we need to run the aws cloudformation package command to upload the Lambda deployment bundle to Amazon S3 and update the template.
aws cloudformation package --template-file serverless.template --s3-bucket pswebcalculator-blog --output-template-file updated.template
Finally, we execute the aws cloudformation deploy command to deploy the updated AWS CloudFormation template, which creates the Lambda function and the API Gateway REST API.
aws cloudformation deploy --template-file updated.template --stack-name PSWebCalculater --capabilities CAPABILITY_IAM
Testing
Let’s test our new PowerShell REST API. First we need to find the URL for our new REST API, which if you noticed was an output parameter on the AWS CloudFormation template. We can view the output parameters by using the AWS web console, by using the Get-CFNStack cmdlet from the AWSPowerShell.NetCore module, or by using the AWS CLI. Because we have already been using the AWS CLI for deployment, let’s use it again and execute the following command to get the output parameters.
PS C:\PSWebCalulator> aws cloudformation describe-stacks --stack-name PSWebCalculator
{
"Stacks": [
{
"StackId": "arn:aws:cloudformation:us-east-1:626492997873:stack/PSWebCalculator/b74618b0-b940-11e8-b442-503aca261635",
"Description": "An AWS Serverless Application.",
"Tags": [],
"Outputs": [
{
"Description": "API endpoint URL for Prod environment",
"OutputKey": "ApiURL",
"OutputValue": "https://kul518aah6.execute-api.us-east-1.amazonaws.com/Prod/"
}
],
"EnableTerminationProtection": false,
"CreationTime": "2018-09-15T23:40:23.888Z",
"Capabilities": [
"CAPABILITY_IAM"
],
"StackName": "PSWebCalculator",
"NotificationARNs": [],
"StackStatus": "UPDATE_COMPLETE",
"DisableRollback": false,
"RollbackConfiguration": {},
"ChangeSetId": "arn:aws:cloudformation:us-east-1:626492997873:changeSet/awscli-cloudformation-package-deploy-1537056807/416d81d8-20e9-49d0-aa92-24c847d3af22",
"LastUpdatedTime": "2018-09-16T00:13:38.917Z"
}
]
}
Using the REST API URL, try a few requests in the browser. For example, try this:
<ApiURL from template>/add/40/2
The browser should show 42.
Summary
In this post we showed how you can create a REST API powered by Lambda and PowerShell. We also talked about how data is returned from a PowerShell-based Lambda function, in this case the response to an API Gateway request.
If you followed along with this post, be sure to delete the AWS CloudFormation stack when you’re done. Here is the AWS CLI command to delete the stack.
aws cloudformation delete-stack --stack-name RDPLockDown
As always, feel free to reach out to us with comments and suggestions on our GitHub repository.