Add installer new option '/closeRunningNpp'

New Notepad++ NSIS installer cmdline option.

If specified on the cmdline, it first tries to use the usual app-closing by sending the WM_CLOSE message to the running Notepad++.
If that standard closing fails, it uses consequently the forceful TerminateProcess WINAPI way.

Partially fix the #8514, followup of the #14251.

Close #15230
pull/15267/head
xomx 2024-06-01 17:29:35 +02:00 committed by Don Ho
parent aea78d71e2
commit 83f1195bc1
2 changed files with 75 additions and 0 deletions

View File

@ -114,6 +114,7 @@ InstType "Minimalist"
Var diffArchDir2Remove Var diffArchDir2Remove
Var noUpdater Var noUpdater
Var closeRunningNpp
!ifdef ARCH64 || ARCHARM64 !ifdef ARCH64 || ARCHARM64
@ -150,6 +151,21 @@ Function .onInit
; ;
; --- PATCH END --- ; --- PATCH END ---
; check for the possible "/closeRunningNpp" cmdline option 1st
${GetParameters} $R0
${GetOptions} $R0 "/closeRunningNpp" $R1 ; case insensitive
IfErrors 0 closeRunningNppYes
StrCpy $closeRunningNpp "false"
Goto closeRunningNppCheckDone
closeRunningNppYes:
StrCpy $closeRunningNpp "true"
closeRunningNppCheckDone:
${If} $closeRunningNpp == "true"
; First try to use the usual app-closing by sending the WM_CLOSE.
; If that closing fails, use the forceful TerminateProcess way.
!insertmacro FindAndCloseOrTerminateRunningNpp ; this has to precede the following silent mode Notepad++ instance mutex check
${EndIf}
; handle the possible Silent Mode (/S) & already running Notepad++ (without this an incorrect partial installation is possible) ; handle the possible Silent Mode (/S) & already running Notepad++ (without this an incorrect partial installation is possible)
IfSilent 0 notInSilentMode IfSilent 0 notInSilentMode
System::Call 'kernel32::OpenMutex(i 0x100000, b 0, t "nppInstance") i .R0' System::Call 'kernel32::OpenMutex(i 0x100000, b 0, t "nppInstance") i .R0'

View File

@ -208,3 +208,62 @@ Function writeInstallInfoInRegistry
WriteUninstaller "$INSTDIR\uninstall.exe" WriteUninstaller "$INSTDIR\uninstall.exe"
FunctionEnd FunctionEnd
!define RUNPROC_WND_CLASS "Notepad++"
!define RUNPROC_WAIT_FOR_EXIT_MAX_MS 5000 ; 5 seconds max
!define RUNPROC_SYNC_TERM 0x00100001 ; dwDesiredAccess ... PROCESS_TERMINATE | SYNCHRONIZE
!include WinMessages.nsh
!macro FindAndCloseOrTerminateRunningNpp
; to not influence the global NSIS vars used here, push them on the stack and pop them out at the end
Push $0 ; running process main HWND
Push $1 ; result of the waiting for the running process object
Push $2 ; running process HANDLE
Push $3 ; possible WIN32 error code (the GetLastError() result)
findRunningProcessByClassName:
FindWindow $0 '${RUNPROC_WND_CLASS}' ''
IntPtrCmp $0 0 processNotRunning
IsWindow $0 0 processNotRunning
IfSilent skipDetailPrint 0
DetailPrint "Closing the ${RUNPROC_WND_CLASS} app running..."
skipDetailPrint:
System::Call 'user32.dll::GetWindowThreadProcessId(i r0, *i .r1) i .r2'
System::Call 'kernel32.dll::OpenProcess(i ${RUNPROC_SYNC_TERM}, i 0, i r1) p .r2 ?e' ; ?e ... the NSIS system plugin will additionally put the GetLastError() code on top of the stack
pop $3 ; a possible WIN32 error code will be here
IntPtrCmp $2 0 openProcessFail
System::Call 'user32.dll::PostMessage(i $0, i ${WM_CLOSE}, i 0, i 0)'
System::Call 'kernel32.dll::WaitForSingleObject(i r2, i ${RUNPROC_WAIT_FOR_EXIT_MAX_MS}) i .r1'
IntCmp $1 0 closeProcessHandle ; 0 == WAIT_OBJECT_0 (signaled state of the process to close...)
; process could not be stopped by the usual WM_CLOSE way, so use a hard termination instead
IfSilent terminateProcess 0
MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION "Installer cannot stop the running ${RUNPROC_WND_CLASS} by usual closing request.$\n$\nDo you want to forcefully terminate that process?" /SD IDYES IDYES terminateProcess IDNO closeProcessHandle
; cancel was selected, so close the opened running process handle and quit immediately
System::Call 'kernel32.dll::CloseHandle(i r2) i .r1'
SetErrorLevel 5 ; set an exit code > 0 otherwise the installer returns 0 aka SUCCESS (5 == ERROR_ACCESS_DENIED)
Quit ; installer will end
terminateProcess:
System::Call 'kernel32.dll::TerminateProcess(i r2, i 0) i .r1'
closeProcessHandle:
System::Call 'kernel32.dll::CloseHandle(i r2) i .r1'
goto findRunningProcessByClassName ; loop, we need to check for all the possible instances of the process running
openProcessFail:
IfSilent skipOpenProcessFailMessage 0
MessageBox MB_OK|MB_ICONSTOP "Installer cannot stop the running ${RUNPROC_WND_CLASS}.\n\nOpenProcess WINAPI failed! (error code: $3)"
skipOpenProcessFailMessage:
SetErrorLevel $3 ; set an exit code > 0 otherwise the installer returns 0 aka SUCCESS
Quit ; installer will end
processNotRunning:
Pop $3
Pop $2
Pop $1
Pop $0
!macroend