How to run commands on Amazon EC2 windows instances during launch time only

In this post I will explain how to run commands on windows EC2 instances during launch time only. While this is easily done on a Linux machine, the solution for windows is not as trivial. First we’ll present the Linux way and explain what we mean by “only during launch time”. Then, we’ll show 3 implementation attempts on windows, until we’ll reach the desired behavior. This post was written for programmers / devops professionals and assumes knowledge of EC2, launching instances and using user data.

The Linux way

EC2 Linux machine has a powerful service named cloud-init. By using user data and runcmd directive we can run commands in “once-per-instance” frequency (More info at amazon user guide, and here in line 304).

Let’s explain exactly what that means by giving an example scenario:

1. Launch a Linux instance with the following user data:

#cloud-config runcmd: - echo 1 >> /home/ubuntu/a.txt

ssh to instance:

$ cat /home/ubuntu/a.txt 1

Result: Script did run.

2. Reboot the machine

$ cat /home/ubuntu/a.txt 1

Result: Script didn’t run.

3. Create Image from instance. Launch an Instance from new image with same user data as above.

$ cat /home/ubuntu/a.txt 1 1

Result: Script did run.

In other words, the script runs only when a new instance is launched from an image. Here in Cloudshare we wanted to achieve the exact same behavior but in windows! So how can we do that?

Windows attempt #1

First, let’s enable user data execution on our original snapshot. We do that by enabling user data box on “EC2 Service Properties”.

(More info at amazon user guide)

Now, let’s try using the following user data:

echo 1 >> c:\a.txt

And repeat the above scenario only now with a windows machine:

1. Launch.

>type c:\a.txt 1

Result: Script did run. Same behavior as Linux, it works!

2. Reboot.

>type c:\a.txt 1

Result: Script didn’t run. Same behavior as Linux, it works!

3. Launch from new image.

>type c:\a.txt 1

Result: Script didn’t run. Oh no, different behavior from Linux, it failed!

The reason for the failure is that the EC2Config service disables user data execution after the first run!

Windows attempt #2

Let’s try to fix that by enabling the “persist” flag (More info at amazon user guide) in user data, so the user data will be:

echo 1 >> c:\a.txt <persist>true</persist>

Now, let’s repeat our scenario with our new user data:

1. Launch.

>type c:\a.txt 1

Result: Script did run. Same behavior as Linux, it works!

2. Reboot.

>type c:\a.txt 1 1

Result: Script did run. Oh no, different behavior, it failed!

When we reboot we also restart the EC2Config service that just executes user data again (despite we really don’t want it to). Actually what we implemented here is cloud-init’s “always” frequency equivalent (more info here in line 304).

Windows attempt #3

Let’s try to fix that by using the following user data:

<powershell> $instances_booted_file_path = "C:\ProgramData\instances_booted.txt" $instance_id = invoke-restmethod http://169.254.169.254/latest/meta-data/instance-id if (!(Test-Path $instances_booted_file_path) -or !(Select-String $instances_booted_file_path -pattern $instance_id)) { echo 1 >> c:\a.txt Add-Content $instances_booted_file_path $instance_id } </powershell> <persist>true</persist>

What we did here is actually very similar to cloud init’s implementation. We use a local file that stores the instance IDs that already booted. When our instance ID doesn’t appear we know that’s the first time the instance boots and then runs the command.

So… let’s test our scenario again with the new user data:

1. Launch.

>type c:\a.txt 1

Result: Script did run. Same behavior as Linux, it works!

2. Reboot.

>type c:\a.txt 1

Result: Script didn’t run. Same behavior as Linux, it works!

3. Launch from new image.

>type c:\a.txt 1 1

Result: Script did run. Same behavior, it works!

Final words

That’s it, this is Cloudshare’s solution. Some of Cloudshare use cases are: windows package selection, change user passwords, and more. It was surprising to me that this problem doesn’t have any built in solution. Will be happy to hear any thoughts\comments.