Files
bible-app/build/installer.nsh
T
christianandClaude Opus 5.5 e7709e8756 Fix the damaged uninstaller that blocked every update from 1.4.35 on
On the TV the update stopped at "Bible App cannot be closed", and uninstalling
from Windows Settings showed "Installer integrity check has failed". The cause is
the uninstaller, not anything running.

An update runs the PREVIOUS version's uninstaller first. Versions 1.4.35-1.4.37
were built on macOS, where electron-builder can't run NSIS to write the uninstaller
and rebuilds it in JavaScript (UninstallerReader) from the installer's stub — without
NSIS's icon patch. makensis computes the uninstaller's CRC over the stub WITH the
uninstaller icon in it, so whenever the two icons differ the result fails NSIS's
integrity check. With no app icon configured, NSIS uses its two different default
icons, so every Mac-built uninstaller was damaged. Verified with a reimplementation
of NSIS's loadHeaders() check: the shipped installers pass; the captured uninstaller
failed (0x4235c8f5 vs stored 0x90f7de2c) and passes with this fix (0xb37e533c).

- build/installerIcon.ico: NSIS's own default install icon, so electron-builder uses
  ONE icon for installer and uninstaller (nothing looks different; the app's icon is
  unchanged). tools/build-win.cjs refuses to build without it.
- build/installer.nsh: for installs of 1.4.35-1.4.37 only, clear the old
  UninstallString so the damaged uninstaller is not run; the new files are copied over
  the old ones and a fresh, valid uninstaller is written. Every other version keeps
  the normal uninstall-then-install path.
- BUILD-AND-DEPLOY.md: why the icon file must stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:07:41 -04:00

75 lines
5.1 KiB
NSIS

!macro customInstall
; Local AI (llama.cpp) requires the Microsoft VC++ 2015-2022 x64 runtime.
; Fresh Windows / TV PCs often do not have it preinstalled.
IfFileExists "$INSTDIR\resources\app.asar.unpacked\vendor\redist\vc_redist.x64.exe" 0 +2
ExecWait '"$INSTDIR\resources\app.asar.unpacked\vendor\redist\vc_redist.x64.exe" /install /quiet /norestart' $0
!macroend
; ---------------------------------------------------------------------------------------------------
; Replaces electron-builder's "is the app running?" check (customCheckAppRunning). Runs at the start of
; installing, BEFORE the old version is removed and before any file is copied.
;
; The stock check only looks for "Bible App.exe". But the app also starts programs from its own folder
; - the local AI server (llama-server.exe), speech (whisper), the upscaler - and if the app is closed
; hard, those are left running. They hold their files open, so copying the new version fails and the
; installer says "Bible App cannot be closed. Please close it manually and click Retry" even though
; the app is plainly closed, and Retry can never succeed. So:
; 1. ask the app to close (it then stops the AI server itself), and give it a moment;
; 2. force-close anything named Bible App.exe that is still there;
; 3. force-close the app's own helper programs by name (plain taskkill, nothing to go wrong);
; 4. as a backstop, force-close EVERY program running from this install folder, whatever its name.
; Step 4 matches by folder, via WMI (works from this 32-bit installer on 64-bit Windows, where a
; 32-bit process cannot read a 64-bit process's path). The folder travels in an environment variable
; so a quote or space in it can never break the command, and it refuses to act on a bare drive.
; No question is asked first: installing this version replaces the app, which is what was asked for.
; ---------------------------------------------------------------------------------------------------
!macro customCheckAppRunning
DetailPrint `Closing "${PRODUCT_NAME}"...`
nsExec::Exec `"$SYSDIR\cmd.exe" /c taskkill /im "${APP_EXECUTABLE_FILENAME}" /fi "USERNAME eq %USERNAME%"`
Pop $0
Sleep 2500
nsExec::Exec `"$SYSDIR\cmd.exe" /c taskkill /f /t /im "${APP_EXECUTABLE_FILENAME}" /fi "USERNAME eq %USERNAME%"`
Pop $0
nsExec::Exec `"$SYSDIR\cmd.exe" /c taskkill /f /t /im llama-server.exe /im whisper-cli.exe /im realesrgan-ncnn-vulkan.exe /fi "USERNAME eq %USERNAME%"`
Pop $0
System::Call 'Kernel32::SetEnvironmentVariable(t "BIBLEAPP_INSTDIR", t "$INSTDIR")'
StrCpy $1 "$SYSDIR\WindowsPowerShell\v1.0\powershell.exe"
${If} ${FileExists} "$WINDIR\Sysnative\WindowsPowerShell\v1.0\powershell.exe"
StrCpy $1 "$WINDIR\Sysnative\WindowsPowerShell\v1.0\powershell.exe"
${EndIf}
StrCpy $2 `"$1" -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "$$r = $$env:BIBLEAPP_INSTDIR.TrimEnd('\'); if ($$r.Split('\').Count -lt 2) { exit }; $$d = $$r + '\'; Get-CimInstance Win32_Process | Where-Object { $$_.ExecutablePath -and $$_.ExecutablePath.StartsWith($$d, [StringComparison]::OrdinalIgnoreCase) } | ForEach-Object { Stop-Process -Id $$_.ProcessId -Force -ErrorAction SilentlyContinue }"`
DetailPrint `Closing anything else still running from "${PRODUCT_NAME}"'s folder...`
nsExec::Exec $2
Pop $0
Sleep 1000
!insertmacro bibleAppSkipDamagedUninstaller SHELL_CONTEXT
!insertmacro bibleAppSkipDamagedUninstaller HKCU
!macroend
; ---------------------------------------------------------------------------------------------------
; Versions 1.4.35 - 1.4.37 were built on macOS before build/installerIcon.ico existed, and their
; uninstallers are damaged: they fail NSIS's own integrity check ("Installer integrity check has
; failed"). electron-builder rebuilds the uninstaller in JavaScript on macOS and leaves out NSIS's
; icon patch, so whenever the installer and uninstaller icons differ (NSIS's two defaults do), the
; checksum is wrong. An update runs the PREVIOUS version's uninstaller first, so those versions could
; never be updated: the damaged uninstaller fails, and the installer reports "Bible App cannot be
; closed" (it also cannot be uninstalled from Windows Settings).
;
; For exactly those versions, the old uninstaller is not run: its UninstallString is cleared, so
; electron-builder's uninstallOldVersion finds nothing to run, and the new files are copied over the
; old ones. This installer then writes a fresh, valid uninstaller and registry entry. Every other
; version still goes through the normal uninstall-then-install path.
; ---------------------------------------------------------------------------------------------------
!macro bibleAppSkipDamagedUninstaller ROOT
ReadRegStr $3 ${ROOT} "${UNINSTALL_REGISTRY_KEY}" DisplayVersion
${If} $3 == "1.4.35"
${OrIf} $3 == "1.4.36"
${OrIf} $3 == "1.4.37"
DetailPrint `Version $3's uninstaller is damaged; installing over it instead.`
DeleteRegValue ${ROOT} "${UNINSTALL_REGISTRY_KEY}" UninstallString
!ifdef UNINSTALL_REGISTRY_KEY_2
DeleteRegValue ${ROOT} "${UNINSTALL_REGISTRY_KEY_2}" UninstallString
!endif
${EndIf}
!macroend