diff --git a/lib/images/windows/templates/generalize.nix b/lib/images/windows/templates/generalize.nix index a8c3a42..b710e0d 100644 --- a/lib/images/windows/templates/generalize.nix +++ b/lib/images/windows/templates/generalize.nix @@ -219,7 +219,59 @@ in # a GPT label, one full-size NTFS partition and the drive letter, while a disk # that already holds data keeps it and only has its letter re-asserted. The # OS disk is added to QEMU first and so is always disk 0. - initDataDiskScript = pkgs.writeText "vmix-init-data-disk.cmd" '' + initDataDiskScript = pkgs.writeText "vmix-init-data-disk.cmd" (if configMedium then '' + @echo off + :: Sealed-image variant. Two extra hazards over the baked path: + :: + :: 1. The per-VM config rides an optical drive, and on the target's first + :: boot the raw data disk has no volume yet -- so Windows letters the CD + :: as ${dataDriveLetter}:, exactly where the profile volume must go. The + :: plain `if exist ${dataDriveLetter}:\` guard would then see the CD and + :: skip, leaving ProfilesDirectory pointed at read-only media. So a first + :: boot is tracked by a marker, not by the letter, and any occupant of + :: ${dataDriveLetter}: is moved aside before the data disk claims it. + :: 2. Left to Shell-Setup's FolderLocations, ProfilesDirectory can be + :: evaluated before this disk exists (unordered within specialize) and + :: silently fall back to C:. Setting it here, in the same step that just + :: created the volume, removes that race. + if exist C:\vmix-data-initialized goto :ensure + + :: First boot: park whatever holds ${dataDriveLetter}: (the config CD) on Y: + :: so the data disk can take the letter. Harmless if the letter is free. + > C:\Windows\Temp\vmix-cd.txt echo select volume ${dataDriveLetter} + >> C:\Windows\Temp\vmix-cd.txt echo assign letter=Y noerr + diskpart /s C:\Windows\Temp\vmix-cd.txt > nul 2>&1 + + :: Lay disk 1 (the host zvol) out from scratch and give it the letter. + > C:\Windows\Temp\vmix-dd-init.txt echo select disk 1 + >> C:\Windows\Temp\vmix-dd-init.txt echo clean + >> C:\Windows\Temp\vmix-dd-init.txt echo convert gpt + >> C:\Windows\Temp\vmix-dd-init.txt echo create partition primary + >> C:\Windows\Temp\vmix-dd-init.txt echo format fs=ntfs quick label="${dataLabel}" + >> C:\Windows\Temp\vmix-dd-init.txt echo assign letter=${dataDriveLetter} + diskpart /s C:\Windows\Temp\vmix-dd-init.txt + echo initialized > C:\vmix-data-initialized + goto :ensure + + :ensure + :: The letter normally persists via MountedDevices; re-assert if it is gone. + if exist ${dataDriveLetter}:\ goto :profiledir + > C:\Windows\Temp\vmix-dd-assign.txt echo select disk 1 + >> C:\Windows\Temp\vmix-dd-assign.txt echo select partition 1 + >> C:\Windows\Temp\vmix-dd-assign.txt echo assign letter=${dataDriveLetter} + diskpart /s C:\Windows\Temp\vmix-dd-assign.txt > nul 2>&1 + + :profiledir + ${lib.optionalString (profilesDirectory != null) '' + :: Point new profiles at the data volume, now that it exists. REG_EXPAND_SZ + :: to match Windows' own ProfilesDirectory type. + if exist ${dataDriveLetter}:\ ( + if not exist "${profilesDirectory}" mkdir "${profilesDirectory}" + reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" /v ProfilesDirectory /t REG_EXPAND_SZ /d "${profilesDirectory}" /f > nul 2>&1 + )''} + del /q C:\Windows\Temp\vmix-cd.txt C:\Windows\Temp\vmix-dd-init.txt C:\Windows\Temp\vmix-dd-assign.txt 2>nul + :done + '' else '' @echo off :: diskpart rather than the Storage cmdlets. New-Partition and :: Format-Volume need services that are not up yet this early in @@ -248,7 +300,7 @@ in :cleanup del /q C:\Windows\Temp\vmix-dd-assign.txt C:\Windows\Temp\vmix-dd-init.txt 2>nul :done - ''; + ''); # PowerShell in its own file, so neither cmd quoting nor Nix's '' need to be # fought. If the account's real profile got backed up to a .bak key (the