Bootstrapping Windows during automated deployments is one of the more frustrating aspects of working with templates that are being deployed by Infrastructure as Code toolsets. When I say bootstrapping, i’m talking about executing post deployment actions on templates such as configuring WinRM or setting Windows Firewall settings to allow for future operations after deployment.
VMware has offered functionality for a number of years via Guest Customizations, but the challenge I found was how to leverage that when deploying templates through Terraform plans. Specifically I needed to try and turn off the Windows Firewall on a Server Core after the cloning operation. There are a number of ways to turn off or set the Windows Firewall… but PowerShell provides the most elegant way to achieve this in a one liner as shown below:
set-NetFirewallProfile -All -Enabled False
The challenge is how to get Terraform to leverage Guest Customizations to execute during the initial template deployment and customization process of the plan apply. In a nutshell we can use the run_once_command_list option that is part of the VMware Tools Guest Customization rule set.
Basically we can list out one or multiple commands to be executed on the deployed Windows Server as shown below:
The working example I have above actually does a couple of things. Firstly we are running two PowerShell Invoke-WebRequest commands to go out and download scripts from GitHub and save them to the local drive. The next set of commands call PowerShell to run the scripts as part of the run_once_command_list. You can see that to make that happen we use auto_logon and auto_logon_count Guest Customization parameters. The actual PowerShell is executed via a cmd.exe command with the /C flag (Run Command and then terminate) and we also specify the ExecutionPolicy to bypass any account access controls.
Within that first.ps1 script is actually the command to disable the Windows Firewall. There is also a couple of other commands that I have to create a new user and add it to the local administrators account.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#Set Windows Firewall to OFF set-NetFirewallProfile -All -Enabled False #Create User and Add to Local Administrator Group $password = ConvertTo-SecureString 'Veeam1!' -AsPlainText -Force new-localuser -Name autodeploy -Password $password add-localgroupmember -Group administrators -Member autodeploy Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -PropertyType String -Force Get-Service sshd | Set-Service -StartupType Automatic Start-Service sshd Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) choco install cygwin cyg-get -y cyg-get openssh python38 python38-pip python38-devel libssl-devel libffi-devel gcc-g++ choco install veeam-backup-and-replication-server -y |
Wrap Up:
While this seems easy enough to show and explain, the reality for me, was to get to this point, it has taken me a lot of trial and error and different approaches. With this solution I can now easily code in a list of PowerShell commands which Terraform will handle the execution of with Guest Customizations via the plan apply. It’s a pretty powerful way to get template deployments working out of the box during setup and deployment!
Full Example of the Terraform Declaration is below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#=============================================================================== # vSphere Resources #=============================================================================== # Create a vSphere VM in the folder # resource "vsphere_virtual_machine" "VBR-PROXY" { # VM placement # count = "${var.vsphere_proxy_number}" name = "${var.vsphere_vm_name}-W${random_integer.priority.result}-${count.index + 1}" resource_pool_id = "${data.vsphere_resource_pool.resource_pool.id}" datastore_id = "${data.vsphere_datastore.datastore.id}" folder = "${var.vsphere_vm_folder}" tags = ["${data.vsphere_tag.tag.id}"] # VM resources # num_cpus = "${var.vsphere_vcpu_number}" memory = "${var.vsphere_memory_size}" # Guest OS # guest_id = "${data.vsphere_virtual_machine.template.guest_id}" scsi_type = "${data.vsphere_virtual_machine.template.scsi_type}" firmware = "${var.vsphere_vm_firmware}" scsi_controller_count = "4" # VM storage # disk { label = "${var.vsphere_vm_name}.vmdk" size = "${data.vsphere_virtual_machine.template.disks.0.size}" thin_provisioned = "${data.vsphere_virtual_machine.template.disks.0.thin_provisioned}" eagerly_scrub = "${data.vsphere_virtual_machine.template.disks.0.eagerly_scrub}" } network_interface { network_id = "${data.vsphere_network.network.id}" adapter_type = "${data.vsphere_virtual_machine.template.network_interface_types[0]}" } clone { template_uuid = "${data.vsphere_virtual_machine.template.id}" customize { windows_options { computer_name = "${var.vsphere_vm_name}-W${random_integer.priority.result}-${count.index + 1}" admin_password = "${var.vsphere_vm_password}" auto_logon = true auto_logon_count = 1 run_once_command_list = [ "cmd.exe /C Powershell.exe Invoke-WebRequest -Uri https://raw.githubusercontent.com/anthonyspiteri/Project-Otosukeru/dev/first.ps1 -OutFile c:\\first.ps1", "cmd.exe /C Powershell.exe -ExecutionPolicy Bypass -File c:\\first.ps1", ] } network_interface { ipv4_address = "${var.vsphere_ipv4_address_proxy_network}${"${var.vsphere_ipv4_address_proxy_host}" + count.index}" ipv4_netmask = "${var.vsphere_ipv4_netmask}" } ipv4_gateway = "${var.vsphere_ipv4_gateway}" dns_server_list = ["${var.vsphere_dns_server1}", "${var.vsphere_dns_server2}"]} } } |