Allow only one installer instance: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
m (→‎Even more advanced: Copy edited. Expansion.)
(Simplified mutex code and added file code)
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{PageAuthor|Vytautas}}
{{PageAuthor|Anders}}
[[Category:System Plugin Examples]]


== Description ==
== Mutex ==
The best way is to use a kernel mutex. You can call the Win32 API using the [[System plug-in]]. See also [[CreateMutex plug-in]].


Put this code in the .onInit function:


== Unicode warning ==
A [http://docs.microsoft.com/en-us/windows/win32/sync/mutex-objects mutex] is perhaps the best way to detect if another instance is running:
Warning! You should use "CreateMutexW" instead of "CreateMutexA" for unicode NSIS! Using a wrong one like
 
<highlight-nsis>
<highlight-nsis>
System::Call 'kernel32::CreateMutexA(i 0, i 0, t "abcdef") ?e'
!define INSTALLERMUTEXNAME "$(^Name)" ; TODO: Should really use a GUID here! (guidgenerator.com or guidgen.com)
</highlight-nsis>
 
will silently create a mutex named "a", not "abcdef"!
!ifndef NSIS_PTR_SIZE & SYSTYPE_PTR
!define SYSTYPE_PTR i ; NSIS v2.x
!else
!define /ifndef SYSTYPE_PTR p ; NSIS v3.0+
!endif
 
!macro ActivateOtherInstance
StrCpy $3 "" ; Start FindWindow with NULL
loop:
FindWindow $3 "#32770" "" "" $3
StrCmp 0 $3 windownotfound
StrLen $0 "$(^UninstallCaption)"
IntOp $0 $0 + 1 ; GetWindowText count includes \0
System::Call 'USER32::GetWindowText(${SYSTYPE_PTR}r3, t.r0, ir0)'
StrCmp $0 "$(^UninstallCaption)" windowfound ""
StrLen $0 "$(^SetupCaption)"
IntOp $0 $0 + 1 ; GetWindowText count includes \0
System::Call 'USER32::GetWindowText(${SYSTYPE_PTR}r3, t.r0, ir0)'
StrCmp $0 "$(^SetupCaption)" windowfound loop
windowfound:
SendMessage $3 0x112 0xF120 0 /TIMEOUT=2000 ; WM_SYSCOMMAND:SC_RESTORE to restore the window if it is minimized
System::Call "USER32::SetForegroundWindow(${SYSTYPE_PTR}r3)"
windownotfound:
!macroend
 
!macro SingleInstanceMutex
!ifndef INSTALLERMUTEXNAME
!error "Must define INSTALLERMUTEXNAME"
!endif
System::Call 'KERNEL32::CreateMutex(${SYSTYPE_PTR}0, i1, t"${INSTALLERMUTEXNAME}")?e'
Pop $0
IntCmpU $0 183 "" launch launch ; ERROR_ALREADY_EXISTS?
!insertmacro ActivateOtherInstance
Abort
launch:
!macroend


You can list all mutex objects using [http://processhacker.sourceforge.net/ process hacker] - open process properties, handles tab: Mutant, \BaseNamedObjects\myMutex, 0xd0
Function .onInit
!insertmacro SingleInstanceMutex
FunctionEnd


== The Script ==
Function un.onInit
<highlight-nsis>
!insertmacro SingleInstanceMutex
  System::Call 'kernel32::CreateMutexA(i 0, i 0, t "myMutex") ?e'
FunctionEnd
  Pop $R0
  StrCmp $R0 0 +3
    MessageBox MB_OK "The installer is already running."
    Abort
</highlight-nsis>
</highlight-nsis>
'myMutex' should be replaced with a unique value (e.g. ProgramSetup.exe).


== The advanced way ==
== File ==
This code will bring the already running instance to front, instead of opening a new one.
 
Side Note (Jamyn): Strangely, if the user has first minimized the installer, and then tries to launch another copy, the following code will correctly set the first installer as the active window, but it won't "pop" it back up from being minimized. I am not sure how to do that simply, but that may be useful.
A open file handle that denies shared access can be used to detect a existing instance:
Note ([http://www.emsai.net Rescator]): Try something like System::Call "user32::ShowWindow(i r1, i 9) i." The 9 is the flag #SW_RESTORE see PSDK for more info on ShowWindow()


<highlight-nsis>
<highlight-nsis>
System::Call "kernel32::CreateMutexA(i 0, i 0, t '$(^Name)') i .r0 ?e"
#TODO !define INSTALLERLOCKFILEGUID "{....}" ; Use a GUID here! (guidgenerator.com or guidgen.com)
Pop $0
 
StrCmp $0 0 launch
!macro SingleInstanceFile
  StrLen $0 "$(^Name)"
!ifndef INSTALLERLOCKFILEGUID
  IntOp $0 $0 + 1
!error "Must define INSTALLERLOCKFILEGUID"
loop:
!endif
  FindWindow $1 '#32770' '' 0 $1
!if "${NSIS_PTR_SIZE}" > 4
  IntCmp $1 0 +4
!include util.nsh
  System::Call "user32::GetWindowText(i r1, t .r2, i r0) i."
!else ifndef IntPtrCmp
  StrCmp $2 "$(^Name)" 0 loop
!define IntPtrCmp IntCmp
  System::Call "user32::SetForegroundWindow(i r1) i."
!endif
  Abort
!ifndef NSIS_PTR_SIZE & SYSTYPE_PTR
launch:
!define SYSTYPE_PTR i ; NSIS v2.x
</highlight-nsis>
!else
!define /ifndef SYSTYPE_PTR p ; NSIS v3.0+
!endif
!if "${NSIS_CHAR_SIZE}" < 2
Push "$TEMP\${INSTALLERLOCKFILEGUID}.lock"
!else
Push "$APPDATA\${INSTALLERLOCKFILEGUID}.lock"
!endif
System::Call 'KERNEL32::CreateFile(ts,i0x40000000,i0,${SYSTYPE_PTR}0,i4,i0x04000000,${SYSTYPE_PTR}0)${SYSTYPE_PTR}.r0'
${IntPtrCmp} $0 -1 "" launch launch
MessageBox MB_ICONSTOP "Already running!"
Abort
launch:
!macroend


== Even more advanced ==
This uses the "advanced way" method.  Unlike the function above, it will restore the window if the installer is minimized. Note (Akshay): I tested this and it works on Windows&nbsp;XP 64-bit, but on Windows&nbsp;7 the installer fails, saying "Installer integrity check has failed. Common causes include incomplete download and damaged media. Contact the installer's author to obtain a new copy. More information is at http://nsis.sf.net/NSIS_Error".
Note (smartcom): Tested on Windows&nbsp;Vista and on Windows&nbsp;7 64 bit and no problem found.
<highlight-nsis>
Function .onInit
Function .onInit
  BringToFront
!insertmacro SingleInstanceFile
; Check if already running
FunctionEnd
; If so don't open another but bring to front
 
  System::Call "kernel32::CreateMutexA(i 0, i 0, t '$(^Name)') i .r0 ?e"
Function un.onInit
  Pop $0
!insertmacro SingleInstanceFile
  StrCmp $0 0 launch
  StrLen $0 "$(^Name)"
  IntOp $0 $0 + 1
  loop:
    FindWindow $1 '#32770' '' 0 $1
    IntCmp $1 0 +5
    System::Call "user32::GetWindowText(i r1, t .r2, i r0) i."
    StrCmp $2 "$(^Name)" 0 loop
    System::Call "user32::ShowWindow(i r1,i 9) i."        ; If minimized then restore
    System::Call "user32::SetForegroundWindow(i r1) i."    ; Bring to front
    Abort
  launch:
FunctionEnd
FunctionEnd
</highlight-nsis>
</highlight-nsis>


== See also ==
== See also ==
[[Check whether your application is running]]


[[Category:System Plugin Examples]]
* [[CreateMutex plug-in]]
* [[Check whether your application is running]]

Latest revision as of 18:31, 4 September 2019

Author: Anders (talk, contrib)


Mutex

A mutex is perhaps the best way to detect if another instance is running:

!define INSTALLERMUTEXNAME "$(^Name)" ; TODO: Should really use a GUID here! (guidgenerator.com or guidgen.com)
 
!ifndef NSIS_PTR_SIZE & SYSTYPE_PTR
!define SYSTYPE_PTR i ; NSIS v2.x
!else
!define /ifndef SYSTYPE_PTR p ; NSIS v3.0+
!endif
 
!macro ActivateOtherInstance
StrCpy $3 "" ; Start FindWindow with NULL
loop:
	FindWindow $3 "#32770" "" "" $3
	StrCmp 0 $3 windownotfound
	StrLen $0 "$(^UninstallCaption)"
	IntOp $0 $0 + 1 ; GetWindowText count includes \0
	System::Call 'USER32::GetWindowText(${SYSTYPE_PTR}r3, t.r0, ir0)'
	StrCmp $0 "$(^UninstallCaption)" windowfound ""
	StrLen $0 "$(^SetupCaption)"
	IntOp $0 $0 + 1 ; GetWindowText count includes \0
	System::Call 'USER32::GetWindowText(${SYSTYPE_PTR}r3, t.r0, ir0)'
	StrCmp $0 "$(^SetupCaption)" windowfound loop
windowfound:
	SendMessage $3 0x112 0xF120 0 /TIMEOUT=2000 ; WM_SYSCOMMAND:SC_RESTORE to restore the window if it is minimized
	System::Call "USER32::SetForegroundWindow(${SYSTYPE_PTR}r3)"
windownotfound:
!macroend
 
!macro SingleInstanceMutex
!ifndef INSTALLERMUTEXNAME
!error "Must define INSTALLERMUTEXNAME"
!endif
System::Call 'KERNEL32::CreateMutex(${SYSTYPE_PTR}0, i1, t"${INSTALLERMUTEXNAME}")?e'
Pop $0
IntCmpU $0 183 "" launch launch ; ERROR_ALREADY_EXISTS?
	!insertmacro ActivateOtherInstance
	Abort
launch:
!macroend
 
Function .onInit
!insertmacro SingleInstanceMutex
FunctionEnd
 
Function un.onInit
!insertmacro SingleInstanceMutex
FunctionEnd

File

A open file handle that denies shared access can be used to detect a existing instance:

#TODO !define INSTALLERLOCKFILEGUID "{....}" ; Use a GUID here! (guidgenerator.com or guidgen.com)
 
!macro SingleInstanceFile
!ifndef INSTALLERLOCKFILEGUID
!error "Must define INSTALLERLOCKFILEGUID"
!endif
!if "${NSIS_PTR_SIZE}" > 4
!include util.nsh
!else ifndef IntPtrCmp
!define IntPtrCmp IntCmp
!endif
!ifndef NSIS_PTR_SIZE & SYSTYPE_PTR
!define SYSTYPE_PTR i ; NSIS v2.x
!else
!define /ifndef SYSTYPE_PTR p ; NSIS v3.0+
!endif
!if "${NSIS_CHAR_SIZE}" < 2
Push "$TEMP\${INSTALLERLOCKFILEGUID}.lock"
!else
Push "$APPDATA\${INSTALLERLOCKFILEGUID}.lock"
!endif
System::Call 'KERNEL32::CreateFile(ts,i0x40000000,i0,${SYSTYPE_PTR}0,i4,i0x04000000,${SYSTYPE_PTR}0)${SYSTYPE_PTR}.r0'
${IntPtrCmp} $0 -1 "" launch launch
	MessageBox MB_ICONSTOP "Already running!"
	Abort
launch:
!macroend
 
Function .onInit
!insertmacro SingleInstanceFile
FunctionEnd
 
Function un.onInit
!insertmacro SingleInstanceFile
FunctionEnd

See also