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

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...

Related

RelGotoPage function goes to next page instead of going back

My current defined order of pages in the Installer are:
!insertmacro MUI_PAGE_WELCOME ; Welcome page
!insertmacro MUI_PAGE_DIRECTORY ; Select a directory
!define MUI_PAGE_CUSTOMFUNCTION_PRE CheckInstallDirectory ; Check selected directory
!insertmacro MUI_PAGE_COMPONENTS ; Choose install components
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipLicense ; Component1's check before license page
!insertmacro MUI_PAGE_LICENSE "license.txt" ; Component1's license
!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipLicenseForComp2 ; Component2's check before license page
!insertmacro MUI_PAGE_LICENSE "License2.txt" ; Component2's license
!insertmacro CUSTOM_PAGES ; Custom install pages
!insertmacro MUI_PAGE_INSTFILES ; Install components' files
!insertmacro MUI_PAGE_FINISH ; Finish page
In the CheckInstallDirectory function, which is defined below, it checks if the end-user selected a directory that already houses an .exe that would otherwise be installed by the installer.
If the directory exists, the user is presented a pop-up message box with the "OK" and "CANCEL" buttons.
"OK" selection should simply skip the next few pages and go to MUI_PAGE_INSTFILES page.
"CANCEL" selection should return the user to the MUI_PAGE_DIRECTORY page.
Function CheckInstallDirectory
IfFileExists "$INSTDIR\Component1.exe" file_found end_of_check
file_found:
Var /GLOBAL ver_
${GetFileVersion} "$INSTDIR\Component1.exe" $ver_
StrCpy $0 $ver_ 1 ; Determine the major version of the installed exe (1st char of the returned string)
StrCpy $1 $ver_1 1 ; Determine the major version of the exe we are installing (1st char of the returned string)
${If} $0 == $1
; The major version between .exe's is the same
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A copy of this software is already installed in the selected directory.$\nSelect $\"OK$\" if you would like to update the software to the latest version.$\nOtherwise select $\"Cancel$\" to go back and select a different installation directory." IDOK OK IDCANCEL CANCEL
OK:
; User selected to update this software - continue with the installation like normal
StrCpy $R9 "7" ; Skip to the installation page
Call RelGotoPage
goto end_of_check
CANCEL:
StrCpy $R9 "-1" ; Go back to directory selection page
Call RelGotoPage
goto end_of_check
${Else}
; The major version between the .exe's is NOT the same - cannot update
MessageBox MB_OK "A copy of this software is already installed in the selected directory.$\nIt is not part of the latest major-version group and cannot be updated. To update this installation, uninstall and use the installer to install the latest version.$\nOtherwise, select$\"OK$\" to go back and select a different installation directory."
StrCpy $R9 "-1" ; Go back to directory selection page
Call RelGotoPage
goto end_of_check
${EndIf}
end_of_check:
FunctionEnd
I have found NSIS's description for how to move across installer pages here.
Function RelGotoPage
IntCmp $R9 0 0 Move Move
StrCmp $R9 "X" 0 Move
StrCpy $R9 "120"
Move:
SendMessage $HWNDPARENT "0x408" "$R9" ""
FunctionEnd
The problem that is occurring is that StrCpy $R9 "7" does successfully skip to the MUI_PAGE_INSTFILES page. Yet, StrCpy $R9 "-1" does not return to the previous page (which should be MUI_PAGE_DIRECTORY). Instead, if "CANCEL" is selected in that pop-up message, the installer goes to the next page, MUI_PAGE_COMPONENTS.
What is incorrect here?
Is there a better way to achieve the desired result?
Any help is sincerely appreciated! Thank you!
In the page pre-callback the page is not fully loaded and I assume that is why -1 does not work correctly. -1 does work if you use MUI_PAGE_CUSTOMFUNCTION_SHOW but the proper way to do this is to block the page change:
!include MUI2.nsh
!insertmacro MUI_PAGE_WELCOME ; Welcome page
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE CheckInstallDirectory
!insertmacro MUI_PAGE_DIRECTORY
...
Function CheckInstallDirectory
;Snipped
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "A copy of this software is already installed in the selected directory.$\nSelect $\"OK$\" if you would like to update the software to the latest version.$\nOtherwise select $\"Cancel$\" to go back and select a different installation directory." IDOK OK IDCANCEL CANCEL
OK:
; User selected to update this software - continue with the installation like normal
StrCpy $R9 "7" ; Skip to the installation page
Call RelGotoPage
goto end_of_check
CANCEL:
Abort
;Snipped
end_of_check:
FunctionEnd

Space required on MUI_PAGE_DIRECTORY is 0.0KB

I'm writing an installer for Windows application. I'm using MUI for NSIS. One of the pages of the installer is Directory Page, where I have the "Space required" field. The problem is that the space required is always 0.0KB.
I was looking for some answers, but all I found was that space is calculated automatically. I wonder if there is some way to check which folder size this macro gets? Or any other ideas?
;Pages
; Installation
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "#CPACK_RESOURCE_FILE_LICENSE#"
!insertmacro MUI_PAGE_DIRECTORY
Page custom pgAppLanguageCreate
The size is automatically calculated by the File instructions in Sections plus any extra you add with AddSize.
If you are not happy with this calculation you can force a specific size in .onInit with SectionSetSize.
I found what causes the problem.
In the "Installer Section" I used a function that copies all files.
Instead of calling it here, I just paste the entire function and now that works. It looks like you cant use "File" instruction in function because it doesn't add size to the required space this way.
If you use it in section, it works perfectly.
; Installer Sections
Section "install" Install
${If} $instState != "1"
Call deleteAllFiles
Call deleteInfoFromRegistry
Call deleteShortcut
${EndIf}
SetOutPath "$INSTDIR"
; copy files
File ${ICON}
File "slicer_license"
File /r "${INSTALL_FILE}\package\"
;File "README.txt" ;Readme file.
Call createShortcut
Call addInfoToRegistry
Call setAppLanguageShortcut
Call createLanguageFile
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

Creating shortcut with NSIS

I am trying to create an installer using NSIS, installing just the program that I have made works just fine but when I add code to the script to install a shortcut as well things aren't working.
I am very new to this program and what happens is the "Start in:" property path is incomplete but if i add the "\IndieBrowser\IndieBrowser\bin\Debug\IndieBrowser.exe"" at the end it just turns a lot of things into folders.
Full code:
!include "MUI2.nsh"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Section "Desktop Shortcut" SectionS
SetShellVarContext current
SetOutPath "$INSTDIR"
CreateShortCut "$DESKTOP\IndieBrowser.lnk" "$INSTDIR\IndieBrowser\IndieBrowser\bin\Debug\IndieBrowser.exe"
SectionEnd
Here is an example:
; Create application shortcut (first in installation dir to have the correct "start in" target)
SetOutPath "$INSTDIR\bin"
CreateShortCut "$INSTDIR\bin\${NAME}.lnk" "$INSTDIR\bin\${NAME}.exe"
; Start menu entries
SetOutPath "$SMPROGRAMS\${NAME}\"
CopyFiles "$INSTDIR\bin\${NAME}.lnk" "$SMPROGRAMS\${NAME}\"
Delete "$INSTDIR\bin\${NAME}.lnk"

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