Kanji
・Cloud engineer / freelance ・Born in 1993 ・From Ehime / Living in Shibuya, Tokyo ・5 years AWS experience Profile details
Table of Contents
python3.12
python3.11
PowerShell 7.4
InputPayload
def handler(events, context):
sys.argv
outputs
$.Payload.*
$0.00003/sec
aws:executeScript
While standardizing multi-account operations, I wanted operators to run Python maintenance scripts safely from the AWS Management Console with approval workflows—without granting them broad IAM permissions. That led me to adopt the aws:executeScript action in AWS Systems Manager (SSM) Automation.
Expecting it to behave exactly like a standard AWS Lambda deployment, I wrote my CloudFormation template and Python script, deployed them, and immediately ran into three frustrating roadblocks on day one:
FalconPythonWrapper.lambda_handler
def script_handler(events, context):
dict
events
sha256
ModuleNotFoundError
modules.zip
files.checksums.sha256
time.sleep()
while
time.sleep(30)
$0.00003 / second
aws:waitForAwsResourceProperty
Based on those hands-on troubleshooting sessions, this guide covers the internal execution architecture of aws:executeScript , accurate payload and output wiring, production-ready CloudFormation and Terraform templates, least-privilege IAM policies, and cost optimization practices.
▲ Internal execution architecture (Falcon Sandbox) and data flow for aws:executeScript in AWS Systems Manager Automation
The aws:executeScript action lets you execute custom Python or PowerShell scripts serverlessly as part of an SSM Automation runbook.
Dumping os.environ inside aws:executeScript reveals that your script actually runs inside an AWS-managed AWS Lambda environment (internally prefixed Falcon- ) hosted in an AWS service account:
os.environ
Falcon-
{ "_HANDLER": "FalconPythonWrapper.lambda_handler", "AWS_EXECUTION_ENV": "AWS_Lambda_python3.12", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "512", "AWS_LAMBDA_FUNCTION_NAME": "Falcon-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/Falcon-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "LAMBDA_TASK_ROOT": "/var/task", "PYTHONPATH": "/var/runtime:/tmp/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "TZ": ":UTC" }
Understanding this underlying sandbox clarifies the exact architectural limits you must design around:
TimeoutSeconds
/tmp
aws:runCommand
aws:invokeLambdaFunction
aws:approve
Older runtimes such as python3.7 and python3.8 have reached End of Support. Always specify python3.12 or python3.11 for Python, or PowerShell 7.4 for PowerShell:
python3.7
python3.8
Runtime
python3.10
python3.9
PowerShell 7.2
PowerShell Core 6.0
$.Payload
runbook.py
For Python steps, set inputs.Handler to <filename_without_ext>.<function_name> (or just <function_name> for inline scripts). Define your entry point as def main(events, context): and read parameters from the events dictionary:
inputs.Handler
<filename_without_ext>.<function_name>
<function_name>
def main(events, context):
# src/runbook.py import logging from typing import Any, Dict, List import boto3 from botocore.exceptions import ClientError logger = logging.getLogger() logger.setLevel(logging.INFO) def main(events: Dict[str, Any], context: Any) -> Dict[str, Any]: """ Entry point for SSM Automation aws:executeScript. InputPayload is passed as the first argument `events` (dict). """ target_tag_key = events.get("TagKey", "Environment") target_tag_value = events.get("TagValue", "Staging") dry_run = str(events.get("DryRun", "true")).lower() == "true" print(f"[INFO] Starting scan: Tag {target_tag_key}={target_tag_value}, DryRun={dry_run}") ec2 = boto3.client("ec2") matched_instance_ids: List[str] = [] try: paginator = ec2.get_paginator("describe_instances") page_iterator = paginator.paginate( Filters=[ {"Name": f"tag:{target_tag_key}", "Values": [target_tag_value]}, {"Name": "instance-state-name", "Values": ["running"]}, ] ) for page in page_iterator: for reservation in page.get("Reservations", []): for instance in reservation.get("Instances", []): matched_instance_ids.append(instance["InstanceId"]) print(f"[INFO] Found {len(matched_instance_ids)} running instances: {matched_instance_ids}") # Returned dictionary is automatically wrapped under $.Payload.<Key> return { "MatchedCount": len(matched_instance_ids), "InstanceIds": matched_instance_ids, "DryRun": dry_run, "ExecutionStatus": "SUCCESS", } except ClientError as err: print(f"[ERROR] AWS API Error: {err}") raise
PowerShell Difference : PowerShell scripts do not require the Handler property. InputPayload is injected into the environment variable $env:InputPayload as a JSON string, which you can parse via $payload = $env:InputPayload | ConvertFrom-Json .
Handler
$env:InputPayload
$payload = $env:InputPayload | ConvertFrom-Json
To bundle third-party libraries (or a newer version of boto3 ), install packages directly into the root of your build folder alongside runbook.py before zipping:
boto3
mkdir -p build_pkg cp src/runbook.py build_pkg/ pip install -r src/requirements.txt -t build_pkg/ --upgrade # Zip the contents of build_pkg at the root level (do not include the parent folder) (cd build_pkg && zip -rq ../modules.zip .) # Compute the SHA-256 hash required for the SSM Document 'files' section shasum -a 256 modules.zip
AWS::SSM::Document
AWSTemplateFormatVersion: "2010-09-09" Description: SSM Automation Runbook with aws:executeScript (Python 3.12) Parameters: ArtifactBucketName: Type: String ModuleZipSha256: Type: String AutomationAssumeRoleArn: Type: String Resources: InstanceScanRunbook: Type: AWS::SSM::Document Properties: DocumentType: Automation DocumentFormat: YAML UpdateMethod: NewVersion Attachments: - Key: S3FileUrl Name: modules.zip Values: - !Sub "https://${ArtifactBucketName}.s3.${AWS::Region}.amazonaws.com/runbooks/modules.zip" Content: schemaVersion: "0.3" description: "Scan running EC2 instances by tag using Python 3.12" assumeRole: "{{ AutomationAssumeRole }}" parameters: AutomationAssumeRole: type: String default: !Ref AutomationAssumeRoleArn TagKey: type: String default: "Environment" TagValue: type: String default: "Staging" DryRun: type: String allowedValues: ["true", "false"] default: "true" mainSteps: - name: ScanInstancesStep action: "aws:executeScript" timeoutSeconds: 300 inputs: Runtime: "python3.12" Handler: "runbook.main" Attachment: "modules.zip" InputPayload: TagKey: "{{ TagKey }}" TagValue: "{{ TagValue }}" DryRun: "{{ DryRun }}" outputs: - Name: InstanceIds Selector: "$.Payload.InstanceIds" Type: StringList - Name: MatchedCount Selector: "$.Payload.MatchedCount" Type: Integer files: modules.zip: checksums: sha256: !Ref ModuleZipSha256
aws_ssm_document
Using data.archive_file in Terraform eliminates manual SHA-256 calculations and prevents S3 object caching issues by embedding the SHA-256 hash into the S3 key:
data.archive_file
resource "terraform_data" "pip_install" { triggers_replace = [ filesha256("${path.module}/src/requirements.txt"), filesha256("${path.module}/src/runbook.py"), ] provisioner "local-exec" { command = <<-EOT rm -rf "${path.module}/build_pkg" mkdir -p "${path.module}/build_pkg" cp "${path.module}/src/runbook.py" "${path.module}/build_pkg/" pip install -r "${path.module}/src/requirements.txt" -t "${path.module}/build_pkg" --upgrade EOT } } data "archive_file" "modules_zip" { depends_on = [terraform_data.pip_install] type = "zip" source_dir = "${path.module}/build_pkg" output_path = "${path.module}/dist/modules.zip" } resource "aws_s3_object" "modules_zip" { bucket = var.artifact_bucket_name key = "runbooks/modules-${data.archive_file.modules_zip.output_sha256}.zip" source = data.archive_file.modules_zip.output_path etag = data.archive_file.modules_zip.output_md5 } resource "aws_ssm_document" "instance_scan_runbook" { name = "InstanceScanRunbook" document_type = "Automation" document_format = "JSON" attachments_source { key = "S3FileUrl" name = "modules.zip" values = ["https://${var.artifact_bucket_name}.s3.${data.aws_region.current.name}.amazonaws.com/${aws_s3_object.modules_zip.key}"] } content = jsonencode({ schemaVersion = "0.3" description = "Scan running EC2 instances by tag using Python 3.12" assumeRole = var.automation_assume_role_arn parameters = { TagKey = { type = "String", default = "Environment" } TagValue = { type = "String", default = "Staging" } DryRun = { type = "String", default = "true" } } mainSteps = [ { name = "ScanInstancesStep" action = "aws:executeScript" timeoutSeconds = 300 inputs = { Runtime = "python3.12" Handler = "runbook.main" Attachment = "modules.zip" InputPayload = { TagKey = "{{ TagKey }}" TagValue = "{{ TagValue }}" DryRun = "{{ DryRun }}" } } outputs = [ { Name = "InstanceIds" Selector = "$.Payload.InstanceIds" Type = "StringList" }, { Name = "MatchedCount" Selector = "$.Payload.MatchedCount" Type = "Integer" } ] } ] files = { "modules.zip" = { checksums = { sha256 = data.archive_file.modules_zip.output_sha256 } } } }) }
AssumeRole
ssm:StartAutomationExecution
arn:aws:ssm:<region>:<account>:automation-definition/<RunbookName>:*
ssm:GetAutomationExecution
arn:aws:ssm:<region>:<account>:automation-execution/*
ssm:StopAutomationExecution
ssm:SendAutomationSignal
iam:PassRole
assumeRole
iam:PassedToService: ssm.amazonaws.com
aws:sleep
concurrent.futures.ThreadPoolExecutor
botocore.config.Config(retries={"max_attempts": 10, "mode": "adaptive"})
print()
AWS-*
aws:executeAwsApi
Once you account for the Falcon sandbox constraints (512 MB memory, 600-second max duration, no direct VPC attachment) and wire up events and $.Payload properly, aws:executeScript becomes one of the fastest ways to deliver auditable, least-privilege operational tooling in AWS. In my own projects, I keep scripts under ~300 lines in aws:executeScript and move heavier or VPC-bound workloads to Step Functions + Lambda—a boundary that continues to work well in production.
Master conversation skills with realistic AI phone training and 5-axis feedback. Includes 16-lesson interactive academy and studio AI portraits.
Effortlessly snap receipts for instant AI itemization. Integrated with LINE for seamless group expense tracking and pain-free settlement.
Interactive AI personas (Fortune Teller, Butler, Bartender) host real-time games, challenges, and conversation starters for unforgettable gatherings.
Real-time AI quant analysis of earnings reports, corporate filings, and momentum indicators for global equities and Japanese stocks.
Unbiased healthcare database featuring zero-additive filters and active ingredient cost performance metrics.
Get the complete configuration pack featured in this article: universal AGENTS.md, deterministic hooks.json, Git safety guardrails, and validation scripts tested across Antigravity, Kiro, Claude Code, and Codex.