Add fixes for Linux environment and add Windows environment

This commit is contained in:
2026-04-19 09:00:33 +02:00
parent d12d90ea54
commit e14faf82ea
6 changed files with 712 additions and 60 deletions

View File

@@ -7,14 +7,14 @@ $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
PG-VLAN10 (HQ-SERVER)
PG-VLAN11 (HQ-DMZ)
PG-VLAN12 (HQ-CLIENT)
PG-VLAN20 (INET-HQ)
PG-VLAN21 (INET-BRANCH)
PG-VLAN23 (HOME)
PG-VLAN30 (BR-SERVER)
PG-VLAN51 (BR-CLIENT)
What the script does:
- Creates the ES2025 Linux topology VMs as linked clones
@@ -33,9 +33,11 @@ param()
$ErrorActionPreference = 'Stop'
$TemplateFolderName = 'Templates'
$TargetParentFolderName = 'Euroskills 2025'
$TargetFolderName = 'Linux'
$DatacenterName = 'Ilica'
$TemplateFolderName = 'Templates'
$SkillsFolderName = 'Skills'
$TargetParentFolderName = 'ES2025'
$TargetFolderName = 'Linux'
$ServerTemplateName = 'debian-13-template'
$GuiTemplateName = 'debian-13-gui-template'
$ReferenceSnapshotName = 'start'
@@ -51,10 +53,14 @@ $CloudInitShutdownTimeoutSeconds = 900
function Get-SingleFolder {
param(
[Parameter(Mandatory = $true)]
[string]$Name
[string]$Name,
$Location = $null
)
$folders = @(Get-Folder -Name $Name -ErrorAction SilentlyContinue)
$params = @{ Name = $Name; Type = 'VM'; ErrorAction = 'SilentlyContinue' }
if ($Location) { $params['Location'] = $Location }
$folders = @(Get-Folder @params)
if ($folders.Count -eq 0) {
throw "Folder '$Name' was not found."
@@ -76,7 +82,7 @@ function Get-SingleChildFolder {
$ParentFolder
)
$folders = @(Get-Folder -Location $ParentFolder -Name $Name -ErrorAction SilentlyContinue)
$folders = @(Get-Folder -Location $ParentFolder -Name $Name -Type VM -ErrorAction SilentlyContinue)
if ($folders.Count -eq 0) {
throw "Folder '$Name' was not found under '$($ParentFolder.Name)'."
@@ -280,9 +286,11 @@ function Wait-ForVmToPowerOff {
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
$datacenter = Get-Datacenter -Name $DatacenterName -ErrorAction Stop
$skillsFolder = Get-SingleFolder -Name $SkillsFolderName -Location $datacenter
$templateFolder = Get-SingleChildFolder -Name $TemplateFolderName -ParentFolder $skillsFolder
$targetParentFolder = Get-SingleChildFolder -Name $TargetParentFolderName -ParentFolder $skillsFolder
$targetFolder = Get-SingleChildFolder -Name $TargetFolderName -ParentFolder $targetParentFolder
$serverTemplate = Get-SingleVmFromFolder -Name $ServerTemplateName -Folder $templateFolder
$guiTemplate = Get-SingleVmFromFolder -Name $GuiTemplateName -Folder $templateFolder
@@ -291,18 +299,18 @@ $serverReferenceSnapshot = Get-OrCreateReferenceSnapshot -VM $serverTemplate -Sn
$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') }
@{ Name = 'R-HQ'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('PG-VLAN10', 'PG-VLAN11', 'PG-VLAN12', 'PG-VLAN20') },
@{ Name = 'R-INT'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('PG-VLAN20', 'PG-VLAN21', 'PG-VLAN23') },
@{ Name = 'R-BR'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('PG-VLAN30', 'PG-VLAN51', 'PG-VLAN21') },
@{ Name = 'HQ-DC'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('PG-VLAN10') },
@{ Name = 'HQ-SAM-1'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('PG-VLAN10') },
@{ Name = 'HQ-SAM-2'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('PG-VLAN10') },
@{ Name = 'HQ-DMZ-1'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('PG-VLAN11') },
@{ Name = 'HQ-DMZ-2'; Template = $serverTemplate; Snapshot = $serverReferenceSnapshot; Networks = @('PG-VLAN11') },
@{ Name = 'BR-SRV'; Template = $guiTemplate; Snapshot = $guiReferenceSnapshot; Networks = @('PG-VLAN30') },
@{ Name = 'HQ-CL'; Template = $guiTemplate; Snapshot = $guiReferenceSnapshot; Networks = @('PG-VLAN12') },
@{ Name = 'HOME'; Template = $guiTemplate; Snapshot = $guiReferenceSnapshot; Networks = @('PG-VLAN23') },
@{ Name = 'BR-CL'; Template = $guiTemplate; Snapshot = $guiReferenceSnapshot; Networks = @('PG-VLAN51') }
)
foreach ($definition in $vmDefinitions) {
@@ -332,6 +340,11 @@ foreach ($definition in $vmDefinitions) {
Ensure-ExtraDisks -VM $vm -DiskCount $ExtraDiskCount -DiskSizeGB $ExtraDiskSizeGB
Set-ExactNetworkAdapters -VM $vm -PortGroupNames $definition.Networks
Set-CloudInitUserData -VM $vm -Username $DefaultUsername -Password $DefaultPassword
$templateCdroms = @(Get-CDDrive -VM $definition.Template | Sort-Object Name)
$vmCdroms = @(Get-CDDrive -VM $vm | Sort-Object Name)
for ($i = 0; $i -lt $templateCdroms.Count; $i++) {
$vmCdroms[$i] | Set-CDDrive -IsoPath $templateCdroms[$i].IsoPath -StartConnected $true -Confirm:$false | Out-Null
}
$startSnapshot = Get-Snapshot -VM $vm -Name $StartSnapshotName -ErrorAction SilentlyContinue

View File

@@ -0,0 +1,291 @@
<#
Adjust these values only if your vCenter names are different:
$WinTemplateName Template VM used for all Windows Server VMs.
$LinuxTemplateName Template VM used for DEV-PC (Debian 13 GUI).
This script expects these port groups to already exist:
PG-VLAN10 (CPH-INT)
PG-VLAN11 (CPH-DEV)
PG-VLAN12 (INET)
PG-VLAN20 (AAL-INT)
What the script does:
- Creates the ES2025 Windows topology VMs as linked clones
- Sets each VM to 2 vCPU and 4 GB RAM
- Configures network adapters to match the topology
- Injects cloud-init userdata for DEV-PC only
- Windows VMs are snapshotted without powering on
- DEV-PC boots, runs cloud-init, powers off, then gets snapshotted
Run:
.\Create-VMs.ps1
#>
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
$DatacenterName = 'Ilica'
$SkillsFolderName = 'Skills'
$TargetParentFolderName = 'ES2025'
$TargetFolderName = 'Windows'
$TemplateFolderName = 'Templates'
$WinTemplateName = 'ws2022'
$LinuxTemplateName = 'debian-13-gui-template'
$ReferenceSnapshotName = 'Start'
$StartSnapshotName = 'Start'
$CpuCount = 2
$MemoryGB = 4
$DefaultUsername = 'user'
$DefaultPassword = 'Passw0rd!'
$CloudInitShutdownTimeoutSeconds = 900
function Get-SingleFolder {
param(
[Parameter(Mandatory = $true)]
[string]$Name,
$Location = $null
)
$params = @{ Name = $Name; Type = 'VM'; ErrorAction = 'SilentlyContinue' }
if ($Location) { $params['Location'] = $Location }
$folders = @(Get-Folder @params)
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." }
return $folders[0]
}
function Get-SingleChildFolder {
param(
[Parameter(Mandatory = $true)]
[string]$Name,
[Parameter(Mandatory = $true)]
$ParentFolder
)
$folders = @(Get-Folder -Location $ParentFolder -Name $Name -Type VM -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 '$($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) {
Remove-NetworkAdapter -NetworkAdapter $adapters[-1] -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 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 Set-CloudInitUserData {
param(
[Parameter(Mandatory = $true)]
$VM,
[Parameter(Mandatory = $true)]
[string]$Username,
[Parameter(Mandatory = $true)]
[string]$Password
)
$userData = @"
#cloud-config
hostname: $($VM.Name)
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
"@
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($userData))
Set-AdvancedSettingValue -Entity $VM -Name 'guestinfo.userdata' -Value $encoded
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 {
if ((Get-VM -Id $VM.Id).PowerState -eq 'PoweredOff') { return }
Start-Sleep -Seconds 5
} while ((Get-Date) -lt $deadline)
throw "VM '$($VM.Name)' did not power off within $TimeoutSeconds seconds."
}
# ── Folder and template lookup ────────────────────────────────────────────────
$datacenter = Get-Datacenter -Name $DatacenterName -ErrorAction Stop
$skillsFolder = Get-SingleFolder -Name $SkillsFolderName -Location $datacenter
$templateFolder = Get-SingleChildFolder -Name $TemplateFolderName -ParentFolder $skillsFolder
$targetParentFolder = Get-SingleChildFolder -Name $TargetParentFolderName -ParentFolder $skillsFolder
$targetFolder = Get-SingleChildFolder -Name $TargetFolderName -ParentFolder $targetParentFolder
$winTemplate = Get-SingleVmFromFolder -Name $WinTemplateName -Folder $templateFolder
$linuxTemplate = Get-SingleVmFromFolder -Name $LinuxTemplateName -Folder $templateFolder
$winSnapshot = Get-OrCreateReferenceSnapshot -VM $winTemplate -SnapshotName $ReferenceSnapshotName
$linuxSnapshot = Get-OrCreateReferenceSnapshot -VM $linuxTemplate -SnapshotName $ReferenceSnapshotName
# ── VM definitions ────────────────────────────────────────────────────────────
$vmDefinitions = @(
@{ Name = 'INET'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN12'); IsLinux = $false },
@{ Name = 'RTR-CPH'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN12', 'PG-VLAN10', 'PG-VLAN11'); IsLinux = $false },
@{ Name = 'RTR-AAL'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN12', 'PG-VLAN20'); IsLinux = $false },
@{ Name = 'DC'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN10'); IsLinux = $false },
@{ Name = 'SRV1'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN10'); IsLinux = $false },
@{ Name = 'SRV2'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN10'); IsLinux = $false },
@{ Name = 'RODC'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN20'); IsLinux = $false },
@{ Name = 'DEV-SRV'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN11'); IsLinux = $false },
@{ Name = 'DEV-PC'; Template = $linuxTemplate; Snapshot = $linuxSnapshot; Networks = @('PG-VLAN11'); IsLinux = $true },
@{ Name = 'CLIENT'; Template = $winTemplate; Snapshot = $winSnapshot; Networks = @('PG-VLAN20'); IsLinux = $false }
)
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
Set-ExactNetworkAdapters -VM $vm -PortGroupNames $definition.Networks
$templateCdroms = @(Get-CDDrive -VM $definition.Template | Sort-Object Name)
$vmCdroms = @(Get-CDDrive -VM $vm | Sort-Object Name)
for ($i = 0; $i -lt $templateCdroms.Count; $i++) {
$vmCdroms[$i] | Set-CDDrive -IsoPath $templateCdroms[$i].IsoPath -StartConnected $true -Confirm:$false | Out-Null
}
if ($definition.IsLinux) {
Set-CloudInitUserData -VM $vm -Username $DefaultUsername -Password $DefaultPassword
}
$startSnapshot = Get-Snapshot -VM $vm -Name $StartSnapshotName -ErrorAction SilentlyContinue
if (-not $startSnapshot) {
if ($definition.IsLinux) {
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' -Memory:$false -Quiesce:$false | Out-Null
}
Write-Host "$($definition.Name) ready"
}