Allow only one installer instance: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
(Check CreateMutex GetLastError against ERROR_ALREADY_EXISTS. Compare caption with $(^SetupCaption) and not $(^Name).)
(Simplified mutex code and added file code)
 
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]].


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


== Note ==
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:
<span style="color:red">'''You should check the return value against ERROR_ALREADY_EXISTS (183) rather than 0. CreateMutex can set other errors such as ERROR_ACCESS_DENIED which results in a false positive.'''</span>


== The Script ==
Put this code in the .onInit function:
<highlight-nsis>
<highlight-nsis>
  System::Call 'kernel32::CreateMutex(i 0, i 0, t "myMutex") ?e'
!define INSTALLERMUTEXNAME "$(^Name)" ; TODO: Should really use a GUID here! (guidgenerator.com or guidgen.com)
  Pop $R0
 
  StrCmp $R0 0 +3
!ifndef NSIS_PTR_SIZE & SYSTYPE_PTR
    MessageBox MB_OK "The installer is already running."
!define SYSTYPE_PTR i ; NSIS v2.x
    Abort
!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
</highlight-nsis>
</highlight-nsis>
'myMutex' should be replaced with a unique value (e.g. "MyCompany.MyApp" or a [http://www.guidgenerator.com/ generate] [http://www.guidgen.com/ a unique] [https://developer.mozilla.org/en/docs/Generating_GUIDs GUID]).


== 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.
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>
A open file handle that denies shared access can be used to detect a existing instance:
System::Call "kernel32::CreateMutex(i 0, i 0, t '$(^Name)') i .r0 ?e"
Pop $0
StrCmp $0 0 launch
  StrLen $0 "$(^Name)"
  IntOp $0 $0 + 1
loop:
  FindWindow $1 '#32770' '' 0 $1
  IntCmp $1 0 +4
  System::Call "user32::GetWindowText(i r1, t .r2, i r0) i."
  StrCmp $2 "$(^Name)" 0 loop
  System::Call "user32::SetForegroundWindow(i r1) i."
  Abort
launch:
</highlight-nsis>


== Even more advanced ==
This uses the "advanced way" method.  Unlike the function above, it will restore the window if the installer is minimized.
<highlight-nsis>
<highlight-nsis>
!define INSTALLERMUTEXNAME "$(^Name)" ; TODO: Should really use a GUID here!
#TODO !define INSTALLERLOCKFILEGUID "{....}" ; Use a GUID here! (guidgenerator.com or guidgen.com)


Function .onInit
!macro SingleInstanceFile
BringToFront
!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
!ifndef NSIS_PTR_SIZE & SYSTYPE_PTR
!define SYSTYPE_PTR i ; NSIS v2.4x
!define SYSTYPE_PTR i ; NSIS v2.x
!else
!else
!define /ifndef SYSTYPE_PTR p ; NSIS v3.0+
!define /ifndef SYSTYPE_PTR p ; NSIS v3.0+
!endif
!endif
System::Call 'kernel32::CreateMutex(${SYSTYPE_PTR}0, i1, t"${INSTALLERMUTEXNAME}")?e'
!if "${NSIS_CHAR_SIZE}" < 2
Pop $0
Push "$TEMP\${INSTALLERLOCKFILEGUID}.lock"
IntCmpU $0 183 0 launch launch ; ERROR_ALREADY_EXISTS
!else
StrLen $0 "$(^SetupCaption)"
Push "$APPDATA\${INSTALLERLOCKFILEGUID}.lock"
IntOp $0 $0 + 1 ; GetWindowText count includes \0
!endif
StrCpy $1 "" ; Start FindWindow with NULL
System::Call 'KERNEL32::CreateFile(ts,i0x40000000,i0,${SYSTYPE_PTR}0,i4,i0x04000000,${SYSTYPE_PTR}0)${SYSTYPE_PTR}.r0'
loop:
${IntPtrCmp} $0 -1 "" launch launch
FindWindow $1 "#32770" "" "" $1
MessageBox MB_ICONSTOP "Already running!"
StrCmp 0 $1 notfound
Abort
System::Call 'user32::GetWindowText(${SYSTYPE_PTR}r1, t.r2, ir0)'
StrCmp $2 "$(^SetupCaption)" 0 loop
SendMessage $1 0x112 0xF120 0 /TIMEOUT=2000 ; WM_SYSCOMMAND:SC_RESTORE to restore the window if it is minimized
System::Call "user32::SetForegroundWindow(${SYSTYPE_PTR}r1)"
notfound:
Abort
launch:
launch:
!macroend
Function .onInit
!insertmacro SingleInstanceFile
FunctionEnd
Function un.onInit
!insertmacro SingleInstanceFile
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