Using SSH on Amazon EC2 Windows servers

 28 October 2013 -  ~6 Minutes Cloud Computing

Amazon EC2’s images for Windows servers can be made accessible with passwordless SSH login, just like Linux, without requiring custom images.

Amazon EC2 offers images for Windows servers. However they are harder to use, as when a new server boots up, the only way to access it is with an RDP client. This isn’t very good for automation. By contrast, Linux servers come up with an SSH server installed and accessible, making it easy to automate and send commands to a Linux server. Fortunately, there are ways to get SSH onto a Windows server, and ways to get EC2 to install this service for you automatically.

There are a few options for Windows SSH support, but the one I prefer is Bitvise SSH Server.

Note: Bitvise SSH Server is a commercial product. The scripts in this post will install the product as a 30-day trial of the commercial version, but it is up to you to read the product license and understand your responsibilities.

It’s possible to download the Bitvise installer straight from the Bitvise website, and script the installation to work completely silently. We can use a facility called Ec2ConfigService which allows us to specify the installation script in the instance’s startup user data blob - Ec2ConfigService will parse the user data blob and execute our script. We can even use the EC2 instance metadata service to obtain the public SSH key given when booting the instance, and register this with the SSH server. The result is - just like Linux - you can SSH straight into a privileged user using public key authentication and no password, and begin executing shell commands.

Let’s break this down into steps, starting with a PowerShell script that does the installation and configuration of BitVise.

Download the BitVise installer and run it

Here we use the URL of the BitVise installer download, and a bit of filesystem mangling to find a place to save it, and then use Net.WebClient to download it. Then, we use Start-Process to start the installer and wait for it to complete.

New-Variable -Name "installer" -Value "BvSshServer-Inst.exe"<br />
New-Variable -Name "installer_url" -Value ""<br />
New-Variable -Name "installer_file" -Value ( Join-Path -Path $ENV:USERPROFILE -ChildPath $installer )<br />
( New-Object Net.WebClient ).DownloadFile($installer_url, $installer_file)<br />
Start-Process -FilePath $installer_file -ArgumentList @( "-acceptEULA", "-defaultSite" ) -Wait

Note: -acceptEULA was passed to this command. Ensure that you really want to accept the EULA before running this step.

Get the SSH public key for logging in

When you create an instance, you’ll provide details of SSH keys that allow you to log in to the instance without a password. Normally you don’t do this for Windows - instead there is a mechanism to return a randomly-generated Administrator password back to the user. However, we’d like our SSH server to make our Windows server behave like a Linux server - so we want to install the provided SSH public key into the server, just like Linux servers do.

The SSH public key is stored in the instance metadata service, so we can download this key and save it to a file:

New-Variable -Name "key" -Value "ssh-public-key.txt"<br />
New-Variable -Name "key_file" -Value ( Join-Path -Path $ENV:USERPROFILE -ChildPath $key )<br />
New-Variable -Name "key_url" -Value ""<br />
( New-Object Net.WebClient ).DownloadFile($key_url, $key_file)

Generate a settings file and import it into BitVise SSH Server

Now we turn to configuring BitVise SSH server. It has a facility to describe configuration in a text file, and then import the file into the server. We configure the following items:

  • Open the Windows firewall to the global scope, so that it can be accessed from anywhere on the Internet. The default is to open to “local” scope only, which isn’t a useful configuration in a cloud, so we open it up to global access. (You can use EC2’s security groups to restrict SSH if necessary.)
  • Open the Administrator account configuration, import the SSH key we downloaded in the last step, and commit the changes to the Administrator user.
New-Variable -Name "settings" -Value "bitvise-ssh-server-settings.txt"<br />
New-Variable -Name "settings_file" -Value ( Join-Path -Path $ENV:USERPROFILE -ChildPath $settings )<br />
Set-Content -Path $settings_file -Value "server.windowsFirewall.sshPortsFirewallSetting globalScope"<br />
Add-Content -Path $settings_file -Value "access.winAccounts.New.winAccount `"Administrator`""<br />
Add-Content -Path $settings_file -Value (( "access.winAccounts.New.keys.Import `""+$key_file+"`"" ).Replace("", ""))<br />
Add-Content -Path $settings_file -Value "access.winAccounts.NewCommit"<br />
& ([System.IO.Path]::Combine($env:ProgramFiles, "BitVise SSH Server", "BssCfg.exe")) settings importText "$settings_file"


Finally, we need to reboot the server, to complete the BitVise installation. If we don’t do this, limitations in Windows means that it’s not possible to log in using an SSH key, only a password.

Shutdown /r /t 0 /d p:4:2 /c "Post install of SSH server"

Putting it all together

To make this work, you need to do two things:

  • Firstly, surround the script with <powershell> and </powershell> tags
  • Make sure that the file has Windows-style line endings. If you’re using Linux or MacOSX to write this script, use the unix2dos command to fix this.

When the instance starts up, the EC2ConfigService will read the userdata blob. When it sees the tags, it saves the contents to a script file on disk, and executes it. Our code then runs to install and configure the SSH server, before rebooting. EC2ConfigService will only ever run the script once, so it does not get run a second time after the reboot.

Here’s the complete script:


New-Variable -Name "installer" -Value "BvSshServer-Inst.exe"
New-Variable -Name "installer_url" -Value ""
New-Variable -Name "installer_file" -Value ( Join-Path -Path $ENV:USERPROFILE -ChildPath $installer )
New-Variable -Name "settings" -Value "bitvise-ssh-server-settings.txt"
New-Variable -Name "settings_file" -Value ( Join-Path -Path $ENV:USERPROFILE -ChildPath $settings )
New-Variable -Name "key" -Value "ssh-public-key.txt"
New-Variable -Name "key_file" -Value ( Join-Path -Path $ENV:USERPROFILE -ChildPath $key )
New-Variable -Name "key_url" -Value ""
( New-Object Net.WebClient ).DownloadFile($key_url, $key_file)
( New-Object Net.WebClient ).DownloadFile($installer_url, $installer_file)
Start-Process -FilePath $installer_file -ArgumentList @( "-acceptEULA", "-defaultSite" ) -Wait
Set-Content -Path $settings_file -Value "server.windowsFirewall.sshPortsFirewallSetting globalScope"
Add-Content -Path $settings_file -Value "access.winAccounts.New.winAccount `"Administrator`""
Add-Content -Path $settings_file -Value (( "access.winAccounts.New.keys.Import `""+$key_file+"`"" ).Replace("\", "\\"))
Add-Content -Path $settings_file -Value "access.winAccounts.NewCommit"
& ([System.IO.Path]::Combine($env:ProgramFiles, "BitVise SSH Server", "BssCfg.exe")) settings importText "$settings_file"
Shutdown /r /t 0 /d p:4:2 /c "Post install of SSH server"

To launch an instance using this userdata through the web console, go through the Launch Instance wizard; at Step 3, expand the ‘Advanced Options’ group, check ‘As file’, and upload this file. If you use the EC2 command line utilities, you can spin up an instance with this script using a command like this:

ec2-run-instances --region eu-west-1 ami-0a3fda7d --user-data-file install-winsshd-from-web.ps1.userdata --group ssh --key mykeypairname --instance-type m1.small

(Note that this AMI ID works at the time of writing, but EC2 Windows AMI IDs change regularly, so check the AWS web site to find the current ID for your chosen flavour of Windows.)

About the author

Richard Downer is a software engineer turned cloud solutions architect, specialising in AWS, and based in Scotland. Richard's interest in technology extends to retro computing and amateur hardware hacking with Raspberry Pi and FPGA.