Tom Butcher 2216a28ce3
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
Refactor Windows installer update process to improve file handling and cleanup
- Updated the `swapUpdateStaging` macro to streamline the update process by swapping the old and new versions of Farm Control more efficiently.
- Introduced a cleanup step to remove leftover `.old` directories from previous updates, enhancing the installer’s reliability.
- Improved logging for better visibility during the update and removal processes, ensuring users are informed of actions taken.
2026-08-08 23:16:03 +01:00

312 lines
9.9 KiB
NSIS

!include "FileFunc.nsh"
!insertmacro GetParameters
!insertmacro GetOptions
Var ProgressLogFile
Var IsInAppUpdate
Var RestartAfterInstall
Var FinalInstDir
!macro initProgressLog
StrCpy $ProgressLogFile ""
StrCpy $IsInAppUpdate "0"
StrCpy $RestartAfterInstall "0"
StrCpy $FinalInstDir ""
${GetParameters} $R9
ClearErrors
${GetOptions} $R9 "/UPDATE" $R8
${IfNot} ${Errors}
StrCpy $IsInAppUpdate "1"
${EndIf}
ClearErrors
${GetOptions} $R9 "/RESTARTFC" $R8
${IfNot} ${Errors}
StrCpy $RestartAfterInstall "1"
${EndIf}
; Prefer env var so paths with spaces are reliable; /LOG= remains supported.
ReadEnvStr $ProgressLogFile "FARMCONTROL_INSTALL_LOG"
${If} $ProgressLogFile == ""
ClearErrors
${GetOptions} $R9 "/LOG=" $ProgressLogFile
${If} ${Errors}
StrCpy $ProgressLogFile ""
${EndIf}
${EndIf}
${If} $ProgressLogFile != ""
; Strip surrounding quotes from /LOG="C:\path with spaces\log.log"
StrCpy $R8 $ProgressLogFile 1
${If} $R8 == '"'
StrCpy $ProgressLogFile $ProgressLogFile "" 1
StrLen $R8 $ProgressLogFile
IntOp $R8 $R8 - 1
${If} $R8 > 0
StrCpy $ProgressLogFile $ProgressLogFile $R8
${EndIf}
${EndIf}
; Truncate any previous log so progress starts clean.
Push $0
FileOpen $0 "$ProgressLogFile" w
${If} $0 != ""
FileClose $0
${EndIf}
Pop $0
${EndIf}
!macroend
; Open/append/close each line so the updater can read the log concurrently
; (avoids an exclusive lock for the whole install; LogEx SHARE_READ is an alternative).
!macro progressLog line
DetailPrint "${line}"
${If} $ProgressLogFile != ""
Push $0
FileOpen $0 "$ProgressLogFile" a
${If} $0 != ""
FileSeek $0 0 END
FileWrite $0 "${line}$\r$\n"
FileClose $0
${EndIf}
Pop $0
${EndIf}
!macroend
!macro progressPercent percent
!insertmacro progressLog "installer:%${percent}"
!macroend
!macro progressPhase phase
!insertmacro progressLog "installer:PHASE:${phase}"
!macroend
!macro progressStatus status
!insertmacro progressLog "installer:STATUS:${status}"
!macroend
!macro progressSuccess
!insertmacro progressPercent 100
!insertmacro progressStatus "Installation complete. Restarting Farm Control..."
!insertmacro progressLog "installer: The install was successful."
!macroend
!macro progressFailure message
!insertmacro progressLog "installer:STATUS:${message}"
!insertmacro progressLog "installer: The install failed."
!macroend
!macro quitFarmControl
!insertmacro progressPhase "Stopping Farm Control"
!insertmacro progressStatus "Stopping running Farm Control processes..."
DetailPrint "Stopping running Farm Control processes..."
ExecWait 'taskkill /F /IM FarmControl.exe /T' $R0
ExecWait 'taskkill /F /IM launcher.exe /T' $R0
!macroend
; Side-by-side update: install into Farm Control.new while the running app
; keeps using Farm Control, then swap folders after the process exits.
!macro prepareSideBySideUpdate
!insertmacro progressPhase "Preparing update"
!insertmacro progressStatus "Preparing staging folder for update..."
StrCpy $FinalInstDir $INSTDIR
StrCpy $INSTDIR "$FinalInstDir.new"
${If} ${FileExists} "$INSTDIR"
DetailPrint "Removing leftover staging folder..."
RMDir /r "$INSTDIR"
${EndIf}
!macroend
; Swap order: Farm Control -> .old, Farm Control.new -> Farm Control, delete .old.
!macro swapUpdateStaging
!insertmacro progressPhase "Applying update"
!insertmacro progressStatus "Replacing Farm Control with the new version..."
DetailPrint "Replacing Farm Control with staged update..."
; Clear a leftover .old from a previous interrupted update.
${If} ${FileExists} "$FinalInstDir.old"
DetailPrint "Removing leftover Farm Control.old..."
RMDir /r "$FinalInstDir.old"
${EndIf}
${If} ${FileExists} "$FinalInstDir"
!insertmacro progressStatus "Waiting for Farm Control to release install files..."
DetailPrint "Waiting to rename install directory to Farm Control.old..."
StrCpy $R7 0
swap_rename_old_retry:
ClearErrors
Rename "$FinalInstDir" "$FinalInstDir.old"
${If} ${Errors}
IntOp $R7 $R7 + 1
${If} $R7 < 60
Sleep 500
Goto swap_rename_old_retry
${EndIf}
!insertmacro progressFailure "Could not move the previous Farm Control installation aside."
Abort
${EndIf}
${EndIf}
!insertmacro progressStatus "Activating new Farm Control installation..."
DetailPrint "Renaming Farm Control.new to Farm Control..."
ClearErrors
Rename "$INSTDIR" "$FinalInstDir"
${If} ${Errors}
; Best-effort rollback so the previous install is usable again.
${If} ${FileExists} "$FinalInstDir.old"
Rename "$FinalInstDir.old" "$FinalInstDir"
${EndIf}
!insertmacro progressFailure "Could not activate the new Farm Control installation."
Abort
${EndIf}
StrCpy $INSTDIR $FinalInstDir
!insertmacro progressLog "installer:STATUS:Update files activated"
${If} ${FileExists} "$FinalInstDir.old"
!insertmacro progressStatus "Removing previous Farm Control installation..."
DetailPrint "Removing Farm Control.old..."
StrCpy $R7 0
swap_delete_old_retry:
RMDir /r "$FinalInstDir.old"
${If} ${FileExists} "$FinalInstDir.old"
IntOp $R7 $R7 + 1
${If} $R7 < 20
Sleep 500
Goto swap_delete_old_retry
${EndIf}
; New version is already active; leftover .old is non-fatal.
DetailPrint "Warning: could not fully remove Farm Control.old"
!insertmacro progressLog "installer:STATUS:Warning: could not fully remove previous installation"
${EndIf}
${EndIf}
!macroend
!macro uninstallPreviousFarmControl
ClearErrors
ReadRegStr $R0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" "UninstallString"
StrCmp $R0 "" check_install_dir run_uninstall
check_install_dir:
ReadRegStr $R0 HKCU "Software\Tom Butcher\Farm Control" "InstallDir"
StrCmp $R0 "" check_legacy_uninstall
StrCpy $R0 "$R0\Uninstall.exe"
Goto run_uninstall
check_legacy_uninstall:
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" "UninstallString"
StrCmp $R0 "" check_legacy_install_dir run_uninstall
check_legacy_install_dir:
ReadRegStr $R0 HKLM "Software\Tom Butcher\Farm Control" "InstallDir"
StrCmp $R0 "" check_default_dir
StrCpy $R0 "$R0\Uninstall.exe"
Goto run_uninstall
check_default_dir:
StrCpy $R0 "$LOCALAPPDATA\Programs\Farm Control\Uninstall.exe"
IfFileExists $R0 run_uninstall check_legacy_default_dir
check_legacy_default_dir:
StrCpy $R0 "$PROGRAMFILES64\Farm Control\Uninstall.exe"
IfFileExists $R0 run_uninstall done_uninstall
run_uninstall:
IfFileExists $R0 0 done_uninstall
!insertmacro progressPhase "Removing previous version"
!insertmacro progressStatus "Removing previous Farm Control installation..."
DetailPrint "Removing previous Farm Control installation..."
!insertmacro quitFarmControl
ExecWait '"$R0" /S' $R1
DetailPrint "Previous installation removed (exit code: $R1)"
!insertmacro progressLog "installer:STATUS:Previous installation removed"
done_uninstall:
!macroend
!macro createDesktopShortcut
SetShellVarContext current
SetOutPath "$FinalInstDir\bin"
CreateShortCut "$DESKTOP\Farm Control.lnk" "$FinalInstDir\bin\FarmControl.exe" "" "$FinalInstDir\bin\FarmControl.exe" 0 SW_SHOWNORMAL "" "Farm Control"
!macroend
!macro createStartMenuShortcut
SetShellVarContext current
CreateDirectory "$SMPROGRAMS\Farm Control"
SetOutPath "$FinalInstDir\bin"
CreateShortCut "$SMPROGRAMS\Farm Control\Farm Control.lnk" "$FinalInstDir\bin\FarmControl.exe" "" "$FinalInstDir\bin\FarmControl.exe" 0 SW_SHOWNORMAL "" "Farm Control"
!macroend
!macro removeDesktopShortcut
SetShellVarContext current
Delete "$DESKTOP\Farm Control.lnk"
!macroend
!macro removeStartMenuShortcut
SetShellVarContext current
Delete "$SMPROGRAMS\Farm Control\Farm Control.lnk"
RMDir "$SMPROGRAMS\Farm Control"
!macroend
!macro customInstall
!insertmacro progressPhase "Configuring Farm Control"
!insertmacro progressStatus "Registering farmcontrol URI handler..."
DetailPrint "Register farmcontrol URI Handler"
DeleteRegKey HKCU "Software\Classes\farmcontrol"
WriteRegStr HKCU "Software\Classes\farmcontrol" "" "URL:farmcontrol"
WriteRegStr HKCU "Software\Classes\farmcontrol" "URL Protocol" ""
WriteRegStr HKCU "Software\Classes\farmcontrol\DefaultIcon" "" "$FinalInstDir\bin\FarmControl.exe"
WriteRegStr HKCU "Software\Classes\farmcontrol\shell" "" ""
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open" "" ""
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open\command" "" '"$FinalInstDir\bin\bun.exe" "$FinalInstDir\bin\deeplink.js" "%1"'
!insertmacro progressStatus "Creating shortcuts..."
DetailPrint "Creating shortcuts"
!insertmacro createDesktopShortcut
!insertmacro createStartMenuShortcut
!macroend
!macro customUnInstall
!insertmacro removeDesktopShortcut
!insertmacro removeStartMenuShortcut
DeleteRegKey HKCU "Software\Classes\farmcontrol"
!macroend
; Wait for the running app to exit, swap staged update into place, then relaunch.
; Only used when /RESTARTFC is passed.
Function restartFarmControlAfterUpdate
${If} $RestartAfterInstall != "1"
Return
${EndIf}
!insertmacro progressStatus "Waiting for Farm Control to close..."
Sleep 1000
restart_wait_loop:
ExecWait 'cmd.exe /c tasklist /FI "IMAGENAME eq FarmControl.exe" 2>nul | find /I "FarmControl.exe"' $R0
${If} $R0 == 0
Sleep 1000
Goto restart_wait_loop
${EndIf}
ExecWait 'cmd.exe /c tasklist /FI "IMAGENAME eq launcher.exe" 2>nul | find /I "launcher.exe"' $R0
${If} $R0 == 0
Sleep 1000
Goto restart_wait_loop
${EndIf}
${If} $IsInAppUpdate == "1"
!insertmacro swapUpdateStaging
${EndIf}
!insertmacro progressStatus "Starting Farm Control..."
SetOutPath "$FinalInstDir\bin"
Exec "$FinalInstDir\bin\FarmControl.exe"
FunctionEnd