Repository: cdklabs/cdk-ssm-documents (language: python)

Issue:
[Feature Request] Execute script step inputs should allow more flexibility
When creating a step of type `aws:executeScript`, the automation runbook schema allows for more flexibility than is possible in cdk-ssm-documents currently. Here's an example [from ASR](https://github.com/aws-solutions/aws-security-hub-automated-response-and-remediation/blob/main/source/remediation_runbooks/EnableAWSConfig.yaml#L69):

```yaml
  - name: CreateConfigBucket
    action: 'aws:executeScript'
    isEnd: false
    outputs:
      - Name: ConfigBucketName
        Selector: $.Payload.config_bucket
        Type: String
    inputs:
      InputPayload:
        logging_bucket: 'so0111-accesslogs-{{global:ACCOUNT_ID}}-{{global:REGION}}'
        account: '{{global:ACCOUNT_ID}}'
        region: '{{global:REGION}}'
        partition: '{{global:AWS_PARTITION}}'
        kms_key_arn: '{{KMSKeyArn}}'
      Runtime: python3.8
      Handler: create_encrypted_bucket
      Script: |-
        %%SCRIPT=EnableAWSConfig_createconfigbucket.py%%
```

Specifically, it is possible to:
* Pass in parameters, globals, and step outputs
* Name the key differently from the source variable (e.g. to follow Python naming conventions)
* Use string substitution to define arbitrary values for the input keys

As far as I can tell [from the source](https://github.com/cdklabs/cdk-ssm-documents/blob/main/src/parent-steps/automation/execute-script-step.ts#L55), it's only possible currently with this package to pass in variables where the key matches the variable name, and it's not possible to perform string substitution at all.

I think all of the machinery is here since this behavior has to be implemented for other parts of the schema to work. __Suggestion__: There should be a new type for an InputPayload item that has a key and a value. The key is a `HardCodedString`, and the value is an `IGenericVariable`.

This suggestion is based on my understanding of the way script steps work with the Python runtime. I'm not sure if there's anything about the Powershell runtime that might make this more complicated.


Discussion / hints:

Fix patch:
diff --git a/API.md b/API.md
index 54942f5..fbd2b64 100644
--- a/API.md
+++ b/API.md
@@ -7578,7 +7578,6 @@ public variables(): {[ key: string ]: any}
 | **Name** | **Description** |
 | --- | --- |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.isConstruct">isConstruct</a></code> | Checks if `x` is a construct. |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.getLanguage">getLanguage</a></code> | *No description.* |
 
 ---
 
@@ -7600,20 +7599,6 @@ Any object.
 
 ---
 
-##### `getLanguage` <a name="getLanguage" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStep.getLanguage"></a>
-
-```typescript
-import { ExecuteScriptStep } from '@cdklabs/cdk-ssm-documents'
-
-ExecuteScriptStep.getLanguage(runtime: string)
-```
-
-###### `runtime`<sup>Required</sup> <a name="runtime" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStep.getLanguage.parameter.runtime"></a>
-
-- *Type:* string
-
----
-
 #### Properties <a name="Properties" id="Properties"></a>
 
 | **Name** | **Type** | **Description** |
@@ -7632,9 +7617,8 @@ ExecuteScriptStep.getLanguage(runtime: string)
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.explicitNextStep">explicitNextStep</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.StepRef">StepRef</a></code> | *No description.* |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.allStepsInExecution">allStepsInExecution</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.AutomationStep">AutomationStep</a>[]</code> | *No description.* |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.nextStep">nextStep</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.AutomationStep">AutomationStep</a></code> | *No description.* |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.fullPathToCode">fullPathToCode</a></code> | <code>string</code> | *No description.* |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.handlerName">handlerName</a></code> | <code>string</code> | *No description.* |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.inputs">inputs</a></code> | <code>string[]</code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.code">code</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.ScriptCode">ScriptCode</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.inputs">inputs</a></code> | <code>{[ key: string ]: <a href="#@cdklabs/cdk-ssm-documents.IGenericVariable">IGenericVariable</a>}</code> | *No description.* |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.language">language</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage">ScriptLanguage</a></code> | *No description.* |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.outputs">outputs</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.Output">Output</a>[]</code> | *No description.* |
 
@@ -7782,33 +7766,23 @@ public readonly nextStep: AutomationStep;
 
 ---
 
-##### `fullPathToCode`<sup>Required</sup> <a name="fullPathToCode" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.fullPathToCode"></a>
-
-```typescript
-public readonly fullPathToCode: string;
-```
-
-- *Type:* string
-
----
-
-##### `handlerName`<sup>Required</sup> <a name="handlerName" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.handlerName"></a>
+##### `code`<sup>Required</sup> <a name="code" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.code"></a>
 
 ```typescript
-public readonly handlerName: string;
+public readonly code: ScriptCode;
 ```
 
-- *Type:* string
+- *Type:* <a href="#@cdklabs/cdk-ssm-documents.ScriptCode">ScriptCode</a>
 
 ---
 
 ##### `inputs`<sup>Required</sup> <a name="inputs" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStep.property.inputs"></a>
 
 ```typescript
-public readonly inputs: string[];
+public readonly inputs: {[ key: string ]: IGenericVariable};
 ```
 
-- *Type:* string[]
+- *Type:* {[ key: string ]: <a href="#@cdklabs/cdk-ssm-documents.IGenericVariable">IGenericVariable</a>}
 
 ---
 
@@ -21659,11 +21633,9 @@ const executeScriptStepProps: ExecuteScriptStepProps = { ... }
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.onCancel">onCancel</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.OnCancel">OnCancel</a></code> | (Optional) Fallback action to take in the event that this step is cancelled. |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.onFailure">onFailure</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.OnFailure">OnFailure</a></code> | (Optional) Fallback action to take in the event that this step fails. |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.timeoutSeconds">timeoutSeconds</a></code> | <code>number</code> | (Optional) timeout seconds to run this step. |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.inputs">inputs</a></code> | <code>string[]</code> | (Optional) Inputs that the function needs in order to execute. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.code">code</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.ScriptCode">ScriptCode</a></code> | Inline code to be executed. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.inputPayload">inputPayload</a></code> | <code>{[ key: string ]: <a href="#@cdklabs/cdk-ssm-documents.IGenericVariable">IGenericVariable</a>}</code> | InputPayload that will be passed to the first parameter of the handler. |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.language">language</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage">ScriptLanguage</a></code> | (Required) Language used to execute the script. |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.fullPathToCode">fullPathToCode</a></code> | <code>string</code> | Full path to the code to execute. |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.handlerName">handlerName</a></code> | <code>string</code> | (Optional) Function name in fullPathToCode file to use as entry point for script handler. |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.inlineCode">inlineCode</a></code> | <code>string</code> | Inline code to be executed. |
 | <code><a href="#@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.outputs">outputs</a></code> | <code><a href="#@cdklabs/cdk-ssm-documents.Output">Output</a>[]</code> | (Optional) Outputs that the function is expected to return. |
 
 ---
@@ -21805,73 +21777,46 @@ In a simulation run, this will only be encorced after-the-fact but execution wil
 
 ---
 
-##### `inputs`<sup>Required</sup> <a name="inputs" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.inputs"></a>
+##### `code`<sup>Required</sup> <a name="code" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.code"></a>
 
 ```typescript
-public readonly inputs: string[];
+public readonly code: ScriptCode;
 ```
 
-- *Type:* string[]
-- *Default:* []
-
-(Optional) Inputs that the function needs in order to execute.
-
----
-
-##### `language`<sup>Required</sup> <a name="language" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.language"></a>
-
-```typescript
-public readonly language: ScriptLanguage;
-```
+- *Type:* <a href="#@cdklabs/cdk-ssm-documents.ScriptCode">ScriptCode</a>
 
-- *Type:* <a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage">ScriptLanguage</a>
+Inline code to be executed.
 
-(Required) Language used to execute the script.
+String will be used to produce function in yaml/json.
+Simulation will execute the function in this code using the language specified.
 
 ---
 
-##### `fullPathToCode`<sup>Optional</sup> <a name="fullPathToCode" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.fullPathToCode"></a>
+##### `inputPayload`<sup>Required</sup> <a name="inputPayload" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.inputPayload"></a>
 
 ```typescript
-public readonly fullPathToCode: string;
+public readonly inputPayload: {[ key: string ]: IGenericVariable};
 ```
 
-- *Type:* string
-
-Full path to the code to execute.
-
-File is parsed to produce yaml/json.
-Simulation will execute this file using the language specified.
-Either this OR inclineCode must be provided.
-
----
-
-##### `handlerName`<sup>Optional</sup> <a name="handlerName" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.handlerName"></a>
+- *Type:* {[ key: string ]: <a href="#@cdklabs/cdk-ssm-documents.IGenericVariable">IGenericVariable</a>}
 
-```typescript
-public readonly handlerName: string;
-```
-
-- *Type:* string
-- *Default:* script_handler
+InputPayload that will be passed to the first parameter of the handler.
 
-(Optional) Function name in fullPathToCode file to use as entry point for script handler.
+This can be used to pass input data to the script.
+The key of this dict is the variable name that will be available to the code.
+The value is the Variable object.
 
 ---
 
-##### `inlineCode`<sup>Optional</sup> <a name="inlineCode" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.inlineCode"></a>
+##### `language`<sup>Required</sup> <a name="language" id="@cdklabs/cdk-ssm-documents.ExecuteScriptStepProps.property.language"></a>
 
 ```typescript
-public readonly inlineCode: string;
+public readonly language: ScriptLanguage;
 ```
 
-- *Type:* string
-
-Inline code to be executed.
+- *Type:* <a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage">ScriptLanguage</a>
 
-String will be used to produce function in yaml/json.
-Simulation will execute the function in this string using the language specified.
-Either this OR fullPathToCode must be provided.
+(Required) Language used to execute the script.
 
 ---
 
@@ -31492,6 +31437,126 @@ public readonly props: AwsInvocationSimulationProps;
 ---
 
 
+### FileScriptCode <a name="FileScriptCode" id="@cdklabs/cdk-ssm-documents.FileScriptCode"></a>
+
+#### Initializers <a name="Initializers" id="@cdklabs/cdk-ssm-documents.FileScriptCode.Initializer"></a>
+
+```typescript
+import { FileScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+new FileScriptCode(fullPath: string)
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.FileScriptCode.Initializer.parameter.fullPath">fullPath</a></code> | <code>string</code> | *No description.* |
+
+---
+
+##### `fullPath`<sup>Required</sup> <a name="fullPath" id="@cdklabs/cdk-ssm-documents.FileScriptCode.Initializer.parameter.fullPath"></a>
+
+- *Type:* string
+
+---
+
+#### Methods <a name="Methods" id="Methods"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.FileScriptCode.codeAsString">codeAsString</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.FileScriptCode.createOrGetFile">createOrGetFile</a></code> | If there is a file for this code, return it. |
+
+---
+
+##### `codeAsString` <a name="codeAsString" id="@cdklabs/cdk-ssm-documents.FileScriptCode.codeAsString"></a>
+
+```typescript
+public codeAsString(): string
+```
+
+##### `createOrGetFile` <a name="createOrGetFile" id="@cdklabs/cdk-ssm-documents.FileScriptCode.createOrGetFile"></a>
+
+```typescript
+public createOrGetFile(_suffix: string): string
+```
+
+If there is a file for this code, return it.
+
+Otherwise, create a file with the specified suffix.
+
+###### `_suffix`<sup>Required</sup> <a name="_suffix" id="@cdklabs/cdk-ssm-documents.FileScriptCode.createOrGetFile.parameter._suffix"></a>
+
+- *Type:* string
+
+---
+
+#### Static Functions <a name="Static Functions" id="Static Functions"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.FileScriptCode.fromFile">fromFile</a></code> | Full path to the code to execute. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.FileScriptCode.inline">inline</a></code> | Inline code to be executed. |
+
+---
+
+##### `fromFile` <a name="fromFile" id="@cdklabs/cdk-ssm-documents.FileScriptCode.fromFile"></a>
+
+```typescript
+import { FileScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+FileScriptCode.fromFile(fullPath: string)
+```
+
+Full path to the code to execute.
+
+File is parsed to produce yaml/json.
+Simulation will execute this file using the language specified.
+(Attachments not yet supported)
+
+###### `fullPath`<sup>Required</sup> <a name="fullPath" id="@cdklabs/cdk-ssm-documents.FileScriptCode.fromFile.parameter.fullPath"></a>
+
+- *Type:* string
+
+---
+
+##### `inline` <a name="inline" id="@cdklabs/cdk-ssm-documents.FileScriptCode.inline"></a>
+
+```typescript
+import { FileScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+FileScriptCode.inline(code: string)
+```
+
+Inline code to be executed.
+
+String will be used to produce function in yaml/json.
+Simulation will execute the function in this string using the language specified.
+
+###### `code`<sup>Required</sup> <a name="code" id="@cdklabs/cdk-ssm-documents.FileScriptCode.inline.parameter.code"></a>
+
+- *Type:* string
+
+---
+
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.FileScriptCode.property.fullPath">fullPath</a></code> | <code>string</code> | *No description.* |
+
+---
+
+##### `fullPath`<sup>Required</sup> <a name="fullPath" id="@cdklabs/cdk-ssm-documents.FileScriptCode.property.fullPath"></a>
+
+```typescript
+public readonly fullPath: string;
+```
+
+- *Type:* string
+
+---
+
+
 ### GenericVariable <a name="GenericVariable" id="@cdklabs/cdk-ssm-documents.GenericVariable"></a>
 
 - *Implements:* <a href="#@cdklabs/cdk-ssm-documents.IGenericVariable">IGenericVariable</a>
@@ -34423,6 +34488,126 @@ public readonly authMethod: AuthMethod;
 ---
 
 
+### InlineScriptCode <a name="InlineScriptCode" id="@cdklabs/cdk-ssm-documents.InlineScriptCode"></a>
+
+#### Initializers <a name="Initializers" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.Initializer"></a>
+
+```typescript
+import { InlineScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+new InlineScriptCode(inlineCode: string)
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.InlineScriptCode.Initializer.parameter.inlineCode">inlineCode</a></code> | <code>string</code> | *No description.* |
+
+---
+
+##### `inlineCode`<sup>Required</sup> <a name="inlineCode" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.Initializer.parameter.inlineCode"></a>
+
+- *Type:* string
+
+---
+
+#### Methods <a name="Methods" id="Methods"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.InlineScriptCode.codeAsString">codeAsString</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.InlineScriptCode.createOrGetFile">createOrGetFile</a></code> | If there is a file for this code, return it. |
+
+---
+
+##### `codeAsString` <a name="codeAsString" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.codeAsString"></a>
+
+```typescript
+public codeAsString(): string
+```
+
+##### `createOrGetFile` <a name="createOrGetFile" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.createOrGetFile"></a>
+
+```typescript
+public createOrGetFile(suffix: string): string
+```
+
+If there is a file for this code, return it.
+
+Otherwise, create a file with the specified suffix.
+
+###### `suffix`<sup>Required</sup> <a name="suffix" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.createOrGetFile.parameter.suffix"></a>
+
+- *Type:* string
+
+---
+
+#### Static Functions <a name="Static Functions" id="Static Functions"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.InlineScriptCode.fromFile">fromFile</a></code> | Full path to the code to execute. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.InlineScriptCode.inline">inline</a></code> | Inline code to be executed. |
+
+---
+
+##### `fromFile` <a name="fromFile" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.fromFile"></a>
+
+```typescript
+import { InlineScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+InlineScriptCode.fromFile(fullPath: string)
+```
+
+Full path to the code to execute.
+
+File is parsed to produce yaml/json.
+Simulation will execute this file using the language specified.
+(Attachments not yet supported)
+
+###### `fullPath`<sup>Required</sup> <a name="fullPath" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.fromFile.parameter.fullPath"></a>
+
+- *Type:* string
+
+---
+
+##### `inline` <a name="inline" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.inline"></a>
+
+```typescript
+import { InlineScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+InlineScriptCode.inline(code: string)
+```
+
+Inline code to be executed.
+
+String will be used to produce function in yaml/json.
+Simulation will execute the function in this string using the language specified.
+
+###### `code`<sup>Required</sup> <a name="code" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.inline.parameter.code"></a>
+
+- *Type:* string
+
+---
+
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.InlineScriptCode.property.inlineCode">inlineCode</a></code> | <code>string</code> | *No description.* |
+
+---
+
+##### `inlineCode`<sup>Required</sup> <a name="inlineCode" id="@cdklabs/cdk-ssm-documents.InlineScriptCode.property.inlineCode"></a>
+
+```typescript
+public readonly inlineCode: string;
+```
+
+- *Type:* string
+
+---
+
+
 ### Input <a name="Input" id="@cdklabs/cdk-ssm-documents.Input"></a>
 
 #### Initializers <a name="Initializers" id="@cdklabs/cdk-ssm-documents.Input.Initializer"></a>
@@ -37928,6 +38113,249 @@ public readonly sourceType: string;
 ---
 
 
+### ScriptCode <a name="ScriptCode" id="@cdklabs/cdk-ssm-documents.ScriptCode"></a>
+
+The code to run for the execution.
+
+See "script" parameter here:
+https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeScript.html
+Attachments are not yet supported.
+
+#### Initializers <a name="Initializers" id="@cdklabs/cdk-ssm-documents.ScriptCode.Initializer"></a>
+
+```typescript
+import { ScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+new ScriptCode()
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+
+---
+
+#### Methods <a name="Methods" id="Methods"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptCode.codeAsString">codeAsString</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptCode.createOrGetFile">createOrGetFile</a></code> | If there is a file for this code, return it. |
+
+---
+
+##### `codeAsString` <a name="codeAsString" id="@cdklabs/cdk-ssm-documents.ScriptCode.codeAsString"></a>
+
+```typescript
+public codeAsString(): string
+```
+
+##### `createOrGetFile` <a name="createOrGetFile" id="@cdklabs/cdk-ssm-documents.ScriptCode.createOrGetFile"></a>
+
+```typescript
+public createOrGetFile(suffix: string): string
+```
+
+If there is a file for this code, return it.
+
+Otherwise, create a file with the specified suffix.
+
+###### `suffix`<sup>Required</sup> <a name="suffix" id="@cdklabs/cdk-ssm-documents.ScriptCode.createOrGetFile.parameter.suffix"></a>
+
+- *Type:* string
+
+of the file to create (such as ".py").
+
+---
+
+#### Static Functions <a name="Static Functions" id="Static Functions"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptCode.fromFile">fromFile</a></code> | Full path to the code to execute. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptCode.inline">inline</a></code> | Inline code to be executed. |
+
+---
+
+##### `fromFile` <a name="fromFile" id="@cdklabs/cdk-ssm-documents.ScriptCode.fromFile"></a>
+
+```typescript
+import { ScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+ScriptCode.fromFile(fullPath: string)
+```
+
+Full path to the code to execute.
+
+File is parsed to produce yaml/json.
+Simulation will execute this file using the language specified.
+(Attachments not yet supported)
+
+###### `fullPath`<sup>Required</sup> <a name="fullPath" id="@cdklabs/cdk-ssm-documents.ScriptCode.fromFile.parameter.fullPath"></a>
+
+- *Type:* string
+
+---
+
+##### `inline` <a name="inline" id="@cdklabs/cdk-ssm-documents.ScriptCode.inline"></a>
+
+```typescript
+import { ScriptCode } from '@cdklabs/cdk-ssm-documents'
+
+ScriptCode.inline(code: string)
+```
+
+Inline code to be executed.
+
+String will be used to produce function in yaml/json.
+Simulation will execute the function in this string using the language specified.
+
+###### `code`<sup>Required</sup> <a name="code" id="@cdklabs/cdk-ssm-documents.ScriptCode.inline.parameter.code"></a>
+
+- *Type:* string
+
+---
+
+
+
+### ScriptLanguage <a name="ScriptLanguage" id="@cdklabs/cdk-ssm-documents.ScriptLanguage"></a>
+
+Specifies the script language as described in the "Runtime" argument here: https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeScript.html.
+
+#### Initializers <a name="Initializers" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.Initializer"></a>
+
+```typescript
+import { ScriptLanguage } from '@cdklabs/cdk-ssm-documents'
+
+new ScriptLanguage()
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+
+---
+
+#### Methods <a name="Methods" id="Methods"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage.fileSuffix">fileSuffix</a></code> | The suffix to apply to file names of this type of execution. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage.runtime">runtime</a></code> | The associated runtime of this ScriptLanguage. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage.simulate">simulate</a></code> | Simulate an execution of this ScriptLanguage. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage.ssmInputs">ssmInputs</a></code> | Builds the ssm inputs. |
+
+---
+
+##### `fileSuffix` <a name="fileSuffix" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.fileSuffix"></a>
+
+```typescript
+public fileSuffix(): string
+```
+
+The suffix to apply to file names of this type of execution.
+
+##### `runtime` <a name="runtime" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.runtime"></a>
+
+```typescript
+public runtime(): string
+```
+
+The associated runtime of this ScriptLanguage.
+
+##### `simulate` <a name="simulate" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.simulate"></a>
+
+```typescript
+public simulate(code: ScriptCode, inputs: {[ key: string ]: string}): {[ key: string ]: string}
+```
+
+Simulate an execution of this ScriptLanguage.
+
+Provide the inputs after replaced with the actual values (not variables).
+
+###### `code`<sup>Required</sup> <a name="code" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.simulate.parameter.code"></a>
+
+- *Type:* <a href="#@cdklabs/cdk-ssm-documents.ScriptCode">ScriptCode</a>
+
+---
+
+###### `inputs`<sup>Required</sup> <a name="inputs" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.simulate.parameter.inputs"></a>
+
+- *Type:* {[ key: string ]: string}
+
+---
+
+##### `ssmInputs` <a name="ssmInputs" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.ssmInputs"></a>
+
+```typescript
+public ssmInputs(): {[ key: string ]: string}
+```
+
+Builds the ssm inputs.
+
+#### Static Functions <a name="Static Functions" id="Static Functions"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage.fromRuntime">fromRuntime</a></code> | Creates a ScriptLanguage based on the provided runtime. |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage.python">python</a></code> | Create a new ScriptLanguage for python execution. |
+
+---
+
+##### `fromRuntime` <a name="fromRuntime" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.fromRuntime"></a>
+
+```typescript
+import { ScriptLanguage } from '@cdklabs/cdk-ssm-documents'
+
+ScriptLanguage.fromRuntime(runtime: string, handlerName?: string)
+```
+
+Creates a ScriptLanguage based on the provided runtime.
+
+Prefer one of the other static constructors if possible.
+
+###### `runtime`<sup>Required</sup> <a name="runtime" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.fromRuntime.parameter.runtime"></a>
+
+- *Type:* string
+
+is the runtime name (such as "python3.6").
+
+---
+
+###### `handlerName`<sup>Optional</sup> <a name="handlerName" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.fromRuntime.parameter.handlerName"></a>
+
+- *Type:* string
+
+to be provided for python executions.
+
+---
+
+##### `python` <a name="python" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.python"></a>
+
+```typescript
+import { ScriptLanguage } from '@cdklabs/cdk-ssm-documents'
+
+ScriptLanguage.python(version: PythonVersion, handlerName: string)
+```
+
+Create a new ScriptLanguage for python execution.
+
+###### `version`<sup>Required</sup> <a name="version" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.python.parameter.version"></a>
+
+- *Type:* <a href="#@cdklabs/cdk-ssm-documents.PythonVersion">PythonVersion</a>
+
+is the pythonVersion to use when writing the document (for simulation will not matter).
+
+---
+
+###### `handlerName`<sup>Required</sup> <a name="handlerName" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.python.parameter.handlerName"></a>
+
+- *Type:* string
+
+is the function name in code as entry point for script handler.
+
+---
+
+
+
 ### SecureVariable <a name="SecureVariable" id="@cdklabs/cdk-ssm-documents.SecureVariable"></a>
 
 - *Implements:* <a href="#@cdklabs/cdk-ssm-documents.IGenericVariable">IGenericVariable</a>
@@ -40982,49 +41410,65 @@ That allows customers to validate their CommandDocument against a given platform
 ---
 
 
-### ResponseCode <a name="ResponseCode" id="@cdklabs/cdk-ssm-documents.ResponseCode"></a>
+### PythonVersion <a name="PythonVersion" id="@cdklabs/cdk-ssm-documents.PythonVersion"></a>
 
-Steps report their ResponseCode using this enum.
+Python runtime to use when writing SSM Document.
 
-A successful response will contain the outputs expected.
-A failed/canceled response will contain the stackTrace.
+Simulation will use local python version.
 
 #### Members <a name="Members" id="Members"></a>
 
 | **Name** | **Description** |
 | --- | --- |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ResponseCode.SUCCESS">SUCCESS</a></code> | *No description.* |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ResponseCode.CANCELED">CANCELED</a></code> | *No description.* |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ResponseCode.FAILED">FAILED</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.PythonVersion.VERSION_3_6">VERSION_3_6</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.PythonVersion.VERSION_3_7">VERSION_3_7</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.PythonVersion.VERSION_3_8">VERSION_3_8</a></code> | *No description.* |
 
 ---
 
-##### `SUCCESS` <a name="SUCCESS" id="@cdklabs/cdk-ssm-documents.ResponseCode.SUCCESS"></a>
+##### `VERSION_3_6` <a name="VERSION_3_6" id="@cdklabs/cdk-ssm-documents.PythonVersion.VERSION_3_6"></a>
 
 ---
 
 
-##### `CANCELED` <a name="CANCELED" id="@cdklabs/cdk-ssm-documents.ResponseCode.CANCELED"></a>
+##### `VERSION_3_7` <a name="VERSION_3_7" id="@cdklabs/cdk-ssm-documents.PythonVersion.VERSION_3_7"></a>
 
 ---
 
 
-##### `FAILED` <a name="FAILED" id="@cdklabs/cdk-ssm-documents.ResponseCode.FAILED"></a>
+##### `VERSION_3_8` <a name="VERSION_3_8" id="@cdklabs/cdk-ssm-documents.PythonVersion.VERSION_3_8"></a>
 
 ---
 
 
-### ScriptLanguage <a name="ScriptLanguage" id="@cdklabs/cdk-ssm-documents.ScriptLanguage"></a>
+### ResponseCode <a name="ResponseCode" id="@cdklabs/cdk-ssm-documents.ResponseCode"></a>
+
+Steps report their ResponseCode using this enum.
+
+A successful response will contain the outputs expected.
+A failed/canceled response will contain the stackTrace.
 
 #### Members <a name="Members" id="Members"></a>
 
 | **Name** | **Description** |
 | --- | --- |
-| <code><a href="#@cdklabs/cdk-ssm-documents.ScriptLanguage.PYTHON">PYTHON</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ResponseCode.SUCCESS">SUCCESS</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ResponseCode.CANCELED">CANCELED</a></code> | *No description.* |
+| <code><a href="#@cdklabs/cdk-ssm-documents.ResponseCode.FAILED">FAILED</a></code> | *No description.* |
 
 ---
 
-##### `PYTHON` <a name="PYTHON" id="@cdklabs/cdk-ssm-documents.ScriptLanguage.PYTHON"></a>
+##### `SUCCESS` <a name="SUCCESS" id="@cdklabs/cdk-ssm-documents.ResponseCode.SUCCESS"></a>
+
+---
+
+
+##### `CANCELED` <a name="CANCELED" id="@cdklabs/cdk-ssm-documents.ResponseCode.CANCELED"></a>
+
+---
+
+
+##### `FAILED` <a name="FAILED" id="@cdklabs/cdk-ssm-documents.ResponseCode.FAILED"></a>
 
 ---
 
diff --git a/README.md b/README.md
index 9632ce4..a71b137 100644
--- a/README.md
+++ b/README.md
@@ -41,17 +41,16 @@ export class HelloWorld extends Stack {
 
     myDoc.addStep(new ExecuteScriptStep(this, "MyExecuteStep", {
       name: "step1",
-      handlerName: "my_func",
-      language: ScriptLanguage.PYTHON,
-      fullPathToCode: resolve("test/test_file.py"),
-      // OR .inlineCode("def my_func(args, context):\n  return {'MyReturn': args['MyInput'] + '-suffix'}\n")
+      language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+      code: ScriptCode.fromFile(resolve("test/test_file.py")),
+      // OR ScriptCode.inline("def my_func(args, context):\n  return {'MyReturn': args['MyInput'] + '-suffix'}\n"),
       outputs: [{
         outputType: DataTypeEnum.STRING,
         name: "MyFuncOut",
         selector: "$.Payload.MyReturn"
       }],
       onFailure: OnFailure.abort(),
-      inputs: ["MyInput"]
+      inputPayload: { MyInput: StringVariable.of('MyInput') },
     }));
   }
 }
diff --git a/src/parent-steps/automation/execute-script-step.ts b/src/parent-steps/automation/execute-script-step.ts
index 5be8c62..f536ffc 100644
--- a/src/parent-steps/automation/execute-script-step.ts
+++ b/src/parent-steps/automation/execute-script-step.ts
@@ -1,58 +1,230 @@
 import * as fs from 'fs';
 import { Construct } from 'constructs';
 import { Output } from '../../domain/output';
+import { IGenericVariable } from '../../interface/variables/variable';
+import { PythonScriptHandler } from '../../script/python-script-handler';
 import { AutomationStep, AutomationStepProps } from '../automation-step';
 // eslint-disable-next-line
 const path = require('path');
 // eslint-disable-next-line
 const os = require('os');
 
-export enum ScriptLanguage {
-  PYTHON
+/**
+ * The code to run for the execution.
+ * See "script" parameter here:
+ * https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeScript.html
+ * Attachments are not yet supported.
+ */
+export abstract class ScriptCode {
+  /**
+   * Inline code to be executed. String will be used to produce function in yaml/json.
+   * Simulation will execute the function in this string using the language specified.
+   */
+  static inline(code: string) {
+    return new InlineScriptCode(code);
+  }
+  /**
+   * Full path to the code to execute. File is parsed to produce yaml/json.
+   * Simulation will execute this file using the language specified.
+   * (Attachments not yet supported)
+   */
+  static fromFile(fullPath: string) {
+    return new FileScriptCode(fullPath);
+  }
+
+  /**
+   * @returns code as a string
+   */
+  abstract codeAsString(): string;
+
+  /**
+   * If there is a file for this code, return it.
+   * Otherwise, create a file with the specified suffix.
+   * @param suffix of the file to create (such as ".py")
+   */
+  abstract createOrGetFile(suffix: string): string;
+}
+
+export class InlineScriptCode extends ScriptCode {
+  private static toFile(data: string, suffix: string) {
+    const tempDir = path.join(os.tmpdir(), 'tmp' + new Date().getSeconds() + Math.floor(Math.random() * 100000));
+    fs.mkdirSync(tempDir);
+    const tempFile = path.join(tempDir, 'execution' + suffix);
+    fs.writeFileSync(tempFile, data);
+    console.log(`Successfully wrote file to ${tempFile}`);
+    return tempFile;
+  }
+
+  readonly inlineCode: string;
+  constructor(inlineCode: string) {
+    super();
+    this.inlineCode = inlineCode;
+  }
+
+  codeAsString(): string {
+    return this.inlineCode;
+  }
+
+  createOrGetFile(suffix: string): string {
+    return InlineScriptCode.toFile(this.inlineCode, suffix);
+  }
+}
+
+export class FileScriptCode extends ScriptCode {
+  readonly fullPath: string;
+  constructor(fullPath: string) {
+    super();
+    this.fullPath = fullPath;
+  }
+  codeAsString(): string {
+    return fs.readFileSync(this.fullPath, 'utf8');
+  }
+
+  createOrGetFile(_suffix: string): string {
+    return this.fullPath;
+  }
 }
 
 /**
- * Properties for ExecuteScriptStep
+ * Specifies the script language as described in the "Runtime" argument here:
+ * https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeScript.html
  */
-export interface ExecuteScriptStepProps extends AutomationStepProps {
+export abstract class ScriptLanguage {
 
   /**
-     * (Required) Language used to execute the script.
-     */
-  readonly language: ScriptLanguage;
+   * Create a new ScriptLanguage for python execution.
+   * @param version is the pythonVersion to use when writing the document (for simulation will not matter).
+   * @param handlerName is the function name in code as entry point for script handler.
+   */
+  static python(version: PythonVersion, handlerName: string): ScriptLanguage {
+    return new PythonScript(version, handlerName);
+  }
+
+  /**
+   * Creates a ScriptLanguage based on the provided runtime.
+   * Prefer one of the other static constructors if possible.
+   * @param runtime is the runtime name (such as "python3.6").
+   * @param handlerName to be provided for python executions.
+   */
+  static fromRuntime(runtime: string, handlerName?: string): ScriptLanguage {
+    if (runtime.startsWith('python')) {
+      return ScriptLanguage.python(ScriptLanguage.pythonVersion(runtime), handlerName!);
+    }
+    throw new Error('Unrecognized runtime version ' + runtime);
+  }
+
+  private static pythonVersion(runtime: string) {
+    switch (runtime) {
+      case 'python3.6':
+        return PythonVersion.VERSION_3_6;
+      case 'python3.7':
+        return PythonVersion.VERSION_3_7;
+      case 'python3.8':
+        return PythonVersion.VERSION_3_8;
+      default:
+        throw new Error('Unrecognized python version ' + runtime);
+    }
+  }
+
+  /**
+   * Builds the ssm inputs.
+   */
+  abstract ssmInputs(): { [name: string]: string };
+
+  /**
+   * The associated runtime of this ScriptLanguage.
+   */
+  abstract runtime(): string;
+
+  /**
+   * The suffix to apply to file names of this type of execution.
+   */
+  abstract fileSuffix(): string;
 
   /**
-      * Full path to the code to execute. File is parsed to produce yaml/json.
-      * Simulation will execute this file using the language specified.
-      * Either this OR inclineCode must be provided.
-      */
-  readonly fullPathToCode?: string;
+   * Simulate an execution of this ScriptLanguage.
+   * Provide the inputs after replaced with the actual values (not variables).
+   */
+  abstract simulate(code: ScriptCode, inputs: { [name: string]: string } ): { [name: string]: string };
+}
+
+class PythonScript extends ScriptLanguage {
+  readonly version: PythonVersion;
+  readonly handlerName: string;
+  constructor(version: PythonVersion, handlerName: string) {
+    super();
+    this.version = version;
+    this.handlerName = handlerName;
+  }
+
+  runtime(): string {
+    switch (this.version) {
+      case PythonVersion.VERSION_3_6:
+        return 'python3.6';
+      case PythonVersion.VERSION_3_7:
+        return 'python3.7';
+      case PythonVersion.VERSION_3_8:
+        return 'python3.8';
+    }
+  }
+
+  fileSuffix(): string {
+    return '.py';
+  }
+
+  ssmInputs(): { [name: string]: string } {
+    return {
+      Handler: this.handlerName,
+      Runtime: this.runtime(),
+    };
+  }
+
+  simulate(code: ScriptCode, inputs: { [p: string]: string }): { [p: string]: string } {
+    const pyHandler = new PythonScriptHandler();
+    return pyHandler.run(code.createOrGetFile(this.fileSuffix()), this.handlerName, inputs);
+  }
+}
+
+/**
+ * Python runtime to use when writing SSM Document.
+ * Simulation will use local python version.
+ */
+export enum PythonVersion {
+  VERSION_3_6,
+  VERSION_3_7,
+  VERSION_3_8,
+}
+
+/**
+ * Properties for ExecuteScriptStep
+ */
+export interface ExecuteScriptStepProps extends AutomationStepProps {
 
   /**
-      * Inline code to be executed. String will be used to produce function in yaml/json.
-      * Simulation will execute the function in this string using the language specified.
-      * Either this OR fullPathToCode must be provided.
-      */
-  readonly inlineCode?: string;
+   * (Required) Language used to execute the script.
+   */
+  readonly language: ScriptLanguage;
 
   /**
-      * (Optional) Function name in fullPathToCode file to use as entry point for script handler.
-      * @default script_handler
-      */
-  readonly handlerName?: string;
+   * Inline code to be executed. String will be used to produce function in yaml/json.
+   * Simulation will execute the function in this code using the language specified.
+   */
+  readonly code: ScriptCode;
 
   /**
-      * (Optional) Outputs that the function is expected to return.
-      * Be sure to prefix the selector for these outputs with "$.Payload." for executeScript step outputs.
-      * @default []
-      */
+   * (Optional) Outputs that the function is expected to return.
+   * Be sure to prefix the selector for these outputs with "$.Payload." for executeScript step outputs.
+   * @default []
+   */
   readonly outputs?: Output[];
 
   /**
-      * (Optional) Inputs that the function needs in order to execute.
-      * @default []
-      */
-  readonly inputs: string[];
+   * InputPayload that will be passed to the first parameter of the handler.
+   * This can be used to pass input data to the script.
+   * The key of this dict is the variable name that will be available to the code.
+   * The value is the Variable object.
+   */
+  readonly inputPayload: { [name: string]: IGenericVariable };
 }
 
 /**
@@ -61,59 +233,22 @@ export interface ExecuteScriptStepProps extends AutomationStepProps {
  */
 export class ExecuteScriptStep extends AutomationStep {
 
-  public static getLanguage(runtime: string) {
-    const langugage = ExecuteScriptStep.RUNTIME_TO_LANGUAGE[runtime];
-    if (langugage != undefined) {
-      return langugage;
-    } else {
-      throw new Error(`Runtime ${runtime} not supported.`);
-    }
-  }
-
-  private static readonly RUNTIME_TO_LANGUAGE: { [name: string]: ScriptLanguage } = {
-    'python3.6': ScriptLanguage.PYTHON,
-  };
-
   readonly language: ScriptLanguage;
-  readonly fullPathToCode: string;
-  readonly handlerName: string;
+  readonly code: ScriptCode;
   readonly outputs: Output[];
-  readonly inputs: string[];
+  readonly inputs: { [name: string]: IGenericVariable };
   readonly action: string = 'aws:executeScript';
 
-
   constructor(stage: Construct, id: string, props: ExecuteScriptStepProps) {
     super(stage, id, props);
     this.language = props.language;
-    if ((props.fullPathToCode == undefined) == (props.inlineCode == undefined)) {
-      throw new Error('Exactly one of "fullPathToCode" or "inlineCode" should be defined');
-    }
-    this.fullPathToCode = props.fullPathToCode ?? this.toFile(props.inlineCode ?? '');
-    this.handlerName = props.handlerName ?? 'script_handler';
+    this.code = props.code;
     this.outputs = props.outputs ?? [];
-    this.inputs = props.inputs ?? [];
+    this.inputs = props.inputPayload;
     const nonPayloadOutputs = this.outputs.filter(out => !out.selector.startsWith('$.Payload'));
     if (nonPayloadOutputs.length != 0) {
       throw new Error('All script outputs must specify selector starting with "$.Payload", ' +
-                `but these did not: ${JSON.stringify(nonPayloadOutputs)}`);
-    }
-  }
-
-  private toFile(data: string) {
-    const tempDir = path.join(os.tmpdir(), 'tmp' + new Date().getSeconds() + Math.floor(Math.random() * 1000));
-    fs.mkdirSync(tempDir);
-    const tempFile = path.join(tempDir, 'execution' + this.getFileSuffix());
-    fs.writeFileSync(tempFile, data);
-    console.log(`Successfully wrote file to ${tempFile}`);
-    return tempFile;
-  }
-
-  private getFileSuffix() {
-    switch (this.language) {
-      case ScriptLanguage.PYTHON:
-        return '.py';
-      default:
-        throw new Error(`Language ${this.language} not supported.`);
+        `but these did not: ${JSON.stringify(nonPayloadOutputs)}`);
     }
   }
 
@@ -122,30 +257,15 @@ export class ExecuteScriptStep extends AutomationStep {
   }
 
   public listInputs(): string[] {
-    return this.inputs;
+    return Object.values(this.inputs).flatMap(i => i?.requiredInputs() ?? []);
   }
 
   public toSsmEntry(): { [name: string]: any } {
-    const inputPayload = Object.fromEntries(this.listInputs().map(inp => [inp, `{{ ${inp} }}`]));
     return this.prepareSsmEntry({
-      Runtime: this.runtime(),
-      Handler: this.handlerName,
-      Script: fs.readFileSync(this.fullPathToCode, 'utf8'),
-      InputPayload: inputPayload,
+      ...this.language.ssmInputs(),
+      Script: this.code.codeAsString(),
+      InputPayload: this.inputs,
     });
   }
 
-  /**
-     * @returns the SSM associated runtime argument for the language specified
-     */
-  private runtime(): string {
-    const runtimeEntry = Object.entries(ExecuteScriptStep.RUNTIME_TO_LANGUAGE)
-      .filter(entry => entry[1] == this.language)[0];
-    if (runtimeEntry) {
-      return runtimeEntry[0];
-    } else {
-      throw new Error(`Language ${this.language} not supported.`);
-    }
-  }
-
 }
\ No newline at end of file
diff --git a/src/patterns/automation/string-step.ts b/src/patterns/automation/string-step.ts
index 95de279..aecb2b7 100644
--- a/src/patterns/automation/string-step.ts
+++ b/src/patterns/automation/string-step.ts
@@ -5,16 +5,17 @@ import { DataType } from '../../domain/data-type';
 import { OperationEvaluator } from '../../domain/operation';
 import { Output } from '../../domain/output';
 import { HardCodedString, IStringVariable, StringVariable } from '../../interface/variables/string-variable';
+import { IGenericVariable } from '../../interface/variables/variable';
 import { AutomationStep } from '../../parent-steps/automation-step';
+import { ApproveStep } from '../../parent-steps/automation/approve-step';
 import { AssertAwsResourceStep } from '../../parent-steps/automation/assert-aws-resource-step';
 import { AwsApiStep } from '../../parent-steps/automation/aws-api-step';
 import { BranchStep } from '../../parent-steps/automation/branch-step';
-import { ExecuteScriptStep } from '../../parent-steps/automation/execute-script-step';
+import { ExecuteScriptStep, ScriptCode, ScriptLanguage } from '../../parent-steps/automation/execute-script-step';
 import { PauseStep } from '../../parent-steps/automation/pause-step';
 import { SleepStep } from '../../parent-steps/automation/sleep-step';
 import { WaitForResourceStep } from '../../parent-steps/automation/wait-for-resource-step';
 import { CompositeAutomationStep } from './composite-step';
-import {ApproveStep} from "../../parent-steps/automation/approve-step";
 // eslint-disable-next-line
 const yaml = require('js-yaml');
 
@@ -123,11 +124,12 @@ export class StringStep extends CompositeAutomationStep {
         });
         break;
       case 'aws:executeScript':
+        const inputs: { [name: string]: IGenericVariable } = {};
+        Object.entries(restParams.InputPayload).forEach(([key, value]) => inputs[key] = this.toVariable(value as string));
         this.automationStep = new ExecuteScriptStep(this, props.name, {
-          language: ExecuteScriptStep.getLanguage(restParams.Runtime),
-          inputs: Object.keys(restParams.InputPayload),
-          inlineCode: restParams.Script,
-          handlerName: restParams.Handler,
+          language: ScriptLanguage.fromRuntime(restParams.Runtime, restParams.Handler),
+          inputPayload: inputs,
+          code: ScriptCode.inline(restParams.Script),
           ...sharedProps,
         });
         break;
diff --git a/src/patterns/document/timed-document.ts b/src/patterns/document/timed-document.ts
index fc15051..ed09161 100644
--- a/src/patterns/document/timed-document.ts
+++ b/src/patterns/document/timed-document.ts
@@ -1,5 +1,13 @@
 import { Construct } from 'constructs';
-import { AutomationStep, DataTypeEnum, ExecuteScriptStep, ScriptLanguage } from '../..';
+import {
+  AutomationStep,
+  DataTypeEnum,
+  ExecuteScriptStep,
+  PythonVersion,
+  ScriptCode,
+  ScriptLanguage,
+  StringVariable,
+} from '../..';
 import { AutomationDocument, AutomationDocumentProps } from '../../document/automation-document';
 
 export class TimedDocument extends AutomationDocument {
@@ -16,23 +24,21 @@ export class TimedDocument extends AutomationDocument {
       return middleSteps;
     }
     const recordStartTime = new ExecuteScriptStep(this, 'RecordStartTime', {
-      inlineCode:
-                'from datetime import datetime, timezone\n\n' +
+      code: ScriptCode.inline('from datetime import datetime, timezone\n\n' +
                 'def script_handler(params: dict, context):\n' +
-                '    return datetime.now(timezone.utc).isoformat()\n\n',
-      language: ScriptLanguage.PYTHON,
+                '    return datetime.now(timezone.utc).isoformat()\n\n'),
+      language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'script_handler'),
       outputs: [{ outputType: DataTypeEnum.STRING, name: 'StartTime', selector: '$.Payload' }],
-      inputs: [],
+      inputPayload: {},
     });
     const outputRecoveryTime = new ExecuteScriptStep(this, 'OutputRecoveryTime', {
-      inlineCode:
-                'from datetime import datetime, timezone\n' +
+      code: ScriptCode.inline('from datetime import datetime, timezone\n' +
                 'from dateutil import parser\n\n' +
                 'def script_handler(params: dict, context):\n' +
-                '    return (datetime.now(timezone.utc) - parser.parse(params[\'RecordStartTime.StartTime\'])).seconds\n\n',
-      language: ScriptLanguage.PYTHON,
+                '    return (datetime.now(timezone.utc) - parser.parse(params[\'startTime\'])).seconds\n\n'),
+      language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'script_handler'),
       outputs: [{ outputType: DataTypeEnum.INTEGER, name: 'RecoveryTime', selector: '$.Payload' }],
-      inputs: ['RecordStartTime.StartTime'],
+      inputPayload: { startTime: StringVariable.of('RecordStartTime.StartTime') },
     });
     this.builder.steps.unshift(recordStartTime);
     this.builder.steps.push(outputRecoveryTime);
diff --git a/src/samples/hello-world.ts b/src/samples/hello-world.ts
index e47c0b4..68c58a4 100644
--- a/src/samples/hello-world.ts
+++ b/src/samples/hello-world.ts
@@ -5,7 +5,12 @@ import { DataTypeEnum } from '../domain/data-type';
 import { Input } from '../domain/input';
 import { StringVariable } from '../interface/variables/string-variable';
 import { AwsApiStep } from '../parent-steps/automation/aws-api-step';
-import { ExecuteScriptStep, ScriptLanguage } from '../parent-steps/automation/execute-script-step';
+import {
+  ExecuteScriptStep,
+  PythonVersion,
+  ScriptCode,
+  ScriptLanguage,
+} from '../parent-steps/automation/execute-script-step';
 
 export class HelloWorld extends Stack {
   constructor(app: Construct, id: string) {
@@ -18,12 +23,11 @@ export class HelloWorld extends Stack {
     });
 
     const greeting = new ExecuteScriptStep(this, 'PrependWithGreeting', {
-      language: ScriptLanguage.PYTHON,
-      inlineCode: 'def my_func(args, context):\n' +
-        '    return {"Greeting": "Hello " + args["Someone"]}\n' +
-        '\n',
-      handlerName: 'my_func',
-      inputs: ['Someone'],
+      language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+      code: ScriptCode.inline('def my_func(args, context):\n' +
+        '    return {"Greeting": "Hello " + args["someone"]}\n' +
+        '\n'),
+      inputPayload: { someone: StringVariable.of('Someone') },
       outputs: [{
         outputType: DataTypeEnum.STRING,
         name: 'Greeting',
diff --git a/src/simulation/automation/execute-script-simulation.ts b/src/simulation/automation/execute-script-simulation.ts
index 46818e4..38f01da 100644
--- a/src/simulation/automation/execute-script-simulation.ts
+++ b/src/simulation/automation/execute-script-simulation.ts
@@ -1,5 +1,4 @@
-import { ExecuteScriptStep, ScriptLanguage } from '../../parent-steps/automation/execute-script-step';
-import { PythonScriptHandler } from '../../script/python-script-handler';
+import { ExecuteScriptStep } from '../../parent-steps/automation/execute-script-step';
 import { AutomationSimulationBase } from './automation-simulation-base';
 
 /**
@@ -20,13 +19,9 @@ export class ExecuteScriptSimulation extends AutomationSimulationBase {
    * Switch by language and execute code based on specified language.
    */
   public executeStep(inputs: { [name: string]: any }): { [name: string]: any } {
-    switch (this.executeScriptStep.language) {
-      case ScriptLanguage.PYTHON:
-        const pyHandler = new PythonScriptHandler();
-        return pyHandler.run(this.executeScriptStep.fullPathToCode, this.executeScriptStep.handlerName, inputs);
-      default:
-        throw new Error(`Language ${this.executeScriptStep.language} not supported.`);
-    }
+    const inputValues = Object.fromEntries(Object.entries(this.executeScriptStep.inputs)
+      .map(([k, v]) => [k, v.resolve(inputs)]));
+    return this.executeScriptStep.language.simulate(this.executeScriptStep.code, inputValues);
   }
 
-}
\ No newline at end of file
+}


Test patch:
diff --git a/test/document/automation-document.test.ts b/test/document/automation-document.test.ts
index 2a6d1bc..c292159 100644
--- a/test/document/automation-document.test.ts
+++ b/test/document/automation-document.test.ts
@@ -11,7 +11,8 @@ import {
   Input,
   MockPause,
   PauseStep,
-  ResponseCode,
+  PythonVersion,
+  ResponseCode, ScriptCode,
   ScriptLanguage,
   Simulation,
   StringVariable,
@@ -35,15 +36,14 @@ describe('AutomationDocument', function() {
       myAutomationDoc.addStep(new PauseStep(stack, 'MyPauseStep', { name: 'MyPauseStep' }));
       // Second step
       myAutomationDoc.addStep(new ExecuteScriptStep(stack, 'MyExecuteStep', {
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       }));
 
       // Execute simulation
@@ -71,15 +71,14 @@ describe('AutomationDocument', function() {
       });
       doc.addStep(new PauseStep(stack, 'MyPauseStep', { name: 'MyPauseStep' }));
       doc.addStep(new ExecuteScriptStep(stack, 'MyExecuteStep', {
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       }));
       const simOutput = Simulation.ofAutomation(doc, { pauseHook: new MockPause() }).simulate({});
       assert.deepEqual(simOutput, {
@@ -95,15 +94,14 @@ describe('AutomationDocument', function() {
           super(scope, id, props);
           new PauseStep(this, 'MyPauseStep', { name: 'MyPauseStep' });
           new ExecuteScriptStep(this, 'MyExecuteStep', {
-            handlerName: 'my_func',
-            language: ScriptLanguage.PYTHON,
-            fullPathToCode: resolve('test/test_file.py'),
+            language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+            code: ScriptCode.fromFile(resolve('test/test_file.py')),
             outputs: [{
               outputType: DataTypeEnum.STRING,
               name: 'MyFuncOut',
               selector: '$.Payload.MyReturn',
             }],
-            inputs: ['MyInput'],
+            inputPayload: { MyInput: StringVariable.of('MyInput') },
           });
         }
       }
@@ -130,15 +128,14 @@ describe('AutomationDocument', function() {
       myAutomationDoc.addStep(new PauseStep(stack, 'MyPauseStep', { name: 'MyPauseStep' }));
       myAutomationDoc.addStep(new ExecuteScriptStep(stack, 'MyExecuteStep', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       }));
       SynthUtils.synthesize(stack);
       const ssmJson = myAutomationDoc.print();
@@ -201,15 +198,14 @@ describe('AutomationDocument', function() {
       myAutomationDoc.addStep(new PauseStep(stack, 'MyPauseStep', { name: 'MyPauseStep' }));
       myAutomationDoc.addStep(new ExecuteScriptStep(stack, 'MyExecuteStep', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       }));
       SynthUtils.synthesize(stack);
       const ssmYaml = myAutomationDoc.print();
diff --git a/test/document/stack-cfn.test.ts b/test/document/stack-cfn.test.ts
index e6d3fe1..00b357e 100644
--- a/test/document/stack-cfn.test.ts
+++ b/test/document/stack-cfn.test.ts
@@ -7,8 +7,9 @@ import {
   ExecuteScriptStep,
   Input,
   PauseStep,
+  PythonVersion, ScriptCode,
   ScriptLanguage,
-  StringVariable
+  StringVariable,
 } from '../../lib';
 
 
@@ -32,15 +33,14 @@ describe('AutomationDocument', function () {
       doc.addStep(new PauseStep(stack, 'MyPauseStep', { name: 'MyPauseStep' }));
       doc.addStep(new ExecuteScriptStep(stack, 'MyExecuteStep', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       }));
       Template.fromStack(stack).templateMatches({
         Resources: {
diff --git a/test/parent-steps/automation/execute-script-step.test.ts b/test/parent-steps/automation/execute-script-step.test.ts
index 21f172b..3d39e48 100644
--- a/test/parent-steps/automation/execute-script-step.test.ts
+++ b/test/parent-steps/automation/execute-script-step.test.ts
@@ -1,23 +1,29 @@
 import { strict as assert } from 'assert';
 import { resolve } from 'path';
 import { Stack } from 'aws-cdk-lib';
-import { DataTypeEnum, ExecuteScriptStep, ResponseCode, ScriptLanguage } from '../../../lib';
-import { AutomationStepSimulation } from '../../../lib/simulation/automation-step-simulation';
+import {
+  AutomationStepSimulation,
+  DataTypeEnum,
+  ExecuteScriptStep,
+  PythonVersion,
+  ResponseCode, ScriptCode,
+  ScriptLanguage,
+  StringVariable,
+} from '../../../lib';
 
 describe('ExecuteScriptStep', function() {
   describe('#invoke()', function() {
     it('Invokes and gets python response', function() {
       const scriptStep = new ExecuteScriptStep(new Stack(), 'id', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       });
       const response = new AutomationStepSimulation(scriptStep, {}).invoke({ MyInput: 'a' });
       if (response.responseCode != ResponseCode.SUCCESS) {
@@ -28,20 +34,19 @@ describe('ExecuteScriptStep', function() {
     it('Invokes and gets python response', function() {
       const scriptStep = new ExecuteScriptStep(new Stack(), 'id', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        inlineCode: 'def my_func(args, context):\n' +
-                    '    return {"MyReturn": args["MyInput"] + "-suffix"}\n' +
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.inline('def my_func(args, context):\n' +
+                    '    return {"MyReturn": args["SomeInput"] + "-suffix"}\n' +
                     '\n' +
                     'def bad_func(args, context):\n' +
                     '    return {"MyReturn": args["INPUT_DOES_NOT_EXIST"] + "-suffix"}\n' +
-                    '\n',
+                    '\n'),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { SomeInput: StringVariable.of('MyInput') },
       });
 
       const response = new AutomationStepSimulation(scriptStep, {}).invoke({ MyInput: 'a' });
@@ -50,69 +55,30 @@ describe('ExecuteScriptStep', function() {
       }
       assert.equal(response.outputs && response.outputs['step1.MyFuncOut'], 'a-suffix');
     });
-    it('Must supply either inlineCode or fullPathToCode but not both', function() {
-      assert.throws(() => {
-        new ExecuteScriptStep(new Stack(), 'id', {
-          name: 'step1',
-          handlerName: 'my_func',
-          language: ScriptLanguage.PYTHON,
-          fullPathToCode: resolve('test/test_file.py'),
-          inlineCode: 'def my_func(args, context):\n' +
-                    '    return {"MyReturn": args["MyInput"] + "-suffix"}\n' +
-                    '\n' +
-                    'def bad_func(args, context):\n' +
-                    '    return {"MyReturn": args["INPUT_DOES_NOT_EXIST"] + "-suffix"}\n' +
-                    '\n',
-          outputs: [{
-            outputType: DataTypeEnum.STRING,
-            name: 'MyFuncOut',
-            selector: '$.Payload.MyReturn',
-          }],
-          inputs: ['MyInput'],
-        });
-      });
-    });
-    it('Must supply either inlineCode or fullPathToCode but not both', function() {
-      assert.throws(() => {
-        new ExecuteScriptStep(new Stack(), 'id', {
-          name: 'step1',
-          handlerName: 'my_func',
-          language: ScriptLanguage.PYTHON,
-          outputs: [{
-            outputType: DataTypeEnum.STRING,
-            name: 'MyFuncOut',
-            selector: '$.Payload.MyReturn',
-          }],
-          inputs: ['MyInput'],
-        });
-      });
-    });
     it('Output selectors must start with $.Payload.', function() {
       assert.throws(() => new ExecuteScriptStep(new Stack(), 'id', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       }));
     });
     it('Throws if output selector not found in result', function() {
       const scriptStep = new ExecuteScriptStep(new Stack(), 'id', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.DOES_NOT_EXIST',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       });
 
       assert.equal(new AutomationStepSimulation(scriptStep, {}).invoke({ MyInput: 'a' }).responseCode, ResponseCode.FAILED);
@@ -120,15 +86,14 @@ describe('ExecuteScriptStep', function() {
     it('Fails if input required not provided', function() {
       const scriptStep = new ExecuteScriptStep(new Stack(), 'id', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       });
 
       assert.throws(() => new AutomationStepSimulation(scriptStep, {}).invoke({ NO_INPUT_HERE: 'a' }).responseCode);
@@ -136,15 +101,14 @@ describe('ExecuteScriptStep', function() {
     it('Fails if python has bug', function() {
       const scriptStep = new ExecuteScriptStep(new Stack(), 'id', {
         name: 'step1',
-        handlerName: 'bad_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'bad_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       });
 
       const scriptStepResult = new AutomationStepSimulation(scriptStep, {}).invoke({ MyInput: 'a' });
@@ -158,15 +122,14 @@ describe('ExecuteScriptStep', function() {
     it('Builds entry as per SSM Document', function() {
       const step = new ExecuteScriptStep(new Stack(), 'id', {
         name: 'step1',
-        handlerName: 'my_func',
-        language: ScriptLanguage.PYTHON,
-        fullPathToCode: resolve('test/test_file.py'),
+        language: ScriptLanguage.python(PythonVersion.VERSION_3_6, 'my_func'),
+        code: ScriptCode.fromFile(resolve('test/test_file.py')),
         outputs: [{
           outputType: DataTypeEnum.STRING,
           name: 'MyFuncOut',
           selector: '$.Payload.MyReturn',
         }],
-        inputs: ['MyInput'],
+        inputPayload: { MyInput: StringVariable.of('MyInput') },
       });
 
       assertDeepEqual(step.toSsmEntry(), {
diff --git a/test/pattern/document/timed-document.test.ts b/test/pattern/document/timed-document.test.ts
index 6d5bd1a..821e225 100644
--- a/test/pattern/document/timed-document.test.ts
+++ b/test/pattern/document/timed-document.test.ts
@@ -52,9 +52,9 @@ describe('TimedDocument', function() {
             inputs: {
               Runtime: 'python3.6',
               Handler: 'script_handler',
-              Script: "from datetime import datetime, timezone\nfrom dateutil import parser\n\ndef script_handler(params: dict, context):\n    return (datetime.now(timezone.utc) - parser.parse(params['RecordStartTime.StartTime'])).seconds\n\n",
+              Script: "from datetime import datetime, timezone\nfrom dateutil import parser\n\ndef script_handler(params: dict, context):\n    return (datetime.now(timezone.utc) - parser.parse(params['startTime'])).seconds\n\n",
               InputPayload: {
-                'RecordStartTime.StartTime': '{{ RecordStartTime.StartTime }}',
+                startTime: '{{ RecordStartTime.StartTime }}',
               },
             },
             outputs: [
diff --git a/test/samples/hello-world.test.ts b/test/samples/hello-world.test.ts
index ace45d3..15b8d1d 100644
--- a/test/samples/hello-world.test.ts
+++ b/test/samples/hello-world.test.ts
@@ -34,9 +34,9 @@ describe('HelloWorld', function() {
                     inputs: {
                       Runtime: 'python3.6',
                       Handler: 'my_func',
-                      Script: 'def my_func(args, context):\n    return {"Greeting": "Hello " + args["Someone"]}\n\n',
+                      Script: 'def my_func(args, context):\n    return {"Greeting": "Hello " + args["someone"]}\n\n',
                       InputPayload: {
-                        Someone: '{{ Someone }}',
+                        someone: '{{ Someone }}',
                       },
                     },
                     outputs: [{