switch from HWID to TSforge activation
HWID is hardware-tied and doesn't survive VM migration. TSforge manipulates the physical activation store directly and is portable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4c1b710308
commit
a588dddc1e
6 changed files with 136 additions and 10 deletions
|
|
@ -25,6 +25,10 @@
|
|||
smp ? 4,
|
||||
memSize ? 4096,
|
||||
nicModel ? null,
|
||||
# Flatten COW chain into a standalone qcow2 (removes backing file dependency)
|
||||
compact ? false,
|
||||
# QEMU timeout in seconds (default 30 min, increase for Windows Update)
|
||||
qemuTimeout ? 1800,
|
||||
}:
|
||||
let
|
||||
originalImageName = lib.strings.removeSuffix "-vmix" (lib.strings.removeSuffix ".qcow2" originalImage.name);
|
||||
|
|
@ -107,11 +111,11 @@
|
|||
${cdromArgs} \
|
||||
-nic user,model=${if nicModel != null then nicModel else if isAHCI then "e1000" else "virtio-net-pci"}"
|
||||
|
||||
timeout 1800 qemu-system-x86_64 $VMIX_DISPLAY $QEMU_ARGS || \
|
||||
timeout ${toString qemuTimeout} qemu-system-x86_64 $VMIX_DISPLAY $QEMU_ARGS || \
|
||||
if [[ "$VMIX_DISPLAY" == "-display sdl" ]]; then
|
||||
echo "=== vmix: SDL failed, retrying headless ==="
|
||||
cp ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd vars.fd && chmod +w vars.fd
|
||||
timeout 1800 qemu-system-x86_64 -nographic $QEMU_ARGS
|
||||
timeout ${toString qemuTimeout} qemu-system-x86_64 -nographic $QEMU_ARGS
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -125,6 +129,11 @@
|
|||
[ -n "${diskSize}" ] && qemu-img resize ${resultImg} ${diskSize}
|
||||
${virtWinRegMerge}
|
||||
${auditBootCommands}
|
||||
${lib.optionalString compact ''
|
||||
echo "=== vmix: compacting image ==="
|
||||
qemu-img convert -O qcow2 ${resultImg} compact.qcow2
|
||||
mv compact.qcow2 ${resultImg}
|
||||
''}
|
||||
mv ${resultImg} $out
|
||||
'';
|
||||
builtImage = pkgs.runCommand customImageName ({
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ in rec {
|
|||
bestPerformance = import ./essentials/best-performance.nix args;
|
||||
clearFileAssociations = import ./essentials/clear-file-associations.nix args;
|
||||
virtioDrivers = import ./essentials/virtio-drivers.nix args;
|
||||
windowsUpdate = import ./essentials/windows-update.nix args;
|
||||
};
|
||||
|
||||
# Applications
|
||||
|
|
|
|||
110
lib/images/windows/templates/essentials/windows-update.nix
Normal file
110
lib/images/windows/templates/essentials/windows-update.nix
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# Apply all available Windows Updates via the Windows Update COM API in Audit Mode.
|
||||
# Handles reboots automatically — re-registers via RunOnce and continues updating.
|
||||
# Compacts the image afterward to flatten the COW chain.
|
||||
#
|
||||
# Usage:
|
||||
# essentials.windowsUpdate {}
|
||||
# essentials.windowsUpdate { maxRounds = 5; }
|
||||
{ pkgs, lib, ... }:
|
||||
{ maxRounds ? 3 }:
|
||||
{
|
||||
name = "windows-update";
|
||||
compact = true;
|
||||
memSize = 4096;
|
||||
qemuTimeout = 7200;
|
||||
auditScript = ''
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: Track update round via a counter file
|
||||
set "ROUND_FILE=C:\vmix-update-round.txt"
|
||||
set "MAX_ROUNDS=${toString maxRounds}"
|
||||
|
||||
if exist "%ROUND_FILE%" (
|
||||
set /p ROUND=<"%ROUND_FILE%"
|
||||
) else (
|
||||
set "ROUND=1"
|
||||
)
|
||||
|
||||
echo === vmix: Windows Update round %ROUND% of %MAX_ROUNDS% ===
|
||||
|
||||
:: Ensure Windows Update service is running
|
||||
net start wuauserv 2>nul
|
||||
sc config wuauserv start= auto
|
||||
|
||||
:: Remove any update-blocking policies (LTSC may have these)
|
||||
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f 2>nul
|
||||
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /f 2>nul
|
||||
|
||||
:: Give the service time to initialize on first round
|
||||
if "%ROUND%"=="1" (
|
||||
echo Waiting for Windows Update service to initialize...
|
||||
timeout /t 30 /nobreak >nul
|
||||
)
|
||||
|
||||
:: Run Windows Update via PowerShell COM API
|
||||
powershell -ExecutionPolicy Bypass -Command ^
|
||||
"try {" ^
|
||||
" $session = New-Object -ComObject Microsoft.Update.Session;" ^
|
||||
" $searcher = $session.CreateUpdateSearcher();" ^
|
||||
" Write-Host 'Searching for updates...';" ^
|
||||
" $result = $searcher.Search('IsInstalled=0');" ^
|
||||
" $count = $result.Updates.Count;" ^
|
||||
" Write-Host \"Found $count updates\";" ^
|
||||
" if ($count -eq 0) { exit 0 };" ^
|
||||
" foreach ($u in $result.Updates) { Write-Host \" - $($u.Title)\" };" ^
|
||||
" $updatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl;" ^
|
||||
" foreach ($u in $result.Updates) {" ^
|
||||
" if ($u.EulaAccepted -eq $false) { $u.AcceptEula() };" ^
|
||||
" $updatesToInstall.Add($u) | Out-Null" ^
|
||||
" };" ^
|
||||
" $downloader = $session.CreateUpdateDownloader();" ^
|
||||
" $downloader.Updates = $updatesToInstall;" ^
|
||||
" Write-Host 'Downloading...';" ^
|
||||
" $downloader.Download() | Out-Null;" ^
|
||||
" $installer = $session.CreateUpdateInstaller();" ^
|
||||
" $installer.Updates = $updatesToInstall;" ^
|
||||
" Write-Host 'Installing...';" ^
|
||||
" $installResult = $installer.Install();" ^
|
||||
" Write-Host \"Result: $($installResult.ResultCode)\";" ^
|
||||
" for ($i = 0; $i -lt $updatesToInstall.Count; $i++) {" ^
|
||||
" $hr = $installResult.GetUpdateResult($i).HResult;" ^
|
||||
" Write-Host \" $($updatesToInstall.Item($i).Title): code=$hr\"" ^
|
||||
" };" ^
|
||||
" if ($installResult.RebootRequired) { exit 3010 } else { exit 0 }" ^
|
||||
"} catch {" ^
|
||||
" Write-Host \"ERROR: $_\";" ^
|
||||
" exit 1" ^
|
||||
"}"
|
||||
|
||||
set "WU_EXIT=%ERRORLEVEL%"
|
||||
echo Windows Update exit code: %WU_EXIT%
|
||||
|
||||
:: Cleanup component store
|
||||
echo Cleaning up component store...
|
||||
dism /Online /Cleanup-Image /StartComponentCleanup /ResetBase /Quiet 2>nul
|
||||
|
||||
:: Check if we need to reboot and continue
|
||||
set /a "NEXT_ROUND=%ROUND%+1"
|
||||
|
||||
if "%WU_EXIT%"=="3010" (
|
||||
if %ROUND% LSS %MAX_ROUNDS% (
|
||||
echo Reboot required, scheduling round %NEXT_ROUND%...
|
||||
echo %NEXT_ROUND% > "%ROUND_FILE%"
|
||||
:: Re-register ourselves for after reboot
|
||||
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce" /v vmixUpdate /t REG_SZ /d "C:\vmix-audit-script.cmd" /f
|
||||
:: Immediate reboot (preempts wrapper shutdown)
|
||||
shutdown /r /f /t 0
|
||||
exit /b
|
||||
) else (
|
||||
echo Max update rounds reached.
|
||||
)
|
||||
)
|
||||
|
||||
:: Done — clean up and shutdown
|
||||
:: (shutdown here handles both wrapper-invoked and RunOnce-invoked cases)
|
||||
del /q "%ROUND_FILE%" 2>nul
|
||||
echo === vmix: Windows Update complete ===
|
||||
shutdown /s /f /t 10 /c "vmix: windows-update complete"
|
||||
'';
|
||||
}
|
||||
|
|
@ -81,9 +81,9 @@ in
|
|||
powershell -Command "Get-AppxPackage *MicrosoftEdgeDevToolsClient* | Remove-AppxPackage -ErrorAction SilentlyContinue"
|
||||
|
||||
|
||||
:: Activate Windows using HWID method
|
||||
:: Activate Windows using TSforge (hardware-independent, survives VM migration)
|
||||
if exist C:\MAS_AIO.cmd (
|
||||
echo. | call C:\MAS_AIO.cmd /HWID
|
||||
echo. | call C:\MAS_AIO.cmd /Z-
|
||||
)
|
||||
:: Activate Office using Ohook method (if Office is installed)
|
||||
if exist "C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE" (
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ rec {
|
|||
upstreamISO = upstreamISOs.win10-ltsc-2021;
|
||||
productKey = "M7XTQ-FN8P6-TTKYV-9D4CC-J462D";
|
||||
};
|
||||
basic = customizeImageFold upstream (with templates; [
|
||||
# Apply all available Windows Updates (cumulative, .NET, Defender)
|
||||
updated = customizeImage upstream (templates.essentials.windowsUpdate {});
|
||||
basic = customizeImageFold updated (with templates; [
|
||||
essentials.virtioTools
|
||||
essentials.removeIE
|
||||
essentials.removeWMP
|
||||
|
|
@ -38,8 +40,9 @@ rec {
|
|||
productKey = "M7XTQ-FN8P6-TTKYV-9D4CC-J462D";
|
||||
useAHCI = true;
|
||||
};
|
||||
laptopUpdated = customizeImage laptopUpstream (templates.essentials.windowsUpdate {});
|
||||
|
||||
laptopSlim = customizeImageFold laptopUpstream templates.bundles.laptopSlim;
|
||||
laptopSlim = customizeImageFold laptopUpdated templates.bundles.laptopSlim;
|
||||
|
||||
laptop = customizeImageFold laptopUpstream templates.bundles.laptop;
|
||||
laptop = customizeImageFold laptopUpdated templates.bundles.laptop;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ rec {
|
|||
windowsVersionForVirtioDrivers = "w11";
|
||||
};
|
||||
|
||||
basic = customizeImageFold upstream (with templates; [
|
||||
updated = customizeImage upstream (templates.essentials.windowsUpdate {});
|
||||
basic = customizeImageFold updated (with templates; [
|
||||
essentials.virtioTools
|
||||
essentials.removeIE
|
||||
essentials.removeWMP
|
||||
|
|
@ -45,9 +46,11 @@ rec {
|
|||
windowsVersionForVirtioDrivers = "w11";
|
||||
};
|
||||
|
||||
laptopSlim = customizeImageFold laptopUpstream
|
||||
laptopUpdated = customizeImage laptopUpstream (templates.essentials.windowsUpdate {});
|
||||
|
||||
laptopSlim = customizeImageFold laptopUpdated
|
||||
(templates.bundles.laptopSlim ++ [ templates.reg.disableUCPD ]);
|
||||
|
||||
laptop = customizeImageFold laptopUpstream
|
||||
laptop = customizeImageFold laptopUpdated
|
||||
(templates.bundles.laptop ++ [ templates.reg.disableUCPD ]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue