Post

Running Commands on EC2 On Demand

Building on the Systems Manager Session Manager setup, this guide covers how to run commands on EC2 instances using Run Command.

Running Commands on EC2 On Demand

The previous post walked through connecting to EC2 via Systems Manager.

Once you can open a shell with Session Manager,

the next natural question is:

can I skip logging in one by one,

and just send commands directly from AWS?

That is exactly what Run Command is for.

It is a good fit for tasks like these:

  1. Checking service status across a fleet
  2. Pushing a simple configuration file
  3. Restarting services or scheduled jobs
  4. Applying a quick OS-level fix

This post covers Run Command permission setup,

the key differences between Linux and Windows,

and a hands-on Windows example of changing a local user password via PowerShell.

What is Run Command?

Run Command is the remote execution feature inside AWS Systems Manager.

You pick a set of EC2 instances,

choose an AWS-provided Document such as:

  1. AWS-RunShellScript
  2. AWS-RunPowerShellScript
  3. AWS-RunPatchBaseline

and Systems Manager delivers the commands to the SSM Agent on each node,

which then executes them locally.

Because of this,

the first thing to verify is not the command itself —

it is whether permissions are set correctly.

Prerequisites

Just like Session Manager,

getting Run Command to work on an EC2 instance requires four things:

  1. EC2 is attached to the correct IAM Role
  2. SSM Agent is installed and running on the host
  3. The host can reach Systems Manager endpoints over TCP 443
  4. The caller (person or system sending commands) also has sufficient IAM permissions

The first three are about making the node manageable.

The fourth is about who is allowed to issue commands.

The fourth one is easy to miss —

the instance can show as Online,

yet the command fails because the operator lacks ssm:SendCommand.

Permission Setup

Permissions split into two parts:

  1. The EC2 Instance Role
  2. The caller’s IAM User or Role

EC2 Instance Role

Start by attaching AmazonSSMManagedInstanceCore to the instance.

This AWS Managed Policy covers the baseline permissions the node needs to register with Systems Manager,

receive commands, and report results.

If your script also accesses other AWS services, such as:

  1. Downloading a script from S3
  2. Writing logs to CloudWatch Logs
  3. Reading a secret from Secrets Manager

those permissions must be added to the EC2 Instance Role separately.

AmazonSSMManagedInstanceCore only makes the node manageable —

it does not grant access to every AWS resource your script might touch.

Caller IAM Permissions

The person or system sending the command needs at minimum:

  1. ssm:SendCommand
  2. ssm:GetCommandInvocation
  3. ssm:ListCommandInvocations
  4. ssm:ListCommands

If you are operating through the AWS Console, you will also typically need:

  1. ssm:DescribeInstanceInformation
  2. ssm:ListDocuments
  3. ssm:DescribeDocument

Here is a minimal example policy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:SendCommand",
        "ssm:GetCommandInvocation",
        "ssm:ListCommandInvocations",
        "ssm:ListCommands",
        "ssm:DescribeInstanceInformation",
        "ssm:ListDocuments",
        "ssm:DescribeDocument"
      ],
      "Resource": "*"
    }
  ]
}

For tighter control, narrow down Resource to specific Documents and EC2 Tag conditions.

Avoid defaulting to AdministratorAccess

Run Command is essentially remote execution,

and overly broad permissions translate directly into a high level of host control.

Linux vs Windows: The Differences That Matter

This section is important.

Even though both platforms use “Run Command”,

Linux and Windows differ significantly in how you configure and execute commands.

1. Different Documents

Linux typically uses:

AWS-RunShellScript

Windows typically uses:

AWS-RunPowerShellScript

Linux receives shell commands.

Windows receives PowerShell commands.

2. Different Execution Identity

On Linux, SSM Agent runs commands as root by default.

On Windows, SSM Agent runs commands as NT AUTHORITY\SYSTEM by default.

This directly affects two things:

  1. Whether you still need sudo
  2. Whether the command can access certain user-scoped resources

On Linux, since you are already root,

you generally do not need to prefix commands with sudo.

On Windows, SYSTEM has very high privileges,

but it is not an interactive logon user,

so behaviors tied to user profiles, desktop sessions, mapped drives, or certain certificate stores

should not be assumed to work the same way as when you RDP in.

3. Different Permission Supplementation

On Linux, if you only need to modify system files, query services, or restart a daemon,

the node being managed by SSM is usually enough.

On Windows, if you are touching local accounts, service accounts, the registry, or scheduled tasks,

you need to consider whether SYSTEM is allowed to perform that specific operation.

In the password change example at the end of this post,

changing a local Windows account password is achievable through Run Command —

but changing a domain account is an entirely different story.

4. Different Script Format and Escaping

Linux typically looks like:

1
2
systemctl restart nginx
cat /etc/os-release

Windows typically looks like:

1
2
Restart-Service W32Time
Get-ComputerInfo

PowerShell and Bash also handle quotes, variables, and multiline scripts differently.

That is why I recommend maintaining separate scripts for Linux and Windows

rather than trying to shoehorn a single script into both.

Platform-Specific Things to Watch

Linux

  1. Confirm the target has a supported shell environment
  2. Check that file paths, permission model, and service management match your Linux distribution
  3. If your script relies on external tools like jq, aws, or python3, make sure they are pre-installed

For example, to check service status:

1
systemctl status amazon-ssm-agent --no-pager

If your command relies on a home directory,

remember the executor is root, not ec2-user or whatever account you normally log in as.

Windows

  1. The target must have a PowerShell execution environment
  2. For newer Cmdlets, verify Windows Server version and PowerShell version compatibility
  3. Operations that depend on an interactive desktop session are generally not suitable for Run Command

For example, to check SSM Agent service status:

1
Get-Service AmazonSSMAgent

If you need to manage local user accounts,

confirm the Microsoft.PowerShell.LocalAccounts module is available on that host version,

otherwise fall back to net user.

How to Send a Run Command

Via AWS Console

  1. Open Systems Manager
  2. Navigate to Run Command
  3. Click Run command
  4. Select a Document
  5. Choose target EC2 instances (by Instance ID or Tag)
  6. Enter your command content
  7. Submit and check the execution output

Via AWS CLI

Sending a shell script to Linux:

1
2
3
4
aws ssm send-command \
  --document-name "AWS-RunShellScript" \
  --targets "Key=instanceids,Values=i-0123456789abcdef0" \
  --parameters 'commands=["uname -a","id","systemctl status amazon-ssm-agent --no-pager"]'

Sending PowerShell to Windows:

1
2
3
4
aws ssm send-command \
  --document-name "AWS-RunPowerShellScript" \
  --targets "Key=instanceids,Values=i-0123456789abcdef0" \
  --parameters 'commands=["Get-ComputerInfo | Select-Object WindowsProductName,OsVersion","Get-Service AmazonSSMAgent"]'

If you are calling the AWS CLI from a Windows machine using PowerShell,

quoting and escaping get considerably more complex.

It helps to build the parameters as a variable or load them from a file

instead of constructing everything inline on the command line.

Windows Example: Changing a Local User Password via PowerShell

The following example targets a Windows Server local account.

Scope clarification upfront:

  1. This changes a Windows local user password
  2. It does not change an AD domain account password
  3. The command is sent via Run Command using AWS-RunPowerShellScript

Option 1: Using Set-LocalUser

If the Windows Server has the LocalAccounts module available:

1
2
3
4
5
6
$userName = "app-user"
$plainPassword = "ChangeMe_2026!"
$securePassword = ConvertTo-SecureString $plainPassword -AsPlainText -Force

Set-LocalUser -Name $userName -Password $securePassword
Write-Output "Password changed for local user: $userName"

This snippet can be placed directly into the commands parameter of AWS-RunPowerShellScript.

Option 2: Using net user

If Set-LocalUser is not available:

1
net user app-user "ChangeMe_2026!"

This works for local accounts,

but the password appears directly in the command string,

which is less readable and harder to protect down the line.

Full CLI Example with Run Command

1
2
3
4
5
aws ssm send-command \
  --document-name "AWS-RunPowerShellScript" \
  --targets "Key=instanceids,Values=i-0123456789abcdef0" \
  --comment "Rotate local Windows password" \
  --parameters 'commands=["$userName = \"app-user\"","$plainPassword = \"ChangeMe_2026!\"","$securePassword = ConvertTo-SecureString $plainPassword -AsPlainText -Force","Set-LocalUser -Name $userName -Password $securePassword","Write-Output \"Password changed for local user: $userName\""]'

Security Risk to Be Aware Of

The example above uses the most straightforward approach on purpose,

but you should have already noticed one issue:

the password appears in plain text inside the command.

In a production environment, this is usually not acceptable,

because the password can show up in:

  1. Command history
  2. Run Command execution logs
  3. Audit or debugging views

A more appropriate production flow is:

  1. Generate the new password from a secure external source
  2. Run Command performs the password change
  3. After the change, securely store the password or trigger a downstream process

How to Confirm a Command Executed Successfully

You can review results in the Systems Manager execution history,

or query via CLI:

1
2
3
aws ssm list-command-invocations \
  --command-id <command-id> \
  --details

For a single instance:

1
2
3
aws ssm get-command-invocation \
  --command-id <command-id> \
  --instance-id <instance-id>

Key fields to check:

  1. Status
  2. StandardOutputContent
  3. StandardErrorContent

Success Interpretation Also Differs by Platform

On Linux, exit code and stdout/stderr are the standard signals.

On Windows, in addition to the exit code,

watch for PowerShell silently swallowing errors.

For stricter failure detection, add this at the top of your script:

1
$ErrorActionPreference = "Stop"

With this set, any Cmdlet error will cause Run Command to fail outright,

rather than appearing to succeed while quietly ignoring mid-script errors.

Common Issues

1) Instance is Online but Run Command still fails

Check:

  1. Whether the caller has ssm:SendCommand
  2. Whether the correct Document is selected (don’t send PowerShell to Linux or Shell to Windows)
  3. Whether the EC2 Instance Role has permissions for any AWS resources the script accesses

2) A command works when logged in manually on Linux, but fails via Run Command

Typical causes:

  1. You logged in as ec2-user, but Run Command runs as root
  2. User-specific environment variables, PATH, or home directory differ
  3. The command depends on settings loaded only in an interactive shell session

3) Command succeeds on Windows but the result is unexpected

Typical causes:

  1. You expected it to run as a specific logged-in user, but it actually runs as SYSTEM
  2. The operation requires an interactive desktop session, which Run Command does not provide
  3. The required PowerShell version or module is not present

Summary

The value of Run Command is that you can centrally issue commands,

collect results, and maintain an audit trail

without logging into each host individually.

But it is not just a remote shell wrapper —

the real focus is the permission model.

Linux and Windows diverge on Document selection, execution identity, and script format,

and those differences will trip you up if you don’t address them upfront.

Homework

If you can already use Run Command to change a local Windows password,

the next question to answer is:

where do you safely store the new password after rotating it?

Two directions worth thinking about:

  1. Securely write the secret to Secrets Manager
  2. Publish an event via SNS to hand off to a downstream process for storage or notification

One important distinction to keep in mind:

SNS is better suited for notifications and triggering workflows,

not for storing a plaintext password directly.

When the goal is to securely store the credential itself,

Secrets Manager is typically the right answer.

This post is licensed under CC BY 4.0 by the author.