Add Win2025-Sysprep template files
This commit is contained in:
@@ -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.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||||
|
<settings pass="generalize">
|
||||||
|
<component name="Microsoft-Windows-PnpSysprep" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" versionScope="nonSxS">
|
||||||
|
<PersistAllDeviceInstalls>true</PersistAllDeviceInstalls>
|
||||||
|
<DoNotCleanUpNonPresentDevices>true</DoNotCleanUpNonPresentDevices>
|
||||||
|
</component>
|
||||||
|
<component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" versionScope="nonSxS">
|
||||||
|
<SkipRearm>1</SkipRearm>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
<settings pass="oobeSystem">
|
||||||
|
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" versionScope="nonSxS">
|
||||||
|
<InputLocale>en-US</InputLocale>
|
||||||
|
<SystemLocale>en-US</SystemLocale>
|
||||||
|
<UILanguage>en-US</UILanguage>
|
||||||
|
<UserLocale>en-US</UserLocale>
|
||||||
|
</component>
|
||||||
|
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" versionScope="nonSxS">
|
||||||
|
<TimeZone>__TIME_ZONE__</TimeZone>
|
||||||
|
<OOBE>
|
||||||
|
<HideEULAPage>true</HideEULAPage>
|
||||||
|
<HideLocalAccountScreen>true</HideLocalAccountScreen>
|
||||||
|
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
|
||||||
|
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
|
||||||
|
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
|
||||||
|
<NetworkLocation>Work</NetworkLocation>
|
||||||
|
<ProtectYourPC>3</ProtectYourPC>
|
||||||
|
</OOBE>
|
||||||
|
<RegisteredOwner>Infrastructure</RegisteredOwner>
|
||||||
|
<RegisteredOrganization>Automation</RegisteredOrganization>
|
||||||
|
</component>
|
||||||
|
</settings>
|
||||||
|
</unattend>
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user