Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
# Windows AMIs don't have WinRM enabled by default -- this script will enable WinRM
# AND install 7-zip, curl and .NET 4 if its missing.
# Then use the EC2 tools to create a new AMI from the result, and you have a system
# that will execute user-data as a PowerShell script after the instance fires up!
# This has been tested on Windows 2008 SP2 64bits AMIs provided by Amazon
# Inject this as user-data of a Windows 2008 AMI, like this (edit the adminPassword to your needs):
# <powershell>
# Set-ExecutionPolicy Unrestricted
# icm $executioncontext.InvokeCommand.NewScriptBlock((New-Object Net.WebClient).DownloadString('')) -ArgumentList "adminPassword"
# </powershell>
Start-Transcript -Path 'c:\bootstrap-transcript.txt' -Force
Set-StrictMode -Version Latest
Set-ExecutionPolicy Unrestricted
$log = 'c:\Bootstrap.txt'
while (($AdminPassword -eq $null) -or ($AdminPassword -eq ''))
$AdminPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR((Read-Host "Enter a non-null / non-empty Administrator password" -AsSecureString)))
$systemPath = [Environment]::GetFolderPath([Environment+SpecialFolder]::System)
$sysNative = [IO.Path]::Combine($env:windir, "sysnative")
$Is32Bit = (($Env:PROCESSOR_ARCHITECTURE -eq 'x86') -and ($Env:PROCESSOR_ARCHITEW6432 -eq $null))
Add-Content $log -value "Is 32-bit [$Is32Bit]"
$coreEditions = @(0x0c,0x27,0x0e,0x29,0x2a,0x0d,0x28,0x1d)
$IsCore = $coreEditions -contains (Get-WmiObject -Query "Select OperatingSystemSKU from Win32_OperatingSystem" | Select -ExpandProperty OperatingSystemSKU)
Add-Content $log -value "Is Core [$IsCore]"
# move to home, PS is incredibly complex :)
Set-Location -Path $Env:USERPROFILE
[Environment]::CurrentDirectory=(Get-Location -PSProvider FileSystem).ProviderPath
#change admin password
net user Administrator $AdminPassword
Add-Content $log -value "Changed Administrator password"
$client = new-object System.Net.WebClient 4
if ((Test-Path "${Env:windir}\Microsoft.NET\Framework\v4.0.30319") -eq $false)
$netUrl = if ($IsCore) {'' } `
else { '' }
$client.DownloadFile( $netUrl, 'dotNetFx40_Full.exe')
Start-Process -FilePath 'C:\Users\Administrator\dotNetFx40_Full.exe' -ArgumentList '/norestart /q /ChainingPackage ADMINDEPLOYMENT' -Wait -NoNewWindow
del dotNetFx40_Full.exe
Add-Content $log -value "Found that .NET4 was not installed and downloaded / installed"
#configure powershell to use .net 4
$config = @'
<?xml version="1.0" encoding="utf-8" ?>
<!-- -->
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" />
<supportedRuntime version="v2.0.50727" />
if (Test-Path "${Env:windir}\SysWOW64\WindowsPowerShell\v1.0\powershell.exe")
$config | Set-Content "${Env:windir}\SysWOW64\WindowsPowerShell\v1.0\powershell.exe.config"
Add-Content $log -value "Configured 32-bit Powershell on x64 OS to use .NET 4"
if (Test-Path "${Env:windir}\system32\WindowsPowerShell\v1.0\powershell.exe")
$config | Set-Content "${Env:windir}\system32\WindowsPowerShell\v1.0\powershell.exe.config"
Add-Content $log -value "Configured host OS specific Powershell at ${Env:windir}\system32\ to use .NET 4"
#check winrm id, if it's not valid and LocalAccountTokenFilterPolicy isn't established, do it
$id = &winrm id
if (($id -eq $null) -and (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -name LocalAccountTokenFilterPolicy -ErrorAction SilentlyContinue) -eq $null)
New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System -name LocalAccountTokenFilterPolicy -value 1 -propertyType dword
Add-Content $log -value "Added LocalAccountTokenFilterPolicy since winrm id could not be executed"
#enable powershell servermanager cmdlets (only for 2008 r2 + above)
if ($IsCore)
DISM /Online /Enable-Feature /FeatureName:MicrosoftWindowsPowerShell /FeatureName:ServerManager-PSH-Cmdlets /FeatureName:BestPractices-PSH-Cmdlets
Add-Content $log -value "Enabled ServerManager and BestPractices Cmdlets"
#enable .NET flavors - on server core only -- errors on regular 2008
DISM /Online /Enable-Feature /FeatureName:NetFx2-ServerCore /FeatureName:NetFx2-ServerCore-WOW64 /FeatureName:NetFx3-ServerCore /FeatureName:NetFx3-ServerCore-WOW64
Add-Content $log -value "Enabled .NET frameworks 2 and 3 for x86 and x64"
$7zUri = if ($Is32Bit) { '' } `
else { '' }
$client.DownloadFile( $7zUri, '7z922.msi')
Start-Process -FilePath "msiexec.exe" -ArgumentList '/i 7z922.msi /norestart /q INSTALLDIR="c:\program files\7-zip"' -Wait
SetX Path "${Env:Path};C:\Program Files\7-zip" /m
$Env:Path += ';C:\Program Files\7-Zip'
del 7z922.msi
Add-Content $log -value "Installed 7-zip from $7zUri and updated path"
#vc 2010 redstributable
$vcredist = if ($Is32Bit) { ''} `
else { '' }
$client.DownloadFile( $vcredist, 'vcredist.exe')
Start-Process -FilePath 'C:\Users\Administrator\vcredist.exe' -ArgumentList '/norestart /q' -Wait
del vcredist.exe
Add-Content $log -value "Installed VC++ 2010 Redistributable from $vcredist and updated path"
#vc 2008 redstributable
$vcredist = if ($Is32Bit) { ''} `
else { '' }
$client.DownloadFile( $vcredist, 'vcredist.exe')
Start-Process -FilePath 'C:\Users\Administrator\vcredist.exe' -ArgumentList '/norestart /q' -Wait
del vcredist.exe
Add-Content $log -value "Installed VC++ 2008 Redistributable from $vcredist and updated path"
$curlUri = if ($Is32Bit) { '' } `
else { '' }
$client.DownloadFile( $curlUri, '')
&7z e `-o`"c:\program files\curl`"
if ($Is32Bit)
$client.DownloadFile( '', '')
&7z e `-o`"c:\program files\curl`"
SetX Path "${Env:Path};C:\Program Files\Curl" /m
$Env:Path += ';C:\Program Files\Curl'
Add-Content $log -value "Installed Curl from $curlUri and updated path"
& 'C:\Program Files\Curl\curl.exe' -# -G -k -L -o 2>&1 > "$log"
& 'C:\Program Files\Curl\curl.exe' -# -G -k -L -o 2>&1 > "$log"
Get-ChildItem -Filter vim73*.zip |
% { &7z x `"$($_.FullName)`"; del $_.FullName; }
SetX Path "${Env:Path};C:\Program Files\Vim" /m
$Env:Path += ';C:\Program Files\Vim'
Move-Item .\vim\vim73 -Destination "${Env:ProgramFiles}\Vim"
Add-Content $log -value "Installed Vim text editor and updated path"
#chocolatey - standard one line installer doesn't work on Core b/c Shell.Application can't unzip
if (-not $IsCore)
Invoke-Expression ((new-object net.webclient).DownloadString(''))
#[Environment]::SetEnvironmentVariable('ChocolateyInstall', 'c:\nuget', [System.EnvironmentVariableTarget]::User)
#if (![System.IO.Directory]::Exists('c:\nuget')) {[System.IO.Directory]::CreateDirectory('c:\nuget')}
$tempDir = Join-Path $env:TEMP "chocInstall"
if (![System.IO.Directory]::Exists($tempDir)) {[System.IO.Directory]::CreateDirectory($tempDir)}
$file = Join-Path $tempDir ""
$client.DownloadFile("", $file)
&7z x $file `-o`"$tempDir`"
Add-Content $log -value 'Extracted Chocolatey'
$chocInstallPS1 = Join-Path (Join-Path $tempDir 'tools') 'chocolateyInstall.ps1'
& $chocInstallPS1
Add-Content $log -value 'Installed Chocolatey / Verifying Paths'
Add-Content $log -value "Installed Chocolatey"
# install puppet
& 'C:\Program Files\Curl\curl.exe' -# -G -k -L -o puppet-3.2.4.msi 2>&1 > "$log"
Start-Process -FilePath "msiexec.exe" -ArgumentList '/qn /passive /i puppet-3.2.4.msi /norestart' -Wait
SetX Path "${Env:Path};C:\Program Files\Puppet Labs\Puppet\bin" /m
&sc.exe config puppet start= demand
Add-Content $log -value "Installed Puppet"
&winrm quickconfig `-q
&winrm set winrm/config/client/auth '@{Basic="true"}'
&winrm set winrm/config/service/auth '@{Basic="true"}'
&winrm set winrm/config/service '@{AllowUnencrypted="true"}'
Add-Content $log -value "Ran quickconfig for winrm"
&netsh firewall set portopening tcp 445 smb enable
Add-Content $log -value "Ran firewall config to allow incoming smb/tcp"
#run SMRemoting script to enable event log management, etc - available only on R2
$remotingScript = [IO.Path]::Combine($systemPath, 'Configure-SMRemoting.ps1')
if (-not (Test-Path $remotingScript)) { $remotingScript = [IO.Path]::Combine($sysNative, 'Configure-SMRemoting.ps1') }
Add-Content $log -value "Found Remoting Script: [$(Test-Path $remotingScript)] at $remotingScript"
if (Test-Path $remotingScript)
. $remotingScript -force -enable
Add-Content $log -value 'Ran Configure-SMRemoting.ps1'
#wait a bit, it's windows after all
Start-Sleep -m 10000
#Write-Host "Press any key to reboot and finish image configuration"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment