How to display progress during a NSIS Installation, when installer calls the uninstaller in silent mode? - nsis

I have an NSIS installer which uninstalls the older version through this command
ExecWait "$INSTDIR\temp\uninstall.exe /S _?=$INSTDIR"
The installer is supposed to wait till uninstall of the old version is complete and then proceed with the new installation. The problem is, the user does not see any progress in the installation window for a long time (the uninstaller has to delete some huge directories, so it takes its own sweet time).
We do not want to remove the /S switch as it will pop up the uninstaller window and the user has to do a couple of clicks to proceed with uninstall and finally close the uninstaller.
Is there anyway by which i can show some progress in the installer window while executing the uninstaller in silent mode?

There is no way to get feedback when /S is used. What you could do is to make up your own parameter and tweak the uninstaller:
!include FileFunc.nsh
UninstPage uninstConfirm un.skipifsilentprogress
UninstPage instFiles
Function un.skipifsilentprogress
ClearErrors
${GetParameters} $0
${GetOptions} "$0" "/UIS" $1
${IfNot} ${Errors}
SetAutoClose true ;Make sure user does not have to click close
Abort
${EndIf}
FunctionEnd
Section uninstall
Detailprint uninstalling...
Sleep 555
Sleep 555
Sleep 555
SectionEnd
And run it with the special /UIS switch...

Related

NSIS roll-back installer if ExecWait command gets a specific return code

I'm currently using the following script to install drivers along with my application:
!macro customInstall
ExecWait '"$INSTDIR\resources\DPInst.exe" /sw'
!macroend
However, if DPInst returns >= 0x80010000, this means one or more of the driver installs has failed so I need to roll-back the installation and quit. Any idea how I would do this?
ExecWait can store the process exit code in the 2nd parameter. Not much you can do to roll it back, it is best just to do it early in the install phase:
!include LogicLib.nsh
Section
SetOutPath "$instdir\resources"
File "whatever\DPInst.exe"
ExecWait '"$INSTDIR\resources\DPInst.exe" /sw' $0
${If} $0 U>= 0x80010000
Delete "$INSTDIR\resources\DPInst.exe"
RMDir $instdir\resources
RMDir $instdir
MessageBox mb_iconstop "Error blah blah"
Abort
${EndIf}
SectionEnd

Signing NSIS Uninstaller from Linux or Mac

I am porting our NSI installer to Linux and Mac instead of Windows to better integrate with our Maven build system.
We need to sign our installer and uninstaller. This was done as suggested at http://nsis.sourceforge.net/Signing_an_Uninstaller, but I just realized that it tries to run the tempinstaller to force it to produce the uninstaller.exe which can then be signed.
Obviously this trick doesn't work too well on *Nix systems and make this part of the process non-portable.
Does anyone has a better solution. I'm no expert at NSIS and wondering if there is a clever way to get the uninstall.exe so that it can be signed?
I don't think there is a real solution to this.
The installer and uninstaller uses the same exe code and only checks a flag (FH_FLAGS_UNINSTALL in firstheader) on startup to see if it is a uninstaller. Just flipping this bit is not enough though, the program would fail the CRC check and even if you bypass that the uninstaller data is compressed so you would have to decompress that to the correct location in the file. To actually accomplish this you would have to write a custom tool. You can see this operation in the NSIS source in exec.c if you search for EW_WRITEUNINSTALLER.
We need to sign our installer and uninstaller. This was done as suggested at http://nsis.sourceforge.net/Signing_an_Uninstaller, but I just realized that it tries to run the tempinstaller to force it to produce the uninstaller.exe which can then be signed. [...] this trick doesn't work too well on *Nix systems and make this part of the process non-portable.
If you exploit a stub installer for uninstall operations (no payload), this appears to be possible.
It will spawn an uninstall.exe process from the $TEMP folder, which is then capable of deleting $INSTDIR.
This script will create a stub (un)installer which can then be Authenticode Signed. It will compile on Windows, MacOS and Linux.
Caveats:
You'll have to manually bundle this into the installer (trivial)
You'll have to manage your own uninstall registry entries (trivial)
The look and feel may not match NSIS's default for uninstallers
You'll see the installer open twice (first from $INSTDIR, second from $TEMP). This is a child process which allows uninstall.exe to delete itself, similar to how NSIS does it in the Section "Uninstall".
You'll need a secondary .nsi script dedicated to uninstall operations, cumbersome if you have a lot of shared logic between your install/uninstall sections.
Worse, you'll have to AVOID the "Uninstall" section title, as you'll be placed into the same problem as the OP when that bytecode is generated.
When explicitly running from $TEMP some relative file logic will be incorrect. The example passes these back in as a $DELETE_DIR, $DELETE_EXE respectively.
The code:
!include MUI2.nsh
!include x64.nsh
!include LogicLib.nsh
!include FileFunc.nsh
!include WinMessages.nsh
!define MUI_PRODUCT "My App"
!define MUI_VERSION "1.0.0"
; Masquerade the title
!define MUI_PAGE_HEADER_TEXT "Uninstall My App"
!define MUI_PAGE_HEADER_SUBTEXT "Remove My App from your computer"
!define MUI_INSTFILESPAGE_FINISHHEADER_TEXT "Uninstallation Complete"
!define MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT "Uninstall was completed successfully."
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
!insertmacro GetParameters
RequestExecutionLevel admin
CRCCheck On
OutFile "uninstall.exe"
Name "Uninstall"
Var /GLOBAL RESPAWN
Var /GLOBAL DELETE_DIR
Var /GLOBAL DELETE_EXE
Section
; Masquerade as uninstall
SendMessage $HWNDPARENT ${WM_SETTEXT} 0 "STR:Uninstall"
${GetParameters} $0
${GetOptions} "$0" "/RESPAWN=" $RESPAWN
${GetOptions} "$0" "/DELETE_DIR=" $DELETE_DIR
${GetOptions} "$0" "/DELETE_EXE=" $DELETE_EXE
${If} $RESPAWN != ""
; We're running from $TEMP; Perform the uninstall
!define yay "We're running from $EXEPATH, yay, we can remove the install directory!$\n$\n"
!define myvars "$\tRESPAWN$\t$RESPAWN$\n$\tDELETE_EXE$\t$DELETE_EXE$\n$\tDELETE_DIR$\t$DELETE_DIR"
MessageBox MB_OK "${yay}${myvars}"
; Your uninstall code goes here
; RMDir /r $DELETE_DIR\*.*
; Delete "$DESKTOP\${MUI_PRODUCT}.lnk"
; Delete "$SMPROGRAMS\${MUI_PRODUCT}\*.*"
; RmDir "$SMPROGRAMS\${MUI_PRODUCT}"
; Delete Uninstaller And Unistall Registry Entries
; DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\${MUI_PRODUCT}"
; DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MUI_PRODUCT}"
; Remove the old version of ourself
ClearErrors
Delete $DELETE_EXE
IfErrors 0 +3
MessageBox MB_OK "File could NOT be deleted: $DELETE_EXE"
Goto +2
MessageBox MB_OK "File was successfully deleted: $DELETE_EXE"
; Remove ourself from $TEMP after reboot
Delete /REBOOTOK $EXEPATH
; ${If} ${RunningX64}
; ${EnableX64FSRedirection}
; ${EndIf}
SetDetailsPrint textonly
DetailPrint "Completed"
${Else}
; We're NOT running from $TEMP, copy to temp and respawn ourself
GetTempFileName $0
CopyFiles "$EXEPATH" "$0"
Exec '"$0" /RESPAWN=1 /DELETE_DIR="$EXEDIR" /DELETE_EXE="$EXEPATH"'
Quit
${EndIf}
SectionEnd
Function .onInit
; ${If} ${RunningX64}
; SetRegView 64
; ${DisableX64FSRedirection}
; ${EndIf}
FunctionEnd

NSIS - Radio button to select one of many programs to install

I have 4 programs that I'd like to package into one installer and allow the user to select which one they would like to install.
I've never used NSIS before but I was recommended to give it a shot, however, I've no idea where to begin with this.
Basically I just need one page that asks the user to select a radio button then click next to install one of the following programs:
-- Install components --------------------
Select a program from the list below and
click Next to continue.
O Program 1
O Program 2
O Program 3
O Program 4
-------------------------------------------
Cancel Next
Then depending on what they choose it launches program1_setup.exe or program2_setup.exe etc.
As each of my 4 programs are installers in their own right, I take it I don't need to set up the uninstall script in NSIS as that's taken care of already?
Thanks,
Greg.
This code is similar to the one-section.nsi example.
...
!include sections.nsh
Page components
Page instfiles
Section /o "Program 1" P1
File "/oname=$pluginsdir\Setup.exe" "myfiles\Setup1.exe"
SectionEnd
Section "Program 2" P2
File "/oname=$pluginsdir\Setup.exe" "myfiles\Setup2.exe"
SectionEnd
Section ; Hidden section that runs the show
DetailPrint "Installing selected application..."
SetDetailsPrint none
ExecWait '"$pluginsdir\Setup.exe"'
SetDetailsPrint lastused
SectionEnd
Function .onInit
Initpluginsdir ; Make sure $pluginsdir exists
StrCpy $1 ${P2} ;The default
FunctionEnd
Function .onSelChange
!insertmacro StartRadioButtons $1
!insertmacro RadioButton ${P1}
!insertmacro RadioButton ${P2}
!insertmacro EndRadioButtons
FunctionEnd
You can use the CheckBitmap attribute to change the checkbox icons if you want...

Checking if the application is running in NSIS before uninstalling

I am new to NSIS, and I need to know that in the uninstaller, how I can check if the application (which is in C++) is running and close it before uninstalling.
Here is a slightly more friendly version for using NSProcess that requests the app to close rather than terminates it (Owen's answer)
${nsProcess::FindProcess} "${APP_EXE}" $R0
${If} $R0 == 0
DetailPrint "${AppName} is running. Closing it down"
${nsProcess::CloseProcess} "${APP_EXE}" $R0
DetailPrint "Waiting for ${AppName} to close"
Sleep 2000
${Else}
DetailPrint "${APP_EXE} was not found to be running"
${EndIf}
${nsProcess::Unload}
Use the NsProcess plugin. Download it here -> NSProcess
How to use it? As simple as:
${nsProcess::KillProcess} "${APP_EXE}" $R4
where APP_EXE is the name of your application...
The download will also tell you how to use it... :)
Depending on the application, you have a couple of choices:
If your application has a window with a somewhat unique class name, you could use FindWindow
If your application creates a named kernel object (Mutex etc) you can check for it by calling the correct native win32 API with the system plugin
Use a 3rd party plugin like FindProcDLL
Just make sure that the first thing that install or un-install does is to delete all xyz.tmp files in %TEMP (or any other app writable directory) before the below for loop runs. No plugins required.
!macro IsRunning
ExecWait "cmd /c for /f $\"tokens=1,2$\" %i in ('tasklist') do (if /i %i EQU xyz.exe fsutil file createnew $TEMP\xyz.tmp 0)"
IfFileExists $TEMP\xyz.tmp 0 notRunning
;we have atleast one main window active
MessageBox MB_OK|MB_ICONEXCLAMATION "XYZ is running. Please close all instances and retry." /SD IDOK
Abort
notRunning:
!macroEnd

How to hide all windows until I need them in NSIS

I have a NSIS installer I want to be totally silent unless it needs to download additional files. I can make it totally silent with SilentInstall, but then i can't make my download dialog appear (i'm using InetLoad::load).
I would like to tell NSIS not to show any windows until I say so. The best I can come up with is HideWindow. Unfortantly it looks like NSIS defaults to showing the window and then hiding it causing a flicker.
How can I prevent a flickering window?
Example code:
Name "Flicker test"
OutFile "flickertest.exe"
AutoCloseWindow true
Section
HideWindow
SectionEnd
This is a hack way of doing it:
!include "${NSISDIR}\Examples\System\System.nsh"
Name "No Flicker test"
OutFile "noflickertest.exe"
AutoCloseWindow true
Function .onGUIInit
; move window off screen
System::Call "User32::SetWindowPos(i, i, i, i, i, i, i) b ($HWNDPARENT, 0, -10000, -10000, 0, 0, ${SWP_NOOWNERZORDER}|${SWP_NOSIZE})"
FunctionEnd
Section -main
HideWindow
SectionEnd
You can use skipping pages Example for MUI2 (hide directory page if mode is update):
!define MUI_PAGE_CUSTOMFUNCTION_PRE dirPre
!insertmacro MUI_PAGE_DIRECTORY
Function dirPre
StrCmp $Mode "update" +1 +2
abort
FunctionEnd
OutFile "example.exe"
SilentInstall silent
RequestExecutionLevel user<br>
ReserveFile test.exe
Section ""<br>
 InitPluginsDir<br>
 File /oname=$PLUGINSDIR\test.exe test.exe<br>
 ExecWait "$PLUGINSDIR\test.exe"<br>
SectionEnd

Resources