From 2bbe956644f90a69d2ee9e25f1be20e9136c0835 Mon Sep 17 00:00:00 2001 From: ogrechko Date: Wed, 6 May 2026 10:18:57 +0300 Subject: [PATCH] Add Win2025-Sysprep template files --- templates/win2025-sysprep/README.md | 79 +++++++++++++++ templates/win2025-sysprep/files/unattend.xml | 34 +++++++ .../scripts/Prepare-Win2025Sysprep.ps1 | 95 +++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 templates/win2025-sysprep/README.md create mode 100644 templates/win2025-sysprep/files/unattend.xml create mode 100644 templates/win2025-sysprep/scripts/Prepare-Win2025Sysprep.ps1 diff --git a/templates/win2025-sysprep/README.md b/templates/win2025-sysprep/README.md new file mode 100644 index 0000000..e74059e --- /dev/null +++ b/templates/win2025-sysprep/README.md @@ -0,0 +1,79 @@ +# Win2025-Sysprep + +Files for building a reusable `Win2025-Sysprep` template for vSphere, Terraform, and Ansible Semaphore. + +## Included + +- `scripts/Prepare-Win2025Sysprep.ps1` prepares Windows Server 2025 for templating. +- `files/unattend.xml` is the answer file used by `sysprep`. + +## Manual prep before running the script + +1. Install Windows Server 2025 in a dedicated VM. +2. Install VMware Tools. +3. Sign in as local `Administrator`. +4. Apply Windows updates and any baseline OS settings you want baked into the template. +5. Copy this folder to the VM, for example `C:\Build\Win2025-Sysprep`. + +## What the script does + +- enables WinRM for Ansible; +- opens firewall rules for WinRM and RDP; +- enables RDP; +- enables the built-in `Administrator` account; +- switches network profiles to `Private` when possible; +- cleans temporary files; +- generates `unattend.xml`; +- runs `sysprep /generalize /oobe /shutdown`. + +## Run + +Open PowerShell as Administrator and run: + +```powershell +Set-ExecutionPolicy Bypass -Scope Process -Force +cd C:\Build\Win2025-Sysprep +.\scripts\Prepare-Win2025Sysprep.ps1 +``` + +If you want OpenSSH installed too: + +```powershell +.\scripts\Prepare-Win2025Sysprep.ps1 -InstallOpenSsh +``` + +If you want a different timezone: + +```powershell +.\scripts\Prepare-Win2025Sysprep.ps1 -TimeZone "Russian Standard Time" +``` + +## After the VM shuts down + +1. Confirm the VM is powered off in vSphere. +2. Convert the VM to a template. +3. Name the template `Win2025-Sysprep`. +4. Use that name in Terraform variable `vm_template`. + +## Checklist before converting to template + +- VMware Tools are installed and healthy. +- WinRM is responding. +- The VM shut down because of `sysprep`. +- The VM is not domain joined. +- No machine-specific secrets or unique data remain on the server. + +## Quick WinRM check + +Before running `sysprep`, you can verify locally: + +```powershell +winrm enumerate winrm/config/listener +Test-WSMan +``` + +## Notes + +- The computer name inside the source VM is not important because Terraform `windows_options` will rename cloned VMs. +- Do not join the template to the domain in advance. +- If you plan to use WinRM over HTTPS, it is usually better to issue the certificate after cloning, not inside the golden image. diff --git a/templates/win2025-sysprep/files/unattend.xml b/templates/win2025-sysprep/files/unattend.xml new file mode 100644 index 0000000..dfaf53f --- /dev/null +++ b/templates/win2025-sysprep/files/unattend.xml @@ -0,0 +1,34 @@ + + + + + true + true + + + 1 + + + + + en-US + en-US + en-US + en-US + + + __TIME_ZONE__ + + true + true + true + true + true + Work + 3 + + Infrastructure + Automation + + + diff --git a/templates/win2025-sysprep/scripts/Prepare-Win2025Sysprep.ps1 b/templates/win2025-sysprep/scripts/Prepare-Win2025Sysprep.ps1 new file mode 100644 index 0000000..6d56e53 --- /dev/null +++ b/templates/win2025-sysprep/scripts/Prepare-Win2025Sysprep.ps1 @@ -0,0 +1,95 @@ +[CmdletBinding()] +param( + [string]$TimeZone = "UTC", + [switch]$InstallOpenSsh +) + +$ErrorActionPreference = "Stop" + +$root = Split-Path -Parent $PSScriptRoot +$unattendTemplate = Join-Path $root "files\\unattend.xml" +$generatedUnattend = "C:\\Windows\\Panther\\Unattend.xml" + +function Write-Step { + param([string]$Message) + Write-Host "==> $Message" -ForegroundColor Cyan +} + +function Ensure-AdministratorEnabled { + Write-Step "Enabling built-in Administrator account" + & net user Administrator /active:yes | Out-Null +} + +function Enable-RemoteDesktop { + Write-Step "Enabling Remote Desktop" + Set-ItemProperty -Path "HKLM:\\System\\CurrentControlSet\\Control\\Terminal Server" -Name "fDenyTSConnections" -Value 0 + Enable-NetFirewallRule -DisplayGroup "Remote Desktop" | Out-Null +} + +function Enable-WinRmForAnsible { + Write-Step "Configuring WinRM" + winrm quickconfig -quiet | Out-Null + Set-Item -Path WSMan:\localhost\Service\AllowUnencrypted -Value $true + Set-Item -Path WSMan:\localhost\Service\Auth\Basic -Value $true + Set-Item -Path WSMan:\localhost\Service\Auth\Kerberos -Value $true + Restart-Service WinRM +} + +function Set-NetworkProfilesPrivate { + Write-Step "Switching detected network profiles to Private where possible" + Get-NetConnectionProfile | ForEach-Object { + if ($_.NetworkCategory -ne "Private") { + Set-NetConnectionProfile -InterfaceIndex $_.InterfaceIndex -NetworkCategory Private + } + } +} + +function Ensure-FirewallRules { + Write-Step "Opening firewall for WinRM" + Enable-NetFirewallRule -Name "WINRM-HTTP-In-TCP" -ErrorAction SilentlyContinue | Out-Null +} + +function Install-OpenSshServer { + Write-Step "Installing OpenSSH Server" + Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null + Set-Service -Name sshd -StartupType Automatic + Start-Service sshd + if (Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue) { + Enable-NetFirewallRule -Name "OpenSSH-Server-In-TCP" | Out-Null + } +} + +function Clear-TemporaryFiles { + Write-Step "Cleaning temporary files" + Get-ChildItem -Path "$env:TEMP" -Force -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue + Get-ChildItem -Path "C:\\Windows\\Temp" -Force -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue +} + +function Write-UnattendFile { + Write-Step "Generating unattend file" + $content = Get-Content -Path $unattendTemplate -Raw + $content = $content.Replace("__TIME_ZONE__", $TimeZone) + Set-Content -Path $generatedUnattend -Value $content -Encoding UTF8 +} + +function Run-Sysprep { + Write-Step "Running sysprep" + $sysprepExe = "C:\\Windows\\System32\\Sysprep\\Sysprep.exe" + $arguments = "/oobe /generalize /shutdown /unattend:$generatedUnattend" + Start-Process -FilePath $sysprepExe -ArgumentList $arguments -Wait +} + +Write-Step "Starting Windows Server 2025 template preparation" +Ensure-AdministratorEnabled +Set-NetworkProfilesPrivate +Enable-WinRmForAnsible +Ensure-FirewallRules +Enable-RemoteDesktop + +if ($InstallOpenSsh) { + Install-OpenSshServer +} + +Clear-TemporaryFiles +Write-UnattendFile +Run-Sysprep