Archive: Prevent Multiple Instances with UMUI


Bug in macro UMUI_FUNCTION_MULTILANGUAGEPAGE in UMUI
  The following code finds a running instance on language change:

Name Test

OutFile Test
.exe

>!include UMUI.nsh

>!insertmacro UMUI_PAGE_MULTILANGUAGE
>!insertmacro MUI_PAGE_WELCOME

>!insertmacro MUI_LANGUAGE English
>!insertmacro MUI_LANGUAGE German

Section Install
SectionEnd

>Function .onInit
System::Call 'kernel32::CreateMutexA(i 0, i 0, t "myMutex") i .r1 ?e'
Pop $R0
StrCmp $R0 0+3
MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running."
Abort
!insertmacro UMUI_MULTILANG_GET
FunctionEnd
>
This is due to a bug in the macro UMUI_FUNCTION_MULTILANGUAGEPAGE where ExecWait is used instead of Exec. Change lines 5212-5216 to:

ifndef MUI_UNINSTALLER

Exec "$R0 $R1 /L=$LANGUAGE /NCRC"
>!else
Exec "$R0 $R1 /L=$LANGUAGE _?=$INSTDIR"
>!endif
Best Regards,
Yves

ExecWait was used because the Exec function break the NSIS UAC plugin:

Quote:


You may be use this code:


Name Test
OutFile Test.exe

!include UMUI.nsh

!insertmacro UMUI_PAGE_MULTILANGUAGE
!insertmacro MUI_PAGE_WELCOME

!insertmacro MUI_LANGUAGE English
!insertmacro MUI_LANGUAGE German

Section Install
SectionEnd

Function .onInit

;This macro set the UMUI_LANGISSET install flag if a langid if it is passed in the commandline
!insertmacro UMUI_MULTILANG_GET

;If the langid is not passeed in the command line
!insertmacro UMUI_IF_INSTALLFLAG_ISNOT ${UMUI_LANGISSET}

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

!insertmacro UMUI_ENDIF_INSTALLFLAG

FunctionEnd

Originally posted by LoRd_MuldeR
I noticed a serious problem with UMUI and the UAC plugin: The UAC plugin will create two processes. The "outer" process is running with user privileges, the "inner" one with elevated rights. The "inner" process is the actual installer, but whenever it needs to do something as the normal user (e.g. launch an application that we don't want elevated) it will ask to the "outer" process to do that. So far this works fine and fixed my problems.

Now the UMUI problem: When UMUI changes the language, it will re-start the installer. When using UAC this means: The "inner" process will terminate (and re-start), the "outer" process will terminate after the "inner" has terminated. Now only the "inner" process (the new instance created by UMUI after language switch) is running, but the "outer" process is missing. Consequently all actions that needed the "outer" process will fail (do nothing in fact).

How can I workaround that problem? Thanks in advance :)


Thanks for your quick answer. But this means that if someone is calling the installer with the language flag set (s)he can call it as often as (s)he likes... it's not 100% but it is working.

Isn't there a way to check that the "new" installer is run depending on the "old" one?

Best Regards,
Yves


You can use two mutex to prevent the execution:


Name Test
OutFile Test.exe

!include UMUI.nsh

!insertmacro UMUI_PAGE_MULTILANGUAGE
!insertmacro MUI_PAGE_WELCOME

!insertmacro MUI_LANGUAGE English
!insertmacro MUI_LANGUAGE German

Section Install
SectionEnd

Function .onInit

;This macro set the UMUI_LANGISSET install flag if a langid if it is passed in the commandline
!insertmacro UMUI_MULTILANG_GET

;If the langid is not passeed in the command line
!insertmacro UMUI_IF_INSTALLFLAG_IS ${UMUI_LANGISSET}

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

!insertmacro UMUI_ENDIF_INSTALLFLAG
!insertmacro UMUI_IF_INSTALLFLAG_ISNOT ${UMUI_LANGISSET}

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

!insertmacro UMUI_ENDIF_INSTALLFLAG

FunctionEnd

Thanks for your quick suggestion.

This works 99,99%; however I can still run a maximum of 2 installers, one with default language and one with language flag set on start.

Isn't there a way to check that the one executed from within the installer is dependant on the installer? This would be 100%... ;-)

Best Regards,
Yves


A little improvement with the two mutex methods to prevent the execution:


Name Test
OutFile Test.exe

!include UMUI.nsh

!insertmacro UMUI_PAGE_MULTILANGUAGE
!insertmacro MUI_PAGE_WELCOME

!insertmacro MUI_LANGUAGE English
!insertmacro MUI_LANGUAGE German

Section Install
SectionEnd

Function .onInit

;This macro set the UMUI_LANGISSET install flag if a langid if it is passed in the commandline
!insertmacro UMUI_MULTILANG_GET

;If the langid is not passeed in the command line
!insertmacro UMUI_IF_INSTALLFLAG_IS ${UMUI_LANGISSET}

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

; create the second mutex to prevent the instalelr to be relaunch without the /L parameter
System::Call 'kernel32::CreateMutexA(i 0, i 0, t "myMutex2") i .r1 ?e'
Pop $R0

!insertmacro UMUI_ENDIF_INSTALLFLAG
!insertmacro UMUI_IF_INSTALLFLAG_ISNOT ${UMUI_LANGISSET}

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

!insertmacro UMUI_ENDIF_INSTALLFLAG

FunctionEnd

I have finally managed to program what i described/asked for. The following .onInit function will check if the newly launched installer (if mutex fails) is a subprocess (the language has been chosen in UMUI) or if it is a new process (another instance of the installer has been launched).


onInit

!insertmacro UMUI_MULTILANG_GET
System
::Call 'kernel32::CreateMutexA(i 0, i 0, t "MutexA") ?e'
Pop $R0
${IfNot} 0 = $R0
# MutexA already exists!
# Now the choice has to be made:
# 1) another installer lauch (new process)
# 2) language selection in UMUI (subprocess)
# First get the process id of the current process and store it in $R0
System::Call "kernel32::GetCurrentProcessId() i .R0"
# Then create a snapshot of all running processes
System::Call "kernel32::CreateToolhelp32Snapshot(i 0x00000002, i 0) i .r0"
# And prepare the PROCESSENTRY32 structure
System::Call "*(i, i, i, i, i, i, i, i, i, &t1024) i .r1"
# Get the size of the PROCESSENTRY32 structure
System::Call "*$1(_, &l0 .r2)"
# Set the size of the PROCESSENTRY32 structure in its first member (as required)
System::Call "*$1(i r2, i, i, i, i, i, i, i, i, &t1024)"
# Retrieve the information about the first process
System::Call "kernel32::Process32First(i r0, i r1) i .r2"
# Loop as long as the call is successful (TRUE)
${DoWhile} 1 = $2
# Retrieve the members "process id" in $R1 and "parent process id" in $R2
System::Call "*$1(i, i, i .R1, i, i, i, i .R2, i, i, &t1024)"
# Check if the current process and the one just looking at in the snapshot have the same id
${If} $R0 = $R1
# If they so exit the loop
${Break}
${EndIf}
# If they do not retrieve the information about the next process
System::Call "kernel32::Process32Next(i r0, i r1) i .r2"
${Loop}
# Free the created structure
System::Free $1
# Release the snapshot
System::Call "kernel32::CloseHandle(i r0)"

# Now we have stored:
# $R0: the current process id
# $R1: the process id of the process we last looked at in the snapshot
# $R2: the parent process id of the process we last looked at in the snapshot
# If $R0 = $R1 (what we hope for) than $R2 is the process id of the parent process of the current one

# Inistialisations
StrCpy $0 0
StrCpy$1 0
# Loop until we find some exist condition in the loop code
${Do}
# Get the first/next NSIS window handle
FindWindow $0 "#32770" "" 0 $0
# If the found handle is 0 or if $R0 != $R1 we have to abort
${If} 0 = $0
${OrIfNot} $R0 = $R1
# If we didn't find the main installer window, we show an error message
${If} 0 = $1
MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running!"
# If we did find the main installer window, we make it the foreground window
${Else}
System::Call "user32::ShowWindow(i r1, i 9)"
System::Call "user32::SetForegroundWindow(i r1)"
${EndIf}
Abort
# We found an NSIS installer
${Else}
# We get its window title
System::Call "user32::GetWindowText(i r0, t .r2, i 1024) i .r3"
# We strip the NULL character at the end
IntOp $3 $3 - 1
StrCpy$2 $2 $3
# If it is the same as the one of this installer, we might have found the parent
# or we discover its a new one
# Here you need to change "$(^Name) Setup" if you changed the caption of the installer
${If} "$(^Name) Setup" == $2
# Get the process id from the window handle
System::Call "user32::GetWindowThreadProcessId(i r0, *i .r2)"
# If it is the same as the one of our parent, than the language was chosen and we break the loop
${If} $2 = $R2
${Break}
# If it is not the same, than we store the window handle (of the first one discovered;
# normally there is only one) to be used later on to set that window to the foreground
${ElseIf} 0 = $1
StrCpy$1 $0
${EndIf}
${EndIf}
${EndIf}
${Loop}
${EndIf}
>FunctionEnd
>
Best Regards,
Yves