diff --git a/es2025/linux/Create-PortGroups.ps1 b/es2025/linux/Create-PortGroups.ps1 new file mode 100644 index 0000000..3f0a265 --- /dev/null +++ b/es2025/linux/Create-PortGroups.ps1 @@ -0,0 +1,61 @@ +<# +Required parameter: +-SwitchName Name of the existing standard vSwitch where the port groups will be created. + +Optional parameter: +-BaseVlanId Starting VLAN ID. Default is 100. + The script creates: + HQ-SERVER-Linux = BaseVlanId + HQ-DMZ-Linux = BaseVlanId + 1 + HQ-CLIENT-Linux = BaseVlanId + 2 + INET-HQ-Linux = BaseVlanId + 3 + INET-BRANCH-Linux = BaseVlanId + 4 + HOME-Linux = BaseVlanId + 5 + BR-SERVER-Linux = BaseVlanId + 6 + BR-CLIENT-Linux = BaseVlanId + 7 + +Example: +.\Create-PortGroups.ps1 -SwitchName vSwitch0 +#> +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$SwitchName, + + [ValidateRange(1, 4087)] + [int]$BaseVlanId = 100 +) + +$ErrorActionPreference = 'Stop' + +$switch = Get-VirtualSwitch -Name $SwitchName -ErrorAction Stop + +$portGroups = @( + @{ Name = 'HQ-SERVER-Linux'; VlanId = $BaseVlanId }, + @{ Name = 'HQ-DMZ-Linux'; VlanId = $BaseVlanId + 1 }, + @{ Name = 'HQ-CLIENT-Linux'; VlanId = $BaseVlanId + 2 }, + @{ Name = 'INET-HQ-Linux'; VlanId = $BaseVlanId + 3 }, + @{ Name = 'INET-BRANCH-Linux'; VlanId = $BaseVlanId + 4 }, + @{ Name = 'HOME-Linux'; VlanId = $BaseVlanId + 5 }, + @{ Name = 'BR-SERVER-Linux'; VlanId = $BaseVlanId + 6 }, + @{ Name = 'BR-CLIENT-Linux'; VlanId = $BaseVlanId + 7 } +) + +foreach ($portGroup in $portGroups) { + $existing = Get-VirtualPortGroup -VirtualSwitch $switch -Name $portGroup.Name -ErrorAction SilentlyContinue + + if ($existing) { + if ($existing.VLanId -ne $portGroup.VlanId) { + Set-VirtualPortGroup -VirtualPortGroup $existing -VLanId $portGroup.VlanId | Out-Null + Write-Host "Updated $($portGroup.Name) VLAN to $($portGroup.VlanId)" + } + else { + Write-Host "$($portGroup.Name) already exists" + } + + continue + } + + New-VirtualPortGroup -VirtualSwitch $switch -Name $portGroup.Name -VLanId $portGroup.VlanId | Out-Null + Write-Host "Created $($portGroup.Name) with VLAN $($portGroup.VlanId)" +} diff --git a/es2025/linux/Create-VMs.ps1 b/es2025/linux/Create-VMs.ps1 new file mode 100644 index 0000000..cbdf28e --- /dev/null +++ b/es2025/linux/Create-VMs.ps1 @@ -0,0 +1,350 @@ +<# +Adjust these values only if your vCenter names are different: +$TemplateFolderName Folder that contains the source template VMs. +$TargetParentFolderName Parent folder that contains the target folder. +$TargetFolderName Folder where the linked clones will be created. +$ServerTemplateName Template VM used for server and router clones. +$GuiTemplateName Template VM used for workstation clones. + +This script expects these port groups to already exist: +HQ-SERVER-Linux +HQ-DMZ-Linux +HQ-CLIENT-Linux +INET-HQ-Linux +INET-BRANCH-Linux +HOME-Linux +BR-SERVER-Linux +BR-CLIENT-Linux + +What the script does: +- Creates the ES2025 Linux topology VMs as linked clones +- Sets each VM to 2 vCPU and 4 GB RAM +- Adds 4 extra 10 GB thin disks +- Configures network adapters to match the topology +- Injects cloud-init userdata for root and user +- Powers on the VM, waits for cloud-init shutdown, then creates a snapshot named start + +Run: +.\Create-VMs.ps1 +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' + +$TemplateFolderName = 'Templates' +$TargetParentFolderName = 'Euroskills 2025' +$TargetFolderName = 'Linux' +$ServerTemplateName = 'debian-13-template' +$GuiTemplateName = 'debian-13-gui-template' +$ReferenceSnapshotName = 'start' +$StartSnapshotName = 'start' +$CpuCount = 2 +$MemoryGB = 4 +$ExtraDiskCount = 4 +$ExtraDiskSizeGB = 10 +$DefaultUsername = 'localadmin' +$DefaultPassword = 'Passw0rd!' +$CloudInitShutdownTimeoutSeconds = 900 + +function Get-SingleFolder { + param( + [Parameter(Mandatory = $true)] + [string]$Name + ) + + $folders = @(Get-Folder -Name $Name -ErrorAction SilentlyContinue) + + if ($folders.Count -eq 0) { + throw "Folder '$Name' was not found." + } + + if ($folders.Count -gt 1) { + throw "More than one folder named '$Name' was found. Make the name unique before running this script." + } + + return $folders[0] +} + +function Get-SingleChildFolder { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + + [Parameter(Mandatory = $true)] + $ParentFolder + ) + + $folders = @(Get-Folder -Location $ParentFolder -Name $Name -ErrorAction SilentlyContinue) + + if ($folders.Count -eq 0) { + throw "Folder '$Name' was not found under '$($ParentFolder.Name)'." + } + + if ($folders.Count -gt 1) { + throw "More than one folder named '$Name' was found under '$($ParentFolder.Name)'." + } + + return $folders[0] +} + +function Get-SingleVmFromFolder { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + + [Parameter(Mandatory = $true)] + $Folder + ) + + $vms = @(Get-VM -Location $Folder -Name $Name -ErrorAction SilentlyContinue) + + if ($vms.Count -eq 0) { + throw "VM '$Name' was not found in folder '$($Folder.Name)'." + } + + if ($vms.Count -gt 1) { + throw "More than one VM named '$Name' was found in folder '$($Folder.Name)'." + } + + return $vms[0] +} + +function Get-OrCreateReferenceSnapshot { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [string]$SnapshotName + ) + + $snapshot = Get-Snapshot -VM $VM -Name $SnapshotName -ErrorAction SilentlyContinue + + if (-not $snapshot) { + Write-Host "Creating snapshot '$SnapshotName' on source VM '$($VM.Name)'" + $snapshot = New-Snapshot -VM $VM -Name $SnapshotName -Description 'Base snapshot for linked clones' -Memory:$false -Quiesce:$false + } + + return $snapshot +} + +function Set-ExactNetworkAdapters { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [string[]]$PortGroupNames + ) + + $adapters = @(Get-NetworkAdapter -VM $VM | Sort-Object Name) + + while ($adapters.Count -gt $PortGroupNames.Count) { + $adapterToRemove = $adapters[-1] + Remove-NetworkAdapter -NetworkAdapter $adapterToRemove -Confirm:$false | Out-Null + $adapters = @(Get-NetworkAdapter -VM $VM | Sort-Object Name) + } + + for ($i = 0; $i -lt $PortGroupNames.Count; $i++) { + if ($i -ge $adapters.Count) { + New-NetworkAdapter -VM $VM -Type Vmxnet3 -NetworkName $PortGroupNames[$i] -StartConnected:$true -Confirm:$false | Out-Null + $adapters = @(Get-NetworkAdapter -VM $VM | Sort-Object Name) + } + + Set-NetworkAdapter -NetworkAdapter $adapters[$i] -NetworkName $PortGroupNames[$i] -StartConnected:$true -Confirm:$false | Out-Null + } +} + +function Ensure-ExtraDisks { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [int]$DiskCount, + + [Parameter(Mandatory = $true)] + [int]$DiskSizeGB + ) + + $hardDisks = @(Get-HardDisk -VM $VM | Sort-Object Name) + $extraDisksNeeded = $DiskCount - ($hardDisks.Count - 1) + + while ($extraDisksNeeded -gt 0) { + New-HardDisk -VM $VM -CapacityGB $DiskSizeGB -StorageFormat Thin | Out-Null + $extraDisksNeeded-- + } +} + +function Set-AdvancedSettingValue { + param( + [Parameter(Mandatory = $true)] + $Entity, + + [Parameter(Mandatory = $true)] + [string]$Name, + + [Parameter(Mandatory = $true)] + [string]$Value + ) + + $setting = Get-AdvancedSetting -Entity $Entity -Name $Name -ErrorAction SilentlyContinue + + if ($setting) { + Set-AdvancedSetting -AdvancedSetting $setting -Value $Value -Confirm:$false | Out-Null + return + } + + New-AdvancedSetting -Entity $Entity -Name $Name -Value $Value -Confirm:$false | Out-Null +} + +function New-CloudInitUserData { + param( + [Parameter(Mandatory = $true)] + [string]$VmName, + + [Parameter(Mandatory = $true)] + [string]$Username, + + [Parameter(Mandatory = $true)] + [string]$Password + ) + + return @" +#cloud-config +hostname: $VmName +users: + - default + - name: $Username + lock_passwd: false + plain_text_passwd: '$Password' + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + groups: sudo +ssh_pwauth: true +disable_root: false +chpasswd: + expire: false + list: | + root:$Password + ${Username}:$Password +power_state: + mode: poweroff + timeout: 30 + condition: true +"@ +} + +function Set-CloudInitUserData { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [string]$Username, + + [Parameter(Mandatory = $true)] + [string]$Password + ) + + $userData = New-CloudInitUserData -VmName $VM.Name -Username $Username -Password $Password + $encodedUserData = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($userData)) + + Set-AdvancedSettingValue -Entity $VM -Name 'guestinfo.userdata' -Value $encodedUserData + Set-AdvancedSettingValue -Entity $VM -Name 'guestinfo.userdata.encoding' -Value 'base64' +} + +function Wait-ForVmToPowerOff { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [int]$TimeoutSeconds + ) + + $deadline = (Get-Date).AddSeconds($TimeoutSeconds) + + do { + $currentVm = Get-VM -Id $VM.Id + + if ($currentVm.PowerState -eq 'PoweredOff') { + return + } + + Start-Sleep -Seconds 5 + } while ((Get-Date) -lt $deadline) + + throw "VM '$($VM.Name)' did not power off within $TimeoutSeconds seconds." +} + +$templateFolder = Get-SingleFolder -Name $TemplateFolderName +$targetParentFolder = Get-SingleFolder -Name $TargetParentFolderName +$targetFolder = Get-SingleChildFolder -Name $TargetFolderName -ParentFolder $targetParentFolder + +$serverTemplate = Get-SingleVmFromFolder -Name $ServerTemplateName -Folder $templateFolder +$guiTemplate = Get-SingleVmFromFolder -Name $GuiTemplateName -Folder $templateFolder + +$serverReferenceSnapshot = Get-OrCreateReferenceSnapshot -VM $serverTemplate -SnapshotName $ReferenceSnapshotName +$guiReferenceSnapshot = Get-OrCreateReferenceSnapshot -VM $guiTemplate -SnapshotName $ReferenceSnapshotName + +$vmDefinitions = @( + @{ Name = 'R-HQ'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('HQ-SERVER-Linux', 'HQ-DMZ-Linux', 'HQ-CLIENT-Linux', 'INET-HQ-Linux') }, + @{ Name = 'R-INT'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('INET-HQ-Linux', 'INET-BRANCH-Linux', 'HOME-Linux') }, + @{ Name = 'R-BR'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('BR-SERVER-Linux', 'BR-CLIENT-Linux', 'INET-BRANCH-Linux') }, + @{ Name = 'HQ-DC'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('HQ-SERVER-Linux') }, + @{ Name = 'HQ-SAM-1'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('HQ-SERVER-Linux') }, + @{ Name = 'HQ-SAM-2'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('HQ-SERVER-Linux') }, + @{ Name = 'HQ-DMZ-1'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('HQ-DMZ-Linux') }, + @{ Name = 'HQ-DMZ-2'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('HQ-DMZ-Linux') }, + @{ Name = 'BR-SRV'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('BR-SERVER-Linux') }, + @{ Name = 'HQ-CL'; Template = $guiTemplate; Snapshot = $guiReferenceSnapshot; Networks = @('HQ-CLIENT-Linux') }, + @{ Name = 'HOME'; Template = $guiTemplate; Snapshot = $guiReferenceSnapshot; Networks = @('HOME-Linux') }, + @{ Name = 'BR-CL'; Template = $guiTemplate; Snapshot = $guiReferenceSnapshot; Networks = @('BR-CLIENT-Linux') } +) + +foreach ($definition in $vmDefinitions) { + $vm = Get-VM -Name $definition.Name -ErrorAction SilentlyContinue + + if (-not $vm) { + Write-Host "Creating $($definition.Name)" + + $resourcePool = Get-ResourcePool -Id $definition.Template.ExtensionData.ResourcePool + $vmHost = Get-VMHost -Id $definition.Template.ExtensionData.Runtime.Host + + $vm = New-VM ` + -Name $definition.Name ` + -VM $definition.Template ` + -ReferenceSnapshot $definition.Snapshot ` + -LinkedClone ` + -Location $targetFolder ` + -ResourcePool $resourcePool ` + -VMHost $vmHost + } + else { + Write-Host "$($definition.Name) already exists, updating configuration" + } + + Set-VM -VM $vm -NumCpu $CpuCount -MemoryGB $MemoryGB -Confirm:$false | Out-Null + + Ensure-ExtraDisks -VM $vm -DiskCount $ExtraDiskCount -DiskSizeGB $ExtraDiskSizeGB + Set-ExactNetworkAdapters -VM $vm -PortGroupNames $definition.Networks + Set-CloudInitUserData -VM $vm -Username $DefaultUsername -Password $DefaultPassword + + $startSnapshot = Get-Snapshot -VM $vm -Name $StartSnapshotName -ErrorAction SilentlyContinue + + if (-not $startSnapshot) { + if ((Get-VM -Id $vm.Id).PowerState -eq 'PoweredOn') { + Stop-VMGuest -VM $vm -Confirm:$false -ErrorAction SilentlyContinue | Out-Null + Wait-ForVmToPowerOff -VM $vm -TimeoutSeconds $CloudInitShutdownTimeoutSeconds + } + + Start-VM -VM $vm | Out-Null + Wait-ForVmToPowerOff -VM $vm -TimeoutSeconds $CloudInitShutdownTimeoutSeconds + New-Snapshot -VM $vm -Name $StartSnapshotName -Description 'Initial state after cloud-init customization' -Memory:$false -Quiesce:$false | Out-Null + } + + Write-Host "$($definition.Name) ready" +} diff --git a/ws2024/linux/Create-PortGroups.ps1 b/ws2024/linux/Create-PortGroups.ps1 new file mode 100644 index 0000000..2b3db86 --- /dev/null +++ b/ws2024/linux/Create-PortGroups.ps1 @@ -0,0 +1,53 @@ +<# +Required parameter: +-SwitchName Name of the existing standard vSwitch where the port groups will be created. + +Optional parameter: +-BaseVlanId Starting VLAN ID. Default is 100. + The script creates: + WAN-Linux = BaseVlanId + INT-Linux = BaseVlanId + 1 + DMZ-Linux = BaseVlanId + 2 + VPN-Linux = BaseVlanId + 3 + +Example: +.\Create-PortGroups.ps1 -SwitchName vSwitch0 +#> +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$SwitchName, + + [ValidateRange(1, 4094)] + [int]$BaseVlanId = 100 +) + +$ErrorActionPreference = 'Stop' + +$switch = Get-VirtualSwitch -Name $SwitchName -ErrorAction Stop + +$portGroups = @( + @{ Name = 'WAN-Linux'; VlanId = $BaseVlanId }, + @{ Name = 'INT-Linux'; VlanId = $BaseVlanId + 1 }, + @{ Name = 'DMZ-Linux'; VlanId = $BaseVlanId + 2 }, + @{ Name = 'VPN-Linux'; VlanId = $BaseVlanId + 3 } +) + +foreach ($portGroup in $portGroups) { + $existing = Get-VirtualPortGroup -VirtualSwitch $switch -Name $portGroup.Name -ErrorAction SilentlyContinue + + if ($existing) { + if ($existing.VLanId -ne $portGroup.VlanId) { + Set-VirtualPortGroup -VirtualPortGroup $existing -VLanId $portGroup.VlanId | Out-Null + Write-Host "Updated $($portGroup.Name) VLAN to $($portGroup.VlanId)" + } + else { + Write-Host "$($portGroup.Name) already exists" + } + + continue + } + + New-VirtualPortGroup -VirtualSwitch $switch -Name $portGroup.Name -VLanId $portGroup.VlanId | Out-Null + Write-Host "Created $($portGroup.Name) with VLAN $($portGroup.VlanId)" +} diff --git a/ws2024/linux/Create-VMs.ps1 b/ws2024/linux/Create-VMs.ps1 new file mode 100644 index 0000000..e15da75 --- /dev/null +++ b/ws2024/linux/Create-VMs.ps1 @@ -0,0 +1,342 @@ +<# +Adjust these values only if your vCenter names are different: +$TemplateFolderName Folder that contains the source template VMs. +$TargetParentFolderName Parent folder that contains the target folder. +$TargetFolderName Folder where the linked clones will be created. +$ServerTemplateName Template VM used for server clones. +$GuiTemplateName Template VM used for the workstation clone. + +This script expects these port groups to already exist: +WAN-Linux +INT-Linux +DMZ-Linux +VPN-Linux + +What the script does: +- Creates the WS2024 Linux topology VMs as linked clones +- Sets each VM to 2 vCPU and 4 GB RAM +- Adds 4 extra 10 GB hard disks +- Configures network adapters to match the topology +- Injects cloud-init userdata for root and user +- Powers on the VM, waits for cloud-init shutdown, then creates a snapshot named Start + +Run: +.\Create-VMs.ps1 +#> + +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' + +$TemplateFolderName = 'Templates' +$TargetParentFolderName = 'Worldskills 2024' +$TargetFolderName = 'Linux' +$ServerTemplateName = 'debian-13-template' +$GuiTemplateName = 'debian-13-gui-template' +$ReferenceSnapshotName = 'Start' +$StartSnapshotName = 'Start' +$CpuCount = 2 +$MemoryGB = 4 +$ExtraDiskCount = 4 +$ExtraDiskSizeGB = 10 +$DefaultUsername = 'user' +$DefaultPassword = 'Skill39@Lyon' +$CloudInitShutdownTimeoutSeconds = 900 + +function Get-SingleFolder { + param( + [Parameter(Mandatory = $true)] + [string]$Name + ) + + $folders = @(Get-Folder -Name $Name -ErrorAction SilentlyContinue) + + if ($folders.Count -eq 0) { + throw "Folder '$Name' was not found." + } + + if ($folders.Count -gt 1) { + throw "More than one folder named '$Name' was found. Make the name unique before running this script." + } + + return $folders[0] +} + +function Get-SingleChildFolder { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + + [Parameter(Mandatory = $true)] + $ParentFolder + ) + + $folders = @(Get-Folder -Location $ParentFolder -Name $Name -ErrorAction SilentlyContinue) + + if ($folders.Count -eq 0) { + throw "Folder '$Name' was not found under '$($ParentFolder.Name)'." + } + + if ($folders.Count -gt 1) { + throw "More than one folder named '$Name' was found under '$($ParentFolder.Name)'." + } + + return $folders[0] +} + +function Get-SingleVmFromFolder { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + + [Parameter(Mandatory = $true)] + $Folder + ) + + $vms = @(Get-VM -Location $Folder -Name $Name -ErrorAction SilentlyContinue) + + if ($vms.Count -eq 0) { + throw "VM '$Name' was not found in folder '$($Folder.Name)'." + } + + if ($vms.Count -gt 1) { + throw "More than one VM named '$Name' was found in folder '$($Folder.Name)'." + } + + return $vms[0] +} + +function Get-OrCreateReferenceSnapshot { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [string]$SnapshotName + ) + + $snapshot = Get-Snapshot -VM $VM -Name $SnapshotName -ErrorAction SilentlyContinue + + if (-not $snapshot) { + Write-Host "Creating snapshot '$SnapshotName' on source VM '$($VM.Name)'" + $snapshot = New-Snapshot -VM $VM -Name $SnapshotName -Description 'Base snapshot for linked clones' -Memory:$false -Quiesce:$false + } + + return $snapshot +} + +function Set-ExactNetworkAdapters { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [string[]]$PortGroupNames + ) + + $adapters = @(Get-NetworkAdapter -VM $VM | Sort-Object Name) + + while ($adapters.Count -gt $PortGroupNames.Count) { + $adapterToRemove = $adapters[-1] + Remove-NetworkAdapter -NetworkAdapter $adapterToRemove -Confirm:$false | Out-Null + $adapters = @(Get-NetworkAdapter -VM $VM | Sort-Object Name) + } + + for ($i = 0; $i -lt $PortGroupNames.Count; $i++) { + if ($i -ge $adapters.Count) { + New-NetworkAdapter -VM $VM -Type Vmxnet3 -NetworkName $PortGroupNames[$i] -StartConnected:$true -Confirm:$false | Out-Null + $adapters = @(Get-NetworkAdapter -VM $VM | Sort-Object Name) + } + + Set-NetworkAdapter -NetworkAdapter $adapters[$i] -NetworkName $PortGroupNames[$i] -StartConnected:$true -Confirm:$false | Out-Null + } +} + +function Ensure-ExtraDisks { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [int]$DiskCount, + + [Parameter(Mandatory = $true)] + [int]$DiskSizeGB + ) + + $hardDisks = @(Get-HardDisk -VM $VM | Sort-Object Name) + $extraDisksNeeded = $DiskCount - ($hardDisks.Count - 1) + + while ($extraDisksNeeded -gt 0) { + New-HardDisk -VM $VM -CapacityGB $DiskSizeGB -StorageFormat Thin | Out-Null + $extraDisksNeeded-- + } +} + +function Set-AdvancedSettingValue { + param( + [Parameter(Mandatory = $true)] + $Entity, + + [Parameter(Mandatory = $true)] + [string]$Name, + + [Parameter(Mandatory = $true)] + [string]$Value + ) + + $setting = Get-AdvancedSetting -Entity $Entity -Name $Name -ErrorAction SilentlyContinue + + if ($setting) { + Set-AdvancedSetting -AdvancedSetting $setting -Value $Value -Confirm:$false | Out-Null + return + } + + New-AdvancedSetting -Entity $Entity -Name $Name -Value $Value -Confirm:$false | Out-Null +} + +function New-CloudInitUserData { + param( + [Parameter(Mandatory = $true)] + [string]$VmName, + + [Parameter(Mandatory = $true)] + [string]$Username, + + [Parameter(Mandatory = $true)] + [string]$Password + ) + + return @" +#cloud-config +hostname: $VmName +users: + - default + - name: $Username + lock_passwd: false + plain_text_passwd: '$Password' + shell: /bin/bash + sudo: ALL=(ALL) NOPASSWD:ALL + groups: sudo +ssh_pwauth: true +disable_root: false +chpasswd: + expire: false + list: | + root:$Password + ${Username}:$Password +power_state: + mode: poweroff + timeout: 30 + condition: true +"@ +} + +function Set-CloudInitUserData { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [string]$Username, + + [Parameter(Mandatory = $true)] + [string]$Password + ) + + $userData = New-CloudInitUserData -VmName $VM.Name -Username $Username -Password $Password + $encodedUserData = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($userData)) + + Set-AdvancedSettingValue -Entity $VM -Name 'guestinfo.userdata' -Value $encodedUserData + Set-AdvancedSettingValue -Entity $VM -Name 'guestinfo.userdata.encoding' -Value 'base64' +} + +function Wait-ForVmToPowerOff { + param( + [Parameter(Mandatory = $true)] + $VM, + + [Parameter(Mandatory = $true)] + [int]$TimeoutSeconds + ) + + $deadline = (Get-Date).AddSeconds($TimeoutSeconds) + + do { + $currentVm = Get-VM -Id $VM.Id + + if ($currentVm.PowerState -eq 'PoweredOff') { + return + } + + Start-Sleep -Seconds 5 + } while ((Get-Date) -lt $deadline) + + throw "VM '$($VM.Name)' did not power off within $TimeoutSeconds seconds." +} + +$templateFolder = Get-SingleFolder -Name $TemplateFolderName +$targetParentFolder = Get-SingleFolder -Name $TargetParentFolderName +$targetFolder = Get-SingleChildFolder -Name $TargetFolderName -ParentFolder $targetParentFolder + +$serverTemplate = Get-SingleVmFromFolder -Name $ServerTemplateName -Folder $templateFolder +$guiTemplate = Get-SingleVmFromFolder -Name $GuiTemplateName -Folder $templateFolder + +$serverReferenceSnapshot = Get-OrCreateReferenceSnapshot -VM $serverTemplate -SnapshotName $ReferenceSnapshotName +$guiReferenceSnapshot = Get-OrCreateReferenceSnapshot -VM $guiTemplate -SnapshotName $ReferenceSnapshotName + +$vmDefinitions = @( + @{ Name = 'fw'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('WAN-Linux', 'INT-Linux', 'DMZ-Linux', 'VPN-Linux') }, + @{ Name = 'int-srv01'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('INT-Linux') }, + @{ Name = 'mail'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('DMZ-Linux') }, + @{ Name = 'ha-prx01'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('DMZ-Linux') }, + @{ Name = 'ha-prx02'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('DMZ-Linux') }, + @{ Name = 'web01'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('DMZ-Linux') }, + @{ Name = 'web02'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('DMZ-Linux') }, + @{ Name = 'jamie-ws01'; Template = $guiTemplate; Snapshot = $guiReferenceSnapshot; Networks = @('WAN-Linux', 'VPN-Linux') } +) + +foreach ($definition in $vmDefinitions) { + $vm = Get-VM -Name $definition.Name -ErrorAction SilentlyContinue + + if (-not $vm) { + Write-Host "Creating $($definition.Name)" + + $resourcePool = Get-ResourcePool -Id $definition.Template.ExtensionData.ResourcePool + $vmHost = Get-VMHost -Id $definition.Template.ExtensionData.Runtime.Host + + $vm = New-VM ` + -Name $definition.Name ` + -VM $definition.Template ` + -ReferenceSnapshot $definition.Snapshot ` + -LinkedClone ` + -Location $targetFolder ` + -ResourcePool $resourcePool ` + -VMHost $vmHost + } + else { + Write-Host "$($definition.Name) already exists, updating configuration" + } + + Set-VM -VM $vm -NumCpu $CpuCount -MemoryGB $MemoryGB -Confirm:$false | Out-Null + + Ensure-ExtraDisks -VM $vm -DiskCount $ExtraDiskCount -DiskSizeGB $ExtraDiskSizeGB + Set-ExactNetworkAdapters -VM $vm -PortGroupNames $definition.Networks + Set-CloudInitUserData -VM $vm -Username $DefaultUsername -Password $DefaultPassword + + $startSnapshot = Get-Snapshot -VM $vm -Name $StartSnapshotName -ErrorAction SilentlyContinue + + if (-not $startSnapshot) { + if ((Get-VM -Id $vm.Id).PowerState -eq 'PoweredOn') { + Stop-VMGuest -VM $vm -Confirm:$false -ErrorAction SilentlyContinue | Out-Null + Wait-ForVmToPowerOff -VM $vm -TimeoutSeconds $CloudInitShutdownTimeoutSeconds + } + + Start-VM -VM $vm | Out-Null + Wait-ForVmToPowerOff -VM $vm -TimeoutSeconds $CloudInitShutdownTimeoutSeconds + New-Snapshot -VM $vm -Name $StartSnapshotName -Description 'Initial state after clone creation' -Memory:$false -Quiesce:$false | Out-Null + } + + Write-Host "$($definition.Name) ready" +}