!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..." ; The install section ran SetOutPath into the install tree; a directory that ; is any process's working directory cannot be renamed, so move out first. SetOutPath "$TEMP" Sleep 3000 ; 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..." ; Retry every 500ms; success is verified on disk rather than via the ; error flag, which Rename does not set reliably. StrCpy $R7 0 swap_rename_old_retry: Rename "$FinalInstDir" "$FinalInstDir.old" ${IfNot} ${FileExists} "$FinalInstDir" Goto swap_rename_old_done ${EndIf} IntOp $R7 $R7 + 1 ${If} $R7 < 120 Sleep 500 Goto swap_rename_old_retry ${EndIf} !insertmacro progressFailure "Could not move the previous Farm Control installation aside." Abort swap_rename_old_done: DetailPrint "Install directory renamed to Farm Control.old" ${EndIf} !insertmacro progressStatus "Activating new Farm Control installation..." DetailPrint "Renaming Farm Control.new to Farm Control..." StrCpy $R7 0 swap_rename_new_retry: Rename "$INSTDIR" "$FinalInstDir" ${If} ${FileExists} "$FinalInstDir" Goto swap_rename_new_done ${EndIf} IntOp $R7 $R7 + 1 ${If} $R7 < 20 Sleep 500 Goto swap_rename_new_retry ${EndIf} ; 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 swap_rename_new_done: 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