<# Automates section 6-7 of docs/templates/windows/Build Windows Server 2025 Templates.md: cloudbase-init install/config (incl. WindowsLicensingPlugin KMS activation), RDP/pagefile/ password-policy/timezone/keyboard-language tweaks, WinSxS + Windows Update cache cleanup, and disabling WinRM. Does NOT run sysprep - run that manually afterwards (command printed at the end of this script, also in the docs). Run this AFTER DISM /Set-Edition (section 5) has already converted the VM to the real Standard/Datacenter edition. Run as Administrator. One-liner (defaults: kms_host=kms.vietnix.vn:1688, timezone=SE Asia Standard Time): irm https://software.vietnix.tech/datastore/proxmox_manager/windows-server-2025-template-setup.ps1 | iex With custom parameters: iex "& { $(irm https://software.vietnix.tech/datastore/proxmox_manager/windows-server-2025-template-setup.ps1) } -KmsHost 'kms.vietnix.vn:1688'" #> [CmdletBinding()] param( [string]$KmsHost = "kms.vietnix.vn:1688", [string]$TimeZone = "SE Asia Standard Time" ) $ErrorActionPreference = "Stop" function Write-Step { param([string]$Message) Write-Host "" Write-Host "=== $Message ===" -ForegroundColor Cyan } $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw "This script must be run as Administrator." } $confDir = "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf" Write-Step "1/10 Remove non-English keyboard layout (avoid VNI digit-key bug for cloned VMs)" try { Set-WinUserLanguageList -LanguageList en-US -Force Set-WinSystemLocale -SystemLocale en-US Write-Host "Keyboard/language forced to en-US only." } catch { Write-Host "WARNING: could not reset language list: $_" -ForegroundColor Yellow } Write-Step "2/10 Disable RDP NLA" $rdp = Get-WmiObject -Class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -Filter "TerminalName='RDP-tcp'" $rdp.SetUserAuthenticationRequired(0) | Out-Null Write-Step "3/10 Configure pagefile" $sys = Get-WmiObject Win32_ComputerSystem -EnableAllPrivileges $sys.AutomaticManagedPagefile = $false $sys.Put() | Out-Null $pagefile = Get-WmiObject Win32_PageFileSetting | Where-Object { $_.Name -eq "C:\pagefile.sys" } if ($pagefile) { $pagefile.InitialSize = 512 $pagefile.MaximumSize = 1024 $pagefile.Put() | Out-Null } Write-Step "4/10 Disable password complexity policy" secedit /export /cfg C:\secpol.cfg | Out-Null (Get-Content C:\secpol.cfg).Replace("PasswordComplexity = 1", "PasswordComplexity = 0") | Out-File C:\secpol.cfg secedit /configure /db C:\windows\security\local.sdb /cfg C:\secpol.cfg /areas SECURITYPOLICY | Out-Null Remove-Item C:\secpol.cfg -Force Write-Step "5/10 Set timezone ($TimeZone)" tzutil /s "$TimeZone" Write-Step "6/10 Install Cloudbase-Init" $svc = Get-Service cloudbase-init -ErrorAction SilentlyContinue if ($svc) { Write-Host "Cloudbase-Init already installed, skipping download." } else { $installer = Join-Path $env:TEMP "CloudbaseInitSetup.msi" Invoke-WebRequest -UseBasicParsing "https://cloudbase.it/downloads/CloudbaseInitSetup_Stable_x64.msi" -OutFile $installer Start-Process msiexec -ArgumentList "/i `"$installer`" /qn /l*v C:\cloudbase-init-install.log /passive" -Wait Remove-Item $installer -Force } Write-Step "7/10 Write Cloudbase-Init config (cloudbase-init.conf, cloudbase-init-unattend.conf, Unattend.xml)" New-Item -ItemType Directory -Path $confDir -Force | Out-Null $pluginsWithLicensing = "cloudbaseinit.plugins.common.networkconfig.NetworkConfigPlugin,cloudbaseinit.plugins.windows.licensing.WindowsLicensingPlugin,cloudbaseinit.plugins.common.mtu.MTUPlugin,cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin,cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin,cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin,cloudbaseinit.plugins.windows.createuser.CreateUserPlugin,cloudbaseinit.plugins.common.userdata.UserDataPlugin" $cloudbaseInitCfg = @" [DEFAULT] username=administrator groups=Administrators inject_user_password=true first_logon_behaviour=no config_drive_raw_hhd=true config_drive_cdrom=true config_drive_vfat=true bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\ locations=cdrom verbose=true debug=true logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\ logfile=cloudbase-init.log default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN logging_serial_port_settings=COM1,115200,N,8 mtu_use_dhcp_config=false ntp_use_dhcp_config=false local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\ metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService plugins=$pluginsWithLicensing check_latest_version=true activate_windows=true set_kms_product_key=true kms_host=$KmsHost "@ $cloudbaseInitUnattendCfg = @" [DEFAULT] username=administrator groups=Administrators inject_user_password=true first_logon_behaviour=no config_drive_raw_hhd=true config_drive_cdrom=true config_drive_vfat=true bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\ locations=cdrom verbose=true debug=true logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\ logfile=cloudbase-init-unattend.log default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN logging_serial_port_settings=COM1,115200,N,8 mtu_use_dhcp_config=false ntp_use_dhcp_config=false local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\ check_latest_version=false metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService plugins=$pluginsWithLicensing allow_reboot=false stop_service_on_exit=false activate_windows=true set_kms_product_key=true kms_host=$KmsHost "@ $unattendXml = @" true true Work 3 true true P@ssw0rd-Temp-ReplacedByCloudbaseInit true</PlainText> </AdministratorPassword> </UserAccounts> </component> </settings> <settings pass="specialize"> <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <RunSynchronous> <RunSynchronousCommand wcm:action="add"> <Path>net user administrator /active:yes</Path> <Order>1</Order> <Description>Enable Administrator Account</Description> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Path>cmd.exe /c ipconfig /release</Path> <Order>2</Order> <Description>Release IP Address</Description> </RunSynchronousCommand> <RunSynchronousCommand wcm:action="add"> <Order>3</Order> <Path>cmd.exe /c ""C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Scripts\cloudbase-init.exe" --config-file "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init-unattend.conf" &amp;&amp; exit 1 || exit 2"</Path> <Description>Run Cloudbase-Init to set the hostname</Description> <WillReboot>OnRequest</WillReboot> </RunSynchronousCommand> </RunSynchronous> </component> </settings> </unattend> "@ Set-Content -Path (Join-Path $confDir "cloudbase-init.conf") -Value $cloudbaseInitCfg -Encoding ASCII Set-Content -Path (Join-Path $confDir "cloudbase-init-unattend.conf") -Value $cloudbaseInitUnattendCfg -Encoding ASCII Set-Content -Path (Join-Path $confDir "Unattend.xml") -Value $unattendXml -Encoding ASCII Write-Host "Config files written to $confDir" # cloudbase-init service is already running (started by the installer) with the default # config loaded in memory - restart it so it picks up the config just written above. Restart-Service cloudbase-init -Force -ErrorAction SilentlyContinue Write-Step "8/10 Cleanup (WinSxS, Windows Update cache)" Dism.exe /online /Cleanup-Image /StartComponentCleanup /ResetBase Stop-Service wuauserv -Force -ErrorAction SilentlyContinue if (Test-Path "C:\Windows\SoftwareDistribution\Download") { Remove-Item "C:\Windows\SoftwareDistribution\Download" -Recurse -Force } New-Item -Path "C:\Windows\SoftwareDistribution\" -Name "Download" -ItemType "directory" -Force | Out-Null Write-Step "9/10 Disable Windows Update services before sysprep" # DcomLaunch is NOT disabled here (core Windows service - disabling it can prevent boot). sc.exe config wuauserv start= disabled | Out-Null sc.exe config bits start= disabled | Out-Null Stop-Service wuauserv -Force -ErrorAction SilentlyContinue Stop-Service bits -Force -ErrorAction SilentlyContinue Write-Step "10/10 Disable WinRM" Stop-Service -Name WinRM -Force -ErrorAction SilentlyContinue Set-Service -Name WinRM -StartupType Disabled -ErrorAction SilentlyContinue $winrm = Get-Service WinRM if ($winrm.Status -eq "Stopped" -and $winrm.StartType -eq "Disabled") { Write-Host "WinRM stopped and disabled." -ForegroundColor Green } else { Write-Host "WARNING: WinRM disable may have failed. Status: $($winrm.Status), Startup: $($winrm.StartType)" -ForegroundColor Yellow } Write-Host "" Write-Host "=== Done. Review the config, then run sysprep manually: ===" -ForegroundColor Cyan Write-Host 'c:\windows\system32\sysprep\sysprep.exe /shutdown /generalize /oobe /unattend:"C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\Unattend.xml"'