Update Windows installer to use FarmControl.exe and enhance process handling
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Replaced references to launcher.exe with FarmControl.exe in the installer script, ensuring consistency in application execution. - Added taskkill command to stop running FarmControl processes during installation. - Updated shortcut creation to point to FarmControl.exe for both desktop and start menu. - Enhanced cleanup process to remove old executable backups and ensure proper application state after updates. - Refactored updater logic to check for the presence of FarmControl.exe, improving application launch reliability.
This commit is contained in:
parent
893804a17c
commit
eb1ee1e82b
@ -100,6 +100,7 @@ Var RestartAfterInstall
|
|||||||
!insertmacro progressPhase "Stopping Farm Control"
|
!insertmacro progressPhase "Stopping Farm Control"
|
||||||
!insertmacro progressStatus "Stopping running Farm Control processes..."
|
!insertmacro progressStatus "Stopping running Farm Control processes..."
|
||||||
DetailPrint "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
|
ExecWait 'taskkill /F /IM launcher.exe /T' $R0
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
@ -126,6 +127,7 @@ Var RestartAfterInstall
|
|||||||
ClearErrors
|
ClearErrors
|
||||||
Rename "$INSTDIR\bin" "$INSTDIR\bin.old"
|
Rename "$INSTDIR\bin" "$INSTDIR\bin.old"
|
||||||
${If} ${Errors}
|
${If} ${Errors}
|
||||||
|
!insertmacro moveAsideIfPresent "$INSTDIR\bin\FarmControl.exe"
|
||||||
!insertmacro moveAsideIfPresent "$INSTDIR\bin\launcher.exe"
|
!insertmacro moveAsideIfPresent "$INSTDIR\bin\launcher.exe"
|
||||||
!insertmacro moveAsideIfPresent "$INSTDIR\bin\bun.exe"
|
!insertmacro moveAsideIfPresent "$INSTDIR\bin\bun.exe"
|
||||||
!insertmacro moveAsideIfPresent "$INSTDIR\bin\bspatch.exe"
|
!insertmacro moveAsideIfPresent "$INSTDIR\bin\bspatch.exe"
|
||||||
@ -179,14 +181,14 @@ Var RestartAfterInstall
|
|||||||
!macro createDesktopShortcut
|
!macro createDesktopShortcut
|
||||||
SetShellVarContext current
|
SetShellVarContext current
|
||||||
SetOutPath "$INSTDIR\bin"
|
SetOutPath "$INSTDIR\bin"
|
||||||
CreateShortCut "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin\launcher.exe" "" "$INSTDIR\bin\launcher.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
CreateShortCut "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin\FarmControl.exe" "" "$INSTDIR\bin\FarmControl.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro createStartMenuShortcut
|
!macro createStartMenuShortcut
|
||||||
SetShellVarContext current
|
SetShellVarContext current
|
||||||
CreateDirectory "$SMPROGRAMS\Farm Control"
|
CreateDirectory "$SMPROGRAMS\Farm Control"
|
||||||
SetOutPath "$INSTDIR\bin"
|
SetOutPath "$INSTDIR\bin"
|
||||||
CreateShortCut "$SMPROGRAMS\Farm Control\Farm Control.lnk" "$INSTDIR\bin\launcher.exe" "" "$INSTDIR\bin\launcher.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
CreateShortCut "$SMPROGRAMS\Farm Control\Farm Control.lnk" "$INSTDIR\bin\FarmControl.exe" "" "$INSTDIR\bin\FarmControl.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
!macro removeDesktopShortcut
|
!macro removeDesktopShortcut
|
||||||
@ -203,6 +205,7 @@ Var RestartAfterInstall
|
|||||||
!macro cleanupUpdateBackups
|
!macro cleanupUpdateBackups
|
||||||
; Best-effort; may fail while the previous process still holds handles.
|
; Best-effort; may fail while the previous process still holds handles.
|
||||||
RMDir /r "$INSTDIR\bin.old"
|
RMDir /r "$INSTDIR\bin.old"
|
||||||
|
Delete "$INSTDIR\bin\FarmControl.exe.old"
|
||||||
Delete "$INSTDIR\bin\launcher.exe.old"
|
Delete "$INSTDIR\bin\launcher.exe.old"
|
||||||
Delete "$INSTDIR\bin\bun.exe.old"
|
Delete "$INSTDIR\bin\bun.exe.old"
|
||||||
Delete "$INSTDIR\bin\bspatch.exe.old"
|
Delete "$INSTDIR\bin\bspatch.exe.old"
|
||||||
@ -216,7 +219,7 @@ Var RestartAfterInstall
|
|||||||
DeleteRegKey HKCU "Software\Classes\farmcontrol"
|
DeleteRegKey HKCU "Software\Classes\farmcontrol"
|
||||||
WriteRegStr HKCU "Software\Classes\farmcontrol" "" "URL:farmcontrol"
|
WriteRegStr HKCU "Software\Classes\farmcontrol" "" "URL:farmcontrol"
|
||||||
WriteRegStr HKCU "Software\Classes\farmcontrol" "URL Protocol" ""
|
WriteRegStr HKCU "Software\Classes\farmcontrol" "URL Protocol" ""
|
||||||
WriteRegStr HKCU "Software\Classes\farmcontrol\DefaultIcon" "" "$INSTDIR\bin\launcher.exe"
|
WriteRegStr HKCU "Software\Classes\farmcontrol\DefaultIcon" "" "$INSTDIR\bin\FarmControl.exe"
|
||||||
WriteRegStr HKCU "Software\Classes\farmcontrol\shell" "" ""
|
WriteRegStr HKCU "Software\Classes\farmcontrol\shell" "" ""
|
||||||
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open" "" ""
|
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open" "" ""
|
||||||
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open\command" "" '"$INSTDIR\bin\bun.exe" "$INSTDIR\bin\deeplink.js" "%1"'
|
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open\command" "" '"$INSTDIR\bin\bun.exe" "$INSTDIR\bin\deeplink.js" "%1"'
|
||||||
@ -248,6 +251,11 @@ Function restartFarmControlAfterUpdate
|
|||||||
Sleep 1000
|
Sleep 1000
|
||||||
|
|
||||||
restart_wait_loop:
|
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
|
ExecWait 'cmd.exe /c tasklist /FI "IMAGENAME eq launcher.exe" 2>nul | find /I "launcher.exe"' $R0
|
||||||
${If} $R0 == 0
|
${If} $R0 == 0
|
||||||
Sleep 1000
|
Sleep 1000
|
||||||
@ -256,5 +264,5 @@ Function restartFarmControlAfterUpdate
|
|||||||
|
|
||||||
!insertmacro progressStatus "Starting Farm Control..."
|
!insertmacro progressStatus "Starting Farm Control..."
|
||||||
SetOutPath "$INSTDIR\bin"
|
SetOutPath "$INSTDIR\bin"
|
||||||
Exec "$INSTDIR\bin\launcher.exe"
|
Exec "$INSTDIR\bin\FarmControl.exe"
|
||||||
FunctionEnd
|
FunctionEnd
|
||||||
|
|||||||
@ -69,10 +69,10 @@ if ($stagedAppBytes -lt 5242880) {
|
|||||||
throw "Staged application is only $([math]::Round($stagedAppBytes / 1KB, 1)) KiB. Expected a full Electrobun app bundle before building the installer."
|
throw "Staged application is only $([math]::Round($stagedAppBytes / 1KB, 1)) KiB. Expected a full Electrobun app bundle before building the installer."
|
||||||
}
|
}
|
||||||
|
|
||||||
$requiredExe = Join-Path $stagingAppDir "bin\launcher.exe"
|
$requiredExe = Join-Path $stagingAppDir "bin\FarmControl.exe"
|
||||||
$requiredDeeplinkScript = Join-Path $stagingAppDir "bin\deeplink.js"
|
$requiredDeeplinkScript = Join-Path $stagingAppDir "bin\deeplink.js"
|
||||||
if (-not (Test-Path $requiredExe)) {
|
if (-not (Test-Path $requiredExe)) {
|
||||||
throw "Staged application is missing bin\launcher.exe"
|
throw "Staged application is missing bin\FarmControl.exe"
|
||||||
}
|
}
|
||||||
if (-not (Test-Path $requiredDeeplinkScript)) {
|
if (-not (Test-Path $requiredDeeplinkScript)) {
|
||||||
throw "Staged application is missing bin\deeplink.js"
|
throw "Staged application is missing bin\deeplink.js"
|
||||||
|
|||||||
@ -619,7 +619,7 @@ async function main() {
|
|||||||
platformDir
|
platformDir
|
||||||
)
|
)
|
||||||
stageWindowsDeeplinkScript(appDir)
|
stageWindowsDeeplinkScript(appDir)
|
||||||
await patchWindowsBinaries(appDir)
|
patchWindowsBinaries(appDir)
|
||||||
|
|
||||||
let published
|
let published
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,79 +1,37 @@
|
|||||||
import { copyFileSync, existsSync, unlinkSync } from 'node:fs'
|
import { existsSync, renameSync } from 'node:fs'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import rcedit from 'rcedit'
|
|
||||||
|
|
||||||
const PATCH_TARGETS = new Set(['launcher.exe'])
|
export const WINDOWS_LAUNCHER_EXE = 'FarmControl.exe'
|
||||||
const MAX_ATTEMPTS = 6
|
const SOURCE_LAUNCHER_EXE = 'launcher.exe'
|
||||||
const BASE_DELAY_MS = 500
|
|
||||||
|
|
||||||
function sleep(ms) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function patchExecutable(executablePath) {
|
|
||||||
let lastError
|
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
||||||
const tempPath = `${executablePath}.rcedit-${process.pid}-${attempt}.tmp`
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Patch a copy then replace — avoids AV/indexer locks on the live PE.
|
|
||||||
copyFileSync(executablePath, tempPath)
|
|
||||||
await rcedit(tempPath, {
|
|
||||||
'requested-execution-level': 'asInvoker'
|
|
||||||
})
|
|
||||||
copyFileSync(tempPath, executablePath)
|
|
||||||
return
|
|
||||||
} catch (error) {
|
|
||||||
lastError = error
|
|
||||||
const message = error instanceof Error ? error.message : String(error)
|
|
||||||
console.warn(
|
|
||||||
`patch-windows-binaries: attempt ${attempt}/${MAX_ATTEMPTS} failed for ${executablePath}: ${message}`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (attempt < MAX_ATTEMPTS) {
|
|
||||||
await sleep(BASE_DELAY_MS * 2 ** (attempt - 1))
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
unlinkSync(tempPath)
|
|
||||||
} catch {
|
|
||||||
// Ignore cleanup failures.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw lastError
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function patchWindowsBinaries(appDir) {
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export function patchWindowsBinaries(appDir) {
|
||||||
const binDir = path.join(appDir, 'bin')
|
const binDir = path.join(appDir, 'bin')
|
||||||
if (!existsSync(binDir)) {
|
if (!existsSync(binDir)) {
|
||||||
throw new Error(`patch-windows-binaries: bin directory not found at ${binDir}`)
|
throw new Error(`patch-windows-binaries: bin directory not found at ${binDir}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const executable of PATCH_TARGETS) {
|
const sourcePath = path.join(binDir, SOURCE_LAUNCHER_EXE)
|
||||||
const executablePath = path.join(binDir, executable)
|
const destPath = path.join(binDir, WINDOWS_LAUNCHER_EXE)
|
||||||
if (!existsSync(executablePath)) {
|
|
||||||
console.warn(`patch-windows-binaries: skipping missing ${executablePath}`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (existsSync(destPath) && !existsSync(sourcePath)) {
|
||||||
await patchExecutable(executablePath)
|
console.log(`patch-windows-binaries: ${WINDOWS_LAUNCHER_EXE} already present`)
|
||||||
console.log(`patch-windows-binaries: set asInvoker on ${executablePath}`)
|
return
|
||||||
} catch (error) {
|
|
||||||
console.warn(
|
|
||||||
`patch-windows-binaries: failed to patch ${executablePath}:`,
|
|
||||||
error instanceof Error ? error.message : error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!existsSync(sourcePath)) {
|
||||||
|
console.warn(`patch-windows-binaries: skipping missing ${sourcePath}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(destPath)) {
|
||||||
|
renameSync(destPath, `${destPath}.old`)
|
||||||
|
}
|
||||||
|
|
||||||
|
renameSync(sourcePath, destPath)
|
||||||
|
console.log(
|
||||||
|
`patch-windows-binaries: renamed ${SOURCE_LAUNCHER_EXE} -> ${WINDOWS_LAUNCHER_EXE}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||||
@ -83,5 +41,5 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
await patchWindowsBinaries(path.resolve(appDirArg))
|
patchWindowsBinaries(path.resolve(appDirArg))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,9 +29,13 @@ console.log('[deeplink] no running instance found, launching Farm Control')
|
|||||||
|
|
||||||
writeDeeplinkSignal(payload)
|
writeDeeplinkSignal(payload)
|
||||||
|
|
||||||
const launcherPath = join(dirname(process.execPath), 'launcher.exe')
|
const binDir = dirname(process.execPath)
|
||||||
if (!existsSync(launcherPath)) {
|
const launcherPath = ['FarmControl.exe', 'launcher.exe']
|
||||||
console.error('[deeplink] launcher.exe not found at', launcherPath)
|
.map((name) => join(binDir, name))
|
||||||
|
.find((candidate) => existsSync(candidate))
|
||||||
|
|
||||||
|
if (!launcherPath) {
|
||||||
|
console.error('[deeplink] FarmControl.exe not found in', binDir)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,14 +9,18 @@ const WINDOWS_INSTALL_DIR_KEYS = [
|
|||||||
'HKCU\\Software\\Tom Butcher\\Farm Control',
|
'HKCU\\Software\\Tom Butcher\\Farm Control',
|
||||||
'HKLM\\Software\\Tom Butcher\\Farm Control'
|
'HKLM\\Software\\Tom Butcher\\Farm Control'
|
||||||
]
|
]
|
||||||
const WINDOWS_DEFAULT_LAUNCHER = path.join(
|
const WINDOWS_LAUNCHER_EXE = 'FarmControl.exe'
|
||||||
|
const WINDOWS_LEGACY_LAUNCHER_EXE = 'launcher.exe'
|
||||||
|
const WINDOWS_DEFAULT_BIN_DIR = path.join(
|
||||||
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
||||||
'Programs',
|
'Programs',
|
||||||
'Farm Control',
|
'Farm Control',
|
||||||
'bin',
|
'bin'
|
||||||
'launcher.exe'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const resolveExistingLauncher = (...candidates) =>
|
||||||
|
candidates.find((candidate) => existsSync(candidate)) || null
|
||||||
|
|
||||||
const quoteBatchArg = (value) => `"${String(value).replaceAll('"', '""')}"`
|
const quoteBatchArg = (value) => `"${String(value).replaceAll('"', '""')}"`
|
||||||
|
|
||||||
const findMacAppBundle = (startPath) => {
|
const findMacAppBundle = (startPath) => {
|
||||||
@ -81,32 +85,44 @@ export const resolveAppLaunchPath = () => {
|
|||||||
// (in-app updates may leave the running process under bin.old).
|
// (in-app updates may leave the running process under bin.old).
|
||||||
const installDir = readWindowsInstallDirFromRegistry()
|
const installDir = readWindowsInstallDirFromRegistry()
|
||||||
if (installDir) {
|
if (installDir) {
|
||||||
const launcherFromRegistry = path.join(installDir, 'bin', 'launcher.exe')
|
const launcherFromRegistry = resolveExistingLauncher(
|
||||||
if (existsSync(launcherFromRegistry)) {
|
path.join(installDir, 'bin', WINDOWS_LAUNCHER_EXE),
|
||||||
|
path.join(installDir, 'bin', WINDOWS_LEGACY_LAUNCHER_EXE)
|
||||||
|
)
|
||||||
|
if (launcherFromRegistry) {
|
||||||
return launcherFromRegistry
|
return launcherFromRegistry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existsSync(WINDOWS_DEFAULT_LAUNCHER)) {
|
const defaultLauncher = resolveExistingLauncher(
|
||||||
return WINDOWS_DEFAULT_LAUNCHER
|
path.join(WINDOWS_DEFAULT_BIN_DIR, WINDOWS_LAUNCHER_EXE),
|
||||||
|
path.join(WINDOWS_DEFAULT_BIN_DIR, WINDOWS_LEGACY_LAUNCHER_EXE)
|
||||||
|
)
|
||||||
|
if (defaultLauncher) {
|
||||||
|
return defaultLauncher
|
||||||
}
|
}
|
||||||
|
|
||||||
const execBase = path.basename(process.execPath).toLowerCase()
|
const execBase = path.basename(process.execPath).toLowerCase()
|
||||||
if (execBase === 'launcher.exe') {
|
if (
|
||||||
|
execBase === WINDOWS_LAUNCHER_EXE.toLowerCase() ||
|
||||||
|
execBase === WINDOWS_LEGACY_LAUNCHER_EXE
|
||||||
|
) {
|
||||||
return process.execPath
|
return process.execPath
|
||||||
}
|
}
|
||||||
|
|
||||||
const launcherBesideExec = path.join(path.dirname(process.execPath), 'launcher.exe')
|
const launcherBesideExec = resolveExistingLauncher(
|
||||||
if (existsSync(launcherBesideExec)) {
|
path.join(path.dirname(process.execPath), WINDOWS_LAUNCHER_EXE),
|
||||||
|
path.join(path.dirname(process.execPath), WINDOWS_LEGACY_LAUNCHER_EXE)
|
||||||
|
)
|
||||||
|
if (launcherBesideExec) {
|
||||||
return launcherBesideExec
|
return launcherBesideExec
|
||||||
}
|
}
|
||||||
|
|
||||||
const launcherInBin = path.join(
|
const launcherInBin = resolveExistingLauncher(
|
||||||
path.dirname(process.execPath),
|
path.join(path.dirname(process.execPath), 'bin', WINDOWS_LAUNCHER_EXE),
|
||||||
'bin',
|
path.join(path.dirname(process.execPath), 'bin', WINDOWS_LEGACY_LAUNCHER_EXE)
|
||||||
'launcher.exe'
|
|
||||||
)
|
)
|
||||||
if (existsSync(launcherInBin)) {
|
if (launcherInBin) {
|
||||||
return launcherInBin
|
return launcherInBin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { existsSync } from 'node:fs'
|
|||||||
import { dirname, join } from 'node:path'
|
import { dirname, join } from 'node:path'
|
||||||
|
|
||||||
const NATIVE_WRAPPER_DLL = 'libNativeWrapper.dll'
|
const NATIVE_WRAPPER_DLL = 'libNativeWrapper.dll'
|
||||||
|
export const WINDOWS_LAUNCHER_EXE = 'FarmControl.exe'
|
||||||
|
const LEGACY_LAUNCHER_EXE = 'launcher.exe'
|
||||||
|
|
||||||
export function resolveWindowsBinDir() {
|
export function resolveWindowsBinDir() {
|
||||||
const candidates = [
|
const candidates = [
|
||||||
@ -13,7 +15,8 @@ export function resolveWindowsBinDir() {
|
|||||||
|
|
||||||
for (const candidate of candidates) {
|
for (const candidate of candidates) {
|
||||||
if (
|
if (
|
||||||
existsSync(join(candidate, 'launcher.exe')) ||
|
existsSync(join(candidate, WINDOWS_LAUNCHER_EXE)) ||
|
||||||
|
existsSync(join(candidate, LEGACY_LAUNCHER_EXE)) ||
|
||||||
existsSync(join(candidate, NATIVE_WRAPPER_DLL))
|
existsSync(join(candidate, NATIVE_WRAPPER_DLL))
|
||||||
) {
|
) {
|
||||||
return candidate
|
return candidate
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user