So in the previous post we created a Ubuntu Template, but we also did some stuff to ensure that the Catalog would be visible. So if you’re reading this post just for the windows template, you may want to review the Ubuntu post for this stuff.
Property Group
So we need to add a property to the propery group that’s missing for windows.
Navigate to Assembler, Design, Property Groups and select your previously created defaultServervalues. Add a New Property called search, choose string, and assign it the value of your domain and subdomains within quotation marks, and comma separated like such: “rainpole.io”, “sfo.rainpole.io”
A not so secret secret
If you’re going to be working with programming, scripting, or automation, you better learn to frequent github, stackoverflow, and other forums. Someone is likely to have done what you’re looking to do already and you can either use their code, or adapt it to work as you require. If you where to only rely on manuals and documentation you would not get far.
With that said most of the CloudConfig in this template comes from Sam Perrins awesome work found here: https://samperrin.com/posts/vra-aa-windows-cloudbase-init-function-update/
I’ve added/removed just a few things.
Create the windows template
To avoid any issues with copy/paste, and maybe get a newer version, or more advanced version, all the templates are available at https://github.com/maxiepax/vcf-automation
By now, you’re hopefully a bit more familiar with how to create templates, so head over to Assembler, Select Design in the top menu, Templates, New From, Blank Canvas.
Name it Windows VM, and select your project.
The IAC code is largely the same as the Ubuntu template, so no need to repeat myself. The changes would be that we are now pointing to the windows image mappings, that you can select the size of the DATA disk. I’ve added so that your user gets added to the Remote Desktop Users, and the local Administrators group for the VM created.
Just copy/paste this into the right hand side of the canvas.
formatVersion: 1
inputs:
hostname:
type: string
title: Hostname
description: The name of the virtual machine
osversion:
type: string
title: Windows Version
description: Choose what version of windows
default: windows-server-2022
oneOf:
- title: Windows 2019
const: windows-server-2019
- title: Windows 2022
const: windows-server-2022
size:
type: string
title: VM Size
description: |-
<b> Select the size of deployment </b></br>
Small: 1 vCPU - 2GB ram - 40GB disk </br>
Medium: 2 vCPU - 4GB ram - 40GB disk </br>
Large: 4 vCPU - 16GB ram - 40GB disk </br>
enum:
- small
- medium
- large
default: medium
environment:
type: string
title: Environment
oneOf:
- title: Development
const: development
- title: Production
const: production
costcenter:
type: integer
title: Cost Center
minLength: 5
maxLength: 5
backup:
type: string
default: backup:24h31d
oneOf:
- title: 12 hours, 31 day retention
const: backup:12h31d
- title: 24 hours, 31 day retention
const: backup:24h31d
- title: none
const: backup:declined
datadisk:
type: integer
title: Datadisk size
minimum: 0
maximum: 1000
default: 0
ad_user:
type: string
title: AD User
description: Provide the ad user that should be permitted RDP login and Administration priviliges.
default: ''
resources:
Network:
type: Cloud.vSphere.Network
properties:
networkType: existing
constraints:
- tag: env:${input.environment}
Cloud_vSphere_Disk_1:
type: Cloud.vSphere.Disk
allocatePerInstance: true
properties:
capacityGb: ${input.datadisk}
Cloud_vSphere_Machine_1:
type: Cloud.vSphere.Machine
properties:
tags:
- key: costcenter
value: ${input.costcenter}
- key: dtap
value: ${input.environment}
- key: backup
value: ${input.backup}
image: ${input.osversion}
flavor: ${input.size}
networks:
- network: ${resource.Network.id}
attachedDisks:
- source: ${resource.Cloud_vSphere_Disk_1.id}
cloudConfig: |
Content-Type: multipart/mixed; boundary="==NewPart=="
MIME-Version: 1.0
--==NewPart==
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config"
set_hostname: ${input.hostname}
--==NewPart==
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="INIT.ps1"
#ps1_sysnative
function CustomLog ($LogMessage) {
$LogFilePath = "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\CustomLog.txt"
Write-Output "$((Get-Date).ToString('T')) $LogMessage" | Out-File -FilePath $LogFilePath -Append
}
function DomainDnsTest {
$DomainDnsTestResult = (Test-NetConnection -ComputerName "${propgroup.defaultServerValues.domain}" -Port 389).TcpTestSucceeded
return $DomainDnsTestResult
}
function DomainJoinStatus {
$DomainJoinStatusResult = (Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain
return $DomainJoinStatusResult
}
function WaitForNetwork {
CustomLog("-- Starting WaitForNetwork. Starting status $((Get-NetAdapter -Name Ethernet0).Status)")
While ((Get-NetAdapter -Name Ethernet0).Status -ne "Up") {
CustomLog("-- Waiting for network Ethernet0 to be up. Current status $((Get-NetAdapter -Name Ethernet0).Status)")
Start-Sleep -Seconds 2
}
CustomLog("-- Ending WaitForNetwork. Ending status $((Get-NetAdapter -Name Ethernet0).Status)")
}
function WaitForDns {
CustomLog("-- Starting WaitForDns. Starting status $(DomainDnsTest)")
While ((DomainDnsTest) -ne "True") {
CustomLog("-- Waiting for DNS to succeed. Current status $(DomainDnsTest)")
Start-Sleep -Seconds 2
}
CustomLog("-- Ending WaitForDns. Ending status $(DomainDnsTest)")
}
function JoinDomain {
CustomLog("-- Starting JoinDomain")
WaitForNetwork
WaitForDns
$DomainJoinCreds = New-Object System.Management.Automation.PSCredential "${propgroup.defaultServerValues.adSvcAccount}@${propgroup.defaultServerValues.domain}" ,(ConvertTo-SecureString -String "${secret.adSvcAccountPassword}" -AsPlainText -Force)
Add-Computer -DomainName "${propgroup.defaultServerValues.domain}" -Credential $DomainJoinCreds -Restart:$false -Force
CustomLog("-- Ending JoinDomain")
}
function WaitForDomainJoin {
CustomLog("-- Starting WaitForDomainJoin. Starting status $(DomainJoinStatus)")
While ((DomainJoinStatus) -ne "True") {
CustomLog("---- Waiting for Domain to be True. Current status $(DomainJoinStatus)")
CustomLog("---- Starting Sleep")
Start-Sleep -Seconds 10
CustomLog("---- Ending Sleep")
JoinDomain
}
CustomLog("-- Ending WaitForDomainJoin. Ending status $(DomainJoinStatus)")
}
CustomLog("Set Local Firewall - Started")
Set-NetFirewallRule -DisplayName "File and Printer Sharing (Echo Request - ICMPv4-In)" -enabled True
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -Name 'NV Domain' -Value "${propgroup.defaultServerValues.domain}"
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters' -Name SyncDomainWithMembership -Value "0"
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -name "fDenyTSConnections" -value 0
CustomLog("Set Local Firewall - Finished")
CustomLog("Initialise Disk - Started")
Get-Disk | Where-Object PartitionStyle -Eq "RAW" | Initialize-Disk -PassThru | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -NewFileSystemLabel "DATA"
CustomLog("Initialise Disk - Finished")
CustomLog("Set DNS Settings - Started")
Get-NetAdapter | Set-DnsClient -RegisterThisConnectionsAddress $False
Get-NetAdapter | Set-DnsClient -UseSuffixWhenRegistering $False
Get-NetAdapter | Set-DnsClient -ConnectionSpecificSuffix ${propgroup.defaultServerValues.domain}
Set-DnsClientGlobalSetting -SuffixSearchList @(${propgroup.defaultServerValues.search})
CustomLog("Set DNS Settings - Finished")
CustomLog("Domain Join - Started")
WaitForDomainJoin
CustomLog("Domain Join - Finished")
CustomLog("Tidying Logs - Started")
(Get-Content "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\cloudbase-init.log") -replace "${secret.adSvcAccountPassword}","<DOMAIN-JOIN-PASSWORD>" | Set-Content "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\cloudbase-init.log" -Verbose
CustomLog("Tidying Logs - Finished")
CustomLog("Adding user priviliges - Started")
add-LocalGroupMember -Group "Remote Desktop Users" -Member "${propgroup.defaultServerValues.domain}\${input.ad_user}"
add-LocalGroupMember -Group "Administrators" -Member "${propgroup.defaultServerValues.domain}\${input.ad_user}"
CustomLog("Adding user priviliges - Finished")
CustomLog("Ejecting CD Drives - Started")
$drives = Get-WmiObject Win32_Volume -Filter "DriveType=5"
$drives | ForEach-Object { (New-Object -ComObject Shell.Application).Namespace(17).ParseName($_.Name).InvokeVerb("Eject") } -ErrorAction SilentlyContinue
CustomLog("Ejecting CD Drives - Finished")
CustomLog("Preparing to Reboot - Started")
shutdown /r /t 10
CustomLog("Preparing to Reboot - Finished")
Publish it
So just as before, click Version , give it a version number, a description/changelog , and check Release this version to catalog, finally click Create.
Make it look pretty
Head over to Service Broker, you should now already see your Windows VM. It has a stock image though, but you already know how to change that, and make a custom form if you want!
One thought on “Part 5 – VCF Automation – Windows Template”