# Generalize image via sysprep + OOBE in two phases. # Phase 1 (sysprep): runs sysprep /generalize /oobe /shutdown in Audit Mode # Phase 2 (oobe): boots through OOBE, creates user, activates Windows, shuts down # Between phases, NTUSER.DAT can be modified offline. # Usage: (templates.generalize { username = "User"; password = ""; }) { pkgs, lib, makeFilesISO, ... }: let masScript = pkgs.fetchurl { url = "https://raw.githubusercontent.com/massgravel/Microsoft-Activation-Scripts/97602941e5724316aa31b6ca1da5c70245d234d5/MAS/All-In-One-Version-KL/MAS_AIO.cmd"; hash = "sha256-1hl89jQf2p+RtE3ue/+cZevSoz7Ra3p3u350aE/Xy74="; }; in { username ? "User", password ? "", autoLogon ? true, hostname ? "WIN-VM", locale ? "en-US", timezone ? "UTC", # Desktop background solid color as hex string (e.g. "8e8cd8") bgColor ? null, }: let # Convert "8e8cd8" hex to "142 140 216" decimal RGB for Windows registry hexToRgbStr = hex: let hexChars = lib.stringToCharacters hex; hexToDec = h: let c = lib.toLower h; m = { "0"=0; "1"=1; "2"=2; "3"=3; "4"=4; "5"=5; "6"=6; "7"=7; "8"=8; "9"=9; "a"=10; "b"=11; "c"=12; "d"=13; "e"=14; "f"=15; }; in m.${c}; r = hexToDec (builtins.elemAt hexChars 0) * 16 + hexToDec (builtins.elemAt hexChars 1); g = hexToDec (builtins.elemAt hexChars 2) * 16 + hexToDec (builtins.elemAt hexChars 3); b = hexToDec (builtins.elemAt hexChars 4) * 16 + hexToDec (builtins.elemAt hexChars 5); in "${toString r} ${toString g} ${toString b}"; stripHash = s: lib.removePrefix "#" s; bgRgb = if bgColor != null then hexToRgbStr (stripHash bgColor) else null; # Post-OOBE script: runs as the created user via FirstLogonCommands. postOobeScript = pkgs.writeText "post-oobe.cmd" '' @echo off ${lib.optionalString (!autoLogon) '' reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon /f 2>nul reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultUserName /f 2>nul reg delete "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultPassword /f 2>nul ''} ${lib.optionalString (bgColor != null) '' :: Set solid background color reg add "HKCU\Control Panel\Desktop" /v WallPaper /t REG_SZ /d "" /f reg add "HKCU\Control Panel\Colors" /v Background /t REG_SZ /d "${bgRgb}" /f reg add "HKCU\Control Panel\Desktop" /v WallpaperStyle /t REG_SZ /d "0" /f ''} :: Remove Edge AppxPackage for current user (runs in user context during OOBE) :: The app is already removed on one of the templates but a ghost appx entry remains that can only be deleted at the user level powershell -Command "Get-AppxPackage *MicrosoftEdge* | Remove-AppxPackage -ErrorAction SilentlyContinue" powershell -Command "Get-AppxPackage *MicrosoftEdgeDevToolsClient* | Remove-AppxPackage -ErrorAction SilentlyContinue" :: Activate Windows using HWID method if exist C:\MAS_AIO.cmd ( echo. | call C:\MAS_AIO.cmd /HWID ) :: Activate Office using Ohook method (if Office is installed) if exist "C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE" ( if exist C:\MAS_AIO.cmd ( echo. | call C:\MAS_AIO.cmd /Ohook ) ) del /q C:\MAS_AIO.cmd 2>nul :: Clean up del /q C:\oobe-unattend.xml 2>nul del /q C:\vmix-audit-script.cmd 2>nul del /q C:\vmix-audit-wrapper.cmd 2>nul shutdown /s /t 5 /c "vmix generalize complete" del /q C:\post-oobe.cmd 2>nul ''; oobeXml = pkgs.writeText "oobe-unattend.xml" '' true Automatic ${locale} ${locale} ${locale} ${locale} true true true true Work true true 3 ${password} Administrators ${username} ${password} true ${username} ${hostname} ${timezone} 1 C:\post-oobe.cmd false ''; in { name = "generalize"; uploads = [ { source = oobeXml; dest = "/oobe-unattend.xml"; } { source = postOobeScript; dest = "/post-oobe.cmd"; } { source = masScript; dest = "/MAS_AIO.cmd"; } ]; # Sysprep reboots into OOBE within the same QEMU session auditScript = '' @echo off C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /reboot /quiet /unattend:C:\oobe-unattend.xml ''; } # :: Enable RDP (sysprep resets offline registry changes) # reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f # reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v UserAuthentication /t REG_DWORD /d 0 /f # netsh advfirewall firewall add rule name="RDP" dir=in protocol=tcp localport=3389 action=allow # :: Start and enable the RDP service # sc config TermService start= auto # net start TermService