!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 ; Returns 0 in $R9 when $R8 can be renamed (file unlocked / path free). ; _ID must be unique per insert so labels do not collide. !macro tryRenameProbe _ID StrCpy $R9 1 ${IfNot} ${FileExists} "$R8" StrCpy $R9 0 Goto try_rename_probe_done_${_ID} ${EndIf} ClearErrors Rename "$R8" "$R8.updating" ${If} ${Errors} Goto try_rename_probe_done_${_ID} ${EndIf} ; Leave the .updating name — the whole tree is about to be deleted. StrCpy $R9 0 try_rename_probe_done_${_ID}: !macroend ; Wait until the live install directory is released by the quitting app. !macro waitForInstallDirWritable !insertmacro progressStatus "Waiting for Farm Control to release install files..." DetailPrint "Waiting for install directory to become writable..." StrCpy $R7 0 wait_install_writable: ; Probe the main binaries — rename fails while the process still holds them. StrCpy $R8 "$FinalInstDir\bin\FarmControl.exe" !insertmacro tryRenameProbe farmcontrol ${If} $R9 != 0 Goto wait_install_writable_retry ${EndIf} StrCpy $R8 "$FinalInstDir\bin\launcher.exe" !insertmacro tryRenameProbe launcher ${If} $R9 != 0 Goto wait_install_writable_retry ${EndIf} ; Also probe creating a temp file in the install root. ClearErrors FileOpen $R8 "$FinalInstDir\.farmcontrol-write-probe" w ${If} $R8 == "" Goto wait_install_writable_retry ${EndIf} FileClose $R8 Delete "$FinalInstDir\.farmcontrol-write-probe" Goto wait_install_writable_done wait_install_writable_retry: IntOp $R7 $R7 + 1 ${If} $R7 < 60 Sleep 500 Goto wait_install_writable ${EndIf} !insertmacro progressFailure "Could not access the previous Farm Control installation." Abort wait_install_writable_done: !macroend !macro swapUpdateStaging !insertmacro progressPhase "Applying update" ${If} ${FileExists} "$FinalInstDir" !insertmacro waitForInstallDirWritable !insertmacro progressStatus "Replacing Farm Control with the new version..." DetailPrint "Replacing Farm Control with staged update..." StrCpy $R7 0 swap_delete_retry: RMDir /r "$FinalInstDir" ${If} ${FileExists} "$FinalInstDir" IntOp $R7 $R7 + 1 ${If} $R7 < 20 Sleep 500 Goto swap_delete_retry ${EndIf} !insertmacro progressFailure "Could not remove the previous Farm Control installation." Abort ${EndIf} ${EndIf} !insertmacro progressStatus "Activating new Farm Control installation..." DetailPrint "Activating staged update..." ClearErrors Rename "$INSTDIR" "$FinalInstDir" ${If} ${Errors} !insertmacro progressFailure "Could not activate the new Farm Control installation." Abort ${EndIf} StrCpy $INSTDIR $FinalInstDir !insertmacro progressLog "installer:STATUS:Update files activated" !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