Compare commits

...

2 commits

Author SHA1 Message Date
Lukas Brübach
1b7f9c0de4
[Windows] Scope running-instance handling to install dir and fix NSIS build
- Drop !include nsExec.nsh: nsExec is a plugin DLL, not a header, so
  makensis aborts before NSIS can build the installer.
- Match processes by image name and executable path under $INSTDIR via
  PowerShell, then close (WM_CLOSE) and force-stop only those PIDs, so an
  unrelated oracle.exe (Oracle DB) is never killed on a silent /R update.
- Gate the stale-runtime-DLL purge on $INSTDIR\cockatrice.exe existing, so
  a first-time install can't recursively delete an unrelated Plugins
  directory.
2026-09-20 21:08:16 +02:00
Lukas Brübach
6d06ae2bcf
[Windows] Close running instances and purge stale runtime DLLs during update 2026-09-20 21:08:16 +02:00

View file

@ -131,6 +131,13 @@ ${EndIf}
; Now that $PortableMode reflects reality, commit InstDir into the correct slot
Call SetModeDestinationFromInstdir
; Make sure no application instance is still running (and holding file locks)
; before the previous version is uninstalled or new files are installed.
; On a silent update (/R /S) running processes are asked to close gracefully
; and waited for, then force-closed only on timeout. On interactive installs
; the user is prompted to close them instead.
Call EnsureAppsNotRunning
${If} $ReinstallMode = 1
${AndIf} $PortableMode = 0
Call AutoUninstallIfNeeded
@ -144,6 +151,9 @@ ${If} ${NSIS_IS_64_BIT} == 1
SetRegView 64
${EndIf}
; Ensure no application instance is still running before removing files.
Call un.EnsureAppsNotRunning
FunctionEnd
Function RequireAdmin
@ -199,6 +209,170 @@ ${EndIf}
FunctionEnd
; --- Running instance handling ---
; Cockatrice, Oracle and Servatrice must not be running while files are
; replaced or deleted. A still-running process holds locks on its .exe and
; Qt runtime DLLs, so a silent upgrade could otherwise end up with a mix of
; old and new Qt DLLs next to the new executable, failing with
; "The procedure entry point X could not be located in the dynamic link
; library ...Qt6Network.dll" on the next start.
;
; Processes are matched by image name AND by their executable path living
; under $INSTDIR, so unrelated processes that merely share an image name
; (e.g. the Oracle DB instance "oracle.exe") are never touched.
; usage: set $R2 to the base image name (without extension, e.g. "cockatrice",
; as accepted by Get-Process -Name), call this, result in $R0
; (1 = running from $INSTDIR, 0 = not running)
Function IsAppRunning
nsExec::ExecToLog 'powershell -NoProfile -Command "Get-Process -Name $\'$R2$\' -ErrorAction SilentlyContinue | Where-Object { $$_.Path -like $\'$INSTDIR\*$\' } | Select-Object -First 1"'
Pop $R0
${If} $R0 = 0
StrCpy $R0 1
${Else}
StrCpy $R0 0
${EndIf}
FunctionEnd
Function CloseMatchingApps
; gracefully ask every matching instance to close (sends WM_CLOSE)
nsExec::ExecToLog 'powershell -NoProfile -Command "Get-Process -Name $\'$R2$\' -ErrorAction SilentlyContinue | Where-Object { $$_.Path -like $\'$INSTDIR\*$\' } | ForEach-Object { $$null = $$_.CloseMainWindow() }"'
Pop $R3
FunctionEnd
Function ForceCloseMatchingApps
nsExec::ExecToLog 'powershell -NoProfile -Command "Get-Process -Name $\'$R2$\' -ErrorAction SilentlyContinue | Where-Object { $$_.Path -like $\'$INSTDIR\*$\' } | Stop-Process -Force -ErrorAction SilentlyContinue"'
Pop $R3
FunctionEnd
Function WaitForAppToClose
; usage: set $R1 to the display name (e.g. "cockatrice.exe") and $R2 to the
; base image name (e.g. "cockatrice")
Call IsAppRunning
${If} $R0 = 0
Return
${EndIf}
${If} ${Silent}
; ask the application to close gracefully (WM_CLOSE), then wait for it to exit
DetailPrint "Closing $R1 ..."
Call CloseMatchingApps
StrCpy $R8 0
ck_wait_loop:
Sleep 500
IntOp $R8 $R8 + 1
Call IsAppRunning
${If} $R0 = 0
DetailPrint "$R1 closed."
Return
${EndIf}
${If} $R8 < 60
Goto ck_wait_loop
${EndIf}
; give up waiting, force close
DetailPrint "Force closing $R1 ..."
Call ForceCloseMatchingApps
Sleep 500
${Else}
ck_wait_prompt:
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION|MB_DEFBUTTON1 \
"$R1 is still running.$\r$\n$\r$\nPlease close it, then click Retry.$\r$\nClick Cancel to abort." \
IDCANCEL ck_abort_install
Call IsAppRunning
${If} $R0 = 0
Return
${EndIf}
Goto ck_wait_prompt
ck_abort_install:
Abort
${EndIf}
FunctionEnd
Function EnsureAppsNotRunning
StrCpy $R1 "cockatrice.exe"
StrCpy $R2 "cockatrice"
Call WaitForAppToClose
StrCpy $R1 "oracle.exe"
StrCpy $R2 "oracle"
Call WaitForAppToClose
StrCpy $R1 "servatrice.exe"
StrCpy $R2 "servatrice"
Call WaitForAppToClose
FunctionEnd
; Uninstaller copies of the same routines (the uninstaller gets its own
; function set compiled in, it cannot call the installer functions).
Function un.IsAppRunning
nsExec::ExecToLog 'powershell -NoProfile -Command "Get-Process -Name $\'$R2$\' -ErrorAction SilentlyContinue | Where-Object { $$_.Path -like $\'$INSTDIR\*$\' } | Select-Object -First 1"'
Pop $R0
${If} $R0 = 0
StrCpy $R0 1
${Else}
StrCpy $R0 0
${EndIf}
FunctionEnd
Function un.CloseMatchingApps
nsExec::ExecToLog 'powershell -NoProfile -Command "Get-Process -Name $\'$R2$\' -ErrorAction SilentlyContinue | Where-Object { $$_.Path -like $\'$INSTDIR\*$\' } | ForEach-Object { $$null = $$_.CloseMainWindow() }"'
Pop $R3
FunctionEnd
Function un.ForceCloseMatchingApps
nsExec::ExecToLog 'powershell -NoProfile -Command "Get-Process -Name $\'$R2$\' -ErrorAction SilentlyContinue | Where-Object { $$_.Path -like $\'$INSTDIR\*$\' } | Stop-Process -Force -ErrorAction SilentlyContinue"'
Pop $R3
FunctionEnd
Function un.WaitForAppToClose
Call un.IsAppRunning
${If} $R0 = 0
Return
${EndIf}
${If} ${Silent}
DetailPrint "Closing $R1 ..."
Call un.CloseMatchingApps
StrCpy $R8 0
un_ck_wait_loop:
Sleep 500
IntOp $R8 $R8 + 1
Call un.IsAppRunning
${If} $R0 = 0
DetailPrint "$R1 closed."
Return
${EndIf}
${If} $R8 < 60
Goto un_ck_wait_loop
${EndIf}
DetailPrint "Force closing $R1 ..."
Call un.ForceCloseMatchingApps
Sleep 500
${Else}
un_ck_wait_prompt:
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION|MB_DEFBUTTON1 \
"$R1 is still running.$\r$\n$\r$\nPlease close it, then click Retry.$\r$\nClick Cancel to abort." \
IDCANCEL un_ck_abort_install
Call un.IsAppRunning
${If} $R0 = 0
Return
${EndIf}
Goto un_ck_wait_prompt
un_ck_abort_install:
Abort
${EndIf}
FunctionEnd
Function un.EnsureAppsNotRunning
StrCpy $R1 "cockatrice.exe"
StrCpy $R2 "cockatrice"
Call un.WaitForAppToClose
StrCpy $R1 "oracle.exe"
StrCpy $R2 "oracle"
Call un.WaitForAppToClose
StrCpy $R1 "servatrice.exe"
StrCpy $R2 "servatrice"
Call un.WaitForAppToClose
FunctionEnd
Function PortableModePageCreate
${If} $ReinstallMode = 1
@ -318,6 +492,26 @@ ${AndIf} ${FileExists} "$INSTDIR\portable.dat"
RMDir "$INSTDIR"
${EndIf}
; Belt and braces: the old uninstaller may have already run in the /R path, so
; ensure no application instance is still holding file locks, then remove any
; runtime DLLs left over from older versions. A mismatched Qt/OpenSSL set next
; to the new executable is what causes "The procedure entry point X could not be
; located in the dynamic link library ...Qt6Network.dll" after an update.
Call EnsureAppsNotRunning
${If} $PortableMode = 0
${AndIf} ${FileExists} "$INSTDIR\cockatrice.exe"
RMDir /r "$INSTDIR\Plugins"
Delete "$INSTDIR\Qt*.dll"
Delete "$INSTDIR\libcrypto*.dll"
Delete "$INSTDIR\libssl*.dll"
Delete "$INSTDIR\zlib*.dll"
Delete "$INSTDIR\libmysql.dll"
Delete "$INSTDIR\icu*.dll"
Delete "$INSTDIR\libeay32.dll"
Delete "$INSTDIR\ssleay32.dll"
${EndIf}
@CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@
@CPACK_NSIS_FULL_INSTALL@