Allow only one installer instance: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
No edit summary
(Check CreateMutex GetLastError against ERROR_ALREADY_EXISTS. Compare caption with $(^SetupCaption) and not $(^Name).)
Line 42: Line 42:


== Even more advanced ==
== 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 XP 64-bit, but on Windows 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".
This uses the "advanced way" method.  Unlike the function above, it will restore the window if the installer is minimized.
Note (smartcom): Tested on Windows Vista and on Windows 7 64 bit and no problem found.
<highlight-nsis>
<highlight-nsis>
!define INSTALLERMUTEXNAME "$(^Name)" ; TODO: Should really use a GUID here!
Function .onInit
Function .onInit
  BringToFront
BringToFront
; Check if already running
!ifndef NSIS_PTR_SIZE & SYSTYPE_PTR
; If so don't open another but bring to front
!define SYSTYPE_PTR i ; NSIS v2.4x
  System::Call "kernel32::CreateMutex(i 0, i 0, t '$(^Name)') i .r0 ?e"
!else
  Pop $0
!define /ifndef SYSTYPE_PTR p ; NSIS v3.0+
  StrCmp $0 0 launch
!endif
  StrLen $0 "$(^Name)"
System::Call 'kernel32::CreateMutex(${SYSTYPE_PTR}0, i1, t"${INSTALLERMUTEXNAME}")?e'
  IntOp $0 $0 + 1
Pop $0
  loop:
IntCmpU $0 183 0 launch launch ; ERROR_ALREADY_EXISTS
    FindWindow $1 '#32770' '' 0 $1
StrLen $0 "$(^SetupCaption)"
    IntCmp $1 0 +5
IntOp $0 $0 + 1 ; GetWindowText count includes \0
    System::Call "user32::GetWindowText(i r1, t .r2, i r0) i."
StrCpy $1 "" ; Start FindWindow with NULL
    StrCmp $2 "$(^Name)" 0 loop
loop:
    System::Call "user32::ShowWindow(i r1,i 9) i."        ; If minimized then restore
FindWindow $1 "#32770" "" "" $1
    System::Call "user32::SetForegroundWindow(i r1) i."   ; Bring to front
StrCmp 0 $1 notfound
    Abort
System::Call 'user32::GetWindowText(${SYSTYPE_PTR}r1, t.r2, ir0)'
  launch:
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:
FunctionEnd
FunctionEnd
</highlight-nsis>
</highlight-nsis>

Revision as of 18:58, 7 July 2014

Author: Vytautas (talk, contrib)


Description

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 process hacker - open process properties, handles tab: Mutant, \BaseNamedObjects\myMutex, 0xd0

Note

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.

The Script

Put this code in the .onInit function:

  System::Call 'kernel32::CreateMutex(i 0, i 0, t "myMutex") ?e'
  Pop $R0
  StrCmp $R0 0 +3
    MessageBox MB_OK "The installer is already running."
    Abort

'myMutex' should be replaced with a unique value (e.g. "MyCompany.MyApp" or a generate a unique GUID).

The advanced way

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 (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()

 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:

Even more advanced

This uses the "advanced way" method. Unlike the function above, it will restore the window if the installer is minimized.

!define INSTALLERMUTEXNAME "$(^Name)" ; TODO: Should really use a GUID here!
 
Function .onInit
BringToFront
!ifndef NSIS_PTR_SIZE & SYSTYPE_PTR
!define SYSTYPE_PTR i ; NSIS v2.4x
!else
!define /ifndef SYSTYPE_PTR p ; NSIS v3.0+
!endif
System::Call 'kernel32::CreateMutex(${SYSTYPE_PTR}0, i1, t"${INSTALLERMUTEXNAME}")?e'
Pop $0
IntCmpU $0 183 0 launch launch ; ERROR_ALREADY_EXISTS
	StrLen $0 "$(^SetupCaption)"
	IntOp $0 $0 + 1 ; GetWindowText count includes \0
	StrCpy $1 "" ; Start FindWindow with NULL
	loop:
		FindWindow $1 "#32770" "" "" $1
		StrCmp 0 $1 notfound
		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:
FunctionEnd

See also

Check whether your application is running