Part 2 – This post.
Welcome back to the second blog post on creating golden Citrix images with Packer, PowerShell, and Azure. In Part 1 we talked about the staging grounds for getting started and kicking everything off. Now that we have the main variables and access configured, we can go over the HCL/JSON file.
In this case we’ll be working with https://github.com/StevenNoel/Packer-Azure-Powershell/blob/main/Packer-CTXAZPK-VDA-Citrix.pkr.hcl. This was a json file that was converted over to the newer HCL format via the convert command – Here. Really no editing was needed. I’m not going to go through this line by line, but I’ll go over some of the pertinent parts. Majority of the commands/syntax in here can be found at this location: https://developer.hashicorp.com/packer/plugins/builders/azure/arm
variable "AZURE_CLIENT_ID" {
type = string
default = "${env("AZURE_CLIENT_ID")}"
sensitive = true
}
variable "AZURE_CLIENT_SECRET" {
type = string
default = "${env("AZURE_CLIENT_SECRET")}"
sensitive = true
}
variable "AZURE_MDT" {
type = string
default = "${env("AZURE_MDT")}"
sensitive = true
}
variable "AZURE_LOCALUSER" {
type = string
default = "${env("AZURE_LOCALUSER")}"
sensitive = true
}
variable "managed_image_name" {
type = string
default = "citrix-packer-image"
}
variable "subscription_id" {
type = string
default = "<azure subscription id>"
}
variable "temp_compute_name" {
type = string
default = "CTXAZPK16LOB001"
}
- This first section is taking the environment variables that we set in Part1 and assigning them so they can be used as values in other sections.
source "azure-arm" "autogenerated_1" {
azure_tags = {
application = "citrix"
}
#Azure Info
subscription_id = "${var.subscription_id}"
client_id = "${var.AZURE_CLIENT_ID}"
client_secret = "${var.AZURE_CLIENT_SECRET}"
cloud_environment_name = "AzureUSGovernmentCloud"
#Packer Azure
build_resource_group_name = "<azure resource grouop>"
custom_resource_build_prefix = "ctxpk"
managed_image_name = "${var.managed_image_name}-${formatdate("YYYY-MM-DD-hhmm-0700",timestamp())}"
managed_image_resource_group_name = "<azure resource group>"
managed_image_storage_account_type = "Premium_LRS"
#Azure Marketplace Sku
os_type = "Windows"
image_offer = "WindowsServer"
image_publisher = "MicrosoftWindowsServer"
image_sku = "2016-datacenter-gensecond"
image_version = "latest"
#VM details
private_virtual_network_with_public_ip = false
#temp_compute_name = "${var.temp_compute_name}${legacy_isotime("06010203")}"
temp_compute_name = "${var.temp_compute_name}"
virtual_network_name = "<azure virtual network>"
virtual_network_resource_group_name = "<azure resource group>"
virtual_network_subnet_name = "<azure subnet name>"
vm_size = "Standard_B4ms"
#WinRM
communicator = "winrm"
winrm_insecure = "true"
winrm_timeout = "5m"
winrm_use_ssl = "true"
winrm_username = "packer"
}
- This section is pretty straight forward and should look something similar for builds in Azure.
- The “Azure Info” section contains the necessary authentication that packer uses to connect to your azure tenant
- The “Packer Azure” section contains already created already pre-created resource/virtual/subnet groups. If you don’t have these created Packer can create them. Also, if you want to use different resource groups, you can use “
temp_resource_group_name
“. - The “VM Details” section provides resource requirements for what type of VM build to make, the VM name, and Network details.
- The “WinRM” section contains standard canned WinRM information. In this scenario Packer will communicate to the the Azure VM via SSL Cert that it generates in a Vault. However, you can specify a password here or another method.
- Make sure to check https://developer.hashicorp.com/packer/plugins/builders/azure/arm for further definitions and caveats for the parameters.
build {
sources = ["source.azure-arm.autogenerated_1"]
provisioner "powershell" {
inline = ["while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }", "while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }", "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12", "Start-sleep -s 5"]
}
provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Machine restarted1.'}\""
}
provisioner "powershell" {
environment_vars = ["AZURE_LOCALUSER=${var.AZURE_LOCALUSER}"]
scripts = ["./Azure-Packer-PreReqs.ps1"]
valid_exit_codes = [0, 1, 3010]
}
provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Machine restarted2.'}\""
}
provisioner "powershell" {
environment_vars = ["Azure_MDT=${var.AZURE_MDT}"]
scripts = ["./Azure-Packer-Base.ps1"]
elevated_user = "Administrator"
elevated_password = "${var.AZURE_LOCALUSER}"
valid_exit_codes = [0, 1, 3010]
}
provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Machine restarted3.'}\""
restart_timeout = "20m"
}
provisioner "powershell" {
environment_vars = ["Azure_MDT=${var.AZURE_MDT}"]
scripts = ["./Azure-Packer-Citrix-Installs.ps1"]
elevated_user = "Administrator"
elevated_password = "${var.AZURE_LOCALUSER}"
valid_exit_codes = [0, 1, 3, 3010]
}
provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Machine restarted4.'}\""
restart_timeout = "20m"
}
provisioner "powershell" {
environment_vars = ["Azure_MDT=${var.AZURE_MDT}"]
scripts = ["./Azure-Packer-Global-App-Installs-1.ps1"]
elevated_user = "Administrator"
elevated_password = "${var.AZURE_LOCALUSER}"
valid_exit_codes = [0, 1, 3, 3010]
}
provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Machine restarted5.'}\""
restart_timeout = "20m"
}
provisioner "powershell" {
environment_vars = ["Azure_MDT=${var.AZURE_MDT}"]
scripts = ["./Azure-Packer-SecurityAgent-Installs-1.ps1"]
elevated_user = "Administrator"
elevated_password = "${var.AZURE_LOCALUSER}"
valid_exit_codes = [0, 1, 3, 3010]
}
provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Machine restarted6.'}\""
restart_timeout = "20m"
}
provisioner "powershell" {
environment_vars = ["Azure_MDT=${var.AZURE_MDT}"]
scripts = ["./Azure-Packer-Final-Seal-2.ps1"]
elevated_user = "Administrator"
elevated_password = "${var.AZURE_LOCALUSER}"
valid_exit_codes = [0, 1, 3, 3010]
}
provisioner "windows-restart" {
restart_check_command = "powershell -command \"& {Write-Output 'Machine restarted7.'}\""
restart_timeout = "20m"
}
provisioner "powershell" {
inline = ["while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }", "while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }", "Set-ExecutionPolicy Bypass -Scope Process -Force", "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12", "Start-sleep -s 5", "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit", "while($true) { $imageState = Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State | Select ImageState; if($imageState.ImageState -ne 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { Write-Output $imageState.ImageState; Start-Sleep -s 10 } else { break } }"]
}
post-processor "manifest" {
output = "./manifests/manifest-${legacy_isotime("2006-01-02-0304")}.json"
strip_path = true
}
}
- This is the final part of the HCL file, which is designed by the ‘build’ provisioner. This is similar to an MDT/SCCM task sequence or a ansible playbook, where we tell Packer all the different things to do or scripts to run.
- I breakdown this into the following section
- Prereq
- restart
- Base
- restart
- Citrix Installs
- restart
- App Installs
- restart
- Security Agent Installs
- restart
- Seal
- restart
- Prereq
- I’ve uploaded a few of the PS1s to this repo to show you how I’m handling the app installs. In a nutshell, each of the PS1s that get called in this HCL file, pass creds, map drives, and then call other PS1s. For instance the Azure-Packer-Citrix-Installs.ps1 will run elevated, pass the creds into PS1, Map a drive, install the VDA, Citrix Optimizer, Connection Quality Indicator, and Citrix Workspace. You can see an example of this in the Azure-Packer-Base.ps1 that I’ve uploaded.
- Once this build provisioner finishes, you will be left with an ‘image’ in azure where you can create/deploy VMs from.
That’s all for this post. In Part 3 I’ll talk about some of the gotchas I ran across.
Part 2 – This Post