• HashiCorp Developer

  • HashiCorp Cloud Platform
  • Terraform
  • Packer
  • Consul
  • Vault
  • Boundary
  • Nomad
  • Waypoint
  • Vagrant
Packer
  • Install
  • Tutorials
  • Documentation
  • Guides
  • Plugins
  • Try Cloud(opens in new tab)
  • Sign up
Integrations

Skip to main content
2 tutorials
  • Post-Processors - Vagrant
  • Build a Windows Image

  • Resources

  • Tutorial Library
  • Community Forum
    (opens in new tab)
  • Support
    (opens in new tab)
  • GitHub
    (opens in new tab)
  1. Developer
  2. Packer
  3. Tutorials
  4. Integrations
  5. Build a Windows Image

Build a Windows Image

  • 11min

  • PackerPacker

Should you decide to follow along and build an AMI from the example template, provided you qualify for free tier usage, you should not be charged for actually building the AMI. However, please note that you will be charged for storage of the snapshot associated with any AMI that you create. If you wish to avoid further charges, follow the steps in the Managing the Image section above to deregister the created AMI and delete the associated snapshot once you're done.

In this example, we are making use of an existing AMI available from the Amazon marketplace as the source, or starting point, for building our own AMI. In brief, Packer will spin up the source AMI, connect to it and then run whatever commands or scripts we've configured in our build template to customize the image. Finally, when all is done, Packer will wrap the whole customized package up into a brand new AMI that will be available from the AWS AMI management page. Any instances we subsequently create from this AMI will have all of our customizations baked in. This is the core benefit we are looking to achieve from using the Amazon EBS builder in this example.

One issue we run into when provisioning Windows AMIs on Amazon is that out of the box, the instance created from our source AMI is not configured to allow Packer to connect to it. So how do we fix it so that Packer can connect to and customize our instance?

Packer can use an Amazon-provided mechanism called "user-data" to run a set of pre-supplied commands within the instance shortly after the instance starts.

This means we need to give Packer the commands required to configure the instance for a remote connection. Once the commands are run, Packer will be able to connect directly in to the instance and make the customizations we need.

Here's a basic example of a file that will configure the instance to allow Packer to connect over WinRM. We will add this file to the build source section of our build template.

Note the <powershell> and </powershell> tags at the top and bottom of the file. These tags tell Amazon we'd like to run the enclosed code with PowerShell. You can also use <script></script> tags to enclose any commands that you would normally run in a Command Prompt window. See Running Commands on Your Windows Instance at Launch for more info about what's going on behind the scenes here.

<powershell>
# Set administrator password
net user Administrator SuperS3cr3t!!!!
wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE

# First, make sure WinRM can't be connected to
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block

# Delete any existing WinRM listeners
winrm delete winrm/config/listener?Address=*+Transport=HTTP  2>$Null
winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null

# Disable group policies which block basic authentication and unencrypted login

Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowUnencryptedTraffic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowUnencryptedTraffic -Value 1


# Create a new WinRM listener and configure
winrm create winrm/config/listener?Address=*+Transport=HTTP
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/client/auth '@{Basic="true"}'

# Configure UAC to allow privilege elevation in remote shells
$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$Setting = 'LocalAccountTokenFilterPolicy'
Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force

# Configure and restart the WinRM Service; Enable the required firewall exception
Stop-Service -Name WinRM
Set-Service -Name WinRM -StartupType Automatic
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
Start-Service -Name WinRM
</powershell>

Warning: Please note that if you're setting up WinRM for provisioning, you'll probably want to turn it off or restrict its permissions as part of a shutdown script at the end of Packer's provisioning process. For more details on the why/how, check out this useful blog post and the associated code: https://cloudywindows.io/post/winrm-for-provisioning---close-the-door-on-the-way-out-eh/

Save the above code in a file named bootstrap_win.txt.

Warning

Windows administrators in the know might be wondering why we haven't simply used a winrm quickconfig -q command in the script above, as this would automatically set up all of the required elements necessary for connecting over WinRM. Why all the extra effort to configure things manually?

Well, use of the winrm quickconfig -q command can sometimes cause the Packer build to fail shortly after the WinRM connection is established. How?

  1. Among other things, as well as setting up the listener for WinRM, the quickconfig command also configures the firewall to allow management messages to be sent over HTTP.
  2. This undoes the previous command in the script that configured the firewall to prevent this access.
  3. The upshot is that the system is configured and ready to accept WinRM connections earlier than intended.
  4. If Packer establishes its WinRM connection immediately after execution of the winrm quickconfig -q command, the later commands within the script that restart the WinRM service will unceremoniously pull the rug out from under the connection.
  5. While Packer does a lot to ensure the stability of its connection in to your instance, this sort of abuse can prove to be too much and may cause your Packer build to stall irrecoverably or fail!

Now that we've done the configuration necessary to connect Packer to our instance, let's get on with the real reason we're doing all this, which is actually configuring and customizing the instance. We do this with Provisioners.

The example config below shows the two different ways of using the PowerShell provisioner: inline and script. The first example, inline, allows you to provide short snippets of code, and Packer will create a script file for you. The second example allows you to run more complex code by providing the path to a script to run on the guest VM.

Here's an example of a sample_script.ps1 that will work with the environment variables we will set in our build template; copy the contents into your own sample_script.ps1 and provide the path to it in your build template:

Write-Host "PACKER_BUILD_NAME is an env var Packer automatically sets for you."
Write-Host "...or you can set it in your builder variables."
Write-Host "The default for this builder is:" $Env:PACKER_BUILD_NAME

Write-Host "The PowerShell provisioner will automatically escape characters"
Write-Host "considered special to PowerShell when it encounters them in"
Write-Host "your environment variables or in the PowerShell elevated"
Write-Host "username/password fields."
Write-Host "For example, VAR1 from our config is:" $Env:VAR1
Write-Host "Likewise, VAR2 is:" $Env:VAR2
Write-Host "VAR3 is:" $Env:VAR3
Write-Host "Finally, VAR4 is:" $Env:VAR4
Write-Host "None of the special characters needed escaping in the template"

Finally, we need to create the actual build template. Remember, this template is the core configuration file that Packer uses to understand what you want to build, and how you want to build it.

As mentioned earlier, the specific builder we are using in this example is the Amazon EBS builder. The template below demonstrates use of the source_ami_filter configuration option available within the builder for automatically selecting the latest suitable source Windows AMI provided by Amazon. We also use the user_data_file configuration option provided by the builder to reference the bootstrap file we created earlier. As you will recall, our bootstrap file contained all the commands we needed to supply in advance of actually spinning up the instance, so that later on, our instance is configured to allow Packer to connect to it.

The "provisioners" section of the template demonstrates use of the powershell and windows-restart provisioners to customize and control the build process:

packer {
  required_plugins {
    amazon = {
      version = ">= 0.0.1"
      source = "github.com/hashicorp/amazon"
    }
  }
}

variable "region" {
  type    = string
  default = "us-east-1"
}

locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }

# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors on a
# source.
source "amazon-ebs" "firstrun-windows" {
  ami_name      = "packer-windows-demo-${local.timestamp}"
  communicator  = "winrm"
  instance_type = "t2.micro"
  region        = "${var.region}"
  source_ami_filter {
    filters = {
      name                = "Windows_Server-2012-R2*English-64Bit-Base*"
      root-device-type    = "ebs"
      virtualization-type = "hvm"
    }
    most_recent = true
    owners      = ["amazon"]
  }
  user_data_file = "./bootstrap_win.txt"
  winrm_password = "SuperS3cr3t!!!!"
  winrm_username = "Administrator"
}

# a build block invokes sources and runs provisioning steps on them.
build {
  name    = "learn-packer"
  sources = ["source.amazon-ebs.firstrun-windows"]

  provisioner "powershell" {
    environment_vars = ["DEVOPS_LIFE_IMPROVER=PACKER"]
    inline           = ["Write-Host \"HELLO NEW USER; WELCOME TO $Env:DEVOPS_LIFE_IMPROVER\"", "Write-Host \"You need to use backtick escapes when using\"", "Write-Host \"characters such as DOLLAR`$ directly in a command\"", "Write-Host \"or in your own scripts.\""]
  }
  provisioner "windows-restart" {
  }
  provisioner "powershell" {
    environment_vars = ["VAR1=A$Dollar", "VAR2=A`Backtick", "VAR3=A'SingleQuote", "VAR4=A\"DoubleQuote"]
    script           = "./sample_script.ps1"
  }
}

Save the build template as firstrun-windows.pkr.hcl.

Before you can build the AMI, you need to provide your AWS credentials to Packer. These credentials have permissions to create, modify and delete EC2 instances. Refer to the documentation to find the full list IAM permissions required to run the amazon-ebs builder.

To alow Packer to access your IAM user credentials, set your AWS access key ID as an environment variable.

$ export AWS_ACCESS_KEY_ID="<YOUR_AWS_ACCESS_KEY_ID>"

Now set your secret key.

$ export AWS_SECRET_ACCESS_KEY="<YOUR_AWS_SECRET_ACCESS_KEY>"

Tip: If you don't have access to IAM user credentials, use another authentication method described in the Packer documentation.

Finally, we can create our new AMI by running packer build firstrun-windows.pkr.hcl

You should see output like this:

learn-packer.amazon-ebs.firstrun-windows: output will be in this color.

==> learn-packer.amazon-ebs.firstrun-windows: Prevalidating any provided VPC information
==> learn-packer.amazon-ebs.firstrun-windows: Prevalidating AMI Name: packer-windows-demo-20210120235100
    learn-packer.amazon-ebs.firstrun-windows: Found Image ID: ami-079fe16082fb837c5
==> learn-packer.amazon-ebs.firstrun-windows: Creating temporary keypair: packer_6008c1e4-9ea9-a8fd-9bae-86ed18ceee5d
==> learn-packer.amazon-ebs.firstrun-windows: Creating temporary security group for this instance: packer_6008c1eb-8457-0e12-6209-75508d61f09b
==> learn-packer.amazon-ebs.firstrun-windows: Authorizing access to port 5985 from [0.0.0.0/0] in the temporary security groups...
==> learn-packer.amazon-ebs.firstrun-windows: Launching a source AWS instance...
==> learn-packer.amazon-ebs.firstrun-windows: Adding tags to source instance
    learn-packer.amazon-ebs.firstrun-windows: Adding tag: "Name": "Packer Builder"
    learn-packer.amazon-ebs.firstrun-windows: Instance ID: i-0773dbb84a486a583
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for instance (i-0773dbb84a486a583) to become ready...
==> learn-packer.amazon-ebs.firstrun-windows: Skipping waiting for password since WinRM password set...
==> learn-packer.amazon-ebs.firstrun-windows: Using winrm communicator to connect: 18.212.5.32
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for WinRM to become available...
    learn-packer.amazon-ebs.firstrun-windows: WinRM connected.
==> learn-packer.amazon-ebs.firstrun-windows: Connected to WinRM!
==> learn-packer.amazon-ebs.firstrun-windows: Provisioning with Powershell...
==> learn-packer.amazon-ebs.firstrun-windows: Provisioning with powershell script: /var/folders/8t/0yb5q0_x6mb2jldqq_vjn3lr0000gn/T/powershell-provisioner326516307
    learn-packer.amazon-ebs.firstrun-windows: HELLO NEW USER; WELCOME TO PACKER
    learn-packer.amazon-ebs.firstrun-windows: You need to use backtick escapes when using
    learn-packer.amazon-ebs.firstrun-windows: characters such as DOLLAR$ directly in a command
    learn-packer.amazon-ebs.firstrun-windows: or in your own scripts.
==> learn-packer.amazon-ebs.firstrun-windows: Restarting Machine
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for machine to restart...
==> learn-packer.amazon-ebs.firstrun-windows: A system shutdown is in progress.(1115)
    learn-packer.amazon-ebs.firstrun-windows: WIN-533JTH6PQI1 restarted.
==> learn-packer.amazon-ebs.firstrun-windows: Machine successfully restarted, moving on
==> learn-packer.amazon-ebs.firstrun-windows: Provisioning with Powershell...
==> learn-packer.amazon-ebs.firstrun-windows: Provisioning with powershell script: ./sample_script.ps1
    learn-packer.amazon-ebs.firstrun-windows: PACKER_BUILD_NAME is an env var Packer automatically sets for you.
    learn-packer.amazon-ebs.firstrun-windows: ...or you can set it in your builder variables.
    learn-packer.amazon-ebs.firstrun-windows: The default for this builder is: firstrun-windows
    learn-packer.amazon-ebs.firstrun-windows: The PowerShell provisioner will automatically escape characters
    learn-packer.amazon-ebs.firstrun-windows: considered special to PowerShell when it encounters them in
    learn-packer.amazon-ebs.firstrun-windows: your environment variables or in the PowerShell elevated
    learn-packer.amazon-ebs.firstrun-windows: username/password fields.
    learn-packer.amazon-ebs.firstrun-windows: For example, VAR1 from our config is: A$Dollar
    learn-packer.amazon-ebs.firstrun-windows: Likewise, VAR2 is: A`Backtick
    learn-packer.amazon-ebs.firstrun-windows: VAR3 is: A'SingleQuote
    learn-packer.amazon-ebs.firstrun-windows: Finally, VAR4 is: A"DoubleQuote
    learn-packer.amazon-ebs.firstrun-windows: None of the special characters needed escaping in the template
==> learn-packer.amazon-ebs.firstrun-windows: Stopping the source instance...
    learn-packer.amazon-ebs.firstrun-windows: Stopping instance
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for the instance to stop...
==> learn-packer.amazon-ebs.firstrun-windows: Creating AMI packer-windows-demo-20210120235100 from instance i-0773dbb84a486a583
    learn-packer.amazon-ebs.firstrun-windows: AMI: ami-067a1880caf9d35b7
==> learn-packer.amazon-ebs.firstrun-windows: Waiting for AMI to become ready...
==> learn-packer.amazon-ebs.firstrun-windows: Terminating the source AWS instance...
==> learn-packer.amazon-ebs.firstrun-windows: Cleaning up any extra volumes...
==> learn-packer.amazon-ebs.firstrun-windows: No volumes to clean up, skipping
==> learn-packer.amazon-ebs.firstrun-windows: Deleting temporary security group...
==> learn-packer.amazon-ebs.firstrun-windows: Deleting temporary keypair...
Build 'learn-packer.amazon-ebs.firstrun-windows' finished after 5 minutes 30 seconds.

==> Wait completed after 5 minutes 30 seconds

==> Builds finished. The artifacts of successful builds are:
--> learn-packer.amazon-ebs.firstrun-windows: AMIs were created:
us-east-1: ami-067a1880caf9d35b7

And if you navigate to your EC2 dashboard you should see your shiny new AMI listed in the main window of the Images -> AMIs section.

Why stop there though?

As you'll see, with one simple change to the template above, it's just as easy to create your own Windows 2008 or Windows 2016 AMIs. Just set the value for the name field within source_ami_filter as required:

For Windows 2008 SP2:

          name = "Windows_Server-2008-SP2*English-64Bit-Base*",

For Windows 2016:

          name = "Windows_Server-2016-English-Full-Base*",

The bootstrapping and sample provisioning should work the same across all Windows server versions.

 Previous
 Browse Tutorials
Give Feedback(opens in new tab)
  • Certifications
  • System Status
  • Terms of Use
  • Security
  • Privacy
  • Trademark Policy
  • Trade Controls
  • Give Feedback(opens in new tab)