diff --git a/.github/workflows/test-installer-build.yml b/.github/workflows/test-installer-build.yml new file mode 100644 index 0000000000..5487d95348 --- /dev/null +++ b/.github/workflows/test-installer-build.yml @@ -0,0 +1,112 @@ +name: Test Installer Build (No Sign) + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - dev + paths: + - "Installer/**" + pull_request: + paths: + - "Installer/**" + +jobs: + build-installer-no-sign: + runs-on: windows-2025 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Inno Setup + run: winget install --id=JRSoftware.InnoSetup --exact --source winget --scope machine --accept-package-agreements --accept-source-agreements + shell: pwsh + + - name: Setup .NET 9.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "9.0.x" + cache: true + cache-dependency-path: Directory.Packages.props + + - name: Build SoundSwitch Application + run: | + echo "Cleaning build directories" + if exist bin rmdir /q /s bin + if exist obj rmdir /q /s obj + if exist Release rmdir /q /s Release + if exist Final rmdir /q /s Final + mkdir Final + + echo "Building SoundSwitch CLI and Main Application" + dotnet publish -c Release SoundSwitch.CLI\SoundSwitch.CLI.csproj -o Final + dotnet publish -c Release SoundSwitch\SoundSwitch.csproj -o Final + shell: cmd + + - name: Prepare Installer Assets + run: | + echo "Generate dummy Changelog" + echo ^^^Dummy Changelog, markdown-html is required^^^ > Final\Changelog.html + + echo "Generate dummy README" + echo ^^^Dummy README, markdown-html is required^^^ > Final\Readme.html + + echo "Copy soundSwitched image" + xcopy /y img\soundSwitched.png Final >nul 2>nul + + echo "Copy CLI README" + xcopy /y SoundSwitch.CLI\README.md Final >nul 2>nul + + echo "Copy LICENSE" + xcopy /y LICENSE.txt Final >nul 2>nul + xcopy /y Terms.txt Final >nul 2>nul + shell: cmd + + - name: Remove Signing from Installer Script + run: | + echo "Removing signing configurations from setup.iss" + + REM Remove SignTool lines + powershell -Command "(Get-Content 'Installer\setup.iss') -replace '^SignTool=.*$', '' | Set-Content 'Installer\setup.iss'" + + REM Remove SignedUninstaller + powershell -Command "(Get-Content 'Installer\setup.iss') -replace '^SignedUninstaller=.*$', '' | Set-Content 'Installer\setup.iss'" + + REM Remove signonce flags from Files section + powershell -Command "(Get-Content 'Installer\setup.iss') -replace ' signonce', '' | Set-Content 'Installer\setup.iss'" + + echo "Signing configurations removed" + shell: cmd + + - name: Build Installer + run: .\\Installer\\Make-Installer.bat Nightly + shell: cmd + + - name: Run Installer in Silent Mode (Machine Scope) + run: | + echo "Finding installer executable..." + for /f "delims=" %%i in ('dir /b Final\Installer\*.exe') do set INSTALLER_NAME=%%i + if not defined INSTALLER_NAME ( + echo "ERROR: No installer found in Final\Installer\" + exit /b 1 + ) + echo "Found installer: %INSTALLER_NAME%" + + echo "Running installer in full silent mode for machine scope..." + Final\Installer\%INSTALLER_NAME% /VERYSILENT /ALLUSERS /NORESTART /SUPPRESSMSGBOXES /LOG="Final\Installer\install.log" + + echo "Installation completed. Exit code: %ERRORLEVEL%" + if %ERRORLEVEL% neq 0 ( + echo "Installation failed with exit code %ERRORLEVEL%" + if exist "Final\Installer\install.log" ( + echo "Installation log:" + type "Final\Installer\install.log" + ) + exit /b %ERRORLEVEL% + ) + + echo "Installation successful!" + shell: cmd diff --git a/.roo/win-10-ltsc-Win-Update-Plan.md b/.roo/win-10-ltsc-Win-Update-Plan.md new file mode 100644 index 0000000000..1140cd2fba --- /dev/null +++ b/.roo/win-10-ltsc-Win-Update-Plan.md @@ -0,0 +1,105 @@ +# Plan: Integrate Windows Update Check (KB5053606) into SoundSwitch InnoSetup Installer + +**Overall Goal:** +The installer will first check if the operating system is Windows 10 LTSC. If it is, it will then use a PowerShell script to check if Windows Update KB5053606 is installed. If the update is not installed, the same PowerShell script will be invoked to attempt to trigger its installation via the Windows Update service. All operations and their outcomes will be logged to the InnoSetup log file. The main SoundSwitch installation will proceed regardless of the outcome of these update checks or installation attempts (silent failure with logging). + +**Detailed Plan:** + +1. **Helper Script (PowerShell):** + + - **Filename:** `ManageWindowsUpdate.ps1` + - **Location:** To be placed in `Installer/scripts/` relative to `setup.iss`. + - **Functionality:** + - Accepts parameters: + - `-KBArticleID ` (e.g., "KB5053606") + - `-Action ` (e.g., "CheckInstalled" or "TriggerInstall") + - **If `Action` is "CheckInstalled":** + - Uses `Get-HotFix -Id $KBArticleID` or queries WMI (`Get-WmiObject -Class Win32_QuickFixEngineering -Filter "HotFixID='$KBArticleID'"`) to check if the specified KB is installed. + - Exits with code `0` if found. + - Exits with code `1` if not found. + - Exits with other codes for errors (e.g., `2` if `Get-HotFix` fails or WMI query fails). + - Logs its findings (e.g., using `Write-Output` for InnoSetup to capture, or to a dedicated log if more detail is needed from the PS script). + - **If `Action` is "TriggerInstall":** + - Uses the Windows Update Agent API (e.g., `Microsoft.Update.Session`, `Microsoft.Update.Searcher`, `IUpdateDownloader`, `IUpdateInstaller2`) to search for the specific KB, download it if necessary, and initiate the installation. + - Logs its progress and outcome. + - This action should be designed to run without blocking the InnoSetup installer for too long (e.g., by initiating the update and exiting). + +2. **Inno Setup Script (`Installer/setup.iss`) Modifications:** + + - **`[Files]` Section:** + + - Add an entry to include `ManageWindowsUpdate.ps1`. It will be extracted to `{tmp}` during setup and deleted after installation. + + ```iss + [Files] + ; ... existing files ... + Source: "scripts\ManageWindowsUpdate.ps1"; DestDir: "{tmp}"; Flags: confirmoverwrite deleteafterinstall + ``` + + - **`[Code]` Section:** + - **Constants:** + ```pascal + const + KBID_TO_CHECK = 'KB5053606'; + LTSC_PRODUCT_NAME_1 = 'Windows 10 Enterprise LTSC'; + LTSC_PRODUCT_NAME_2 = 'Windows 10 Enterprise LTSB'; // Older naming + ``` + - **Logging Function:** + ```pascal + procedure LogMsg(Msg: String); + begin + Log(FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + ': ' + Msg); + end; + ``` + - **`IsWin10LTSC(): Boolean;` Function:** + - Reads `ProductName` from `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion`. + - Checks if it contains `LTSC_PRODUCT_NAME_1` or `LTSC_PRODUCT_NAME_2`. + - Logs the detected OS and the result of the LTSC check. + - Returns `True` if LTSC, `False` otherwise. Logs an error and returns `False` if the registry key cannot be read. + - **`IsKBInstalledPS(KBID: String): Boolean;` Function:** + - Locates `powershell.exe`. + - Constructs the command: `powershell.exe -NoProfile -ExecutionPolicy Bypass -File "{tmp}\ManageWindowsUpdate.ps1" -KBArticleID "" -Action CheckInstalled`. + - Executes this command hidden using `Exec` and waits for it to terminate. + - Checks the `ExitCode`. `0` means installed (`True`). `1` means not installed (`False`). Any other code means an error occurred (treat as `False`, log the actual exit code). + - Logs the command executed and the outcome. + - **`AttemptKBInstallPS(KBID: String);` Procedure:** + - Locates `powershell.exe`. + - Constructs the command: `powershell.exe -NoProfile -ExecutionPolicy Bypass -File "{tmp}\ManageWindowsUpdate.ps1" -KBArticleID "" -Action TriggerInstall`. + - Executes this command hidden using `Exec` with the `ewNoWait` flag. + - Logs the attempt to trigger the installation. + - **`InitializeSetup(): Boolean;` Event Function:** + - This function is called when setup is initializing. + - Calls `LogMsg('Starting Windows Update prerequisite check.')`. + - Calls `IsWin10LTSC()`. + - If LTSC is detected: + - Calls `IsKBInstalledPS(KBID_TO_CHECK)`. + - If the KB is not installed, calls `AttemptKBInstallPS(KBID_TO_CHECK)`. + - Else (not LTSC): + - Calls `LogMsg('System is not Windows 10 LTSC. Skipping update check for ' + KBID_TO_CHECK + '.')`. + - All results and error conditions encountered by these functions will be logged. + - _Always_ returns `True` to allow the SoundSwitch installation to proceed. + - Calls `LogMsg('Windows Update prerequisite check finished. Proceeding with main installation.')`. + - Ensure `SetupLogging=yes` is present in the `[Setup]` section. + +**Mermaid Diagram of the Logic:** + +```mermaid +graph TD + A[Installer Starts] --> LogInit[Log: Begin Update Check]; + LogInit --> B{Call IsWin10LTSC()}; + B -- True --> LogLTSC[Log: LTSC Detected]; + LogLTSC --> C{Call IsKBInstalledPS('KB5053606') via ManageWindowsUpdate.ps1 -Action CheckInstalled}; + B -- False --> LogNotLTSC[Log: Not LTSC / Skip Update Check]; + LogNotLTSC --> Z[Proceed with SoundSwitch Installation]; + C -- Exit Code 0 (Installed) --> LogKBFound[Log: KB5053606 Installed]; + LogKBFound --> Z; + C -- Exit Code 1 (Not Installed) --> LogKBNotFound[Log: KB5053606 Not Installed]; + LogKBNotFound --> D[Call AttemptKBInstallPS('KB5053606') via ManageWindowsUpdate.ps1 -Action TriggerInstall]; + D --> LogAttempt[Log: Attempted to Trigger KB Install]; + LogAttempt --> Z; + C -- Other Exit Codes (Error) --> LogKBCheckError[Log: Error checking KB5053606 (Exit Code: )]; + LogKBCheckError --> Z; + B -- Error During LTSC Check --> LogLTSCError[Log: Error Checking LTSC]; + LogLTSCError --> Z; + Z --> LogFinal[Log: Update Check Finished. Proceeding with Main Install.] +``` diff --git a/Installer/Make-Installer.bat b/Installer/Make-Installer.bat index 51fd1dde1a..794cfdb1ee 100644 --- a/Installer/Make-Installer.bat +++ b/Installer/Make-Installer.bat @@ -22,14 +22,20 @@ for /f "eol=; tokens=1,2*" %%a in ('REG QUERY "%innoSetupRegistryNode%" /v Insta ) set innoSetupExe="%innoSetupExe%ISCC.exe" if not exist %innoSetupExe% (set errorMessage=Inno Setup not found in %innoSetupExe% & goto ERROR_QUIT) -if not exist "..\Final\Installer" mkdir ..\Final\Installer +if not exist "..\Final\Installer" ( + echo Creating installer output directory: ..\Final\Installer + mkdir ..\Final\Installer +) +echo Cleaning previous installer files: ..\Final\Installer\*Installer.exe del ..\Final\Installer\*Installer.exe echo Building installer... +echo Running Inno Setup: %innoSetupExe% setup.iss /DReleaseState=%1 %innoSetupExe% setup.iss /DReleaseState=%1 if not %ERRORLEVEL%==0 (set errorMessage=Installer script setup.iss failed & goto ERROR_QUIT) +echo Moving installer to final location: ..\Final\*Installer.exe -^> ..\Final\Installer\ move ..\Final\*Installer.exe ..\Final\Installer\ echo Installer created successfully. diff --git a/Installer/scripts/ManageWindowsUpdate.ps1 b/Installer/scripts/ManageWindowsUpdate.ps1 new file mode 100644 index 0000000000..a1b8b6a8a8 --- /dev/null +++ b/Installer/scripts/ManageWindowsUpdate.ps1 @@ -0,0 +1,192 @@ +#Requires -Version 5.0 +[CmdletBinding()] +param ( + [Parameter(Mandatory = $true)] + [string]$KBArticleID, + + [Parameter(Mandatory = $true)] + [ValidateSet("CheckInstalled", "TriggerInstall")] + [string]$Action +) + +function Write-LogOutput { + param ([string]$Message) + Write-Output "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') PS: $Message" +} + +function Test-KBInstalled { + param ([string]$KBID) + Write-LogOutput "Checking if $KBID is installed." + try { + $hotfix = Get-HotFix -Id $KBID -ErrorAction SilentlyContinue + if ($hotfix) { + Write-LogOutput "$KBID is installed." + Exit 0 # Installed + } + else { + Write-LogOutput "$KBID is not installed." + Exit 1 # Not Installed + } + } + catch { + Write-LogOutput "Error checking for $KBID using Get-HotFix: $($_.Exception.Message)" + # Fallback to WMI check if Get-HotFix fails (e.g. on older systems or if service is off) + try { + Write-LogOutput "Attempting WMI check for $KBID." + $wmiHotfix = Get-WmiObject -Class Win32_QuickFixEngineering -Filter "HotFixID = '$KBID'" -ErrorAction SilentlyContinue + if ($wmiHotfix) { + Write-LogOutput "$KBID found via WMI." + Exit 0 # Installed + } + else { + Write-LogOutput "$KBID not found via WMI." + Exit 1 # Not Installed + } + } + catch { + Write-LogOutput "Error checking for $KBID using WMI: $($_.Exception.Message)" + Exit 2 # Error during check + } + } +} + +function Invoke-KBInstall { + param ([string]$KBID) + Write-LogOutput "Attempting to trigger installation for $KBID via Windows Update service." + + try { + Write-LogOutput "Creating Update Session..." + $updateSession = New-Object -ComObject Microsoft.Update.Session + $updateSearcher = $updateSession.CreateUpdateSearcher() + + Write-LogOutput "Searching for update $KBID..." + # Search criteria for a specific KB article. + # May need refinement if the KBID isn't directly searchable this way, + # or if it's part of a cumulative update. + # Using a general search for the KBID in the title or description. + # Use a more targeted search to reduce the number of updates returned. + # Try to match the KBID in the title, which is usually present for individual updates. + $searchCriteria = "IsInstalled=0 and Type='Software' and Title like '%$KBID%'" + # Note: The Windows Update API does not support direct search by KBArticleID in the query string. + # If $KBID is in the form "KB123456", this will match updates with that KB in the title. + # It's often better to search for "KBxxxxxxx" within the title or description if a direct ID search fails. + # $searchCriteria = "IsInstalled=0 and Type='Software' and Title like '%$KBID%'" + # For more specific targeting, one might need to know if it's a security update, critical update etc. + + $searchResult = $updateSearcher.Search($searchCriteria) + + if ($searchResult.Updates.Count -eq 0) { + Write-LogOutput "No applicable updates found for $KBID with current criteria. It might be already installed, superseded, or not applicable." + # Check if it's already installed as a fallback, as search criteria might miss it. + $hotfixCheck = Get-HotFix -Id $KBID -ErrorAction SilentlyContinue + if ($hotfixCheck) { + Write-LogOutput "$KBID appears to be already installed (found by Get-HotFix during trigger)." + Exit 0 # Already installed + } + Exit 3 # Update not found or not applicable + } + + $updatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl + $updateFound = $false + + foreach ($update in $searchResult.Updates) { + # Iterate through KBNames if available, or check title/description + $kbMatch = $false + if ($update.KBArticleIDs) { + foreach ($kb in $update.KBArticleIDs) { + if ($kb -eq $KBID.Replace("KB", "")) { $kbMatch = $true; break } + } + } + if (-not $kbMatch -and ($update.Title -like "*$KBID*" -or ($update.Description -and $update.Description -like "*$KBID*"))) { + $kbMatch = $true + } + + if ($kbMatch) { + Write-LogOutput "Found update: $($update.Title)" + if ($update.IsDownloaded) { + Write-LogOutput "Update '$($update.Title)' is already downloaded." + } + else { + Write-LogOutput "Adding update '$($update.Title)' to download list." + } + $updatesToDownload.Add($update) | Out-Null + $updateFound = $true + # Typically, we'd install one specific update if found. + # If multiple updates match a loose criteria, this logic might need adjustment. + # For a specific KB, we usually expect one primary match. + break + } + } + + if (-not $updateFound) { + Write-LogOutput "Specific update $KBID not found in search results, though other updates were listed." + Exit 3 # Update not found + } + + $needsDownload = $false + foreach ($updateToInstall in $updatesToDownload) { + if (-not $updateToInstall.IsDownloaded) { + $needsDownload = $true + break + } + } + + if ($needsDownload) { + Write-LogOutput "Downloading updates..." + $downloader = $updateSession.CreateUpdateDownloader() + $downloader.Updates = $updatesToDownload + $downloader.Download() | Out-Null + Write-LogOutput "Download Result: $($downloader.GetProgress().ResultCode)" # Log download result + } + else { + Write-LogOutput "All required updates are already downloaded." + } + + Write-LogOutput "Installing updates..." + $installer = $updateSession.CreateUpdateInstaller() + $installer.Updates = $updatesToDownload + + # For non-interactive install, this should be fine. + # If user interaction is somehow triggered, this might hang in a hidden window. + # The ewNoWait in InnoSetup should help if this call blocks for too long. + $installationResult = $installer.Install() + + Write-LogOutput "Installation Result Code: $($installationResult.ResultCode)" + Write-LogOutput "Reboot Required: $($installationResult.RebootRequired)" + + if ($installationResult.ResultCode -eq 2) { + # or suSucceeded + Write-LogOutput "Installation of $KBID appears successful." + Exit 0 # Success + } + else { + Write-LogOutput "Installation of $KBID may have failed or requires attention. ResultCode: $($installationResult.ResultCode)" + Exit 4 # Installation failed or other issue + } + + } + catch { + $exceptionMessage = $_.Exception.Message + $scriptStackTraceFull = $_.ScriptStackTrace + Write-LogOutput ("Error during Windows Update operations for {0}: {1}" -f $KBID, $exceptionMessage) + Write-LogOutput ("ScriptStackTrace: {0}" -f $scriptStackTraceFull) + Exit 5 # Error during update process + } +} + + +# Main script logic +Write-LogOutput "ManageWindowsUpdate.ps1 called with Action: $Action, KBArticleID: $KBArticleID" + +switch ($Action) { + "CheckInstalled" { + Test-KBInstalled -KBID $KBArticleID + } + "TriggerInstall" { + Invoke-KBInstall -KBID $KBArticleID + } + default { + Write-LogOutput "Invalid action: $Action" + Exit 99 # Invalid action + } +} \ No newline at end of file diff --git a/Installer/scripts/windows_update_helper.iss b/Installer/scripts/windows_update_helper.iss new file mode 100644 index 0000000000..07754c5145 --- /dev/null +++ b/Installer/scripts/windows_update_helper.iss @@ -0,0 +1,188 @@ +#ifndef WINDOWS_UPDATE_HELPER_ISS +#define WINDOWS_UPDATE_HELPER_ISS + +[Code] +const + KBID_TO_CHECK = 'KB5053606'; + LTSC_PRODUCT_NAME_1 = 'Windows 10 Enterprise LTSC'; + LTSC_PRODUCT_NAME_2 = 'Windows 10 Enterprise LTSB'; // Older naming + +var + PowerShellExePath_WU: String; // Added _WU suffix to avoid potential global name clashes + +procedure LogMsg_WU(Msg: String); // Added _WU suffix +begin + Log(GetDateTimeString('yyyy-mm-dd hh:nn:ss', '-', ':') + ' [WUHelper]: ' + Msg); +end; + +function IsWin10LTSC_WU(): Boolean; // Added _WU suffix +var + ProductName: String; +begin + Result := False; // Default to False + if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion', 'ProductName', ProductName) then + begin + LogMsg_WU('Detected OS ProductName: ' + ProductName); + if (Pos(LTSC_PRODUCT_NAME_1, ProductName) > 0) or (Pos(LTSC_PRODUCT_NAME_2, ProductName) > 0) then + begin + LogMsg_WU('System is Windows 10 LTSC.'); + Result := True; + end + else + begin + LogMsg_WU('System is not Windows 10 LTSC.'); + end; + end + else + begin + LogMsg_WU('Error: Could not read ProductName from registry.'); + end; +end; + +function FindPowerShell_WU(): String; // Added _WU suffix +var + SystemDir: String; + TestPath: String; +begin + Result := ''; + SystemDir := ExpandConstant('{sys}'); + TestPath := SystemDir + '\WindowsPowerShell\v1.0\powershell.exe'; + if FileExists(TestPath) then + begin + Result := TestPath; + LogMsg_WU('Found PowerShell at: ' + Result); + Exit; + end; + + // Fallback to PATH + LogMsg_WU('PowerShell not found in explicit system path, will rely on PATH for powershell.exe'); + Result := 'powershell.exe'; +end; + +function IsKBInstalledPS_WU(KBID: String): Boolean; // Added _WU suffix +var + ResultCode: Integer; + Cmd: String; +begin + Result := False; + if PowerShellExePath_WU = '' then + begin + LogMsg_WU('PowerShell executable path not determined. Cannot check KB.'); + Exit; + end; + + Cmd := AddQuotes(PowerShellExePath_WU) + ' -NoProfile -ExecutionPolicy Bypass -File "' + ExpandConstant('{tmp}') + '\ManageWindowsUpdate.ps1" -KBArticleID "' + KBID + '" -Action CheckInstalled'; + LogMsg_WU('Executing: ' + Cmd); + + if Exec(Cmd, '', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + begin + LogMsg_WU('PowerShell script (CheckInstalled) executed. Exit Code: ' + IntToStr(ResultCode)); + if ResultCode = 0 then // 0 means installed + begin + Result := True; + end + else if ResultCode = 1 then // 1 means not installed + begin + Result := False; + end + else + begin + LogMsg_WU('PowerShell script (CheckInstalled) for ' + KBID + ' returned an error or unexpected code: ' + IntToStr(ResultCode)); + Result := False; + end; + end + else + begin + LogMsg_WU('Failed to execute PowerShell script (CheckInstalled) for ' + KBID + '. Last Error Code (from Exec): ' + IntToStr(ResultCode)); + Result := False; + end; +end; + +procedure AttemptKBInstallPS_WU(KBID: String); // Added _WU suffix +var + ResultCode: Integer; + Cmd: String; +begin + if PowerShellExePath_WU = '' then + begin + LogMsg_WU('PowerShell executable path not determined. Cannot attempt KB install.'); + Exit; + end; + + Cmd := AddQuotes(PowerShellExePath_WU) + ' -NoProfile -ExecutionPolicy Bypass -File "' + ExpandConstant('{tmp}') + '\ManageWindowsUpdate.ps1" -KBArticleID "' + KBID + '" -Action TriggerInstall'; + LogMsg_WU('Attempting to trigger KB install (no wait): ' + Cmd); + + if Exec(Cmd, '', '', SW_HIDE, ewNoWait, ResultCode) then + begin + LogMsg_WU('PowerShell script (TriggerInstall) for ' + KBID + ' launched.'); + end + else + begin + LogMsg_WU('Failed to launch PowerShell script (TriggerInstall) for ' + KBID + '. Last Error Code (from Exec): ' + IntToStr(ResultCode)); + end; +end; + +// This InitializeSetup function will be called by Inno Setup. +// Ensure no other InitializeSetup exists or rename this one and call it from the main InitializeSetup. +// For this approach, we assume this IS the main InitializeSetup or it's called by it. +// If setup.iss already has an InitializeSetup, this needs to be integrated. +// For now, let's assume this will be the primary logic or called by the primary. + +function InitializeSetup_WindowsUpdatePrerequisites(): Boolean; +begin + PowerShellExePath_WU := FindPowerShell_WU(); + if PowerShellExePath_WU = 'powershell.exe' then // FindPowerShell_WU is relying on PATH + begin + LogMsg_WU('PowerShell.exe path determined as "powershell.exe" by FindPowerShell_WU, attempting to use system PATH.'); + // Explicitly test if 'powershell.exe' is resolvable by FileExists (which should check PATH) + if not FileExists(PowerShellExePath_WU) then + begin + LogMsg_WU('Critical: PowerShell.exe (when relying on PATH) was not found by FileExists. Update checks will be skipped.'); + PowerShellExePath_WU := ''; // Mark as unusable + end + else + begin + LogMsg_WU('PowerShell.exe (via PATH) confirmed by FileExists.'); + end; + end + else if PowerShellExePath_WU <> '' then // This means FindPowerShell_WU returned an explicit, existing path + begin + // This is an explicit path. FindPowerShell_WU already confirmed FileExists. + // Re-check in case the file got deleted since FindPowerShell_WU was called. + if not FileExists(PowerShellExePath_WU) then + begin + LogMsg_WU('Critical: PowerShell.exe was found at explicit path "' + PowerShellExePath_WU + '" but is now missing. Update checks will be skipped.'); + PowerShellExePath_WU := ''; // Mark as unusable + end; + // If it's still present, FindPowerShell_WU already logged its discovery. + end; + + LogMsg_WU('Starting Windows Update prerequisite check.'); + if IsWin10LTSC_WU() then + begin + if PowerShellExePath_WU <> '' then // Only proceed if PowerShell is found + begin + if not IsKBInstalledPS_WU(KBID_TO_CHECK) then + begin + AttemptKBInstallPS_WU(KBID_TO_CHECK); + end + else + begin + LogMsg_WU(KBID_TO_CHECK + ' is already installed, or an error occurred during check (details above).'); + end; + end + else + begin + LogMsg_WU('Skipping KB check/install for ' + KBID_TO_CHECK + ' because PowerShell was not found.'); + end; + end + else + begin + LogMsg_WU('System is not Windows 10 LTSC. Skipping update check for ' + KBID_TO_CHECK + '.'); + end; + + LogMsg_WU('Windows Update prerequisite check finished.'); + Result := True; // Always allow main installation to proceed +end; + +#endif // WINDOWS_UPDATE_HELPER_ISS \ No newline at end of file diff --git a/Installer/setup.iss b/Installer/setup.iss index 678cd33d4d..4ca546743e 100644 --- a/Installer/setup.iss +++ b/Installer/setup.iss @@ -80,6 +80,7 @@ Name: deletefiles; Description: "{cm:ExistingSettings}"; GroupDescription: "{cm: Source: "{#ExeDir}SoundSwitch.exe"; DestDir: "{app}"; Flags: signonce ignoreversion Source: "{#ExeDir}SoundSwitch.CLI.exe"; DestDir: "{app}"; Flags: signonce ignoreversion Source: "{#ExeDir}*"; DestDir: "{app}"; Flags: recursesubdirs ignoreversion; +Source: "scripts\ManageWindowsUpdate.ps1"; DestDir: "{tmp}"; Flags: confirmoverwrite deleteafterinstall [Registry] Root: HKCU; Subkey: "SOFTWARE\Microsoft\Windows\CurrentVersion\Run\{#MyAppSetupName}"; Flags: uninsdeletekey @@ -120,5 +121,27 @@ Type: files; Name: {app}\Microsoft.WindowsAPICodePack.* #include "scripts\command_line_utils.iss" #include "scripts\setup_utils.iss" #include "scripts\uninstall_utils.iss" +#include "scripts\windows_update_helper.iss" + +[Code] +function InitializeSetup(): Boolean; +begin + // Call the prerequisite checker from the helper script + // InitializeSetup_WindowsUpdatePrerequisites is defined in windows_update_helper.iss + if InitializeSetup_WindowsUpdatePrerequisites() then + begin + // Prerequisite check passed (or silently continued as per its design) + // Add any other main InitializeSetup logic here if needed in the future. + // For now, just return True as the helper handles its own logic and always returns True. + Result := True; + end + else + begin + // This path should ideally not be taken if the helper function is designed to always return True. + // However, as a robust measure: + Log('Main InitializeSetup: Call to InitializeSetup_WindowsUpdatePrerequisites returned False. This is unexpected given the silent failure design.'); + Result := True; // Still proceed with installation as per overall "silent failure" requirement. + end; +end; diff --git a/Make.bat b/Make.bat index ccd06a1942..8b85ff0a3b 100644 --- a/Make.bat +++ b/Make.bat @@ -87,7 +87,7 @@ xcopy /y Terms.txt %finalDir% >nul 2>nul echo Build Installer rem Run installer compiler script -call ./Installer/Make-Installer.bat %buildPlatform% +call ./Installer/Make-Installer.bat %buildPlatform% "%~2" if not %ERRORLEVEL% == 0 (set errorMessage=Make-installer.bat failed or not found & goto ERROR_QUIT) echo.