[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.
This commit is contained in:
Lukas Brübach 2026-09-20 20:59:08 +02:00 committed by GitHub
parent 6d06ae2bcf
commit 1b7f9c0de4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -16,7 +16,6 @@ Var ReinstallMode
!include LogicLib.nsh !include LogicLib.nsh
!include FileFunc.nsh !include FileFunc.nsh
!include MUI2.nsh !include MUI2.nsh
!include nsExec.nsh
!include x64.nsh !include x64.nsh
!define MUI_ABORTWARNING !define MUI_ABORTWARNING
@ -217,10 +216,16 @@ FunctionEnd
; old and new Qt DLLs next to the new executable, failing with ; 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 ; "The procedure entry point X could not be located in the dynamic link
; library ...Qt6Network.dll" on the next start. ; 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 Function IsAppRunning
; usage: set $R1 to the image name, call this, result in $R0 (1 = running, 0 = not running) nsExec::ExecToLog 'powershell -NoProfile -Command "Get-Process -Name $\'$R2$\' -ErrorAction SilentlyContinue | Where-Object { $$_.Path -like $\'$INSTDIR\*$\' } | Select-Object -First 1"'
nsExec::Exec 'cmd /c tasklist /FI "IMAGENAME eq $R1" /FO CSV /NH | findstr /I "$R1" >nul'
Pop $R0 Pop $R0
${If} $R0 = 0 ${If} $R0 = 0
StrCpy $R0 1 StrCpy $R0 1
@ -229,8 +234,20 @@ Function IsAppRunning
${EndIf} ${EndIf}
FunctionEnd 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 Function WaitForAppToClose
; usage: set $R1 to the image name ; usage: set $R1 to the display name (e.g. "cockatrice.exe") and $R2 to the
; base image name (e.g. "cockatrice")
Call IsAppRunning Call IsAppRunning
${If} $R0 = 0 ${If} $R0 = 0
Return Return
@ -239,8 +256,7 @@ Function WaitForAppToClose
${If} ${Silent} ${If} ${Silent}
; ask the application to close gracefully (WM_CLOSE), then wait for it to exit ; ask the application to close gracefully (WM_CLOSE), then wait for it to exit
DetailPrint "Closing $R1 ..." DetailPrint "Closing $R1 ..."
nsExec::Exec 'cmd /c taskkill /IM $R1' Call CloseMatchingApps
Pop $R2
StrCpy $R8 0 StrCpy $R8 0
ck_wait_loop: ck_wait_loop:
Sleep 500 Sleep 500
@ -255,8 +271,7 @@ Function WaitForAppToClose
${EndIf} ${EndIf}
; give up waiting, force close ; give up waiting, force close
DetailPrint "Force closing $R1 ..." DetailPrint "Force closing $R1 ..."
nsExec::Exec 'cmd /c taskkill /F /IM $R1' Call ForceCloseMatchingApps
Pop $R2
Sleep 500 Sleep 500
${Else} ${Else}
ck_wait_prompt: ck_wait_prompt:
@ -275,17 +290,20 @@ FunctionEnd
Function EnsureAppsNotRunning Function EnsureAppsNotRunning
StrCpy $R1 "cockatrice.exe" StrCpy $R1 "cockatrice.exe"
StrCpy $R2 "cockatrice"
Call WaitForAppToClose Call WaitForAppToClose
StrCpy $R1 "oracle.exe" StrCpy $R1 "oracle.exe"
StrCpy $R2 "oracle"
Call WaitForAppToClose Call WaitForAppToClose
StrCpy $R1 "servatrice.exe" StrCpy $R1 "servatrice.exe"
StrCpy $R2 "servatrice"
Call WaitForAppToClose Call WaitForAppToClose
FunctionEnd FunctionEnd
; Uninstaller copies of the same routines (the uninstaller gets its own ; Uninstaller copies of the same routines (the uninstaller gets its own
; function set compiled in, it cannot call the installer functions). ; function set compiled in, it cannot call the installer functions).
Function un.IsAppRunning Function un.IsAppRunning
nsExec::Exec 'cmd /c tasklist /FI "IMAGENAME eq $R1" /FO CSV /NH | findstr /I "$R1" >nul' nsExec::ExecToLog 'powershell -NoProfile -Command "Get-Process -Name $\'$R2$\' -ErrorAction SilentlyContinue | Where-Object { $$_.Path -like $\'$INSTDIR\*$\' } | Select-Object -First 1"'
Pop $R0 Pop $R0
${If} $R0 = 0 ${If} $R0 = 0
StrCpy $R0 1 StrCpy $R0 1
@ -294,6 +312,16 @@ Function un.IsAppRunning
${EndIf} ${EndIf}
FunctionEnd 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 Function un.WaitForAppToClose
Call un.IsAppRunning Call un.IsAppRunning
${If} $R0 = 0 ${If} $R0 = 0
@ -302,8 +330,7 @@ Function un.WaitForAppToClose
${If} ${Silent} ${If} ${Silent}
DetailPrint "Closing $R1 ..." DetailPrint "Closing $R1 ..."
nsExec::Exec 'cmd /c taskkill /IM $R1' Call un.CloseMatchingApps
Pop $R2
StrCpy $R8 0 StrCpy $R8 0
un_ck_wait_loop: un_ck_wait_loop:
Sleep 500 Sleep 500
@ -317,8 +344,7 @@ Function un.WaitForAppToClose
Goto un_ck_wait_loop Goto un_ck_wait_loop
${EndIf} ${EndIf}
DetailPrint "Force closing $R1 ..." DetailPrint "Force closing $R1 ..."
nsExec::Exec 'cmd /c taskkill /F /IM $R1' Call un.ForceCloseMatchingApps
Pop $R2
Sleep 500 Sleep 500
${Else} ${Else}
un_ck_wait_prompt: un_ck_wait_prompt:
@ -337,10 +363,13 @@ FunctionEnd
Function un.EnsureAppsNotRunning Function un.EnsureAppsNotRunning
StrCpy $R1 "cockatrice.exe" StrCpy $R1 "cockatrice.exe"
StrCpy $R2 "cockatrice"
Call un.WaitForAppToClose Call un.WaitForAppToClose
StrCpy $R1 "oracle.exe" StrCpy $R1 "oracle.exe"
StrCpy $R2 "oracle"
Call un.WaitForAppToClose Call un.WaitForAppToClose
StrCpy $R1 "servatrice.exe" StrCpy $R1 "servatrice.exe"
StrCpy $R2 "servatrice"
Call un.WaitForAppToClose Call un.WaitForAppToClose
FunctionEnd FunctionEnd
@ -471,6 +500,7 @@ ${EndIf}
Call EnsureAppsNotRunning Call EnsureAppsNotRunning
${If} $PortableMode = 0 ${If} $PortableMode = 0
${AndIf} ${FileExists} "$INSTDIR\cockatrice.exe"
RMDir /r "$INSTDIR\Plugins" RMDir /r "$INSTDIR\Plugins"
Delete "$INSTDIR\Qt*.dll" Delete "$INSTDIR\Qt*.dll"
Delete "$INSTDIR\libcrypto*.dll" Delete "$INSTDIR\libcrypto*.dll"