diff --git a/Demos/Common/Utils.nsh b/Demos/Common/Utils.nsh new file mode 100644 index 0000000..069fed4 --- /dev/null +++ b/Demos/Common/Utils.nsh @@ -0,0 +1,56 @@ +!include LogicLib.nsh +!include x64.nsh + +!define ERROR_ALREADY_EXISTS 0x000000b7 +!define ERROR_ACCESS_DENIED 0x5 + +!macro CheckPlatform PLATFORM + ${if} ${RunningX64} + !if ${PLATFORM} == "Win32" + MessageBox MB_ICONSTOP "Please, run the 64-bit installer of ${PRODUCT_NAME} on this version of Windows." /SD IDOK + Quit ; will SetErrorLevel 2 - Installation aborted by script + !endif + ${else} + !if ${PLATFORM} == "Win64" + MessageBox MB_ICONSTOP "Please, run the 32-bit installer of ${PRODUCT_NAME} on this version of Windows." /SD IDOK + Quit ; will SetErrorLevel 2 - Installation aborted by script + !endif + ${endif} +!macroend + +!macro CheckMinWinVer MIN_WIN_VER + ${ifnot} ${AtLeastWin${MIN_WIN_VER}} + MessageBox MB_ICONSTOP "This program requires at least Windows ${MIN_WIN_VER}." /SD IDOK + Quit ; will SetErrorLevel 2 - Installation aborted by script + ${endif} +!macroend + +!macro CheckSingleInstance SINGLE_INSTANCE_ID + System::Call 'kernel32::CreateMutex(i 0, i 0, t "Global\${SINGLE_INSTANCE_ID}") i .r0 ?e' + Pop $1 ; the stack contains the result of GetLastError + ${if} $1 = "${ERROR_ALREADY_EXISTS}" + ${orif} $1 = "${ERROR_ACCESS_DENIED}" ; ERROR_ACCESS_DENIED means the mutex was created by another user and we don't have access to open it, so application is running + System::Call 'kernel32::CloseHandle(i $0)' ; if the user closes the already running instance, allow to start a new one without closing the MessageBox + + ; will display NSIS taskbar button, no way to hide it before GUIInit, $HWNDPARENT is 0 + MessageBox MB_ICONSTOP "The setup of ${PRODUCT_NAME} is already running." /SD IDOK + + Quit ; will SetErrorLevel 2 - Installation aborted by script + ${endif} +!macroend + +Function un.DeleteRetryAbort + ; unlike the File instruction, Delete doesn't abort (un)installation on error - it just sets the error flag and skips the file as if nothing happened + try: + ClearErrors + Delete "$0" + ${if} ${errors} + MessageBox MB_RETRYCANCEL|MB_ICONSTOP "Error deleting file:$\r$\n$\r$\n$0$\r$\n$\r$\nClick Retry to try again, or$\r$\nCancel to stop the uninstall." /SD IDCANCEL IDRETRY try + Abort "Error deleting file $0" ; when called from section, will SetErrorLevel 2 - Installation aborted by script + ${endif} +FunctionEnd + +!macro un.DeleteRetryAbort filename + StrCpy $0 "${filename}" + Call un.DeleteRetryAbort +!macroend diff --git a/Demos/MUI2_Limited/Setup.nsi b/Demos/MUI2_Limited/Setup.nsi new file mode 100644 index 0000000..97ae810 --- /dev/null +++ b/Demos/MUI2_Limited/Setup.nsi @@ -0,0 +1,248 @@ +!addplugindir /x86-ansi ".\..\..\Plugins\x86-ansi" +!addplugindir /x86-unicode ".\..\..\Plugins\x86-unicode" +!addincludedir ".\..\..\Include" + +!include MUI2.nsh +!include UAC.nsh +!include NsisMultiUser.nsh +!include LogicLib.nsh +!include ".\..\Common\Utils.nsh" + +!define PRODUCT_NAME "NsisMultiUser MUI2 Limited Demo" ; name of the application as displayed to the user +!define VERSION "1.0" ; main version of the application (may be 0.1, alpha, beta, etc.) +!define PROGEXE "calc.exe" ; main application filename +!define COMPANY_NAME "Alex Mitev" ; company, used for registry tree hierarchy +!define PLATFORM "Win64" +!define MIN_WIN_VER "XP" +!define SINGLE_INSTANCE_ID "${COMPANY_NAME} ${PRODUCT_NAME} Unique ID" ; do not change this between program versions! +!define LICENSE_FILE "License.txt" ; license file, optional + +; NsisMultiUser optional defines +!define MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS 1 ; value 0 is not supported - previous installation is not fully removed +!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 +!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT 0 +!define MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 1 +!if ${PLATFORM} == "Win64" + !define MULTIUSER_INSTALLMODE_64_BIT 1 +!endif +!define MULTIUSER_INSTALLMODE_DISPLAYNAME "${PRODUCT_NAME} ${VERSION} ${PLATFORM}" + +Var StartMenuFolder + +; Installer Attributes +Name "${PRODUCT_NAME} ${VERSION} ${PLATFORM}" +OutFile "Setup_MUI2_Limited.exe" +BrandingText "©2017 ${COMPANY_NAME}" + +AllowSkipFiles off +SetOverwrite on ; (default setting) set to on except for where it is manually switched off +ShowInstDetails show +SetCompressor /SOLID lzma + +; Pages +!define MUI_ABORTWARNING ; Show a confirmation when cancelling the installation + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre +!insertmacro MUI_PAGE_WELCOME + +!ifdef LICENSE_FILE + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre + !insertmacro MUI_PAGE_LICENSE ".\..\..\${LICENSE_FILE}" +!endif + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre +!insertmacro MUI_PAGE_LICENSE "readme.txt" + +!define MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION PageInstallModeChangeMode +!insertmacro MULTIUSER_PAGE_INSTALLMODE + +!define MUI_COMPONENTSPAGE_SMALLDESC +!insertmacro MUI_PAGE_COMPONENTS + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageDirectoryPre +!define MUI_PAGE_CUSTOMFUNCTION_SHOW PageDirectoryShow +!insertmacro MUI_PAGE_DIRECTORY + +!define MUI_STARTMENUPAGE_NODISABLE ; Do not display the checkbox to disable the creation of Start Menu shortcuts +!define MUI_STARTMENUPAGE_DEFAULTFOLDER "${PRODUCT_NAME}" +!define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" ; writing to $StartMenuFolder happens in MUI_STARTMENU_WRITE_END, so it's safe to use "SHCTX" here +!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" +!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "StartMenuFolder" +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageStartMenuPre +!insertmacro MUI_PAGE_STARTMENU "" "$StartMenuFolder" + +!insertmacro MUI_PAGE_INSTFILES + +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun +!insertmacro MUI_PAGE_FINISH + +!include Uninstall.nsh + +!insertmacro MUI_LANGUAGE "English" ; Set languages (first is default language) - must be inserted after all pages + +InstType "Typical" +InstType "Minimal" +InstType "Full" + +Section "Core Files (required)" SectionCoreFiles + SectionIn 1 2 3 RO + + ${if} $HasCurrentModeInstallation == 1 ; if there's an installed version, remove all optinal components (except "Core Files") + ; Clean up "Documentation" + Delete "$INSTDIR\readme.txt" + + ; Clean up "Program Group" - we check that we created Start menu folder, if $StartMenuFolder is empty, the whole $SMPROGRAMS directory will be removed! + ${if} "$StartMenuFolder" != "" + RMDir /r "$SMPROGRAMS\$StartMenuFolder" + ${endif} + + ; Clean up "Dektop Icon" + Delete "$DESKTOP\${PRODUCT_NAME}.lnk" + + ; Clean up "Start Menu Icon" + Delete "$STARTMENU\${PRODUCT_NAME}.lnk" + + ; Clean up "Quick Launch Icon" + Delete "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" + ${endif} + + SetOutPath $INSTDIR + ; Write uninstaller and registry uninstall info as the first step, + ; so that the user has the option to run the uninstaller if sth. goes wrong + WriteUninstaller "${UNINSTALL_FILENAME}" + !insertmacro MULTIUSER_RegistryAddInstallInfo ; add registry keys + + File "C:\Windows\System32\${PROGEXE}" + !ifdef LICENSE_FILE + File ".\..\..\${LICENSE_FILE}" + !endif +SectionEnd + +Section "Documentation" SectionDocumentation + SectionIn 1 3 + + SetOutPath $INSTDIR + File "readme.txt" + +SectionEnd + +SectionGroup /e "Integration" SectionGroupIntegration + +Section "Program Group" SectionProgramGroup + SectionIn 1 3 + + !insertmacro MUI_STARTMENU_WRITE_BEGIN "" + + CreateDirectory "$SMPROGRAMS\$StartMenuFolder" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" + + !ifdef LICENSE_FILE + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\License Agreement.lnk" "$INSTDIR\${LICENSE_FILE}" + !endif + ${if} $MultiUser.InstallMode == "AllUsers" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/allusers" + ${else} + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall (current user).lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/currentuser" + ${endif} + + !insertmacro MUI_STARTMENU_WRITE_END +SectionEnd + +Section "Dektop Icon" SectionDesktopIcon + SectionIn 1 3 + + CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd + +Section /o "Start Menu Icon" SectionStartMenuIcon + SectionIn 3 + + CreateShortCut "$STARTMENU\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd + +Section /o "Quick Launch" SectionQuickLaunchIcon + SectionIn 3 + + ; The QuickLaunch is always only for the current user + CreateShortCut "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd +SectionGroupEnd + +; Modern install component descriptions +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SectionCoreFiles} "Core files requred to run ${PRODUCT_NAME}." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionDocumentation} "Help files for ${PRODUCT_NAME}." + + !insertmacro MUI_DESCRIPTION_TEXT ${SectionGroupIntegration} "Select how to integrate the program in Windows." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionProgramGroup} "Create a ${PRODUCT_NAME} program group under Start Menu->Programs." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionDesktopIcon} "Create ${PRODUCT_NAME} icon on the Desktop." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionStartMenuIcon} "Create ${PRODUCT_NAME} icon in the Start Menu." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionQuickLaunchIcon} "Create ${PRODUCT_NAME} icon in Quick Launch." +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +; Callbacks +Function .onInit + !insertmacro CheckPlatform ${PLATFORM} + !insertmacro CheckMinWinVer ${MIN_WIN_VER} + ${ifnot} ${UAC_IsInnerInstance} + !insertmacro CheckSingleInstance "${SINGLE_INSTANCE_ID}" + ${endif} + + !insertmacro MULTIUSER_INIT +FunctionEnd + +Function PageWelcomeLicensePre + ${if} $InstallShowPagesBeforeComponents == 0 + Abort ; don't display the Welcome and License pages for the inner instance + ${endif} +FunctionEnd + +Function PageInstallModeChangeMode + !insertmacro MUI_STARTMENU_GETFOLDER "" $StartMenuFolder +FunctionEnd + +Function PageDirectoryPre + GetDlgItem $0 $HWNDPARENT 1 + ${if} ${SectionIsSelected} ${SectionProgramGroup} + SendMessage $0 ${WM_SETTEXT} 0 "STR:$(^NextBtn)" ; this is not the last page before installing + ${else} + SendMessage $0 ${WM_SETTEXT} 0 "STR:$(^InstallBtn)" ; this is the last page before installing + ${endif} +FunctionEnd + +Function PageDirectoryShow + ${if} $CmdLineDir != "" + ${orif} $HasCurrentModeInstallation == 1 + FindWindow $R1 "#32770" "" $HWNDPARENT + + GetDlgItem $0 $R1 1019 ; Directory edit + SendMessage $0 ${EM_SETREADONLY} 1 0 ; read-only is better than disabled, as user can copy contents + + GetDlgItem $0 $R1 1001 ; Browse button + EnableWindow $0 0 + ${endif} +FunctionEnd + +Function PageStartMenuPre + ${ifnot} ${SectionIsSelected} ${SectionProgramGroup} + Abort ; don't display this dialog if SectionProgramGroup is not selected + ${endif} +FunctionEnd + +Function PageFinishRun + ; the installer might exit too soon before the application starts and it loses the right to be the foreground window and starts in the background + ; however, if there's no active window when the application starts, it will become the active window, so we hide the installer + HideWindow + ; the installer will show itself again quickly before closing (w/o Taskbar button), we move it offscreen + !define SWP_NOSIZE 0x0001 + !define SWP_NOZORDER 0x0004 + System::Call "User32::SetWindowPos(i, i, i, i, i, i, i) b ($HWNDPARENT, 0, -1000, -1000, 0, 0, ${SWP_NOZORDER}|${SWP_NOSIZE})" + + !insertmacro UAC_AsUser_ExecShell "open" "$INSTDIR\${PROGEXE}" "" "$INSTDIR" "" +FunctionEnd + +Function .onInstFailed + MessageBox MB_ICONSTOP "${PRODUCT_NAME} ${VERSION} could not be fully installed.$\r$\nPlease, restart Windows and run the setup program again." /SD IDOK +FunctionEnd + diff --git a/Demos/MUI2_Limited/Uninstall.nsh b/Demos/MUI2_Limited/Uninstall.nsh new file mode 100644 index 0000000..0027bfd --- /dev/null +++ b/Demos/MUI2_Limited/Uninstall.nsh @@ -0,0 +1,95 @@ +Var RunningFromInstaller + +; Installer Attributes +ShowUninstDetails show + +; Pages +!define MUI_UNABORTWARNING ; Show a confirmation when cancelling the installation + +!define MULTIUSER_INSTALLMODE_CHANGE_MODE_UNFUNCTION un.PageInstallModeChangeMode +!insertmacro MULTIUSER_UNPAGE_INSTALLMODE + +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.PageComponentsShow +!insertmacro MUI_UNPAGE_COMPONENTS + +!insertmacro MUI_UNPAGE_INSTFILES + +Section "un.Program Files" SectionUninstallProgram + SectionIn RO + + ; Try to delete the EXE as the first step - if it's in use, don't remove anything else + !insertmacro un.DeleteRetryAbort "$INSTDIR\${PROGEXE}" + !ifdef LICENSE_FILE + !insertmacro un.DeleteRetryAbort "$INSTDIR\${LICENSE_FILE}" + !endif + + ; Clean up "Documentation" + !insertmacro un.DeleteRetryAbort "$INSTDIR\readme.txt" + + ; Clean up "Program Group" - we check that we created Start menu folder, if $StartMenuFolder is empty, the whole $SMPROGRAMS directory will be removed! + ${if} "$StartMenuFolder" != "" + RMDir /r "$SMPROGRAMS\$StartMenuFolder" + ${endif} + + ; Clean up "Dektop Icon" + !insertmacro un.DeleteRetryAbort "$DESKTOP\${PRODUCT_NAME}.lnk" + + ; Clean up "Start Menu Icon" + !insertmacro un.DeleteRetryAbort "$STARTMENU\${PRODUCT_NAME}.lnk" + + ; Clean up "Quick Launch Icon" + !insertmacro un.DeleteRetryAbort "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" +SectionEnd + +Section /o "un.Program Settings" SectionRemoveSettings + ; this section is executed only explicitly and shouldn't be placed in SectionUninstallProgram + DeleteRegKey HKCU "Software\${PRODUCT_NAME}" +SectionEnd + +Section "-Uninstall" ; hidden section, must always be the last one! + ; Remove the uninstaller from registry as the very last step - if sth. goes wrong, let the user run it again + !insertmacro MULTIUSER_RegistryRemoveInstallInfo ; Remove registry keys + + Delete "$INSTDIR\${UNINSTALL_FILENAME}" + ; remove the directory only if it is empty - the user might have saved some files in it + RMDir "$INSTDIR" +SectionEnd + +; Modern install component descriptions +!insertmacro MUI_UNFUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SectionUninstallProgram} "Uninstall ${PRODUCT_NAME} files." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionRemoveSettings} "Remove ${PRODUCT_NAME} program settings. Select only if you don't plan to use the program in the future." +!insertmacro MUI_UNFUNCTION_DESCRIPTION_END + +; Callbacks +Function un.onInit + ${GetParameters} $R0 + + ${GetOptions} $R0 "/uninstall" $R1 + ${ifnot} ${errors} + StrCpy $RunningFromInstaller 1 + ${else} + StrCpy $RunningFromInstaller 0 + ${endif} + + ${ifnot} ${UAC_IsInnerInstance} + ${andif} $RunningFromInstaller == "0" + !insertmacro CheckSingleInstance "${SINGLE_INSTANCE_ID}" + ${endif} + + !insertmacro MULTIUSER_UNINIT +FunctionEnd + +Function un.PageInstallModeChangeMode + !insertmacro MUI_STARTMENU_GETFOLDER "" $StartMenuFolder +FunctionEnd + +Function un.PageComponentsShow + ; Show/hide the Back button + GetDlgItem $0 $HWNDPARENT 3 + ShowWindow $0 $UninstallShowBackButton +FunctionEnd + +Function un.onUninstFailed + MessageBox MB_ICONSTOP "${PRODUCT_NAME} ${VERSION} could not be fully uninstalled.$\r$\nPlease, restart Windows and run the uninstaller again." /SD IDOK +FunctionEnd diff --git a/Demos/MUI2_Limited/readme.txt b/Demos/MUI2_Limited/readme.txt new file mode 100644 index 0000000..7c240ad --- /dev/null +++ b/Demos/MUI2_Limited/readme.txt @@ -0,0 +1,9 @@ +NsisMultiUser MUI2 Limited Demo, based on the Modern User Interface 2 + +Features: +- does not fully uninstall previously installed version - only the additional components are removed (except Core files) + +Limitations: +- because new versions can add more components, downgrading is not secure - old version will not uninstall new components +- if previous version is installed, user can not set new installation folder (disabled in interface) +- supports only MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS = 1 diff --git a/Demos/MUI_1_2_Full/Setup.nsi b/Demos/MUI_1_2_Full/Setup.nsi new file mode 100644 index 0000000..c419409 --- /dev/null +++ b/Demos/MUI_1_2_Full/Setup.nsi @@ -0,0 +1,288 @@ +!addplugindir /x86-ansi ".\..\..\Plugins\x86-ansi" +!addplugindir /x86-unicode ".\..\..\Plugins\x86-unicode" +!addincludedir ".\..\..\Include" + +!include MUI.nsh +; OR +; !include MUI2.nsh +!include UAC.nsh +!include NsisMultiUser.nsh +!include LogicLib.nsh +!include ".\..\Common\Utils.nsh" + +!define PRODUCT_NAME "NsisMultiUser MUI_1_2 Full Demo" ; name of the application as displayed to the user +!define VERSION "1.0" ; main version of the application (may be 0.1, alpha, beta, etc.) +!define PROGEXE "calc.exe" ; main application filename +!define COMPANY_NAME "Alex Mitev" ; company, used for registry tree hierarchy +!define PLATFORM "Win64" +!define MIN_WIN_VER "XP" +!define SINGLE_INSTANCE_ID "${COMPANY_NAME} ${PRODUCT_NAME} Unique ID" ; do not change this between program versions! +!define LICENSE_FILE "License.txt" ; license file, optional + +; NsisMultiUser optional defines +!define MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS 0 +!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 +!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT 0 +!define MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 1 +!if ${PLATFORM} == "Win64" + !define MULTIUSER_INSTALLMODE_64_BIT 1 +!endif +!define MULTIUSER_INSTALLMODE_DISPLAYNAME "${PRODUCT_NAME} ${VERSION} ${PLATFORM}" + +Var StartMenuFolder + +; Installer Attributes +Name "${PRODUCT_NAME} ${VERSION} ${PLATFORM}" +OutFile "Setup_MUI_1_2_Full.exe" +BrandingText "©2017 ${COMPANY_NAME}" + +AllowSkipFiles off +SetOverwrite on ; (default setting) set to on except for where it is manually switched off +ShowInstDetails show +SetCompressor /SOLID lzma + +; Pages +!define MUI_ABORTWARNING ; Show a confirmation when cancelling the installation + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre +!insertmacro MUI_PAGE_WELCOME + +!ifdef LICENSE_FILE + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre + !insertmacro MUI_PAGE_LICENSE ".\..\..\${LICENSE_FILE}" +!endif + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre +!insertmacro MUI_PAGE_LICENSE "readme.txt" + +!define MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION PageInstallModeChangeMode +!insertmacro MULTIUSER_PAGE_INSTALLMODE + +!define MUI_COMPONENTSPAGE_SMALLDESC +!insertmacro MUI_PAGE_COMPONENTS + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageDirectoryPre +!define MUI_PAGE_CUSTOMFUNCTION_SHOW PageDirectoryShow +!insertmacro MUI_PAGE_DIRECTORY + +!define MUI_STARTMENUPAGE_NODISABLE ; Do not display the checkbox to disable the creation of Start Menu shortcuts +!define MUI_STARTMENUPAGE_DEFAULTFOLDER "${PRODUCT_NAME}" +!define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" ; writing to $StartMenuFolder happens in MUI_STARTMENU_WRITE_END, so it's safe to use "SHCTX" here +!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" +!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "StartMenuFolder" +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageStartMenuPre +!insertmacro MUI_PAGE_STARTMENU "" "$StartMenuFolder" + +!insertmacro MUI_PAGE_INSTFILES + +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun +!insertmacro MUI_PAGE_FINISH + +!include Uninstall.nsh + +!insertmacro MUI_LANGUAGE "English" ; Set languages (first is default language) - must be inserted after all pages + +InstType "Typical" +InstType "Minimal" +InstType "Full" + +Section "Core Files (required)" SectionCoreFiles + SectionIn 1 2 3 RO + + ; if there's an installed version, uninstall it first (I chose not to start the uninstaller silently, so that user sees what failed) + ; if both per-user and per-machine versions are installed, unistall the one that matches $MultiUser.InstallMode + StrCpy $0 "" + ${if} $HasCurrentModeInstallation == 1 + StrCpy $0 "$MultiUser.InstallMode" + ${else} + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 + ${if} $HasPerMachineInstallation == 1 + StrCpy $0 "AllUsers" ; if there's no per-user installation, but there's per-machine installation, uninstall it + ${elseif} $HasPerUserInstallation == 1 + StrCpy $0 "CurrentUser" ; if there's no per-machine installation, but there's per-user installation, uninstall it + ${endif} + !endif + ${endif} + + ${if} "$0" != "" + ${if} $0 == "AllUsers" + StrCpy $1 "$PerMachineUninstallString" + StrCpy $3 "$PerMachineInstallationFolder" + ${else} + StrCpy $1 "$PerUserUninstallString" + StrCpy $3 "$PerUserInstallationFolder" + ${endif} + ${if} ${silent} + StrCpy $2 "/S" + ${else} + StrCpy $2 "" + ${endif} + + HideWindow + ClearErrors + StrCpy $0 0 + ExecWait '$1 /SS $2 _?=$3' $0 ; $1 is quoted in registry; the _? param stops the uninstaller from copying itself to the temporary directory, which is the only way for ExecWait to work + + ${if} ${errors} ; stay in installer + SetErrorLevel 2 ; Installation aborted by script + BringToFront + Abort "Error executing uninstaller." + ${else} + ${Switch} $0 + ${Case} 0 ; uninstaller completed successfully - continue with installation + BringToFront + ${Break} + ${Case} 1 ; Installation aborted by user (cancel button) + ${Case} 2 ; Installation aborted by script + SetErrorLevel $0 + Quit ; uninstaller was started, but completed with errors - Quit installer + ${Default} ; all other error codes - uninstaller could not start, elevate, etc. - Abort installer + SetErrorLevel $0 + BringToFront + Abort "Error executing uninstaller." + ${EndSwitch} + ${endif} + + Delete "$2\${UNINSTALL_FILENAME}" ; the uninstaller doesn't delete itself when not copied to the temp directory + RMDir "$2" + ${endif} + + SetOutPath $INSTDIR + ; Write uninstaller and registry uninstall info as the first step, + ; so that the user has the option to run the uninstaller if sth. goes wrong + WriteUninstaller "${UNINSTALL_FILENAME}" + !insertmacro MULTIUSER_RegistryAddInstallInfo ; add registry keys + + File "C:\Windows\System32\${PROGEXE}" + !ifdef LICENSE_FILE + File ".\..\..\${LICENSE_FILE}" + !endif +SectionEnd + +Section "Documentation" SectionDocumentation + SectionIn 1 3 + + SetOutPath $INSTDIR + File "readme.txt" + +SectionEnd + +SectionGroup /e "Integration" SectionGroupIntegration + +Section "Program Group" SectionProgramGroup + SectionIn 1 3 + + !insertmacro MUI_STARTMENU_WRITE_BEGIN "" + + CreateDirectory "$SMPROGRAMS\$StartMenuFolder" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" + + !ifdef LICENSE_FILE + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\License Agreement.lnk" "$INSTDIR\${LICENSE_FILE}" + !endif + ${if} $MultiUser.InstallMode == "AllUsers" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/allusers" + ${else} + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall (current user).lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/currentuser" + ${endif} + + !insertmacro MUI_STARTMENU_WRITE_END +SectionEnd + +Section "Dektop Icon" SectionDesktopIcon + SectionIn 1 3 + + CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd + +Section /o "Start Menu Icon" SectionStartMenuIcon + SectionIn 3 + + CreateShortCut "$STARTMENU\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd + +Section /o "Quick Launch" SectionQuickLaunchIcon + SectionIn 3 + + ; The QuickLaunch is always only for the current user + CreateShortCut "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd +SectionGroupEnd + +; Modern install component descriptions +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SectionCoreFiles} "Core files requred to run ${PRODUCT_NAME}." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionDocumentation} "Help files for ${PRODUCT_NAME}." + + !insertmacro MUI_DESCRIPTION_TEXT ${SectionGroupIntegration} "Select how to integrate the program in Windows." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionProgramGroup} "Create a ${PRODUCT_NAME} program group under Start Menu->Programs." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionDesktopIcon} "Create ${PRODUCT_NAME} icon on the Desktop." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionStartMenuIcon} "Create ${PRODUCT_NAME} icon in the Start Menu." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionQuickLaunchIcon} "Create ${PRODUCT_NAME} icon in Quick Launch." +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +; Callbacks +Function .onInit + !insertmacro CheckPlatform ${PLATFORM} + !insertmacro CheckMinWinVer ${MIN_WIN_VER} + ${ifnot} ${UAC_IsInnerInstance} + !insertmacro CheckSingleInstance "${SINGLE_INSTANCE_ID}" + ${endif} + + !insertmacro MULTIUSER_INIT +FunctionEnd + +Function PageWelcomeLicensePre + ${if} $InstallShowPagesBeforeComponents == 0 + Abort ; don't display the Welcome and License pages for the inner instance + ${endif} +FunctionEnd + +Function PageInstallModeChangeMode + !insertmacro MUI_STARTMENU_GETFOLDER "" $StartMenuFolder +FunctionEnd + +Function PageDirectoryPre + GetDlgItem $0 $HWNDPARENT 1 + ${if} ${SectionIsSelected} ${SectionProgramGroup} + SendMessage $0 ${WM_SETTEXT} 0 "STR:$(^NextBtn)" ; this is not the last page before installing + ${else} + SendMessage $0 ${WM_SETTEXT} 0 "STR:$(^InstallBtn)" ; this is the last page before installing + ${endif} +FunctionEnd + +Function PageDirectoryShow + ${if} $CmdLineDir != "" + FindWindow $R1 "#32770" "" $HWNDPARENT + + GetDlgItem $0 $R1 1019 ; Directory edit + SendMessage $0 ${EM_SETREADONLY} 1 0 ; read-only is better than disabled, as user can copy contents + + GetDlgItem $0 $R1 1001 ; Browse button + EnableWindow $0 0 + ${endif} +FunctionEnd + +Function PageStartMenuPre + ${ifnot} ${SectionIsSelected} ${SectionProgramGroup} + Abort ; don't display this dialog if SectionProgramGroup is not selected + ${endif} +FunctionEnd + +Function PageFinishRun + ; the installer might exit too soon before the application starts and it loses the right to be the foreground window and starts in the background + ; however, if there's no active window when the application starts, it will become the active window, so we hide the installer + HideWindow + ; the installer will show itself again quickly before closing (w/o Taskbar button), we move it offscreen + !define SWP_NOSIZE 0x0001 + !define SWP_NOZORDER 0x0004 + System::Call "User32::SetWindowPos(i, i, i, i, i, i, i) b ($HWNDPARENT, 0, -1000, -1000, 0, 0, ${SWP_NOZORDER}|${SWP_NOSIZE})" + + !insertmacro UAC_AsUser_ExecShell "open" "$INSTDIR\${PROGEXE}" "" "$INSTDIR" "" +FunctionEnd + +Function .onInstFailed + MessageBox MB_ICONSTOP "${PRODUCT_NAME} ${VERSION} could not be fully installed.$\r$\nPlease, restart Windows and run the setup program again." /SD IDOK +FunctionEnd + diff --git a/Demos/MUI_1_2_Full/Uninstall.nsh b/Demos/MUI_1_2_Full/Uninstall.nsh new file mode 100644 index 0000000..e0e34b6 --- /dev/null +++ b/Demos/MUI_1_2_Full/Uninstall.nsh @@ -0,0 +1,115 @@ +Var SemiSilentMode ; installer started uninstaller in semi-silent mode using /SS parameter +Var RunningFromInstaller ; installer started uninstaller using /uninstall parameter + +; Installer Attributes +ShowUninstDetails show + +; Pages +!define MUI_UNABORTWARNING ; Show a confirmation when cancelling the installation + +!define MULTIUSER_INSTALLMODE_CHANGE_MODE_UNFUNCTION un.PageInstallModeChangeMode +!insertmacro MULTIUSER_UNPAGE_INSTALLMODE + +!define MUI_PAGE_CUSTOMFUNCTION_PRE un.PageComponentsPre +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.PageComponentsShow +!insertmacro MUI_UNPAGE_COMPONENTS + +!insertmacro MUI_UNPAGE_INSTFILES + +Section "un.Program Files" SectionUninstallProgram + SectionIn RO + + ; Try to delete the EXE as the first step - if it's in use, don't remove anything else + !insertmacro un.DeleteRetryAbort "$INSTDIR\${PROGEXE}" + !ifdef LICENSE_FILE + !insertmacro un.DeleteRetryAbort "$INSTDIR\${LICENSE_FILE}" + !endif + + ; Clean up "Documentation" + !insertmacro un.DeleteRetryAbort "$INSTDIR\readme.txt" + + ; Clean up "Program Group" - we check that we created Start menu folder, if $StartMenuFolder is empty, the whole $SMPROGRAMS directory will be removed! + ${if} "$StartMenuFolder" != "" + RMDir /r "$SMPROGRAMS\$StartMenuFolder" + ${endif} + + ; Clean up "Dektop Icon" + !insertmacro un.DeleteRetryAbort "$DESKTOP\${PRODUCT_NAME}.lnk" + + ; Clean up "Start Menu Icon" + !insertmacro un.DeleteRetryAbort "$STARTMENU\${PRODUCT_NAME}.lnk" + + ; Clean up "Quick Launch Icon" + !insertmacro un.DeleteRetryAbort "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" +SectionEnd + +Section /o "un.Program Settings" SectionRemoveSettings + ; this section is executed only explicitly and shouldn't be placed in SectionUninstallProgram + DeleteRegKey HKCU "Software\${PRODUCT_NAME}" +SectionEnd + +Section "-Uninstall" ; hidden section, must always be the last one! + ; Remove the uninstaller from registry as the very last step - if sth. goes wrong, let the user run it again + !insertmacro MULTIUSER_RegistryRemoveInstallInfo ; Remove registry keys + + Delete "$INSTDIR\${UNINSTALL_FILENAME}" + ; remove the directory only if it is empty - the user might have saved some files in it + RMDir "$INSTDIR" +SectionEnd + +; Modern install component descriptions +!insertmacro MUI_UNFUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SectionUninstallProgram} "Uninstall ${PRODUCT_NAME} files." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionRemoveSettings} "Remove ${PRODUCT_NAME} program settings. Select only if you don't plan to use the program in the future." +!insertmacro MUI_UNFUNCTION_DESCRIPTION_END + +; Callbacks +Function un.onInit + ${GetParameters} $R0 + + ${GetOptions} $R0 "/uninstall" $R1 + ${ifnot} ${errors} + StrCpy $RunningFromInstaller 1 + ${else} + StrCpy $RunningFromInstaller 0 + ${endif} + + ${GetOptions} $R0 "/SS" $R1 + ${ifnot} ${errors} + StrCpy $SemiSilentMode 1 + SetAutoClose true ; auto close (if no errors) if we are called from the installer; if there are errors, will be automatically set to false + ${else} + StrCpy $SemiSilentMode 0 + ${endif} + + ${ifnot} ${UAC_IsInnerInstance} + ${andif} $RunningFromInstaller$SemiSilentMode == "00" + !insertmacro CheckSingleInstance "${SINGLE_INSTANCE_ID}" + ${endif} + + !insertmacro MULTIUSER_UNINIT +FunctionEnd + +Function un.PageInstallModeChangeMode + !insertmacro MUI_STARTMENU_GETFOLDER "" $StartMenuFolder +FunctionEnd + +Function un.PageComponentsPre + ${if} $SemiSilentMode == 1 + Abort ; if user is installing, no use to remove program settings anyway (should be compatible with all versions) + ${endif} +FunctionEnd + +Function un.PageComponentsShow + ; Show/hide the Back button + GetDlgItem $0 $HWNDPARENT 3 + ShowWindow $0 $UninstallShowBackButton +FunctionEnd + +Function un.onUninstFailed + ${if} $SemiSilentMode == 0 + MessageBox MB_ICONSTOP "${PRODUCT_NAME} ${VERSION} could not be fully uninstalled.$\r$\nPlease, restart Windows and run the uninstaller again." /SD IDOK + ${else} + MessageBox MB_ICONSTOP "${PRODUCT_NAME} could not be fully installed.$\r$\nPlease, restart Windows and run the setup program again." /SD IDOK + ${endif} +FunctionEnd diff --git a/Demos/MUI_1_2_Full/readme.txt b/Demos/MUI_1_2_Full/readme.txt new file mode 100644 index 0000000..2535525 --- /dev/null +++ b/Demos/MUI_1_2_Full/readme.txt @@ -0,0 +1,10 @@ +NsisMultiUser MUI2 Full Demo, based on the Modern User Interface 1 and 2 + +Features: +- supports MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS values 0 AND 1 +- fully uninstalls previously installed version (if any) by running its uninstaller before installing new version +- can safely downgrade and upgrade +- because previous version is fully uninstalled, user can set new installation folder, new start menu folder and choose different components + +Limitations: +none \ No newline at end of file diff --git a/Demos/NSIS_Full/Setup.nsi b/Demos/NSIS_Full/Setup.nsi new file mode 100644 index 0000000..3e1d9ec --- /dev/null +++ b/Demos/NSIS_Full/Setup.nsi @@ -0,0 +1,241 @@ +!addplugindir /x86-ansi ".\..\..\Plugins\x86-ansi" +!addplugindir /x86-unicode ".\..\..\Plugins\x86-unicode" +!addincludedir ".\..\..\Include" + +!include UAC.nsh +!include NsisMultiUser.nsh +!include LogicLib.nsh +!include ".\..\Common\Utils.nsh" + +!define PRODUCT_NAME "NsisMultiUser NSIS Full Demo" ; name of the application as displayed to the user +!define VERSION "1.0" ; main version of the application (may be 0.1, alpha, beta, etc.) +!define PROGEXE "calc.exe" ; main application filename +!define COMPANY_NAME "Alex Mitev" ; company, used for registry tree hierarchy +!define PLATFORM "Win64" +!define MIN_WIN_VER "XP" +!define SINGLE_INSTANCE_ID "${COMPANY_NAME} ${PRODUCT_NAME} Unique ID" ; do not change this between program versions! +!define LICENSE_FILE "License.txt" ; license file, optional + +; NsisMultiUser optional defines +!define MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS 0 +!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 +!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT 0 +!define MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 1 +!if ${PLATFORM} == "Win64" + !define MULTIUSER_INSTALLMODE_64_BIT 1 +!endif +!define MULTIUSER_INSTALLMODE_DISPLAYNAME "${PRODUCT_NAME} ${VERSION} ${PLATFORM}" + +; Installer Attributes +Name "${PRODUCT_NAME} ${VERSION} ${PLATFORM}" +OutFile "Setup_NSIS_Full.exe" +BrandingText "©2017 ${COMPANY_NAME}" + +AllowSkipFiles off +SetOverwrite on ; (default setting) set to on except for where it is manually switched off +ShowInstDetails show +SetCompressor /SOLID lzma +XPStyle on + +; Pages +!ifdef LICENSE_FILE + PageEx license + PageCallbacks PageWelcomeLicensePre EmptyCallback EmptyCallback + LicenseData ".\..\..\${LICENSE_FILE}" + PageExEnd +!endif + +PageEx license + PageCallbacks PageWelcomeLicensePre EmptyCallback EmptyCallback + LicenseData "readme.txt" +PageExEnd + +!insertmacro MULTIUSER_PAGE_INSTALLMODE + +PageEx components +PageExEnd + +PageEx directory + PageCallbacks PageDirectoryPre PageDirectoryShow EmptyCallback +PageExEnd + +PageEx instfiles +PageExEnd + +!include Uninstall.nsh + +InstType "Typical" +InstType "Minimal" +InstType "Full" + +Section "Core Files (required)" SectionCoreFiles + SectionIn 1 2 3 RO + + ; if there's an installed version, uninstall it first (I chose not to start the uninstaller silently, so that user sees what failed) + ; if both per-user and per-machine versions are installed, unistall the one that matches $MultiUser.InstallMode + StrCpy $0 "" + ${if} $HasCurrentModeInstallation == 1 + StrCpy $0 "$MultiUser.InstallMode" + ${else} + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 + ${if} $HasPerMachineInstallation == 1 + StrCpy $0 "AllUsers" ; if there's no per-user installation, but there's per-machine installation, uninstall it + ${elseif} $HasPerUserInstallation == 1 + StrCpy $0 "CurrentUser" ; if there's no per-machine installation, but there's per-user installation, uninstall it + ${endif} + !endif + ${endif} + + ${if} "$0" != "" + ${if} $0 == "AllUsers" + StrCpy $1 "$PerMachineUninstallString" + StrCpy $3 "$PerMachineInstallationFolder" + ${else} + StrCpy $1 "$PerUserUninstallString" + StrCpy $3 "$PerUserInstallationFolder" + ${endif} + ${if} ${silent} + StrCpy $2 "/S" + ${else} + StrCpy $2 "" + ${endif} + + HideWindow + ClearErrors + StrCpy $0 0 + ExecWait '$1 /SS $2 _?=$3' $0 ; $1 is quoted in registry; the _? param stops the uninstaller from copying itself to the temporary directory, which is the only way for ExecWait to work + + ${if} ${errors} ; stay in installer + SetErrorLevel 2 ; Installation aborted by script + BringToFront + Abort "Error executing uninstaller." + ${else} + ${Switch} $0 + ${Case} 0 ; uninstaller completed successfully - continue with installation + BringToFront + ${Break} + ${Case} 1 ; Installation aborted by user (cancel button) + ${Case} 2 ; Installation aborted by script + SetErrorLevel $0 + Quit ; uninstaller was started, but completed with errors - Quit installer + ${Default} ; all other error codes - uninstaller could not start, elevate, etc. - Abort installer + SetErrorLevel $0 + BringToFront + Abort "Error executing uninstaller." + ${EndSwitch} + ${endif} + + Delete "$2\${UNINSTALL_FILENAME}" ; the uninstaller doesn't delete itself when not copied to the temp directory + RMDir "$2" + ${endif} + + SetOutPath $INSTDIR + ; Write uninstaller and registry uninstall info as the first step, + ; so that the user has the option to run the uninstaller if sth. goes wrong + WriteUninstaller "${UNINSTALL_FILENAME}" + !insertmacro MULTIUSER_RegistryAddInstallInfo ; add registry keys + + File "C:\Windows\System32\${PROGEXE}" + !ifdef LICENSE_FILE + File ".\..\..\${LICENSE_FILE}" + !endif +SectionEnd + +Section "Documentation" SectionDocumentation + SectionIn 1 3 + + SetOutPath $INSTDIR + File "readme.txt" + +SectionEnd + +SectionGroup /e "Integration" SectionGroupIntegration + +Section "Program Group" SectionProgramGroup + SectionIn 1 3 + + CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" + + !ifdef LICENSE_FILE + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\License Agreement.lnk" "$INSTDIR\${LICENSE_FILE}" + !endif + ${if} $MultiUser.InstallMode == "AllUsers" + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/allusers" + ${else} + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall (current user).lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/currentuser" + ${endif} +SectionEnd + +Section "Dektop Icon" SectionDesktopIcon + SectionIn 1 3 + + CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd + +Section /o "Start Menu Icon" SectionStartMenuIcon + SectionIn 3 + + CreateShortCut "$STARTMENU\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd + +Section /o "Quick Launch" SectionQuickLaunchIcon + SectionIn 3 + + ; The QuickLaunch is always only for the current user + CreateShortCut "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd +SectionGroupEnd + +; Callbacks +Function .onInit + !insertmacro CheckPlatform ${PLATFORM} + !insertmacro CheckMinWinVer ${MIN_WIN_VER} + ${ifnot} ${UAC_IsInnerInstance} + !insertmacro CheckSingleInstance "${SINGLE_INSTANCE_ID}" + ${endif} + + !insertmacro MULTIUSER_INIT +FunctionEnd + +Function EmptyCallback +FunctionEnd + +Function PageWelcomeLicensePre + ${if} $InstallShowPagesBeforeComponents == 0 + Abort ; don't display the Welcome and License pages for the inner instance + ${endif} +FunctionEnd + +Function PageDirectoryPre + GetDlgItem $0 $HWNDPARENT 1 + ${if} ${SectionIsSelected} ${SectionProgramGroup} + SendMessage $0 ${WM_SETTEXT} 0 "STR:$(^NextBtn)" ; this is not the last page before installing + ${else} + SendMessage $0 ${WM_SETTEXT} 0 "STR:$(^InstallBtn)" ; this is the last page before installing + ${endif} +FunctionEnd + +Function PageDirectoryShow + ${if} $CmdLineDir != "" + FindWindow $R1 "#32770" "" $HWNDPARENT + + GetDlgItem $0 $R1 1019 ; Directory edit + SendMessage $0 ${EM_SETREADONLY} 1 0 ; read-only is better than disabled, as user can copy contents + + GetDlgItem $0 $R1 1001 ; Browse button + EnableWindow $0 0 + ${endif} +FunctionEnd + +Function .onUserAbort + MessageBox MB_YESNO|MB_ICONEXCLAMATION "Are you sure you want to quit $(^Name) Setup?" IDYES mui.quit + + Abort + mui.quit: +FunctionEnd + +Function .onInstFailed + MessageBox MB_ICONSTOP "${PRODUCT_NAME} ${VERSION} could not be fully installed.$\r$\nPlease, restart Windows and run the setup program again." /SD IDOK +FunctionEnd + diff --git a/Demos/NSIS_Full/Uninstall.nsh b/Demos/NSIS_Full/Uninstall.nsh new file mode 100644 index 0000000..1165eb4 --- /dev/null +++ b/Demos/NSIS_Full/Uninstall.nsh @@ -0,0 +1,110 @@ +Var SemiSilentMode ; installer started uninstaller in semi-silent mode using /SS parameter +Var RunningFromInstaller ; installer started uninstaller using /uninstall parameter + +; Installer Attributes +ShowUninstDetails show + +; Pages +!insertmacro MULTIUSER_UNPAGE_INSTALLMODE + +UninstPage components un.PageComponentsPre un.PageComponentsShow un.EmptyCallback + +UninstPage instfiles + +Section "un.Program Files" SectionUninstallProgram + SectionIn RO + + ; Try to delete the EXE as the first step - if it's in use, don't remove anything else + !insertmacro un.DeleteRetryAbort "$INSTDIR\${PROGEXE}" + !ifdef LICENSE_FILE + !insertmacro un.DeleteRetryAbort "$INSTDIR\${LICENSE_FILE}" + !endif + + ; Clean up "Documentation" + !insertmacro un.DeleteRetryAbort "$INSTDIR\readme.txt" + + ; Clean up "Program Group" + RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}" + + ; Clean up "Dektop Icon" + !insertmacro un.DeleteRetryAbort "$DESKTOP\${PRODUCT_NAME}.lnk" + + ; Clean up "Start Menu Icon" + !insertmacro un.DeleteRetryAbort "$STARTMENU\${PRODUCT_NAME}.lnk" + + ; Clean up "Quick Launch Icon" + !insertmacro un.DeleteRetryAbort "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" +SectionEnd + +Section /o "un.Program Settings" SectionRemoveSettings + ; this section is executed only explicitly and shouldn't be placed in SectionUninstallProgram + DeleteRegKey HKCU "Software\${PRODUCT_NAME}" +SectionEnd + +Section "-Uninstall" ; hidden section, must always be the last one! + ; Remove the uninstaller from registry as the very last step - if sth. goes wrong, let the user run it again + !insertmacro MULTIUSER_RegistryRemoveInstallInfo ; Remove registry keys + + Delete "$INSTDIR\${UNINSTALL_FILENAME}" + ; remove the directory only if it is empty - the user might have saved some files in it + RMDir "$INSTDIR" +SectionEnd + +; Callbacks +Function un.onInit + StrCpy $RunningFromInstaller 0 + ${GetParameters} $R0 + + ${GetOptions} $R0 "/uninstall" $R1 + ${ifnot} ${errors} + StrCpy $RunningFromInstaller 1 + ${else} + StrCpy $RunningFromInstaller 0 + ${endif} + + ${GetOptions} $R0 "/SS" $R1 + ${ifnot} ${errors} + StrCpy $SemiSilentMode 1 + SetAutoClose true ; auto close (if no errors) if we are called from the installer; if there are errors, will be automatically set to false + ${else} + StrCpy $SemiSilentMode 0 + ${endif} + + ${ifnot} ${UAC_IsInnerInstance} + ${andif} $RunningFromInstaller$SemiSilentMode == "00" + !insertmacro CheckSingleInstance "${SINGLE_INSTANCE_ID}" + ${endif} + + !insertmacro MULTIUSER_UNINIT +FunctionEnd + +Function un.EmptyCallback +FunctionEnd + +Function un.PageComponentsPre + ${if} $RunningFromInstaller == 1 + Abort ; if user is installing, no use to remove program settings anyway (should be compatible with all versions) + ${endif} +FunctionEnd + +Function un.PageComponentsShow + ; Show/hide the Back button + GetDlgItem $0 $HWNDPARENT 3 + ShowWindow $0 $UninstallShowBackButton +FunctionEnd + +Function un.onUserAbort + MessageBox MB_YESNO|MB_ICONEXCLAMATION "Are you sure you want to quit $(^Name) Uninstall?" IDYES mui.quit + + Abort + mui.quit: +FunctionEnd + + +Function un.onUninstFailed + ${if} $SemiSilentMode == 0 + MessageBox MB_ICONSTOP "${PRODUCT_NAME} ${VERSION} could not be fully uninstalled.$\r$\nPlease, restart Windows and run the uninstaller again." /SD IDOK + ${else} + MessageBox MB_ICONSTOP "${PRODUCT_NAME} could not be fully installed.$\r$\nPlease, restart Windows and run the setup program again." /SD IDOK + ${endif} +FunctionEnd diff --git a/Demos/NSIS_Full/readme.txt b/Demos/NSIS_Full/readme.txt new file mode 100644 index 0000000..1ba84ae --- /dev/null +++ b/Demos/NSIS_Full/readme.txt @@ -0,0 +1,10 @@ +NsisMultiUser NSIS Full Demo, based on the NSIS built-in pages + +Features: +- supports MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS values 0 AND 1 +- fully uninstalls previously installed version (if any) by running its uninstaller before installing new version +- can safely downgrade and upgrade +- because previous version is fully uninstalled, user can set new installation folder, new start menu folder and choose different components + +Limitations: +none \ No newline at end of file diff --git a/Demos/UMUI_Ex_Full/Setup.nsi b/Demos/UMUI_Ex_Full/Setup.nsi new file mode 100644 index 0000000..5bce724 --- /dev/null +++ b/Demos/UMUI_Ex_Full/Setup.nsi @@ -0,0 +1,312 @@ +!addplugindir /x86-ansi ".\..\..\Plugins\x86-ansi" +!addplugindir /x86-unicode ".\..\..\Plugins\x86-unicode" +!addincludedir ".\..\..\Include" + +!include UMUI.nsh +; OR +; !include MUIEx.nsh +!include UAC.nsh +!include NsisMultiUser.nsh +!include LogicLib.nsh +!include ".\..\Common\Utils.nsh" + +!define PRODUCT_NAME "NsisMultiUser UMUI Full Demo" ; name of the application as displayed to the user +!define VERSION "1.0" ; main version of the application (may be 0.1, alpha, beta, etc.) +!define PROGEXE "calc.exe" ; main application filename +!define COMPANY_NAME "Alex Mitev" ; company, used for registry tree hierarchy +!define PLATFORM "Win64" +!define MIN_WIN_VER "XP" +!define SINGLE_INSTANCE_ID "${COMPANY_NAME} ${PRODUCT_NAME} Unique ID" ; do not change this between program versions! +!define LICENSE_FILE "License.txt" ; license file, optional + +; NsisMultiUser optional defines +!define MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS 0 +!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 +!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT 0 +!define MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 1 +!if ${PLATFORM} == "Win64" + !define MULTIUSER_INSTALLMODE_64_BIT 1 +!endif +!define MULTIUSER_INSTALLMODE_DISPLAYNAME "${PRODUCT_NAME} ${VERSION} ${PLATFORM}" + +Var StartMenuFolder + +; Installer Attributes +Name "${PRODUCT_NAME} ${VERSION} ${PLATFORM}" +OutFile "Setup_UMUI_Full.exe" +BrandingText "©2017 ${COMPANY_NAME}" + +AllowSkipFiles off +SetOverwrite on ; (default setting) set to on except for where it is manually switched off +ShowInstDetails show +SetCompressor /SOLID lzma + +; Pages +; You have 2 options here: +; 1. Set "UMUI_XPSTYLE on" to display the Shield on Next button, which causes non-skinnable radio buttons, group boxes and check boxes (see http://forums.winamp.com/showthread.php?t=399899), +; which requires the use of a light theme +; 2. Remove the "UMUI_XPSTYLE on", use the standard (dark) theme and live without the Next button Shield +!define UMUI_SKIN SoftBlue +!define UMUI_NOBOTTOMIMAGE +!define UMUI_NO_BUTTONIMAGE +!define UMUI_XPSTYLE on + +!define MUI_ABORTWARNING ; Show a confirmation when cancelling the installation + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre +!insertmacro MUI_PAGE_WELCOME + +!ifdef LICENSE_FILE + !define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre + !insertmacro MUI_PAGE_LICENSE ".\..\..\${LICENSE_FILE}" +!endif + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomeLicensePre +!insertmacro MUI_PAGE_LICENSE "readme.txt" + +!define MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION PageInstallModeChangeMode +!insertmacro MULTIUSER_PAGE_INSTALLMODE + +!define MUI_COMPONENTSPAGE_SMALLDESC +!define MUI_PAGE_CUSTOMFUNCTION_SHOW PageComponentsShow +!insertmacro MUI_PAGE_COMPONENTS + +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageDirectoryPre +!define MUI_PAGE_CUSTOMFUNCTION_SHOW PageDirectoryShow +!insertmacro MUI_PAGE_DIRECTORY + +!define MUI_STARTMENUPAGE_NODISABLE ; Do not display the checkbox to disable the creation of Start Menu shortcuts +!define MUI_STARTMENUPAGE_DEFAULTFOLDER "${PRODUCT_NAME}" +!define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" ; writing to $StartMenuFolder happens in MUI_STARTMENU_WRITE_END, so it's safe to use "SHCTX" here +!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" +!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "StartMenuFolder" +!define MUI_PAGE_CUSTOMFUNCTION_PRE PageStartMenuPre +!insertmacro MUI_PAGE_STARTMENU "" "$StartMenuFolder" + +!insertmacro MUI_PAGE_INSTFILES + +!define MUI_FINISHPAGE_RUN +!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun +!insertmacro MUI_PAGE_FINISH + +!include Uninstall.nsh + +!insertmacro MUI_LANGUAGE "English" ; Set languages (first is default language) - must be inserted after all pages + +InstType "Typical" +InstType "Minimal" +InstType "Full" + +Section "Core Files (required)" SectionCoreFiles + SectionIn 1 2 3 RO + + ; Ultra Modern UI modifies shell var context, see http://forums.winamp.com/showthread.php?t=399898 + ${if} $MultiUser.InstallMode == "AllUsers" + SetShellVarContext all + ${else} + SetShellVarContext current + ${endif} + + ; if there's an installed version, uninstall it first (I chose not to start the uninstaller silently, so that user sees what failed) + ; if both per-user and per-machine versions are installed, unistall the one that matches $MultiUser.InstallMode + StrCpy $0 "" + ${if} $HasCurrentModeInstallation == 1 + StrCpy $0 "$MultiUser.InstallMode" + ${else} + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 + ${if} $HasPerMachineInstallation == 1 + StrCpy $0 "AllUsers" ; if there's no per-user installation, but there's per-machine installation, uninstall it + ${elseif} $HasPerUserInstallation == 1 + StrCpy $0 "CurrentUser" ; if there's no per-machine installation, but there's per-user installation, uninstall it + ${endif} + !endif + ${endif} + + ${if} "$0" != "" + ${if} $0 == "AllUsers" + StrCpy $1 "$PerMachineUninstallString" + StrCpy $3 "$PerMachineInstallationFolder" + ${else} + StrCpy $1 "$PerUserUninstallString" + StrCpy $3 "$PerUserInstallationFolder" + ${endif} + ${if} ${silent} + StrCpy $2 "/S" + ${else} + StrCpy $2 "" + ${endif} + + HideWindow + ClearErrors + StrCpy $0 0 + ExecWait '$1 /SS $2 _?=$3' $0 ; $1 is quoted in registry; the _? param stops the uninstaller from copying itself to the temporary directory, which is the only way for ExecWait to work + + ${if} ${errors} ; stay in installer + SetErrorLevel 2 ; Installation aborted by script + BringToFront + Abort "Error executing uninstaller." + ${else} + ${Switch} $0 + ${Case} 0 ; uninstaller completed successfully - continue with installation + BringToFront + ${Break} + ${Case} 1 ; Installation aborted by user (cancel button) + ${Case} 2 ; Installation aborted by script + SetErrorLevel $0 + Quit ; uninstaller was started, but completed with errors - Quit installer + ${Default} ; all other error codes - uninstaller could not start, elevate, etc. - Abort installer + SetErrorLevel $0 + BringToFront + Abort "Error executing uninstaller." + ${EndSwitch} + ${endif} + + Delete "$2\${UNINSTALL_FILENAME}" ; the uninstaller doesn't delete itself when not copied to the temp directory + RMDir "$2" + ${endif} + + SetOutPath $INSTDIR + ; Write uninstaller and registry uninstall info as the first step, + ; so that the user has the option to run the uninstaller if sth. goes wrong + WriteUninstaller "${UNINSTALL_FILENAME}" + !insertmacro MULTIUSER_RegistryAddInstallInfo ; add registry keys + + File "C:\Windows\System32\${PROGEXE}" + !ifdef LICENSE_FILE + File ".\..\..\${LICENSE_FILE}" + !endif +SectionEnd + +Section "Documentation" SectionDocumentation + SectionIn 1 3 + + SetOutPath $INSTDIR + File "readme.txt" + +SectionEnd + +SectionGroup /e "Integration" SectionGroupIntegration + +Section "Program Group" SectionProgramGroup + SectionIn 1 3 + + !insertmacro MUI_STARTMENU_WRITE_BEGIN "" + + CreateDirectory "$SMPROGRAMS\$StartMenuFolder" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" + + !ifdef LICENSE_FILE + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\License Agreement.lnk" "$INSTDIR\${LICENSE_FILE}" + !endif + ${if} $MultiUser.InstallMode == "AllUsers" + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/allusers" + ${else} + CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall (current user).lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "/currentuser" + ${endif} + + !insertmacro MUI_STARTMENU_WRITE_END +SectionEnd + +Section "Dektop Icon" SectionDesktopIcon + SectionIn 1 3 + + CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd + +Section /o "Start Menu Icon" SectionStartMenuIcon + SectionIn 3 + + CreateShortCut "$STARTMENU\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd + +Section /o "Quick Launch" SectionQuickLaunchIcon + SectionIn 3 + + ; The QuickLaunch is always only for the current user + CreateShortCut "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" "$INSTDIR\${PROGEXE}" +SectionEnd +SectionGroupEnd + +; Modern install component descriptions +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SectionCoreFiles} "Core files requred to run ${PRODUCT_NAME}." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionDocumentation} "Help files for ${PRODUCT_NAME}." + + !insertmacro MUI_DESCRIPTION_TEXT ${SectionGroupIntegration} "Select how to integrate the program in Windows." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionProgramGroup} "Create a ${PRODUCT_NAME} program group under Start Menu->Programs." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionDesktopIcon} "Create ${PRODUCT_NAME} icon on the Desktop." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionStartMenuIcon} "Create ${PRODUCT_NAME} icon in the Start Menu." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionQuickLaunchIcon} "Create ${PRODUCT_NAME} icon in Quick Launch." +!insertmacro MUI_FUNCTION_DESCRIPTION_END + +; Callbacks +Function .onInit + !insertmacro CheckPlatform ${PLATFORM} + !insertmacro CheckMinWinVer ${MIN_WIN_VER} + ${ifnot} ${UAC_IsInnerInstance} + !insertmacro CheckSingleInstance "${SINGLE_INSTANCE_ID}" + ${endif} + + !insertmacro MULTIUSER_INIT +FunctionEnd + +Function PageWelcomeLicensePre + ${if} $InstallShowPagesBeforeComponents == 0 + Abort ; don't display the Welcome and License pages for the inner instance + ${endif} +FunctionEnd + +Function PageInstallModeChangeMode + !insertmacro MUI_STARTMENU_GETFOLDER "" $StartMenuFolder +FunctionEnd + +Function PageComponentsShow + ; UMUI hides the Back button on the Components page for the inner instance, which makes sense, as this is the 1st page, + ; but we need to show it to return to the outer instance + GetDlgItem $0 $HWNDPARENT 3 + ShowWindow $0 1 +FunctionEnd + +Function PageDirectoryPre + GetDlgItem $0 $HWNDPARENT 1 + ${if} ${SectionIsSelected} ${SectionProgramGroup} + SendMessage $0 ${WM_SETTEXT} 0 "STR:$(^NextBtn)" ; this is not the last page before installing + ${else} + SendMessage $0 ${WM_SETTEXT} 0 "STR:$(^InstallBtn)" ; this is the last page before installing + ${endif} +FunctionEnd + +Function PageDirectoryShow + ${if} $CmdLineDir != "" + FindWindow $R1 "#32770" "" $HWNDPARENT + + GetDlgItem $0 $R1 1019 ; Directory edit + SendMessage $0 ${EM_SETREADONLY} 1 0 ; read-only is better than disabled, as user can copy contents + + GetDlgItem $0 $R1 1001 ; Browse button + EnableWindow $0 0 + ${endif} +FunctionEnd + +Function PageStartMenuPre + ${ifnot} ${SectionIsSelected} ${SectionProgramGroup} + Abort ; don't display this dialog if SectionProgramGroup is not selected + ${endif} +FunctionEnd + +Function PageFinishRun + ; the installer might exit too soon before the application starts and it loses the right to be the foreground window and starts in the background + ; however, if there's no active window when the application starts, it will become the active window, so we hide the installer + HideWindow + ; the installer will show itself again quickly before closing (w/o Taskbar button), we move it offscreen + !define SWP_NOSIZE 0x0001 + !define SWP_NOZORDER 0x0004 + System::Call "User32::SetWindowPos(i, i, i, i, i, i, i) b ($HWNDPARENT, 0, -1000, -1000, 0, 0, ${SWP_NOZORDER}|${SWP_NOSIZE})" + + !insertmacro UAC_AsUser_ExecShell "open" "$INSTDIR\${PROGEXE}" "" "$INSTDIR" "" +FunctionEnd + +Function .onInstFailed + MessageBox MB_ICONSTOP "${PRODUCT_NAME} ${VERSION} could not be fully installed.$\r$\nPlease, restart Windows and run the setup program again." /SD IDOK +FunctionEnd + diff --git a/Demos/UMUI_Ex_Full/Uninstall.nsh b/Demos/UMUI_Ex_Full/Uninstall.nsh new file mode 100644 index 0000000..d4b7150 --- /dev/null +++ b/Demos/UMUI_Ex_Full/Uninstall.nsh @@ -0,0 +1,122 @@ +Var SemiSilentMode ; installer started uninstaller in semi-silent mode using /SS parameter +Var RunningFromInstaller ; installer started uninstaller using /uninstall parameter + +; Installer Attributes +ShowUninstDetails show + +; Pages +!define MUI_UNABORTWARNING ; Show a confirmation when cancelling the installation + +!define MULTIUSER_INSTALLMODE_CHANGE_MODE_UNFUNCTION un.PageInstallModeChangeMode +!insertmacro MULTIUSER_UNPAGE_INSTALLMODE + +!define MUI_PAGE_CUSTOMFUNCTION_PRE un.PageComponentsPre +!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.PageComponentsShow +!insertmacro MUI_UNPAGE_COMPONENTS + +!insertmacro MUI_UNPAGE_INSTFILES + +Section "un.Program Files" SectionUninstallProgram + SectionIn RO + + ; Ultra Modern UI modifies shell var context, see http://forums.winamp.com/showthread.php?t=399898 + ${if} $MultiUser.InstallMode == "AllUsers" + SetShellVarContext all + ${else} + SetShellVarContext current + ${endif} + + ; Try to delete the EXE as the first step - if it's in use, don't remove anything else + !insertmacro un.DeleteRetryAbort "$INSTDIR\${PROGEXE}" + !ifdef LICENSE_FILE + !insertmacro un.DeleteRetryAbort "$INSTDIR\${LICENSE_FILE}" + !endif + + ; Clean up "Documentation" + !insertmacro un.DeleteRetryAbort "$INSTDIR\readme.txt" + + ; Clean up "Program Group" - we check that we created Start menu folder, if $StartMenuFolder is empty, the whole $SMPROGRAMS directory will be removed! + ${if} "$StartMenuFolder" != "" + RMDir /r "$SMPROGRAMS\$StartMenuFolder" + ${endif} + + ; Clean up "Dektop Icon" + !insertmacro un.DeleteRetryAbort "$DESKTOP\${PRODUCT_NAME}.lnk" + + ; Clean up "Start Menu Icon" + !insertmacro un.DeleteRetryAbort "$STARTMENU\${PRODUCT_NAME}.lnk" + + ; Clean up "Quick Launch Icon" + !insertmacro un.DeleteRetryAbort "$QUICKLAUNCH\${PRODUCT_NAME}.lnk" +SectionEnd + +Section /o "un.Program Settings" SectionRemoveSettings + ; this section is executed only explicitly and shouldn't be placed in SectionUninstallProgram + DeleteRegKey HKCU "Software\${PRODUCT_NAME}" +SectionEnd + +Section "-Uninstall" ; hidden section, must always be the last one! + ; Remove the uninstaller from registry as the very last step - if sth. goes wrong, let the user run it again + !insertmacro MULTIUSER_RegistryRemoveInstallInfo ; Remove registry keys + + Delete "$INSTDIR\${UNINSTALL_FILENAME}" + ; remove the directory only if it is empty - the user might have saved some files in it + RMDir "$INSTDIR" +SectionEnd + +; Modern install component descriptions +!insertmacro MUI_UNFUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SectionUninstallProgram} "Uninstall ${PRODUCT_NAME} files." + !insertmacro MUI_DESCRIPTION_TEXT ${SectionRemoveSettings} "Remove ${PRODUCT_NAME} program settings. Select only if you don't plan to use the program in the future." +!insertmacro MUI_UNFUNCTION_DESCRIPTION_END + +; Callbacks +Function un.onInit + ${GetParameters} $R0 + + ${GetOptions} $R0 "/uninstall" $R1 + ${ifnot} ${errors} + StrCpy $RunningFromInstaller 1 + ${else} + StrCpy $RunningFromInstaller 0 + ${endif} + + ${GetOptions} $R0 "/SS" $R1 + ${ifnot} ${errors} + StrCpy $SemiSilentMode 1 + SetAutoClose true ; auto close (if no errors) if we are called from the installer; if there are errors, will be automatically set to false + ${else} + StrCpy $SemiSilentMode 0 + ${endif} + + ${ifnot} ${UAC_IsInnerInstance} + ${andif} $RunningFromInstaller$SemiSilentMode == "00" + !insertmacro CheckSingleInstance "${SINGLE_INSTANCE_ID}" + ${endif} + + !insertmacro MULTIUSER_UNINIT +FunctionEnd + +Function un.PageInstallModeChangeMode + !insertmacro MUI_STARTMENU_GETFOLDER "" $StartMenuFolder +FunctionEnd + +Function un.PageComponentsPre + ${if} $SemiSilentMode == 1 + Abort ; if user is installing, no use to remove program settings anyway (should be compatible with all versions) + ${endif} +FunctionEnd + +Function un.PageComponentsShow + ; Show/hide the Back button + GetDlgItem $0 $HWNDPARENT 3 + ShowWindow $0 $UninstallShowBackButton +FunctionEnd + +Function un.onUninstFailed + ${if} $SemiSilentMode == 0 + MessageBox MB_ICONSTOP "${PRODUCT_NAME} ${VERSION} could not be fully uninstalled.$\r$\nPlease, restart Windows and run the uninstaller again." /SD IDOK + ${else} + MessageBox MB_ICONSTOP "${PRODUCT_NAME} could not be fully installed.$\r$\nPlease, restart Windows and run the setup program again." /SD IDOK + ${endif} +FunctionEnd diff --git a/Demos/UMUI_Ex_Full/readme.txt b/Demos/UMUI_Ex_Full/readme.txt new file mode 100644 index 0000000..278295d --- /dev/null +++ b/Demos/UMUI_Ex_Full/readme.txt @@ -0,0 +1,10 @@ +NsisMultiUser UMUI Full Demo, based on the Ultra Modern and ModernUIEx User Interfaces, using the standard MUI dialogs + +Features: +- supports MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS values 0 AND 1 +- fully uninstalls previously installed version (if any) by running its uninstaller before installing new version +- can safely downgrade and upgrade +- because previous version is fully uninstalled, user can set new installation folder, new start menu folder and choose different components + +Limitations: +none \ No newline at end of file diff --git a/Demos/test.bat b/Demos/test.bat new file mode 100644 index 0000000..af63138 --- /dev/null +++ b/Demos/test.bat @@ -0,0 +1,381 @@ +:: test by running this file both as user and as admin + +@echo OFF +:: set setup="MUI_1_2_Full\Setup_MUI_1_2_Full.exe" +:: set setup="MUI2_Limited\Setup_MUI2_Limited.exe" +:: set setup="NSIS_Full\Setup_NSIS_Full.exe" + set setup="UMUI_Full\Setup_UMUI_Ex_Full.exe" +:: set setup="UMUI_Full2\Setup_UMUI_Ex_Full2.exe" + +cd /D %0\..\%setup%\.. + +set loop=0 +set test=0 + +:Loop +echo Compile with: +echo MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS = 1 +echo MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = %loop% +echo MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT = %loop% +echo ... +pause > nul + + +echo. +set /A test=test+1 +echo ***(%test%/16) THERE'S NO INSTALLATION*** +echo (uninstall program compeletely) +echo ... +pause > nul + +echo. +echo no parameters +START "" /WAIT %setup% +echo Result: %errorlevel% + +echo. +echo /allusers +START "" /WAIT %setup% /allusers +echo Result: %errorlevel% + +echo. +echo /currentuser +START "" /WAIT %setup% /currentuser +echo Result: %errorlevel% + +echo. +echo /allusers /S +START "" /WAIT %setup% /allusers /S +echo Result: %errorlevel% +pause + +echo. +echo /currentuser /S +START "" /WAIT %setup% /currentuser /S +echo Result: %errorlevel% +pause + +echo. +echo /allusers /uninstall +START "" /WAIT %setup% /allusers /uninstall +echo Result: %errorlevel% + +echo. +echo /allusers /uninstall /S +START "" /WAIT %setup% /allusers /uninstall /S +echo Result: %errorlevel% + + +echo. +set /A test=test+1 +echo ***(%test%/16) THERE'S PER-USER INSTALLATION ONLY*** +echo ... +pause > nul + +echo. +echo no parameters +START "" /WAIT %setup% +echo Result: %errorlevel% + +echo. +echo /currentuser +START "" /WAIT %setup% /currentuser +echo Result: %errorlevel% + +echo. +echo /allusers +START "" /WAIT %setup% /allusers +echo Result: %errorlevel% + +echo. +echo /currentuser /S +START "" /WAIT %setup% /currentuser /S +echo Result: %errorlevel% + +echo. +echo /allusers /S +START "" /WAIT %setup% /allusers /S +echo Result: %errorlevel% +pause + +echo. +echo /currentuser /uninstall +START "" /WAIT %setup% /currentuser /uninstall +echo Result: %errorlevel% + +echo. +echo /currentuser /uninstall /S +START "" /WAIT %setup% /currentuser /uninstall /S +echo Result: %errorlevel% + + +echo. +set /A test=test+1 +echo ***(%test%/16) THERE'S PER-MACHINE INSTALLATION ONLY*** +echo (uninstall per-user version) +echo ... +pause > nul + +echo. +echo no parameters +START "" /WAIT %setup% +echo Result: %errorlevel% + +echo. +echo /allusers +START "" /WAIT %setup% /allusers +echo Result: %errorlevel% + +echo. +echo /currentuser +START "" /WAIT %setup% /currentuser +echo Result: %errorlevel% + +echo. +echo /allusers /S +START "" /WAIT %setup% /allusers /S +echo Result: %errorlevel% +pause + +echo. +echo /currentuser /S +START "" /WAIT %setup% /currentuser /S +echo Result: %errorlevel% + + +echo. +set /A test=test+1 +echo ***(%test%/16) THERE ARE BOTH PER-USER AND PER-MACHINE INSTALLATIONS*** +echo ... +pause > nul + +echo. +echo no parameters +START "" /WAIT %setup% +echo Result: %errorlevel% + +echo. +echo /allusers +START "" /WAIT %setup% /allusers +echo Result: %errorlevel% + +echo. +echo /currentuser +START "" /WAIT %setup% /currentuser +echo Result: %errorlevel% + +echo. +echo /allusers /S +START "" /WAIT %setup% /allusers /S +echo Result: %errorlevel% +pause + +echo. +echo /currentuser /S +START "" /WAIT %setup% /currentuser /S +echo Result: %errorlevel% + + +echo. +echo. +echo Compile with: +echo MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS = 0 +echo MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = %loop% +echo MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT = %loop% +echo ... +pause > nul + + +echo. +set /A test=test+1 +echo ***(%test%/16) THERE ARE BOTH PER-USER AND PER-MACHINE INSTALLATIONS*** +echo ... +pause > nul + +echo. +echo no parameters +START "" /WAIT %setup% +echo Result: %errorlevel% + +echo. +echo /allusers +START "" /WAIT %setup% /allusers +echo Result: %errorlevel% + +echo. +echo /currentuser +START "" /WAIT %setup% /currentuser +echo Result: %errorlevel% + +echo. +echo /allusers /S +START "" /WAIT %setup% /allusers /S +echo Result: %errorlevel% +pause + +echo. +echo /currentuser /S +START "" /WAIT %setup% /currentuser /S +echo Result: %errorlevel% + +echo. +echo /currentuser /uninstall +START "" /WAIT %setup% /currentuser /uninstall +echo Result: %errorlevel% + +echo. +echo /currentuser /uninstall /S +START "" /WAIT %setup% /currentuser /uninstall /S +echo Result: %errorlevel% + + +echo. +set /A test=test+1 +echo ***(%test%/16) THERE'S PER-MACHINE INSTALLATION ONLY*** +echo (uninstall per-user version) +echo ... +pause > nul + +echo. +echo no parameters +START "" /WAIT %setup% +echo Result: %errorlevel% + +echo. +echo /allusers +START "" /WAIT %setup% /allusers +echo Result: %errorlevel% + +echo. +echo /currentuser +START "" /WAIT %setup% /currentuser +echo Result: %errorlevel% + +echo. +echo /allusers /S +START "" /WAIT %setup% /allusers /S +echo Result: %errorlevel% +pause + +echo. +echo /currentuser /S +START "" /WAIT %setup% /currentuser /S +echo Result: %errorlevel% + +echo. +echo /allusers /uninstall +START "" /WAIT %setup% /allusers /uninstall +echo Result: %errorlevel% + +echo. +echo /allusers /uninstall /S +START "" /WAIT %setup% /allusers /uninstall /S +echo Result: %errorlevel% + + +echo. +set /A test=test+1 +echo ***(%test%/16) THERE'S PER-USER INSTALLATION ONLY*** +echo ... +pause > nul + +echo. +echo no parameters +START "" /WAIT %setup% +echo Result: %errorlevel% + +echo. +echo /allusers +START "" /WAIT %setup% /allusers +echo Result: %errorlevel% + +echo. +echo /currentuser +START "" /WAIT %setup% /currentuser +echo Result: %errorlevel% + +echo. +echo /allusers /S +START "" /WAIT %setup% /allusers /S +echo Result: %errorlevel% +pause + +echo. +echo /currentuser /S +START "" /WAIT %setup% /currentuser /S +echo Result: %errorlevel% + +echo. +echo /currentuser /uninstall +START "" /WAIT %setup% /currentuser /uninstall +echo Result: %errorlevel% + +echo. +echo /currentuser /uninstall /S +START "" /WAIT %setup% /currentuser /uninstall /S +echo Result: %errorlevel% + + +echo. +set /A test=test+1 +echo ***(%test%/16) THERE'S NO INSTALLATION*** +echo (uninstall per-user version) +echo ... +pause > nul + +echo. +echo no parameters +START "" /WAIT %setup% +echo Result: %errorlevel% + +echo. +echo /allusers +START "" /WAIT %setup% /allusers +echo Result: %errorlevel% + +echo. +echo /currentuser +START "" /WAIT %setup% /currentuser +echo Result: %errorlevel% + +echo. +echo /allusers /S +START "" /WAIT %setup% /allusers /S +echo Result: %errorlevel% +pause + +echo. +echo /currentuser /S +START "" /WAIT %setup% /currentuser /S +echo Result: %errorlevel% + +echo. +echo /allusers /uninstall +START "" /WAIT %setup% /allusers /uninstall +echo Result: %errorlevel% + +echo. +echo /allusers /uninstall /S +START "" /WAIT %setup% /allusers /uninstall /S +echo Result: %errorlevel% + +echo. +echo /currentuser /uninstall +START "" /WAIT %setup% /currentuser /uninstall +echo Result: %errorlevel% + +echo. +echo /currentuser /uninstall /S +START "" /WAIT %setup% /currentuser /uninstall /S +echo Result: %errorlevel% + + + + + +set /A loop=loop+1 +if %loop% LSS 2 goto Loop + + +echo Press a key to exit... +pause > nul \ No newline at end of file diff --git a/Documentation/screenshot1.png b/Documentation/screenshot1.png deleted file mode 100644 index c41e83d..0000000 Binary files a/Documentation/screenshot1.png and /dev/null differ diff --git a/Documentation/screenshot2.png b/Documentation/screenshot2.png deleted file mode 100644 index e3cd294..0000000 Binary files a/Documentation/screenshot2.png and /dev/null differ diff --git a/Documentation/screenshot3.png b/Documentation/screenshot3.png deleted file mode 100644 index b237402..0000000 Binary files a/Documentation/screenshot3.png and /dev/null differ diff --git a/Documentation/screenshot4.png b/Documentation/screenshot4.png deleted file mode 100644 index 4213715..0000000 Binary files a/Documentation/screenshot4.png and /dev/null differ diff --git a/Documentation/screenshot5.png b/Documentation/screenshot5.png deleted file mode 100644 index a4e9892..0000000 Binary files a/Documentation/screenshot5.png and /dev/null differ diff --git a/Documentation/screenshot6.png b/Documentation/screenshot6.png deleted file mode 100644 index 434470e..0000000 Binary files a/Documentation/screenshot6.png and /dev/null differ diff --git a/Documentation/screenshot7.png b/Documentation/screenshot7.png deleted file mode 100644 index f0bd3e9..0000000 Binary files a/Documentation/screenshot7.png and /dev/null differ diff --git a/Include/NsisMultiUser.nsh b/Include/NsisMultiUser.nsh index 7835bc2..5f6f00c 100644 --- a/Include/NsisMultiUser.nsh +++ b/Include/NsisMultiUser.nsh @@ -1,25 +1,17 @@ -/* -SimpleMultiUser.nsh - Installer/Uninstaller that allows installations "per-user" (no admin required) or "per-machine" (asks elevation *only when necessary*) -By Ricardo Drizin (contact at http://drizin.com.br) -This plugin is based on [MultiUser.nsh (by Joost Verburg)](http://nsis.sourceforge.net/Docs/MultiUser/Readme.html) but with some new features and some simplifications: -- Installer allows installations "per-user" (no admin required) or "per-machine" (as original) -- If running user IS part of Administrators group, he is not forced to elevate (only if necessary - for per-machine install) -- If running user is NOT part of Administrators group, he is still able to elevate and install per-machine (I expect that power-users will have administrator password, but will not be part of the administrators group) -- UAC Elevation happens only when necessary (when per-machine is selected), not in the start of the installer -- Uninstaller block is mandatory (why shouldn't it be?) -- If there are both per-user and per-machine installations, user can choose which one to remove during uninstall -- Correctly creates and removes shortcuts and registry (per-user and per-machine are totally independent) -- Fills uninstall information in registry like Icon and Estimated Size. -- If running as non-elevated user, the "per-machine" install can be allowed (automatically invoking UAC elevation) or can be disabled (suggesting to run again as elevated user) -- If elevation is invoked for per-machine install, the calling process automatically hides itself, and the elevated inner process automatically skips the choice screen (cause in this case we know that per-machine installation was chosen) -- If uninstalling from the "add/remove programs", automatically detects if user is trying to remove per-machine or per-user install +/* + +NsisMultiUser.nsh - NSIS plugin that allows "per-user" (no admin required) and "per-machine" (asks elevation *only when necessary*) installations + +Full source code, documentation and demos at https://github.com/Drizin/NsisMultiUser/ + +Copyright 2016-2017 Ricardo Drizin, Alex Mitev + */ !verbose push !verbose 3 -;Standard NSIS header files -!include MUI2.nsh +; Standard NSIS header files !include nsDialogs.nsh !include LogicLib.nsh !include WinVer.nsh @@ -30,58 +22,49 @@ RequestExecutionLevel user ; will ask elevation only if necessary ; exit and error codes !define MULTIUSER_ERROR_INVALID_PARAMETERS 666660 ; invalid command-line parameters -!define MULTIUSER_ERROR_ELEVATION_NOT_ALLOWED 666661 ; elevation is restricted by MULTIUSER_INSTALLMODE_ALLOW_ELEVATION and MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT +!define MULTIUSER_ERROR_ELEVATION_NOT_ALLOWED 666661 ; elevation is restricted by MULTIUSER_INSTALLMODE_ALLOW_ELEVATION or MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT !define MULTIUSER_ERROR_NOT_INSTALLED 666662 ; returned from uninstaller when no version is installed !define MULTIUSER_ERROR_ELEVATION_FAILED 666666 ; returned by the outer instance when the inner instance cannot start (user aborted elevation dialog, Logon service not running, UAC is not supported by the OS, user without admin priv. is used in the runas dialog), or started, but was not admin !define MULTIUSER_INNER_INSTANCE_BACK 666667 ; returned by the inner instance when the user presses the Back button on the first visible page (display outer instance) -;Macros for compile-time defines -!ifmacrondef MUI_DEFAULT - !macro MUI_DEFAULT SYMBOL CONTENT - ;Define symbol if not yet defined - ;For setting default values - !ifndef "${SYMBOL}" - !define "${SYMBOL}" "${CONTENT}" - !endif - !macroend -!endif - !macro MULTIUSER_INIT_VARS ; required defines - !ifndef PRODUCT_NAME | VERSION | PROGEXE | COMPANY_NAME - !error "Should define all variables: PRODUCT_NAME, VERSION, PROGEXE, COMPANY_NAME" + !ifndef PRODUCT_NAME | VERSION | PROGEXE + !error "Should define all variables: PRODUCT_NAME, VERSION, PROGEXE" !endif ; optional defines - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 ; 0 (false) or 1 (true), allow UAC screens in the (un)installer - if set to 0 and user is not admin, per-machine radiobutton will be disabled, or if elevation is required, (un)installer will exit with an error code (and message if not silent) - !if "${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION}" == "" ; old code - just defined with no value, change to this code now: !define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 0 + ; COMPANY_NAME - stored in uninstall info in registry + ; MULTIUSER_INSTALLMODE_NO_HELP_DIALOG - don't show help dialog + + !define /ifndef MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS 1 ; 0 or 1 - whether user can install BOTH per-user and per-machine; this only affects the texts and the required elevation on the page, the actual uninstall of previous version has to be implemented by script + !define /ifndef MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 ; 0 or 1, allow UAC screens in the (un)installer - if set to 0 and user is not admin, per-machine radiobutton will be disabled, or if elevation is always required, (un)installer will exit with an error code (and message if not silent) + !if "${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION}" == "" ; old code - just defined with no value, equivalent to 1 !define /redef MULTIUSER_INSTALLMODE_ALLOW_ELEVATION 1 !endif - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT 0 ; 0 (false) or 1 (true), (only available if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 1) allow UAC screens in the (un)installer in silent mode; if set to 0 and user is not admin and elevation is required, (un)installer will exit with an error code + !define /ifndef MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT 0 ; 0 or 1, (only available if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 1) allow UAC screens in the (un)installer in silent mode; if set to 0 and user is not admin and elevation is always required, (un)installer will exit with an error code !if "${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION}" == 0 !if "${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT}" == 1 !error "MULTIUSER_INSTALLMODE_ALLOW_ELEVATION_IF_SILENT can be set only when MULTIUSER_INSTALLMODE_ALLOW_ELEVATION is set!" !endif - !endif - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS 1 ; 0 (false) or 1 (true) - whether user can install BOTH per-user and per-machine; this only affects the texts (and shield) on the page, and the required elevation, the actual uninstall of previous version has to be implemented by script - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 0 ; 0 (false) or 1 (true), (only available if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 1 or running elevated and there are 0 or 2 installations on the system) when running as user and is set to 1, per-machine installation is pre-selected, otherwise per-user installation - !if "${MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS}" == "" ; old code - just defined with no value, change to this code now: !define MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 0 + !endif + !define /ifndef MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 0 ; 0 or 1, (only available if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 1 and there are 0 or 2 installations on the system) when running as user and is set to 1, per-machine installation is pre-selected, otherwise per-user installation + !if "${MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS}" == "" ; old code - just defined with no value, equivalent to 1 !define /redef MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS 1 !endif - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER 0 ; 0 (false) or 1 (true), (only available if there are 0 or 2 installations on the system) when running as admin and is set to 1, per-user installation is pre-selected, otherwise per-machine installation - !if "${MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER}" == "" ; old code - just defined with no value, change to this code now: !define MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER 0 + !define /ifndef MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER 0 ; 0 or 1, (only available if there are 0 or 2 installations on the system) when running as admin and is set to 1, per-user installation is pre-selected, otherwise per-machine installation + !if "${MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER}" == "" ; old code - just defined with no value, equivalent to 1 !define /redef MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER 1 !endif - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_PROGRAMFILES $PROGRAMFILES ; set to "$PROGRAMFILES64" for 64-bit installers - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCT_NAME}" ; suggested name of directory to install (under $MULTIUSER_INSTALLMODE_PROGRAMFILES or $LOCALAPPDATA) - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY "${PRODUCT_NAME}" ; registry key for UNINSTALL info, placed under [HKLM|HKCU]\Software\Microsoft\Windows\CurrentVersion\Uninstall (can be ${PRODUCT_NAME} or some {GUID}) - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY "Microsoft\Windows\CurrentVersion\Uninstall\${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY}" ; registry key where InstallLocation is stored, placed under [HKLM|HKCU]\Software (can be ${PRODUCT_NAME} or some {GUID}) + !define /ifndef MULTIUSER_INSTALLMODE_64_BIT 0 ; set to 1 for 64-bit installers + !define /ifndef MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCT_NAME}" ; suggested name of directory to install (under $PROGRAMFILES32/$PROGRAMFILES64 or $LOCALAPPDATA) + !define /ifndef MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY "${PRODUCT_NAME}" ; registry key for UNINSTALL info, placed under [HKLM|HKCU]\Software\Microsoft\Windows\CurrentVersion\Uninstall (can be ${PRODUCT_NAME} or some {GUID}) + !define /ifndef MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY "Microsoft\Windows\CurrentVersion\Uninstall\${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY}" ; registry key where InstallLocation is stored, placed under [HKLM|HKCU]\Software (can be ${PRODUCT_NAME} or some {GUID}) !define MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY}" ; full path to registry key storing uninstall information displayed in Windows installed programs list !define MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY2 "Software\${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY}" ; full path to registry key where InstallLocation is stored - !insertmacro MUI_DEFAULT UNINSTALL_FILENAME "uninstall.exe" ; name of uninstaller - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "UninstallString" ; do not change this value - doing so will make the program disappear from the Windows installed programs list - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_DISPLAYNAME "${PRODUCT_NAME} ${VERSION}" ; display name in Windows uninstall list of programs - !insertmacro MUI_DEFAULT MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "InstallLocation" ; name of the registry value containing install directory + !define /ifndef UNINSTALL_FILENAME "uninstall.exe" ; name of uninstaller + !define /ifndef MULTIUSER_INSTALLMODE_DISPLAYNAME "${PRODUCT_NAME} ${VERSION}" ; display name in Windows uninstall list of programs + !define /ifndef MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "InstallLocation" ; name of the registry value containing install directory !ifdef MULTIUSER_INSTALLMODE_FUNCTION !define MULTIUSER_INSTALLMODE_CHANGE_MODE_FUNCTION ${MULTIUSER_INSTALLMODE_FUNCTION} ; old code - changed function name @@ -90,16 +73,20 @@ RequestExecutionLevel user ; will ask elevation only if necessary ; Variables Var MultiUser.Privileges ; Current user level: "Admin", "Power" (up to Windows XP), or else regular user. Var MultiUser.InstallMode ; Current Install Mode ("AllUsers" or "CurrentUser") - Var IsAdmin ; 0 (false) or 1 (true) - Var HasPerMachineInstallation ; 0 (false) or 1 (true) - Var HasPerUserInstallation ; 0 (false) or 1 (true) - Var PerMachineInstallationFolder - Var PerUserInstallationFolder + Var IsAdmin ; 0 or 1, initialized via UserInfo::GetAccountType + Var IsInnerInstance ; 0 or 1, initialized via UAC_IsInnerInstance + Var HasPerMachineInstallation ; 0 or 1 + Var HasPerUserInstallation ; 0 or 1 + Var HasCurrentModeInstallation ; 0 or 1 Var PerMachineInstallationVersion ; contains version number of empty string "" - Var PerUserInstallationVersion ; contains version number of empty string "" - Var HasTwoAvailableOptions ; 0 (false) or 1 (true): 0 means only per-user radio button is enabled on page, 1 means both; will be 0 only when MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 0 and user is not admin - Var InstallHidePagesBeforeComponents ; 0 (false) or 1 (true), use it to hide all pages before Components inside the installer when running as inner instance - Var UninstallHideBackButton ; 0 (false) or 1 (true), use it to hide the Back button on the first visible page of the uninstaller + Var PerUserInstallationVersion ; contains version number of empty string "" + Var PerMachineInstallationFolder + Var PerUserInstallationFolder + Var PerMachineUninstallString + Var PerUserUninstallString + Var PerMachineOptionAvailable ; 0 or 1: 0 means only per-user radio button is enabled on page, 1 means both; will be 0 only when MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 0 and user is not admin + Var InstallShowPagesBeforeComponents ; 0 or 1, when 0, use it to hide all pages before Components inside the installer when running as inner instance + Var UninstallShowBackButton ; 0 or 1, use it to show/hide the Back button on the first visible page of the uninstaller Var DisplayDialog ; (internal) Var PreFunctionCalled ; (internal) Var CmdLineInstallMode ; contains command-line install mode set via /allusers and /currentusers parameters @@ -110,9 +97,9 @@ RequestExecutionLevel user ; will ask elevation only if necessary Var MultiUser.InstallModePage.Text Var MultiUser.InstallModePage.AllUsers Var MultiUser.InstallModePage.CurrentUser - Var MultiUser.RadioButtonLabel1 - ;Var MultiUser.RadioButtonLabel2 - ;Var MultiUser.RadioButtonLabel3 + Var MultiUser.InstallModePage.AllUsersLabel + Var MultiUser.InstallModePage.CurrentUserLabel + Var MultiUser.InstallModePage.Description !macroend !macro MULTIUSER_UNINIT_VARS @@ -128,15 +115,21 @@ RequestExecutionLevel user ; will ask elevation only if necessary !endif !define MULTIUSER_${UNINSTALLER_PREFIX}PAGE_INSTALLMODE - !insertmacro MUI_${UNINSTALLER_PREFIX}PAGE_INIT + !ifmacrodef MUI_${UNINSTALLER_PREFIX}PAGE_INIT + !insertmacro MUI_${UNINSTALLER_PREFIX}PAGE_INIT + !endif !insertmacro MULTIUSER_${UNINSTALLER_PREFIX}INIT_VARS - + !insertmacro MULTIUSER_FUNCTION_INSTALLMODEPAGE "${UNINSTALLER_PREFIX}" "${UNINSTALLER_FUNCPREFIX}" PageEx ${UNINSTALLER_FUNCPREFIX}custom PageCallbacks ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModePre ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeLeave PageExEnd + + !ifmacrodef MUI_${UNINSTALLER_PREFIX}PAGE_END + !insertmacro MUI_${UNINSTALLER_PREFIX}PAGE_END ; MUI 1 MUI_UNPAGE_END macro + !endif !macroend !macro MULTIUSER_PAGE_INSTALLMODE ; create install page - called by user script @@ -183,6 +176,8 @@ RequestExecutionLevel user ; will ask elevation only if necessary StrCpy $MultiUser.InstallMode "AllUsers" SetShellVarContext all + + StrCpy $HasCurrentModeInstallation "$HasPerMachineInstallation" ${if} $CmdLineDir != "" StrCpy $INSTDIR $CmdLineDir @@ -190,8 +185,12 @@ RequestExecutionLevel user ; will ask elevation only if necessary StrCpy $INSTDIR $PerMachineInstallationFolder ${else} !if "${UNINSTALLER_FUNCPREFIX}" == "" - ;Set default installation location for installer - StrCpy $INSTDIR "${MULTIUSER_INSTALLMODE_PROGRAMFILES}\${MULTIUSER_INSTALLMODE_INSTDIR}" + ; Set default installation location for installer + ${if} ${MULTIUSER_INSTALLMODE_64_BIT} == 0 + StrCpy $INSTDIR "$PROGRAMFILES32\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${else} + StrCpy $INSTDIR "$PROGRAMFILES64\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${endif} !endif ${endif} @@ -209,17 +208,19 @@ RequestExecutionLevel user ; will ask elevation only if necessary SetShellVarContext current + StrCpy $HasCurrentModeInstallation "$HasPerUserInstallation" + ${if} $CmdLineDir != "" StrCpy $INSTDIR $CmdLineDir ${elseif} $PerUserInstallationFolder != "" StrCpy $INSTDIR $PerUserInstallationFolder ${else} !if "${UNINSTALLER_FUNCPREFIX}" == "" - ;Set default installation location for installer + ; Set default installation location for installer ${if} ${AtLeastWin2000} StrCpy $INSTDIR "$LOCALAPPDATA\${MULTIUSER_INSTALLMODE_INSTDIR}" - ${else} - StrCpy $INSTDIR "${MULTIUSER_INSTALLMODE_PROGRAMFILES}\${MULTIUSER_INSTALLMODE_INSTDIR}" + ${else} + StrCpy $INSTDIR "$PROGRAMFILES32\${MULTIUSER_INSTALLMODE_INSTDIR}" ; there's no 64-bit of Windows before 2000 (i.e. NT4) ${endif} !endif ${endif} @@ -239,25 +240,23 @@ RequestExecutionLevel user ; will ask elevation only if necessary !endif !endif - Function ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationRequired - ; check if elevation is always required, return result in $0 + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + ; check if elevation on page is always required, return result in $0 + ; when this function is called from InitChecks, InstallMode is "" + ; and when called from InstallModeLeave/SetShieldsAndTexts, InstallMode is not empty StrCpy $0 0 ${if} $IsAdmin == 0 - !if "${UNINSTALLER_FUNCPREFIX}" == "" ; installer - !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 - ${if} $HasPerMachineInstallation == 1 - ${andif} $HasPerUserInstallation == 0 - ; has to uninstall the per-machine istalattion, which requires admin rights - StrCpy $0 1 - ${endif} - !endif - !else ; uninstaller - ${if} $HasPerMachineInstallation == 1 - ${andif} $HasPerUserInstallation == 0 - ; there is only per-machine istalattion, which requires admin rights - StrCpy $0 1 - ${endif} - !endif + ${if} $MultiUser.InstallMode == "AllUsers" + StrCpy $0 1 + ${else} + !if "${UNINSTALLER_FUNCPREFIX}" == "" ; installer + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 + ${if} $HasPerMachineInstallation$HasPerUserInstallation == "10" + StrCpy $0 1 ; has to uninstall the per-machine istalattion, which requires admin rights + ${endif} + !endif + !endif + ${endif} ${endif} FunctionEnd @@ -297,125 +296,107 @@ RequestExecutionLevel user ; will ask elevation only if necessary FunctionEnd Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InitChecks - ;Installer initialization - check privileges and set default install mode + ; Installer initialization - check privileges and set default install mode StrCpy $MultiUser.InstallMode "" - StrCpy $HasTwoAvailableOptions 1 - StrCpy $InstallHidePagesBeforeComponents 0 - StrCpy $UninstallHideBackButton 1 + StrCpy $PerMachineOptionAvailable 1 + StrCpy $InstallShowPagesBeforeComponents 1 StrCpy $DisplayDialog 1 StrCpy $PreFunctionCalled 0 StrCpy $CmdLineInstallMode "" StrCpy $CmdLineDir "" - ; check in the inner instance has admin rights - ${if} ${UAC_IsInnerInstance} - ${andifnot} ${UAC_IsAdmin} - SetErrorLevel ${MULTIUSER_ERROR_ELEVATION_FAILED} ;special return value for outer instance so it knows we did not have admin rights - Quit - ${endif} - + !if ${MULTIUSER_INSTALLMODE_64_BIT} == 0 + SetRegView 32 ; someday, when NSIS is 64-bit... + !else + SetRegView 64 + !endif + UserInfo::GetAccountType Pop $MultiUser.Privileges ${if} $MultiUser.Privileges == "Admin" - ${orif} $MultiUser.Privileges == "Power" + ${orif} $MultiUser.Privileges == "Power" ; under XP (and earlier?), Power users can install programs, but UAC_IsAdmin returns false StrCpy $IsAdmin 1 ${else} StrCpy $IsAdmin 0 - ${endif} - - ; Checks registry for previous installation path (both for upgrading, reinstall, or uninstall) - StrCpy $HasPerMachineInstallation 0 - StrCpy $HasPerUserInstallation 0 - ;Set installation mode to setting from a previous installation - ReadRegStr $PerMachineInstallationFolder HKLM "${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY2}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" ; "InstallLocation" + ${endif} + + ${if} ${UAC_IsInnerInstance} + StrCpy $IsInnerInstance 1 + ${else} + StrCpy $IsInnerInstance 0 + ${endif} + + ; initialize PerXXXInstallationVersion, PerXXXInstallationFolder, PerXXXUninstallString variables ReadRegStr $PerMachineInstallationVersion HKLM "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "DisplayVersion" - ${if} $PerMachineInstallationFolder != "" + ReadRegStr $PerMachineInstallationFolder HKLM "${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY2}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" ; "InstallLocation" + ReadRegStr $PerMachineUninstallString HKLM "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "UninstallString" ; contains the /currentuser or /allusers parameter + ${if} $PerMachineInstallationFolder == "" + StrCpy $HasPerMachineInstallation 0 + ${else} StrCpy $HasPerMachineInstallation 1 - ${endif} - ReadRegStr $PerUserInstallationFolder HKCU "${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY2}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" ; "InstallLocation" + ${endif} ReadRegStr $PerUserInstallationVersion HKCU "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "DisplayVersion" - ${if} $PerUserInstallationFolder != "" + ReadRegStr $PerUserInstallationFolder HKCU "${MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY2}" "${MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME}" ; "InstallLocation" + ReadRegStr $PerUserUninstallString HKCU "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "UninstallString" ; contains the /currentuser or /allusers parameter + ${if} $PerUserInstallationFolder == "" + StrCpy $HasPerUserInstallation 0 + ${else} StrCpy $HasPerUserInstallation 1 ${endif} - ; initialize CmdLineInstallMode and CmdLineDir, needed also if we are the inner instance (UAC passes all parameters from the outer instance) + ; get all parameters ${GetParameters} $R0 - ${GetOptions} $R0 "/?" $R1 - ${ifnot} ${errors} - MessageBox MB_ICONINFORMATION "Usage:$\r$\n$\r$\n\ - /allusers$\t- (un)install for all users (case-insensitive)$\r$\n\ - /currentuser - (un)install for current user only (case-insensitive)$\r$\n\ - /S$\t- silent mode (case-sensitive)$\r$\n\ - /D$\t- set install directory, must be last paramter, without quotes (case-sensitive)$\r$\n\ - /?$\t- display this message$\r$\n$\r$\n\ - Return codes (DEC):$\r$\n$\r$\n\ - 0$\t- normal execution (no error)$\r$\n\ - 1$\t- installation aborted by user (cancel button)$\r$\n\ - 2$\t- installation aborted by script$\r$\n\ - 666660$\t- invalid command-line parameters$\r$\n\ - 666661$\t- elevation is not allowed by defines$\r$\n\ - 666662$\t- uninstaller detects there's no installed version$\r$\n\ - 666666$\t- cannot start elevated instance$\r$\n\ - other$\t- Win32 error code when trying to start elevated instance" - SetErrorLevel 0 - Quit - ${endif} + + ; initialize CmdLineInstallMode and CmdLineDir, needed also if we are the inner instance (UAC passes all parameters from the outer instance) + ; note: the loading of the /D parameter depends on AllowRootDirInstall, see https://sourceforge.net/p/nsis/bugs/1176/ ${GetOptions} $R0 "/allusers" $R1 ${ifnot} ${errors} StrCpy $CmdLineInstallMode "AllUsers" ${endif} + ${GetOptions} $R0 "/currentuser" $R1 ${ifnot} ${errors} - ${if} $CmdLineInstallMode == "AllUsers" - MessageBox MB_ICONSTOP "Provide only on of the /allusers or /currentuser parameters." /SD IDOK + ${if} $CmdLineInstallMode != "" + MessageBox MB_ICONSTOP "Provide only one of the /allusers or /currentuser parameters." /SD IDOK SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} Quit ${endif} StrCpy $CmdLineInstallMode "CurrentUser" ${endif} + !if "${UNINSTALLER_FUNCPREFIX}" == "" ${if} "$INSTDIR" != "" ; if $INSTDIR is not empty here in the installer, it's initialized with the value of the /D command-line parameter StrCpy $CmdLineDir "$INSTDIR" ${endif} !endif - ; initialize $InstallHidePagesBeforeComponents and $UninstallHideBackButton + ; initialize $InstallShowPagesBeforeComponents and $UninstallShowBackButton !if "${UNINSTALLER_FUNCPREFIX}" == "" - ${if} ${UAC_IsInnerInstance} - ${andif} $CmdLineInstallMode == "" - StrCpy $InstallHidePagesBeforeComponents 1 ; make sure we hide pages only if outer instance showed them, i.e. installer was elevated by dialog in outer instance (not by param in the beginning) (see when MultiUser.Elevate is called) + ${if} $IsInnerInstance == 1 + StrCpy $InstallShowPagesBeforeComponents 0 ; we hide pages only if we're the inner instance (the outer instance always shows them) ${endif} !else ${if} $CmdLineInstallMode == "" - ${andif} $HasPerUserInstallation == 1 - ${andif} $HasPerMachineInstallation == 1 - StrCpy $UninstallHideBackButton 0 ; make sure we show Back button only if dialog was displayed, i.e. uninstaller did not elevate in the beginning (see when MultiUser.Elevate is called) + ${andif} $HasPerMachineInstallation$HasPerUserInstallation == "11" + StrCpy $UninstallShowBackButton 1 ; make sure we show Back button only if dialog was displayed, i.e. uninstaller did not elevate in the beginning (see when MultiUser.Elevate is called) + ${else} + StrCpy $UninstallShowBackButton 0 ${endif} !endif - - ; check for limitations - ${if} ${silent} - ${andif} $CmdLineInstallMode == "" - SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} ; one of the /allusers or /currentuser parameters is required in silent mode - Quit - ${endif} - - !if "${UNINSTALLER_FUNCPREFIX}" != "" - ${if} $HasPerMachineInstallation == 0 - ${andif} $HasPerUserInstallation == 0 - MessageBox MB_ICONSTOP "There's no installed version of ${PRODUCT_NAME}." /SD IDOK - SetErrorLevel ${MULTIUSER_ERROR_NOT_INSTALLED} + + ${if} $IsInnerInstance == 1 + ; check if the inner instance has admin rights + ${if} $IsAdmin == 0 + SetErrorLevel ${MULTIUSER_ERROR_ELEVATION_FAILED} ; special return value for outer instance so it knows we did not have admin rights Quit - ${endif} - !endif - - ${If} ${UAC_IsInnerInstance} + ${endif} + !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 !if "${UNINSTALLER_FUNCPREFIX}" == "" !insertmacro UAC_AsUser_Call Function ${UNINSTALLER_FUNCPREFIX}MultiUser.GetInstallMode ${UAC_SYNCREGISTERS} ${if} $0 == "CurrentUser" ; the inner instance was elevated because there is installation per-machine, which needs to be removed and requires admin rights, - ; but the user selected per-user installation in the outer instance, so set context to CurrentUser + ; but the user selected per-user installation in the outer instance, set context to CurrentUser Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser StrCpy $DisplayDialog 0 Return @@ -426,119 +407,222 @@ RequestExecutionLevel user ; will ask elevation only if necessary Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers ; Inner Process (and Admin) - set to AllUsers StrCpy $DisplayDialog 0 Return - ${endif} - - ; check if elevation is always required - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationRequired - ${if} $0 == 1 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationAllowed ${endif} - ; process command-line parameters (both silent and non-silent mode, installer and uninstaller) - ${if} $CmdLineInstallMode != "" - ${if} $CmdLineInstallMode == "AllUsers" - !if "${UNINSTALLER_FUNCPREFIX}" != "" + ; process /? parameter + !ifndef MULTIUSER_INSTALLMODE_NO_HELP_DIALOG ; define MULTIUSER_INSTALLMODE_NO_HELP_DIALOG to display your own help dialog (new options, return codes, etc.) + ${GetOptions} $R0 "/?" $R1 + ${ifnot} ${errors} + MessageBox MB_ICONINFORMATION "Usage:$\r$\n$\r$\n\ + /allusers$\t- (un)install for all users, case-insensitive$\r$\n\ + /currentuser - (un)install for current user only, case-insensitive$\r$\n\ + /uninstall$\t- (installer only) run uninstaller, requires /allusers or /currentuser, case-insensitive$\r$\n\ + /S$\t- silent mode, requires /allusers or /currentuser, case-sensitive$\r$\n\ + /D$\t- (installer only) set install directory, must be last parameter, without quotes, case-sensitive$\r$\n\ + /?$\t- display this message$\r$\n$\r$\n$\r$\n\ + Return codes (decimal):$\r$\n$\r$\n\ + 0$\t- normal execution (no error)$\r$\n\ + 1$\t- (un)installation aborted by user (Cancel button)$\r$\n\ + 2$\t- (un)installation aborted by script$\r$\n\ + 666660$\t- invalid command-line parameters$\r$\n\ + 666661$\t- elevation is not allowed by defines$\r$\n\ + 666662$\t- uninstaller detected there's no installed version$\r$\n\ + 666663$\t- executing uninstaller from the installer failed$\r$\n\ + 666666$\t- cannot start elevated instance$\r$\n\ + other$\t- Windows error code when trying to start elevated instance" + SetErrorLevel 0 + Quit + ${endif} + !endif + + ; process /uninstall parameter + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${GetOptions} $R0 "/uninstall" $R1 + ${ifnot} ${errors} + ${if} $CmdLineInstallMode == "" + MessageBox MB_ICONSTOP "Provide one of the /allusers or /currentuser parameters." /SD IDOK + SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} + Quit + ${endif} + + ${if} $CmdLineInstallMode == "AllUsers" ${if} $HasPerMachineInstallation == 0 - MessageBox MB_ICONSTOP "There is no per-machine installation." /SD IDOK + MessageBox MB_ICONSTOP "There is no per-machine installation of ${PRODUCT_NAME}." /SD IDOK SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} Quit ${endif} - !endif - - ${if} $IsAdmin == 0 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.Elevate - ${endif} - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers - ${else} - !if "${UNINSTALLER_FUNCPREFIX}" != "" + StrCpy $1 "$PerMachineInstallationFolder" + ${else} ${if} $HasPerUserInstallation == 0 - MessageBox MB_ICONSTOP "There is no per-user installation." /SD IDOK + MessageBox MB_ICONSTOP "There is no per-user installation of ${PRODUCT_NAME}." /SD IDOK SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} Quit - ${endif} - !endif - ${ifnot} ${IsNT} ; Not running Windows NT, (so it's Windows XP at best), so per-user installation not supported - MessageBox MB_ICONSTOP "The OS doesn't support per-user installs." /SD IDOK + ${endif} + StrCpy $1 "$PerUserInstallationFolder" + ${endif} + + ; NOTES: + ; - the _? param stops the uninstaller from copying itself to the temporary directory, which is the only way for waiting to work + ; - $R0 passes the original parameters from the installer to the uninstaller (together with /uninstall so that uninstaller knows installer is running and skips opitional single instance checks) + ; - using ExecWait fails if the new process requires elevation, see http://forums.winamp.com/showthread.php?p=3080202&posted=1#post3080202, so we use ShellExecuteEx + System::Call '*(i 60, i 0x140, i 0, t "open", t "$1\${UNINSTALL_FILENAME}", t "$R0 _?=$1", t, i ${SW_SHOW}, i, i, t, i, i, i, i) p .r9' ; allocate and fill values for SHELLEXECUTEINFO structure, returned in $9 (0x140 = SEE_MASK_NOCLOSEPROCESS|SEE_MASK_NOASYNC) + + System::Call 'shell32::ShellExecuteEx(i r9) i .r0' + ${if} $0 != 1 + SetErrorLevel $2 + Quit + ${endif} + + System::Call '*$9(i, i, i, t, t, t, t, i, i, i, t, i, i, i, i .r8)' ; get the process handle + + System::Call 'kernel32::WaitForSingleObject(i r8, i -1) i .r0 ?e' ; wait indefinitely for the process to exit + ${if} $0 != 0 ; WAIT_OBJECT_0 + SetErrorLevel $1 + Quit + ${endif} + + System::Call 'kernel32::GetExitCodeProcess(i r8, *i .r2) i .r0 ?e' + Pop $1 + ${if} $0 != 1 + SetErrorLevel $1 + Quit + ${endif} + + System::Call 'Kernel32::CloseHandle(i r8)' + System::Free $0 + + SetErrorLevel $2 + Quit + ${endif} + !endif + + ; check for limitations + ${if} ${silent} + ${andif} $CmdLineInstallMode == "" + SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} ; one of the /allusers or /currentuser parameters is required in silent mode + Quit + ${endif} + + !if "${UNINSTALLER_FUNCPREFIX}" != "" + ${if} $HasPerMachineInstallation$HasPerUserInstallation == "00" + MessageBox MB_ICONSTOP "There is no installation of ${PRODUCT_NAME}." /SD IDOK + SetErrorLevel ${MULTIUSER_ERROR_NOT_INSTALLED} + Quit + ${endif} + !endif + + ; process /allusers and /currentuser parameters (both silent and non-silent mode, installer and uninstaller) + ${if} $CmdLineInstallMode != "" + ${ifnot} ${IsNT} ; Not running Windows NT, (so it's Windows 95/98/ME), so per-user installation not supported + ${andif} $CmdLineInstallMode == "CurrentUser" + MessageBox MB_ICONSTOP "The OS doesn't support per-user installations." /SD IDOK + SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} + Quit + ${endif} + + ${if} $CmdLineInstallMode == "AllUsers" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + + !if "${UNINSTALLER_FUNCPREFIX}" != "" + ${if} $HasCurrentModeInstallation == 0 + MessageBox MB_ICONSTOP "There is no $CmdLineInstallMode installation of ${PRODUCT_NAME}." /SD IDOK SetErrorLevel ${MULTIUSER_ERROR_INVALID_PARAMETERS} Quit + ${endif} + !endif + + !if "${UNINSTALLER_FUNCPREFIX}" != "" + StrCpy $DisplayDialog 0 ; uninstaller - don't display dialog when there is /allusers or /currentuser parameter + !else + ${if} ${silent} + StrCpy $DisplayDialog 0 ${endif} - - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser - ${endif} - - StrCpy $DisplayDialog 0 + !endif + + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + ${if} $0 == 1 + ${if} $DisplayDialog == 0 ; if we are not displaying the dialog (uninstaller or silent mode) and elevation is required, Elevate now (or Quit with an error) + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.Elevate + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationAllowed ; if we are displaying the dialog and elevation is required, check if elevation is allowed + ${endif} + ${endif} Return ${endif} - ${ifnot} ${IsNT} ; Not running Windows NT, (so it's Windows XP at best), so per-user installation not supported + ; the rest of the code is executed only when there are no /allusers and /currentuser parameters and in non-silent mode + ${ifnot} ${IsNT} ; Not running Windows NT, (so it's Windows 95/98/ME), so per-user installation not supported Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers StrCpy $DisplayDialog 0 Return ${endif} + + ; check if elevation on page is always required (installer only) + !if "${UNINSTALLER_FUNCPREFIX}" == "" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + ${if} $0 == 1 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckElevationAllowed + ${endif} + !endif + + ; if elevation is not allowed and user is not admin, disable the per-machine option + !if ${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION} == 0 + ${if} $IsAdmin == 0 + StrCpy $PerMachineOptionAvailable 0 + ${endif} + !endif - ; uninstaller - check if there's only one installed version; - ; when invoked from the "add/remove programs", Windows will automatically start uninstaller elevated if uninstall keys are in HKLM - !if "${UNINSTALLER_FUNCPREFIX}" != "" - ${if} $HasPerUserInstallation == 0 - ${andif} $HasPerMachineInstallation == 1 + ; if there's only one installed version + ; when uninstaller is invoked from the "add/remove programs", Windows will automatically start uninstaller elevated if uninstall keys are in HKLM + ${if} $HasPerMachineInstallation$HasPerUserInstallation == "10" + !if "${UNINSTALLER_FUNCPREFIX}" == "" + ${if} $PerMachineOptionAvailable == 1 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} + !else ${if} $IsAdmin == 0 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.Elevate + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.Elevate ; if $PerMachineOptionAvailable = 0 (i.e. MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 0), Elevate will call CheckElevationAllowed, which checks if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION = 0 ${endif} - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + StrCpy $DisplayDialog 0 + !endif + ${elseif} $HasPerMachineInstallation$HasPerUserInstallation == "01" + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !if "${UNINSTALLER_FUNCPREFIX}" != "" StrCpy $DisplayDialog 0 - Return - ${elseif} $HasPerUserInstallation == 1 - ${andif} $HasPerMachineInstallation == 0 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser - StrCpy $DisplayDialog 0 - Return - ${endif} - !endif - - ; default initialization (both installer and uninstaller) - we always display the dialog - ${if} $HasPerUserInstallation == 0 ; if there is only per-machine installation, set it as default - ${andif} $HasPerMachineInstallation == 1 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers - ${elseif} $HasPerUserInstallation == 1 ; if there is only per-user installation, set it as default - ${andif} $HasPerMachineInstallation == 0 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser - ${else} ; if there is no installation, or there are 2 installations + !endif + ${else} ; if there is no installed version (installer only), or there are 2 installations - we always display the dialog ${if} $IsAdmin == 1 ; If running as admin, default to per-machine installation (unless default is forced by MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER) - !if ${MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER} == 1 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !if ${MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER} == 0 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers !else - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser !endif - ${else} ; If not running as admin, default to per-user installation (unless default is forced by MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS) - !if ${MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS} == 1 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers - !else + ${else} ; if not running as admin, default to per-user installation (unless default is forced by MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS) + !if ${MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS} == 0 Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + !else + ${if} $PerMachineOptionAvailable == 1 + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.AllUsers + ${else} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser + ${endif} !endif ${endif} - ${endif} - - ; if elevation is not allowed and user is not admin, select the per-user option and disable the per-machine option - !if ${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION} == 0 - ${if} $IsAdmin == 0 - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallMode.CurrentUser - StrCpy $HasTwoAvailableOptions 0 - ${endif} - !endif + ${endif} FunctionEnd Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModePre - ${if} ${UAC_IsInnerInstance} + ${if} $IsInnerInstance == 1 ${andif} $PreFunctionCalled == 1 - !if "${UNINSTALLER_FUNCPREFIX}" == "" - ${if} $CmdLineInstallMode == "" ; installer - user pressed Back button on the first visible page in the inner instance and installer was elevated by dialog in outer instance (not by param in the beginning) - display outer instance - SetErrorLevel ${MULTIUSER_INNER_INSTANCE_BACK} - Quit - ${endif} - !else ; uninstaller - user pressed Back button on the first visible page in the inner instance - display outer instance - SetErrorLevel ${MULTIUSER_INNER_INSTANCE_BACK} - Quit - !endif + ; user pressed Back button on the first visible page in the inner instance - display outer instance + SetErrorLevel ${MULTIUSER_INNER_INSTANCE_BACK} + Quit ${endif} StrCpy $PreFunctionCalled 1 @@ -546,42 +630,63 @@ RequestExecutionLevel user ; will ask elevation only if necessary Abort ${endif} - ;!insertmacro MUI_HEADER_TEXT_PAGE $(MULTIUSER_TEXT_INSTALLMODE_TITLE) $(MULTIUSER_TEXT_INSTALLMODE_SUBTITLE) ; "Choose Users" and "Choose for which users you want to install $(^NameDA)." - - !if "${UNINSTALLER_FUNCPREFIX}" == "" - !insertmacro MUI_HEADER_TEXT "Choose Installation Options" "Who should this application be installed for?" - !else - !insertmacro MUI_HEADER_TEXT "Choose Uninstallation Options" "Which installation should be removed?" + !ifmacrodef MUI_HEADER_TEXT + !if "${UNINSTALLER_FUNCPREFIX}" == "" + !insertmacro MUI_HEADER_TEXT "Choose Installation Options" "Who should this application be installed for?" + !else + !insertmacro MUI_HEADER_TEXT "Choose Uninstallation Options" "Which installation should be removed?" + !endif !endif - !insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE + !ifdef MUI_PAGE_FUNCTION_CUSTOM_PRE + Call "${MUI_PAGE_FUNCTION_CUSTOM_PRE}" + !undef MUI_PAGE_FUNCTION_CUSTOM_PRE + !endif nsDialogs::Create 1018 - Pop $MultiUser.InstallModePage + Pop $MultiUser.InstallModePage ; default was MULTIUSER_TEXT_INSTALLMODE_TITLE "Choose Users" !if "${UNINSTALLER_FUNCPREFIX}" == "" - ${NSD_CreateLabel} 0u 0u 300u 20u "Please select whether you wish to make this software available to all users or just yourself" - StrCpy $8 "Anyone who uses this computer (&all users)" ; this was MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Install for anyone using this computer" - StrCpy $9 "Only for &me" ; this was MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Install just for me" + ${NSD_CreateLabel} 0 0 100% 24u "Please, select whether you wish to make this software available to all users or just yourself." !else - ${NSD_CreateLabel} 0u 0u 300u 20u "This software is installed both per-machine (all users) and per-user. $\r$\nWhich installation you wish to remove?" - StrCpy $8 "Anyone who uses this computer (&all users)" ; this was MULTIUSER_INNERTEXT_INSTALLMODE_ALLUSERS "Install for anyone using this computer" - StrCpy $9 "Only for &me" ; this was MULTIUSER_INNERTEXT_INSTALLMODE_CURRENTUSER "Install just for me" + ${NSD_CreateLabel} 0 0 100% 24u "This software is installed both per-machine (all users) and per-user. $\r$\nWhich installation you wish to remove?" !endif - Pop $MultiUser.InstallModePage.Text - - ; criando os radios (disabled se nao for admin/power) e pegando os hwnds (handles) - ${NSD_CreateRadioButton} 10u 30u 280u 20u "$8" - Pop $MultiUser.InstallModePage.AllUsers - ${if} $HasTwoAvailableOptions == 0 ; install per-machine is not available - SendMessage $MultiUser.InstallModePage.AllUsers ${WM_SETTEXT} 0 "STR:$8 (must run as admin)" ; since radio button is disabled, we add that comment to the disabled control itself - EnableWindow $MultiUser.InstallModePage.AllUsers 0 # start out disabled - ${endif} - - ;${NSD_CreateRadioButton} 20u 70u 280u 10u "$9" - System::Call "advapi32::GetUserName(t.r0,*i${NSIS_MAX_STRLEN})i" - ${NSD_CreateRadioButton} 10u 50u 280u 20u "$9 ($0)" - Pop $MultiUser.InstallModePage.CurrentUser + Pop $MultiUser.InstallModePage.Text + + StrCpy $8 "Anyone who uses this computer (all users)" + ${NSD_CreateRadioButton} 30u 30% 10u 8u "" + Pop $MultiUser.InstallModePage.AllUsers + + System::Call "advapi32::GetUserName(t.r0,*i${NSIS_MAX_STRLEN})i" + StrCpy $9 "Only for me ($0)" + ${NSD_CreateRadioButton} 30u 45% 10u 8u "" + Pop $MultiUser.InstallModePage.CurrentUser + + ; We create the radio buttons with empty text and create separate labels, because radio button font color can't be changed with XP Styles turned on, + ; which creates problems with UMUI themes, see http://forums.winamp.com/showthread.php?p=3079742#post3079742 + ; shortcuts (&) for labels don't work and cause strange behaviour in NSIS - going to another page, etc. + ${NSD_CreateLabel} 44u 30% 280u 8u "$8" + Pop $MultiUser.InstallModePage.AllUsersLabel + nsDialogs::SetUserData $MultiUser.InstallModePage.AllUsersLabel $MultiUser.InstallModePage.AllUsers + ${NSD_CreateLabel} 44u 45% 280u 8u "$9" + Pop $MultiUser.InstallModePage.CurrentUserLabel + nsDialogs::SetUserData $MultiUser.InstallModePage.CurrentUserLabel $MultiUser.InstallModePage.CurrentUser + + ${if} $PerMachineOptionAvailable == 0 ; install per-machine is not available + SendMessage $MultiUser.InstallModePage.AllUsersLabel ${WM_SETTEXT} 0 "STR:$8 (must run as admin)" ; only when $PerMachineOptionAvailable == 0, we add that comment to the disabled control itself + ${orif} $CmdLineInstallMode != "" + EnableWindow $MultiUser.InstallModePage.AllUsersLabel 0 ; start out disabled + EnableWindow $MultiUser.InstallModePage.AllUsers 0 ; start out disabled + ${endif} + + ${if} $CmdLineInstallMode != "" + EnableWindow $MultiUser.InstallModePage.CurrentUserLabel 0 + EnableWindow $MultiUser.InstallModePage.CurrentUser 0 + ${endif} + + ; bind to label click + ${NSD_OnClick} $MultiUser.InstallModePage.CurrentUserLabel ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionLabelClick + ${NSD_OnClick} $MultiUser.InstallModePage.AllUsersLabel ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionLabelClick ; bind to radiobutton change ${NSD_OnClick} $MultiUser.InstallModePage.CurrentUser ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionClick @@ -592,53 +697,45 @@ RequestExecutionLevel user ; will ask elevation only if necessary nsDialogs::OnBack $0 !endif - ${NSD_CreateLabel} 0u 110u 280u 50u "" - Pop $MultiUser.RadioButtonLabel1 - ;${NSD_CreateLabel} 0u 120u 280u 20u "" - ;Pop $RadioButtonLabel2 - ;${NSD_CreateLabel} 0u 130u 280u 20u "" - ;Pop $RadioButtonLabel3 + ${NSD_CreateLabel} 0u -32u 100% 32u "" ; will hold up to 4 lines of text + Pop $MultiUser.InstallModePage.Description ${if} $MultiUser.InstallMode == "AllUsers" ; setting selected radio button - SendMessage $MultiUser.InstallModePage.AllUsers ${BM_SETCHECK} ${BST_CHECKED} 0 ; select radio button - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.SetShieldAndTexts ; simulating click on the control will change $INSTDIR and reset a possible user selection + SendMessage $MultiUser.InstallModePage.AllUsers ${BM_SETCHECK} ${BST_CHECKED} 0 ; select radio button ${else} - SendMessage $MultiUser.InstallModePage.CurrentUser ${BM_SETCHECK} ${BST_CHECKED} 0 ; select radio button - Call ${UNINSTALLER_FUNCPREFIX}MultiUser.SetShieldAndTexts ; simulating click on the control will change $INSTDIR and reset a possible user selection + SendMessage $MultiUser.InstallModePage.CurrentUser ${BM_SETCHECK} ${BST_CHECKED} 0 ; select radio button ${endif} + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.SetShieldAndTexts ; simulating click on the control will change $INSTDIR and reset a possible user selection + + !ifmacrodef UMUI_IOPAGEBGTRANSPARENT_INIT ; UMUI, apply theme to controls + !ifndef USE_MUIEx ; for MUIEx, applying themes causes artifacts + !insertmacro UMUI_IOPAGEBGTRANSPARENT_INIT $MultiUser.InstallModePage + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.Text + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.AllUsers + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.AllUsersLabel + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.CurrentUser + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.CurrentUserLabel + !insertmacro UMUI_IOPAGECTLTRANSPARENT_INIT $MultiUser.InstallModePage.Description + !endif + !endif - !insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW + !ifdef MUI_PAGE_FUNCTION_CUSTOM_SHOW + Call "${MUI_PAGE_FUNCTION_CUSTOM_SHOW}" + !undef MUI_PAGE_FUNCTION_CUSTOM_SHOW + !endif nsDialogs::Show FunctionEnd Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeLeave - !if ${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION} == 1 ; if it's not Power or Admin, but elevation is allowed - StrCpy $0 0 - - !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 - !if "${UNINSTALLER_FUNCPREFIX}" == "" ; installer - ${if} $MultiUser.InstallMode == "CurrentUser" - ${andif} $IsAdmin == 0 - ${andif} $HasPerMachineInstallation == 1 - ${andif} $HasPerUserInstallation == 0 - ; has to uninstall the per-machine istalattion, which requires admin rights - StrCpy $0 1 - ${endif} - !endif - !endif - - ${if} $MultiUser.InstallMode == "AllUsers" - ${andif} $IsAdmin == 0 - StrCpy $0 1 - ${endif} - + !if ${MULTIUSER_INSTALLMODE_ALLOW_ELEVATION} == 1 ; if elevation is allowed + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + ${if} $0 == 1 HideWindow !insertmacro UAC_RunElevated ;MessageBox MB_OK "[$0]/[$1]/[$2]/[$3]" - ;http://www.videolan.org/developers/vlc/extras/package/win32/NSIS/UAC/Readme.html - ;http://nsis.sourceforge.net/UAC_plug-in + ; http://nsis.sourceforge.net/UAC_plug-in ${Switch} $0 ${Case} 0 ${Switch} $1 @@ -661,7 +758,7 @@ RequestExecutionLevel user ; will ask elevation only if necessary MessageBox MB_ICONSTOP "Elevation is not supported by your operating system." /SD IDOK ${EndSwitch} ${Break} - ${Case} 1223 ;user aborted elevation dialog - stay on page + ${Case} 1223 ; user aborted elevation dialog - stay on page ${Break} ${Case} 1062 ; Logon service not running - stay on page MessageBox MB_ICONSTOP "Unable to elevate, Secondary Logon service not running" /SD IDOK @@ -678,43 +775,28 @@ RequestExecutionLevel user ; will ask elevation only if necessary ${endif} !endif - !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE + !ifdef MUI_PAGE_FUNCTION_CUSTOM_LEAVE + Call "${MUI_PAGE_FUNCTION_CUSTOM_LEAVE}" + !undef MUI_PAGE_FUNCTION_CUSTOM_LEAVE + !endif FunctionEnd Function ${UNINSTALLER_FUNCPREFIX}MultiUser.SetShieldAndTexts GetDlgItem $1 $hwndParent 1 ; get item 1 (next button) at parent window, store in $0 - (0 is back, 1 is next .. what about CANCEL? http://nsis.sourceforge.net/Buttons_Header ) - StrCpy $0 0 - ${if} $IsAdmin == 0 - ${if} $MultiUser.InstallMode == "AllUsers" - StrCpy $0 1 - ${else} - !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 - !if "${UNINSTALLER_FUNCPREFIX}" == "" - ${if} $HasPerUserInstallation == 0 - ${andif} $HasPerMachineInstallation == 1 - StrCpy $0 1 - ${endif} - !endif - !endif - ${endif} - ${endif} - SendMessage $1 ${BCM_SETSHIELD} 0 $0 ; display/hide SHIELD + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.CheckPageElevationRequired + SendMessage $1 ${BCM_SETSHIELD} 0 $0 ; display/hide SHIELD (Windows Vista and above) StrCpy $0 "$MultiUser.InstallMode" ; if necessary, display text for different install mode than the actual one in $MultiUser.InstallMode !if ${MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS} == 0 !if "${UNINSTALLER_FUNCPREFIX}" == "" ${if} $MultiUser.InstallMode == "AllUsers" ; user selected "all users" - ${if} $HasPerMachineInstallation == 0 - ${andif} $HasPerUserInstallation == 1 + ${if} $HasPerMachineInstallation$HasPerUserInstallation == "01" StrCpy $0 "CurrentUser" ; display information for the "current user" installation ${endif} - ${else} ; user selected "current user" - ${if} $HasPerUserInstallation == 0 - ${andif} $HasPerMachineInstallation == 1 - StrCpy $0 "AllUsers" ; display information for the "all users" installation - ${endif} + ${elseif} $HasPerMachineInstallation$HasPerUserInstallation == "10" ; user selected "current user" + StrCpy $0 "AllUsers" ; display information for the "all users" installation ${endif} !endif !endif @@ -767,10 +849,22 @@ RequestExecutionLevel user ; will ask elevation only if necessary StrCpy $7 "Fresh install for current user only." ${endif} ${endif} - SendMessage $MultiUser.RadioButtonLabel1 ${WM_SETTEXT} 0 "STR:$7" - ;SendMessage $RadioButtonLabel2 ${WM_SETTEXT} 0 "STR:$8" - ;SendMessage $RadioButtonLabel3 ${WM_SETTEXT} 0 "STR:$9" + SendMessage $MultiUser.InstallModePage.Description ${WM_SETTEXT} 0 "STR:$7" FunctionEnd + + Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionLabelClick + pop $1 ; get clicked control's HWND, which is on the stack in $1 + nsDialogs::GetUserData $1 + pop $2 + + ${NSD_Uncheck} $MultiUser.InstallModePage.AllUsers + ${NSD_Uncheck} $MultiUser.InstallModePage.CurrentUser + ${NSD_Check} $2 ; ${NSD_Check} will check both radio buttons without the above 2 lines + ${NSD_SetFocus} $2 + Push $2 + ; ${NSD_Check} doesn't call Click event + Call ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionClick + FunctionEnd Function ${UNINSTALLER_FUNCPREFIX}MultiUser.InstallModeOptionClick pop $1 ; get clicked control's HWND, which is on the stack in $1 @@ -793,7 +887,6 @@ RequestExecutionLevel user ; will ask elevation only if necessary !endif !macroend -; SHCTX is the hive HKLM if SetShellVarContext all, or HKCU if SetShellVarContext user !macro MULTIUSER_RegistryAddInstallInfo !verbose push !verbose 3 @@ -804,20 +897,22 @@ RequestExecutionLevel user ; will ask elevation only if necessary ; Write the uninstall keys for Windows ${if} $MultiUser.InstallMode == "AllUsers" ; setting defaults WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "DisplayName" "${MULTIUSER_INSTALLMODE_DISPLAYNAME}" - WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" '"$INSTDIR\${UNINSTALL_FILENAME}" /allusers' ; "UninstallString" + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "UninstallString" '"$INSTDIR\${UNINSTALL_FILENAME}" /allusers' ${else} WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "DisplayName" "${MULTIUSER_INSTALLMODE_DISPLAYNAME} (current user)" ; "add/remove programs" will show if installation is per-user - WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "${MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME}" '"$INSTDIR\${UNINSTALL_FILENAME}" /currentuser' ; "UninstallString" + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "UninstallString" '"$INSTDIR\${UNINSTALL_FILENAME}" /currentuser' ${endif} WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "DisplayVersion" "${VERSION}" - WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "DisplayIcon" "$INSTDIR\${PROGEXE},0" - WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "Publisher" "${COMPANY_NAME}" + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "DisplayIcon" "$INSTDIR\${PROGEXE},0" + !ifdef COMPANY_NAME + WriteRegStr SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "Publisher" "${COMPANY_NAME}" + !endif WriteRegDWORD SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "NoModify" 1 WriteRegDWORD SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "NoRepair" 1 ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 ; get folder size, convert to KB IntFmt $0 "0x%08X" $0 - WriteRegDWORD SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "EstimatedSize" "$0" + WriteRegDWORD SHCTX "${MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY2}" "EstimatedSize" "$0" !verbose pop !macroend diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..bc970b7 --- /dev/null +++ b/License.txt @@ -0,0 +1,19 @@ +Copyright (c) 2017 Richard Drizin, Alex Mitev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 5b28500..ccd5264 100644 --- a/README.md +++ b/README.md @@ -1,183 +1,88 @@ # NSIS Multi User Plugin -Installer/Uninstaller that allows installations "per-user" (no admin required) or "per-machine" (asks elevation only when necessary) - -This plugin is based on [MultiUser.nsh (by Joost Verburg)](http://nsis.sourceforge.net/Docs/MultiUser/Readme.html) but with some new features and some simplifications: -- Installer allows installations "per-user" (no admin required) or "per-machine" (as original) -- If running user IS part of Administrators group, he is not forced to elevate (only if necessary - for per-machine install) -- If running user is NOT part of Administrators group, he is still able to elevate and install per-machine (I expect that power-users will have administrator password, but will not be part of the administrators group) -- UAC Elevation happens only when necessary (when per-machine is selected), not in the start of the installer -- Uninstaller block is mandatory (why shouldn't it be?) -- If there are both per-user and per-machine installations, user can choose which one to remove during uninstall -- Correctly creates and removes shortcuts and registry (per-user and per-machine are totally independent) -- Fills uninstall information in registry like Icon and Estimated Size. -- If running as non-elevated user, the "per-machine" install can be allowed (automatically invoking UAC elevation) or can be disabled (suggesting to run again as elevated user) -- If elevation is invoked for per-machine install, the calling process automatically hides itself, and the elevated inner process automatically skips the choice screen (cause in this case we know that per-machine installation was chosen) -- If uninstalling from the "add/remove programs", automatically detects if user is trying to remove per-machine or per-user install -- If someone tries to uninstall a per-machine-installation by running directly uninstall.exe (not using add/remove programs), it will automatically ask for elevation -- Known issue: If uninstalling from Windows 10 - Settings - Apps & features, will always need UAC because uninstall is not signed. - -## Structure: - - `Include` - contains all necessary headers (*.nsh), including [UAC Plugin](http://nsis.sourceforge.net/UAC_plug-in) v0.2.4c (2015-05-26) - - `Plugins` - contains only the DLLs for the [UAC Plugin](http://nsis.sourceforge.net/UAC_plug-in) v0.2.4c (2015-05-26). - -## Installation - -### All Users -1. Copy/Extract `Include` contents to NSIS includes directory (usually `C:\Program Files\Nsis\Include\` or `C:\Program Files (x86)\Nsis\Include\`) -2. Copy/Extract `Plugins` contents to NSIS plugins directory (usually `C:\Program Files\Nsis\Plugins\` or `C:\Program Files (x86)\Nsis\Plugins\`) -3. Add reference to `NsisMultiUser.nsh` in your main NSI file like this: - `!include "NsisMultiUser.nsh"` - -### Local -1. Copy the whole project into any folder (suggestion is a subfolder called `NsisMultiUser` under your NSIS Script folder) -2. Add reference to the DLLs and to the INCLUDE headers like this: - ```nsis - ; if you don't have UAC plug-in installed, add plugin directories (DLLs) to the search path - !addplugindir /x86-ansi ".\NsisMultiUser\Plugins\x86-ansi\" - !addplugindir /x86-unicode ".\NsisMultiUser\Plugins\x86-unicode\" - - ; include the path to header file (full or relative paths), or just add the include directory to the search path (like !addplugindir above) - ;!include ".\NsisMultiUser\Include\NsisMultiUser.nsh" - !addincludedir ".\NsisMultiUser\Include\" - !include "NsisMultiUser.nsh" - - !include "MUI2.nsh" ; NsisMultiUser depends on MUI2 - !include LogicLib.nsh - - - ``` +NSIS plugin that allows "per-user" (no admin required) and "per-machine" (asks elevation *only when necessary*) installations. This plugin was inspired by [MultiUser.nsh (by Joost Verburg)](http://nsis.sourceforge.net/Docs/MultiUser/Readme.html), but supports a lot of new features and is easier to use. -## Usage +## How It Works + +### Installer +The plugin creates a custom Install Options page based on the nsDisalogs library that is displayed before the Components page. The page is displayed always and has two options: install for all users (per-machine) and install for current user only (per-user). When the user starts the setup, he is not forced to elevate in the beginning. If the user selects per-user install, he can install only for himself without being asked for elevation (except when there is per-machine installation that needs to be removed first). If the user selects per-machine install, the Windows shield is displayed on the Next button and elevation is required. Limited users can also install per-machine as long as they know the administrator credentials. + +### Uninstaller +The plugin creates the same custom page and shows it in the beginning of the uninstaller if there are two installations. Elevation is required only when per-machine version is uninstalled. If there is only one installed version or if command-line parameters are passed specifying which version to uninstall, the page is not displayed. In this case, the uninstaller asks for elevation if per-machine version is to be uninstalled. When invoked from the Windows Uninstall dialog or from the Start menu, a parameter to the uninstaller is passed, so that it detects which verion to uninstall, and the page is not displayed. + +### Both +An option (`MULTIUSER_INSTALLMODE_ALLOW_ELEVATION`) defines whether elevation if allowed. If elevation is disabled, the per-machine option becomes available only if the (un)installer is started elevated from Windows and is disabled otherwise. -The include for `NsisMultiUser.nsh` should be done *after* defining the following constants: - -```nsis -!define APP_NAME "Servantt" -!define UNINSTALL_FILENAME "uninstall.exe" -!define MULTIUSER_INSTALLMODE_INSTDIR "${APP_NAME}" ; suggested name of directory to install (under $PROGRAMFILES or $LOCALAPPDATA) -!define MULTIUSER_INSTALLMODE_INSTALL_REGISTRY_KEY "${APP_NAME}" ; registry key for INSTALL info, placed under [HKLM|HKCU]\Software (can be ${APP_NAME} or some {GUID}) -!define MULTIUSER_INSTALLMODE_UNINSTALL_REGISTRY_KEY "${APP_NAME}" ; registry key for UNINSTALL info, placed under [HKLM|HKCU]\Software\Microsoft\Windows\CurrentVersion\Uninstall (can be ${APP_NAME} or some {GUID}) -!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "UninstallString" -!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "InstallLocation" -;!define MULTIUSER_INSTALLMODE_DISPLAYNAME "${APP_NAME} ${VERSION} ${PRODUCT_EDITION}" ; optional... default is "${APP_NAME} ${VERSION}" -!define MULTIUSER_INSTALLMODE_ALLOW_ELEVATION ; OPTIONAL - allow requesting for elevation... if false, radiobutton will be disabled and user will have to restart installer with elevated permissions -!define MULTIUSER_INSTALLMODE_DEFAULT_ALLUSERS ; OPTIONAL (only available if MULTIUSER_INSTALLMODE_ALLOW_ELEVATION) - will mark "all users" (per-machine) as default even if running as non-elevated user. -``` - -Between your pages (normally you'll want to add it after the PAGE_LICENSE), just add this call to MULTIUSER_PAGE_INSTALLMODE: - -```nsis -!insertmacro MUI_PAGE_LICENSE "..\License.rtf" -;... -!insertmacro MULTIUSER_PAGE_INSTALLMODE ; this will show the 2 install options, unless it's an elevated inner process (in that case we know we should install for all users) -;... -!insertmacro MUI_PAGE_DIRECTORY -!insertmacro MUI_PAGE_COMPONENTS -!insertmacro MUI_PAGE_INSTFILES -``` - -In your main section, after writing all files (and uninstaller) just add this call (MULTIUSER_RegistryAddInstallInfo): - -```nsis -Section "MyProgram (required)" - SectionIn RO - - ; Set output path to the installation directory. - SetOutPath $INSTDIR - SetOverwrite on - - ; Put files there - File "..\Release\Obfuscated\${PROGEXE}" - File "..\Release\Obfuscated\${PROGEXE}.config" - File "..\Release\ExternalReference.dll" - ; ... - File "..\License.rtf" - WriteUninstaller "${UNINSTALL_FILENAME}" - !insertmacro MULTIUSER_RegistryAddInstallInfo ; add registry keys -SectionEnd -``` - -In the end of your uninstall, do the same (MULTIUSER_RegistryRemoveInstallInfo): - -```nsis -Section "Uninstall" - ; Remove files and uninstaller - Delete $INSTDIR\*.dll - Delete $INSTDIR\*.exe - Delete $INSTDIR\*.rtf - Delete $INSTDIR\*.config - - ; Remove shortcuts, if any - ;SetShellVarContext all ; all users - Delete "$SMPROGRAMS\Servantt\*.*" - - ; Remove directories used - RMDir "$SMPROGRAMS\Servantt" - RMDir "$INSTDIR" - - !insertmacro MULTIUSER_RegistryRemoveInstallInfo ; Remove registry keys -SectionEnd -``` - -In the shortcuts section, don’t set var context (plugin will do), and use $SMPROGRAMS: - -```nsis -Section "Start Menu Shortcuts" - ;SetShellVarContext all ; all users - CreateDirectory "$SMPROGRAMS\${APP_NAME}" - ;CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" "$INSTDIR\${UNINSTALL_FILENAME}" "" "$INSTDIR\${UNINSTALL_FILENAME}" 0 ; shortcut for uninstall is bad cause user can choose this by mistake during search. - CreateShortCut "$SMPROGRAMS\${APP_NAME}\${PRODUCT_NAME} ${VERSION}.lnk" "$INSTDIR\${PROGEXE}" "" "$INSTDIR\${PROGEXE}" 0 - Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME} 1.0*" ; old versions -SectionEnd -``` - -Initialize the plugin both for install and for uninstall (MULTIUSER_INIT and MULTIUSER_UNINIT): - -```nsis -Function .onInit - !insertmacro MULTIUSER_INIT -FunctionEnd - -Function un.onInit - !insertmacro MULTIUSER_UNINIT -FunctionEnd -``` +An option (`MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS`) defines whether simultaneous per-user and per-machine installations on the same machine are allowed. If set to disallow, the installer alaways requires elevation when there's per-machine installation in order to remove it first. +## Features +- Not tied or dependant on any particular user interface. Supports Modern UI 1/2, ModernUIEx, Ultra Modern UI, the native NSIS interface, as well as any other interface that supports nsDialogs pages. +- Includes fully functional demos for all supported interfaces that you can use as skeletons to start your scripts from. +- Support for 64-bit installations +- Correctly creates and removes full registry uninstall information like icon and estimated size (separate per-user and per-machine entries) +- Fully supports silent mode, command-line switches and error level handling +- Fully documented ## Screenshots -User runs the installer, no elevation is required unless/until it's necessary. +When `MULTIUSER_INSTALLMODE_ALLOW_ELEVATION` is `1`, there is no existing istallation and running as a regular user (Ultra Modern UI). +Installation for current user requires no elevation: + +![Installation for current user requires no elevation](./Screenshots/01.png?raw=true "Installation for current user requires no elevation") +![Per-user installation folder](./Screenshots/02.png?raw=true "Per-user installation folder") + +Installation for all users requires elevation: + +![Installation for all users requires elevation](./Screenshots/03.png?raw=true "Installation for all users requires elevation") -If the **ALLOW_ELEVATION** is NOT defined and user is NOT running as admin, only per-user installation is offered: +When running as admin, no elevation is required: -![Per-user install](/Documentation/screenshot1.png?raw=true) -![Per-user install](/Documentation/screenshot2.png?raw=true) +![When running as admin, no elevation is required](./Screenshots/04.png?raw=true "When running as admin, no elevation is required") +When there is an existing installation, it is always selected (Modern UI 2): -If the user is running as admin or if **ALLOW_ELEVATION** is defined, both options are offered: +![Existing instalation is always selected](./Screenshots/05.png?raw=true "Existing instalation is always selected") -![Per-user install](/Documentation/screenshot3.png?raw=true) +When `MULTIUSER_INSTALLMODE_ALLOW_BOTH_INSTALLATIONS` is `0`, there is existing per-machine installation and running as a regular user, elevation to install per-user is required (Modern UI 2): -PS: If running as regular user, default is to suggest a per-user install, unless **DEFAULT_ALLUSERS** is defined +![When there is per-machine installation and elevation per-user is required](./Screenshots/06.png?raw=true "When there is per-machine installation and elevation per-user is required") -Reinstallations/Upgrades will always suggest to use the existing installation: +When `MULTIUSER_INSTALLMODE_ALLOW_ELEVATION` is `0` and running as a regular user, per-machine option is disabled (native NSIS interface): -![Per-user install](/Documentation/screenshot4.png?raw=true) +![Per-machine option is disabled](./Screenshots/07.png?raw=true "Per-machine option is disabled") -![Per-user install](/Documentation/screenshot5.png?raw=true) +When invoked with the `/allusers` parameter and `MULTIUSER_INSTALLMODE_ALLOW_ELEVATION` is `1` (native NSIS interface): +![/allusers parameter](./Screenshots/08.png?raw=true "/allusers parameter") -**If there are both per-user and per-machine installations**, uninstaller will ask which one should be removed. +When invoked with the `/allusers` parameter and `MULTIUSER_INSTALLMODE_ALLOW_ELEVATION` is `0`: -![Per-user install](/Documentation/screenshot6.png?raw=true) +![/allusers parameter and elevation is disabled](./Screenshots/09.png?raw=true "/allusers parameter and elevation is disabled") -The "add/remove programs" will show individual installations (one is stored in HKLM and other in HKCU): +When there are both per-user and per-machine installations and uninstaller is invoked without parameters, page is displayed (Ultra Modern UI): + +![Uninstaller page](./Screenshots/10.png?raw=true "Uninstaller page") + +The Windows Uninstall list of programs will show individual entries when there are both per-machine and per-user installations (one is stored in `HKLM` and other in `HKCU`): + +![The Windows Uninstall list of programs](./Screenshots/11.png?raw=true "The Windows Uninstall list of programs") + +The help dialog, invoked with the `/?` parameter: + +![The help dialog](./Screenshots/12.png?raw=true "The help dialog") + +## Usage -![Per-user install](/Documentation/screenshot7.png?raw=true) +Please look at the fully functional demos in the `Demos` folder. -If you choose to uninstall the per-machine installation (first row) from this "add/remove" screen, command-line argument "/allusers" will make the uninstaller **automatically remove the per-machine installation** (skip the which-installation-screen, even if you also have a per-user installation on the Administrator account) +## Documentation -If you choose uninstall the per-user installation (second row) from this "**add/remove**" screen, command-line argument "/currentuser" will make the uninstaller **automatically remove the per-user installation** (skip the which-installation-screen, even if you also have a per-machine installation) +The full NsisMultiUser documentation is avaialable on the [Wiki](https://github.com/Drizin/NsisMultiUser/wiki). -If you run the uninstaller from the program folder (that is, without passing command-line arguments), this "**which installation to remove**" screen will be shown **if there is both per-user and per-machine installations**. +You can also look at: +- [UAC plugin page](http://nsis.sourceforge.net/UAC_plug-in) +- [The original MultiUser.nsh plugin](http://nsis.sourceforge.net/Docs/MultiUser/Readme.html) +- [nsDialogs plugin](http://nsis.sourceforge.net/Docs/nsDialogs/Readme.html) +- [Modern UI](http://nsis.sourceforge.net/Docs/Modern%20UI/Readme.html) +- [Ultra Modern UI](http://ultramodernui.sourceforge.net/) +- [MSDN documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/dd765197.aspx) diff --git a/Screenshots/01.png b/Screenshots/01.png new file mode 100644 index 0000000..f4f8d60 Binary files /dev/null and b/Screenshots/01.png differ diff --git a/Screenshots/02.png b/Screenshots/02.png new file mode 100644 index 0000000..70dfd11 Binary files /dev/null and b/Screenshots/02.png differ diff --git a/Screenshots/03.png b/Screenshots/03.png new file mode 100644 index 0000000..90206fb Binary files /dev/null and b/Screenshots/03.png differ diff --git a/Screenshots/04.png b/Screenshots/04.png new file mode 100644 index 0000000..edc5d3b Binary files /dev/null and b/Screenshots/04.png differ diff --git a/Screenshots/05.png b/Screenshots/05.png new file mode 100644 index 0000000..719f572 Binary files /dev/null and b/Screenshots/05.png differ diff --git a/Screenshots/06.png b/Screenshots/06.png new file mode 100644 index 0000000..ac6cb02 Binary files /dev/null and b/Screenshots/06.png differ diff --git a/Screenshots/07.png b/Screenshots/07.png new file mode 100644 index 0000000..4b61fc0 Binary files /dev/null and b/Screenshots/07.png differ diff --git a/Screenshots/08.png b/Screenshots/08.png new file mode 100644 index 0000000..ee2dc2d Binary files /dev/null and b/Screenshots/08.png differ diff --git a/Screenshots/09.png b/Screenshots/09.png new file mode 100644 index 0000000..84c9ce5 Binary files /dev/null and b/Screenshots/09.png differ diff --git a/Screenshots/10.png b/Screenshots/10.png new file mode 100644 index 0000000..e818d10 Binary files /dev/null and b/Screenshots/10.png differ diff --git a/Screenshots/11.png b/Screenshots/11.png new file mode 100644 index 0000000..734a6df Binary files /dev/null and b/Screenshots/11.png differ diff --git a/Screenshots/12.png b/Screenshots/12.png new file mode 100644 index 0000000..cec1f56 Binary files /dev/null and b/Screenshots/12.png differ