Uninstall only installed files NSIS - nsis

I want to find a way to uninstall only installed files.
I tried http://nsis.sourceforge.net/Uninstall_only_installed_files but I have around 10.000 files nested in many directories, therefore specifying each file separately in a nsis script is not a solution.

You can use the !system command to execute external tools at compile-time. This external tool/script should generate include files with install and uninstall instructions.
You can also use NSIS on the fly to generate the instructions but it is a bit messy when you put all of it in a single script:
; Generate some files for this example:
!system 'mkdir "$%temp%\testdir\subdir\sub2\sub3"'
!appendfile "$%temp%\testdir\file1.txt" ""
!appendfile "$%temp%\testdir\subdir\file2.txt" ""
!appendfile "$%temp%\testdir\subdir\sub2\file3.txt" ""
!appendfile "$%temp%\testdir\subdir\sub2\sub3\file4.txt" ""
!macro GENFILELIST_Enum SourceDir InstDir
Push "${SourceDir}"
Push "${InstDir}"
Call GENFILELIST_Enum
!macroend
### Start editing here ###
!macro GeneratePages
OutFile "test.exe"
Name "Test"
RequestExecutionLevel admin
InstallDir "$ProgramFiles\MyTestApp"
Page Directory
Page InstFiles
UninstPage UninstConfirm
UninstPage InstFiles
!macroend
!macro GENFILELIST
!insertmacro GENFILELIST_Enum "$%temp%\testdir" "" ; Files to install
!macroend
!macro GenerateInstallSections FileInstructions
Section
SetOutPath $InstDir
!include "${FileInstructions}"
WriteUninstaller "$InstDir\Uninst.exe"
SectionEnd
!macroend
!macro GenerateUninstallSections FileInstructions
Section Uninstall
SetOutPath $InstDir
!include "${FileInstructions}"
SetOutPath $Temp
Delete "$InstDir\Uninst.exe"
RmDir $InstDir
SectionEnd
!macroend
### Stop editing here ###
!ifndef GENFILELIST_IN
!tempfile GENFILELISTAPP
!tempfile GENFILELIST_IN
!tempfile GENFILELIST_UN
!appendfile "${GENFILELIST_IN}" '!define GENFILELIST_UN "${GENFILELIST_UN}"$\n'
!appendfile "${GENFILELIST_IN}" 'OutFile "${GENFILELISTAPP}"$\n'
!system '"${NSISDIR}/makensis" "/DGENFILELIST_IN=${GENFILELIST_IN}" "${__FILE__}"' = 0
!system '"${GENFILELISTAPP}" /S' = 0
!delfile "${GENFILELISTAPP}"
!insertmacro GeneratePages
!insertmacro GenerateInstallSections "${GENFILELIST_IN}"
!insertmacro GenerateUninstallSections "${GENFILELIST_UN}"
!delfile "${GENFILELIST_IN}"
!delfile "${GENFILELIST_UN}"
!else
!include LogicLib.nsh
!include "${GENFILELIST_IN}"
!delfile "${GENFILELIST_IN}"
!macro GENFILELIST_Append file string tmpvar
FileOpen ${tmpvar} "${file}" a
FileSeek ${tmpvar} 0 END
FileWrite ${tmpvar} '${string}'
FileClose ${tmpvar}
!macroend
!define DOLLAR "$$"
Function GENFILELIST_Enum
System::Store S
Pop $9 ; Relative path
Pop $8 ; Base path
${IfThen} $9 == "" ${|} StrCpy $9 "${DOLLAR}OutDir" ${|}
FindFirst $0 $1 "$8\*"
loop:
StrCmp $1 "" stop
StrCmp $1 "." next
StrCmp $1 ".." next
StrCpy $3 $1
${If} ${FileExists} "$8\$1\*"
!insertmacro GENFILELIST_Append "${GENFILELIST_IN}" 'SetOutPath "${DOLLAR}OutDir\$1"$\n' $2
Push $8\$1
Push $9\$1
Call GENFILELIST_Enum
${Else}
!insertmacro GENFILELIST_Append "${GENFILELIST_IN}" 'File "$8\$1"$\n' $2
!insertmacro GENFILELIST_Append "${GENFILELIST_UN}" 'Delete "$9\$1"$\n' $2
${EndIf}
next:
FindNext $0 $1
Goto loop
stop:
FindClose $0
!insertmacro GENFILELIST_Append "${GENFILELIST_UN}" 'RmDir "$9"$\n' $2
System::Store L
FunctionEnd
RequestExecutionLevel user
Section
!insertmacro GENFILELIST
SectionEnd
!endif

Related

NSIS script doesn't display service status

I created section at the end of script which suppose to display myservice status, but no messagebox displays after exec.
What might be the reason?
Section "Create Service"
ExecShellWait '' 'sc.exe' 'create myservice error= "severe" displayname= "myservice" type= "own" start= "auto" binpath= "$INSTDIR\MyService.exe"' SW_HIDE
SectionEnd
Section "Start Service"
ExecShellWait '' 'sc.exe' 'start myservice' SW_HIDE
SectionEnd
Section "Ensure Running"
StrCpy $R0 '"$SYSDIR\cmd.exe" /c "sc QUERY myservice | FIND /C "RUNNING""'
nsExec::ExecToStack '$R0'
Pop $R1 # contains return code
Pop $R2 # contains output
${If} $R1 == "0"
DetailPrint "checking if command is success"
${If} $R2 == "1"
MessageBox mb_ok "myservice is Running" # it's running
${Else}
MessageBox mb_ok "Not Running" # it's not running
${EndIf}
${Else}
DetailPrint "command failed"
${EndIf}
SectionEnd ```
nsExec::ExecToStack /OEM '$R0' didn't change anything. During installation it rushes to finish page after sc exec ..Do I need to add additional page page here ?
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE ..\license.rtf
; Page custom pageRegistration
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_LANGUAGE English
Without seeing the output I can only guess that there is some sort of Unicode issue.
Change nsExec::ExecToStack '$R0' to nsExec::ExecToStack /OEM '$R0'.
That being said, I don't know if the RUNNING string is always in English. There are several service plug-ins and macros on the NSIS wiki you can use instead.
Or you can use this:
!include "LogicLib.nsh"
!include "Win\COM.nsh"
!include "Win\Propkey.nsh"
Function IsServiceRunning
Exch $0
Push $1
StrCpy $1 $0
!insertmacro ComHlpr_CreateInProcInstance {13709620-C279-11ce-A49E-444553540000} {A4C6892C-3BA9-11D2-9DEA-00C04FB16162} r0 "" ; IE5+?
${If} $0 P<> 0
System::Call "$0->36(wr1,#r1)i.s"
${IUnknown::Release} $0 ""
Pop $0
${If} $0 >= 0
StrCpy $0 0
Push $2
${V_GetVT} $1 r2
${V_GetBOOL} $1 r1
${If} $2 = ${VT_BOOL}
${AndIf} $1 <> ${VARIANT_FALSE}
StrCpy $0 1
${EndIf}
Pop $2
${Else}
StrCpy $0 0
${EndIf}
${EndIf}
Pop $1
Exch $0
FunctionEnd
Section "Example"
Push "RemoteRegistry"
Call IsServiceRunning
Pop $0
DetailPrint "RemoteRegistry running=$0"
Push "Themes"
Call IsServiceRunning
Pop $0
DetailPrint "Themes running=$0"
SectionEnd

Files for both applications are installed in both destination folders

Please note that I initially created an installer for two applications based on https://nsis.sourceforge.io/Two_installations_in_one_installer where the first application is the main one while the second application is the license manager. That said, I did remove the nested checkboxes with only one layer of checkboxes. FYI, I have also been referencing this article: https://www.codeproject.com/Articles/24187/Creating-an-Installer?msg=5782957#xx5782957xx
The issue that I am having is that when I run the installer, all of the files from MyApp and FlexLM are installed in the $PROGRAMFILES64\${PRODUCT_NAME} folder and in the C:\${FLEX_DIR}. So when I am installing two applications, it looks like both SEC1 and SEC3 are executed for the MyApp installation and both SEC1 and SEC3 are executed for the FlexLM installation:
!include "LogicLib.nsh"
!include "Sections.nsh"
;Include Modern UI
!include "MUI2.nsh"
!define MAJOR_VERSION "1"
!define MINOR_VERSION "2"
!define PATCH_VERSION "3"
!define BUILD_VERSION "4"
!define APP_COPYRIGHT "MyApp © MyCompany 2021"
!define COMPANY_NAME "MyCompany"
!define FLEX_LM "FlexLM"
!define FLEX_DIR "FlexSQI"
!define LANG_ENGLSH "English"
!define PRODUCT_NAME "MyApp"
!define PRODUCT_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}.${BUILD_VERSION}"
!define SETUP_NAME "MyAppSetup.exe"
BrandingText "MyCompany"
OutFile ${SETUP_NAME}
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}"
InstallDirRegKey HKLM "Software\$PRODUCT_NAME" ""
ShowInstDetails hide
ShowUnInstDetails hide
SetCompressor /SOLID lzma
SetCompressorDictSize 12
Var MyAppCheckboxState
Var FlexLmCheckboxState
;Request application privileges for Windows
RequestExecutionLevel admin
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "MyAppLicense.txt"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesCheck
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ComponentsLeave
!insertmacro MUI_PAGE_COMPONENTS
## This is the title on the MyApp Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_A)"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesA
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
## This is the title on the FlexLM Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_B)"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesB
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE DeleteSectionsINI
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
LangString NoSectionsSelected ${LANG_ENGLSH} "You haven't selected any sections!"
LangString MUI_DIRECTORYPAGE_TEXT_TOP_A ${LANG_ENGLSH} "Setup will install \
${PRODUCT_NAME} in the following folder..."
LangString MUI_DIRECTORYPAGE_TEXT_TOP_B ${LANG_ENGLSH} "Setup will install \
${FLEX_LM} in the following folder..."
;--------------------------------
; Settings
!define PROG1_InstDir "$PROGRAMFILES64\${PRODUCT_NAME}"
!define PROG1_StartIndex ${SEC1}
!define PROG1_EndIndex ${SEC1}
!define PROG2_InstDir "C:\${FLEX_DIR}"
!define PROG2_StartIndex ${SEC3}
!define PROG2_EndIndex ${SEC3}
;--------------------------------
; Start sections
Section "MyApp" SEC1
StrCpy $MyAppCheckboxState ${BST_CHECKED}
${If} $MyAppCheckboxState == ${BST_CHECKED}
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
# specify files to go in output path
File config.dat
File MyApp.exe
File ReleaseNotes.txt
File MyCompany_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$PROGRAMFILES64\${PRODUCT_NAME}\${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Version' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'AppID' "{0be21143-9089-47fa-9736-c45609d13d70}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64\${PRODUCT_NAME}\${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"AppID" "{0be21143-9089-47fa-9736-c45609d13d70}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
${EndIf}
# messagebox mb_ok sec1
SectionEnd
Section "FlexLM" SEC3
StrCpy $FlexLmCheckboxState ${BST_CHECKED}
${If} $FlexLmCheckboxState == ${BST_CHECKED}
##All the files in Group 2 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
File installs.exe
File lmdown.exe
File lmflex.exe
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
${EndIf}
# messagebox mb_ok sec3
SectionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecMyApp ${LANG_ENGLISH} "MyAppTM software is an easy-to-use suite of tools."
LangString DESC_SecFlexLM ${LANG_ENGLISH} "FlexSQI contains all the files necessary to implement the FlexLM license server."
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC1} $(DESC_SecMyApp)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC3} $(DESC_SecFlexLM)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
Section "Uninstall"
# Always delete uninstaller first
Delete $INSTDIR\uninstaller.exe
# now delete installed files and registry keys for MyApp
Delete $INSTDIR\config.dat
Delete $INSTDIR\MyApp.exe
Delete $INSTDIR\ReleaseNotes.txt
Delete $INSTDIR\MyCompany_LandingPage_114.bmp
Delete $INSTDIR\MyAppLicense.txt
Delete "$SMPROGRAMS\MyApp.lnk"
DeleteRegKey HKCU "SOFTWARE\${PRODUCT_NAME}"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
DeleteRegKey /ifempty HKCU "Software\Modern UI Test"
# now delete installed files and registry keys for FlexLM
Delete $INSTDIR\lmdown.exe
Delete $INSTDIR\lmflex.exe
Delete $INSTDIR\installs.exe
# Delete the MyApp and FlexLM directories
RMDir $INSTDIR
SectionEnd
;--------------------------------
; Please don`t modify below here unless you`re a NSIS 'wiz-kid'
## Create $PLUGINSDIR
Function .onInit
InitPluginsDir
SetOutPath $TEMP
File /oname=spltmp.bmp "MyCompany_LandingPage_114.bmp"
splash::show 2000 $TEMP\spltmp
Pop $0 ; $0 has '1' if the user closed the splash screen early,
; '0' if everything closed normally, and '-1' if some error occurred.
Delete $TEMP\spltmp.bmp
FunctionEnd
## If user goes back to this page from 1st Directory page
## we need to put the sections back to how they were before
Var IfBack
Function SelectFilesCheck
StrCmp $IfBack 1 0 NoCheck
;Call ResetFiles
NoCheck:
FunctionEnd
## Also if no sections are selected, warn the user!
Function ComponentsLeave
Push $R0
Push $R1
Call IsPROG1Selected
Pop $R0
Call IsPROG2Selected
Pop $R1
StrCmp $R0 1 End
StrCmp $R1 1 End
Pop $R1
Pop $R0
MessageBox MB_OK|MB_ICONEXCLAMATION "$(NoSectionsSelected)"
Abort
End:
Pop $R1
Pop $R0
FunctionEnd
Function IsPROG1Selected
Push $R0
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
SectionGetFlags 0 $R0 # Get section flags
IntOp $R0 $R0 & ${SF_SELECTED}
StrCmp $R0 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R0 1
Exch $R0
FunctionEnd
Function IsPROG2Selected
Push $R1
StrCpy $R1 ${PROG2_StartIndex} # Group 2 start
IntOp $R1 $R1 + 1
SectionGetFlags 1 $R1 # Get section flags
IntOp $R1 $R1 & ${SF_SELECTED}
StrCmp $R1 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R1 1
Exch $R1
FunctionEnd
## Here we are selecting first sections to install
## by unselecting all the others!
Function SelectFilesA
# If user clicks Back now, we will know to reselect Group 2`s sections for
# Components page
StrCpy $IfBack 1
# We need to save the state of the Group 2 Sections
# for the next InstFiles page
Push $R0
Push $R1
StrCpy $R0 ${PROG2_StartIndex} # Group 2 start
; Loop:
; IntOp $R0 $R0 + 1
; SectionGetFlags $R0 $R1 # Get section flags
; WriteINIStr "$PLUGINSDIR\sections.ini" Sections $R0 $R1 # Save state
; !insertmacro UnselectSection $R0 # Then unselect it
; StrCmp $R0 ${PROG2_EndIndex} 0 Loop
# Don`t install prog 1?
Call IsPROG1Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG1_InstDir define
StrCpy $INSTDIR "${PROG1_InstDir}"
Pop $R1
Pop $R0
FunctionEnd
## Here we need to unselect all Group 1 sections
## and then re-select those in Group 2 (that the user had selected on
## Components page)
Function SelectFilesB
Push $R0
;Push $R1
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
; Loop:
; IntOp $R0 $R0 + 1
; !insertmacro UnselectSection $R0 # Unselect it
; StrCmp $R0 ${PROG1_EndIndex} 0 Loop
; Call ResetFiles
# Don't install prog 2?
Call IsPROG2Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG2_InstDir define
StrCpy $INSTDIR "${PROG2_InstDir}"
;Pop $R1
Pop $R0
FunctionEnd
## Here we are deleting the temp INI file at the end of installation
Function DeleteSectionsINI
FlushINI "$PLUGINSDIR\Sections.ini"
Delete "$PLUGINSDIR\Sections.ini"
FunctionEnd
How can I get only the MyApp files to be installed in $PROGRAMFILES64${PRODUCT_NAME} (i.e., the files from SEC1), and only the Files from FlexLM to be installed in C:\FlexLM? TIA.
UPDATE:
Please find my updated code here:
!include "LogicLib.nsh"
!include "Sections.nsh"
;Include Modern UI
!include "MUI2.nsh"
!define MAJOR_VERSION "1"
!define MINOR_VERSION "2"
!define PATCH_VERSION "3"
!define BUILD_VERSION "4"
!define APP_COPYRIGHT "MyApp © MyCompany 2021"
!define COMPANY_NAME "MyCompany"
!define FLEX_LM "FlexLM"
!define FLEX_DIR "FlexSQI"
!define LANG_ENGLSH "English"
!define PRODUCT_NAME "MyApp"
!define PRODUCT_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}.${BUILD_VERSION}"
!define SETUP_NAME "MyAppSetup.exe"
BrandingText "MyCompany"
OutFile ${SETUP_NAME}
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}\"
InstallDirRegKey HKLM "Software\$PRODUCT_NAME" ""
ShowInstDetails hide
ShowUnInstDetails hide
SetCompressor /SOLID lzma
SetCompressorDictSize 12
Var MyAppCheckboxState
Var FlexLmCheckboxState
Var MyAppInstallDir
Var FlexLmInstallDir
;Request application privileges for Windows
RequestExecutionLevel admin
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "MyAppLicense.txt"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesCheck
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ComponentsLeave
!insertmacro MUI_PAGE_COMPONENTS
## This is the title on the MyApp Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_A)"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesA
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
## This is the title on the FlexLM Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_B)"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesB
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE DeleteSectionsINI
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
LangString NoSectionsSelected ${LANG_ENGLSH} "You haven't selected any sections!"
LangString MUI_DIRECTORYPAGE_TEXT_TOP_A ${LANG_ENGLSH} "Setup will install \
${PRODUCT_NAME} in the following folder..."
LangString MUI_DIRECTORYPAGE_TEXT_TOP_B ${LANG_ENGLSH} "Setup will install \
${FLEX_LM} in the following folder..."
;--------------------------------
; Settings
!define PROG1_InstDir "$PROGRAMFILES64\${PRODUCT_NAME}\"
!define PROG1_StartIndex ${SEC1}
!define PROG1_EndIndex ${SEC1}
!define PROG2_InstDir "C:\${FLEX_DIR}"
!define PROG2_StartIndex ${SEC3}
!define PROG2_EndIndex ${SEC3}
;--------------------------------
; Function
; StrContains
; This function does a case sensitive searches for an occurrence of a substring in a string.
; It returns the substring if it is found.
; Otherwise it returns null("").
; Written by kenglish_hi
; Adapted from StrReplace written by dandaman32
Var STR_HAYSTACK
Var STR_NEEDLE
Var STR_CONTAINS_VAR_1
Var STR_CONTAINS_VAR_2
Var STR_CONTAINS_VAR_3
Var STR_CONTAINS_VAR_4
Var STR_RETURN_VAR
Function StrContains
Exch $STR_NEEDLE
Exch 1
Exch $STR_HAYSTACK
; Uncomment to debug
;MessageBox MB_OK 'STR_NEEDLE = $STR_NEEDLE STR_HAYSTACK = $STR_HAYSTACK '
StrCpy $STR_RETURN_VAR ""
StrCpy $STR_CONTAINS_VAR_1 -1
StrLen $STR_CONTAINS_VAR_2 $STR_NEEDLE
StrLen $STR_CONTAINS_VAR_4 $STR_HAYSTACK
loop:
IntOp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_1 + 1
StrCpy $STR_CONTAINS_VAR_3 $STR_HAYSTACK $STR_CONTAINS_VAR_2 $STR_CONTAINS_VAR_1
StrCmp $STR_CONTAINS_VAR_3 $STR_NEEDLE found
StrCmp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_4 done
Goto loop
found:
StrCpy $STR_RETURN_VAR $STR_NEEDLE
Goto done
done:
Pop $STR_NEEDLE ;Prevent "invalid opcode" errors and keep the
Exch $STR_RETURN_VAR
FunctionEnd
!macro _StrContainsConstructor OUT NEEDLE HAYSTACK
Push `${HAYSTACK}`
Push `${NEEDLE}`
Call StrContains
Pop `${OUT}`
!macroend
!define StrContains '!insertmacro "_StrContainsConstructor"'
;--------------------------------
; Start sections
Section "MyApp" SEC1
${If} ${SectionIsSelected} ${SEC1}
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} $0 "MyQpp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is $0"
StrCmp $0 "" notfoundMyApp
StrCpy $MyAppInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 MyAppInstallDir is $MyAppInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
doneMyApp:
${StrContains} $0 "Flex" "$INSTDIR"
StrCmp $0 "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
# specify files to go in output path
File config.dat
File MyApp.exe
File ReleaseNotes.txt
File MyCompany_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$InstDir\${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Version' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'AppID' "{A0E84732-E2B2-46E5-8CA2-462B8DF92DCD}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64\${PRODUCT_NAME}\${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"AppID" "{A0E84732-E2B2-46E5-8CA2-462B8DF92DCD}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
doneFlex:
${EndIf}
# messagebox mb_ok sec1
SectionEnd
Section "FlexLM" SEC3
${If} ${SectionIsSelected} ${SEC3}
##All the files in Group 2 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
;MessageBox MB_OK "SEC3 #1 INSTDIR is $INSTDIR"
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} $0 "Pro" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is $0"
StrCmp $0 "" notfoundMyApp
StrCpy $MyAppInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 MyAppInstallDir is $MyAppInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
${StrContains} $0 "Flex" "$INSTDIR"
StrCmp $0 "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
doneFlex:
File installs.exe
File lmdown.exe
File lmflex.exe
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
doneQI:
${EndIf}
# messagebox mb_ok sec3
SectionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecMyApp ${LANG_ENGLISH} "MyAppTM software is an easy-to-use suite of tools."
LangString DESC_SecFlexLM ${LANG_ENGLISH} "FlexSQI contains all the files necessary to implement the FlexLM license server."
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC1} $(DESC_SecMyApp)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC3} $(DESC_SecFlexLM)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
Section "Uninstall"
# Always delete uninstaller first
Delete $INSTDIR\uninstaller.exe
# now delete installed files and registry keys for MyApp
Delete $INSTDIR\config.dat
Delete $INSTDIR\MyApp.exe
Delete $INSTDIR\ReleaseNotes.txt
Delete $INSTDIR\MyCompany_LandingPage_114.bmp
Delete $INSTDIR\MyAppLicense.txt
Delete "$SMPROGRAMS\MyApp.lnk"
DeleteRegKey HKCU "SOFTWARE\${PRODUCT_NAME}"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
DeleteRegKey /ifempty HKCU "Software\Modern UI Test"
# now delete installed files and registry keys for FlexLM
Delete $INSTDIR\installs.exe
Delete $INSTDIR\lmborrow.exe
Delete $INSTDIR\lmflex.exe
# Delete the MyApp and FlexLM directories
RMDir $INSTDIR
SectionEnd
;--------------------------------
; Please don`t modify below here unless you`re a NSIS 'wiz-kid'
## Create $PLUGINSDIR
Function .onInit
InitPluginsDir
SetOutPath $TEMP
File /oname=spltmp.bmp "MyCompany_LandingPage_114.bmp"
splash::show 2000 $TEMP\spltmp
Pop $0 ; $0 has '1' if the user closed the splash screen early,
; '0' if everything closed normally, and '-1' if some error occurred.
Delete $TEMP\spltmp.bmp
FunctionEnd
## If user goes back to this page from 1st Directory page
## we need to put the sections back to how they were before
Var IfBack
Function SelectFilesCheck
StrCmp $IfBack 1 0 NoCheck
;Call ResetFiles
NoCheck:
FunctionEnd
## Also if no sections are selected, warn the user!
Function ComponentsLeave
Push $R0
Push $R1
Call IsPROG1Selected
Pop $R0
Call IsPROG2Selected
Pop $R1
StrCmp $R0 1 End
StrCmp $R1 1 End
Pop $R1
Pop $R0
MessageBox MB_OK|MB_ICONEXCLAMATION "$(NoSectionsSelected)"
Abort
End:
Pop $R1
Pop $R0
FunctionEnd
Function IsPROG1Selected
Push $R0
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
SectionGetFlags 0 $R0 # Get section flags
IntOp $R0 $R0 & ${SF_SELECTED}
StrCmp $R0 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R0 1
Exch $R0
FunctionEnd
Function IsPROG2Selected
Push $R1
StrCpy $R1 ${PROG2_StartIndex} # Group 2 start
IntOp $R1 $R1 + 1
SectionGetFlags 1 $R1 # Get section flags
IntOp $R1 $R1 & ${SF_SELECTED}
StrCmp $R1 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R1 1
Exch $R1
FunctionEnd
## Here we are selecting first sections to install
## by unselecting all the others!
Function SelectFilesA
# If user clicks Back now, we will know to reselect Group 2`s sections for
# Components page
StrCpy $IfBack 1
# We need to save the state of the Group 2 Sections
# for the next InstFiles page
Push $R0
Push $R1
StrCpy $R0 ${PROG2_StartIndex} # Group 2 start
; Loop:
; IntOp $R0 $R0 + 1
; SectionGetFlags $R0 $R1 # Get section flags
; WriteINIStr "$PLUGINSDIR\sections.ini" Sections $R0 $R1 # Save state
; !insertmacro UnselectSection $R0 # Then unselect it
; StrCmp $R0 ${PROG2_EndIndex} 0 Loop
# Don`t install prog 1?
Call IsPROG1Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG1_InstDir define
StrCpy $INSTDIR "${PROG1_InstDir}"
Pop $R1
Pop $R0
FunctionEnd
## Here we need to unselect all Group 1 sections
## and then re-select those in Group 2 (that the user had selected on
## Components page)
Function SelectFilesB
Push $R0
;Push $R1
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
; Loop:
; IntOp $R0 $R0 + 1
; !insertmacro UnselectSection $R0 # Unselect it
; StrCmp $R0 ${PROG1_EndIndex} 0 Loop
; Call ResetFiles
# Don't install prog 2?
Call IsPROG2Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG2_InstDir define
StrCpy $INSTDIR "${PROG2_InstDir}"
;Pop $R1
Pop $R0
FunctionEnd
## Here we are deleting the temp INI file at the end of installation
Function DeleteSectionsINI
FlushINI "$PLUGINSDIR\Sections.ini"
Delete "$PLUGINSDIR\Sections.ini"
# FlexLM libs
;MessageBox MB_OK "DeleteSectionsINI #1 MyAppInstallDir is $MyAppInstallDir"
Delete $MyAppInstallDir\installs.exe
Delete $MyAppInstallDir\lmborrow.exe
# QI Pro files
;MessageBox MB_OK "DeleteSectionsINI #1 FlexLmInstallDir is $FlexLmInstallDir"
Delete $FlexLmInstallDir\config.dat
Delete $FlexLmInstallDir\MyApp.exe
Delete $FlexLmInstallDir\ReleaseNotes.txt
Delete $FlexLmInstallDir\MyApp_LandingPage_114.bmp
Delete $FlexLmInstallDir\MyAppLicense.txt
FunctionEnd
I just tested the code from the Wiki and it works perfectly. You commented out the loop in SelectFilesA/B so no wonder the section logic is broken in your example.
You also have some other issues:
StrCpy $MyAppCheckboxState ${BST_CHECKED}
${If} $MyAppCheckboxState == ${BST_CHECKED}
This makes no sense, this will always be true!
Also, CreateShortcut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$PROGRAMFILES64\${PRODUCT_NAME}\${PRODUCT_NAME}.exe" is wrong, it should be CreateShortcut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$InstDir\${PRODUCT_NAME}.exe".
UPDATE 2:
Here is my final solution--many thanks to #Anders. It may not be the most ideal or elegant solution, but it works for what I need. I tried to add this to my question but for some reason SO won't let me.
!include "LogicLib.nsh"
!include "Sections.nsh"
;Include Modern UI
!include "MUI2.nsh"
!define MAJOR_VERSION "1"
!define MINOR_VERSION "2"
!define PATCH_VERSION "3"
!define BUILD_VERSION "4"
!define APP_COPYRIGHT "MyApp © MyCompany 2021"
!define COMPANY_NAME "MyCompany"
!define FLEX_LM "FlexLM"
!define FLEX_DIR "FlexSQI"
!define LANG_ENGLSH "English"
!define PRODUCT_NAME "MyApp"
!define PRODUCT_VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}.${BUILD_VERSION}"
!define SETUP_NAME "MyAppSetup.exe"
BrandingText "MyCompany"
OutFile ${SETUP_NAME}
Name "${PRODUCT_NAME}"
InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}\"
InstallDirRegKey HKLM "Software\$PRODUCT_NAME" ""
ShowInstDetails hide
ShowUnInstDetails hide
SetCompressor /SOLID lzma
SetCompressorDictSize 12
Var MyAppInstallDir
Var FlexLmInstallDir
;Request application privileges for Windows
RequestExecutionLevel admin
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "MyAppLicense.txt"
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesCheck
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ComponentsLeave
!insertmacro MUI_PAGE_COMPONENTS
## This is the title on the MyApp Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_A)"
!define MUI_PAGE_HEADER_SUBTEXT "Please choose the folder in which to install MyApp."
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesA
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
## This is the title on the FlexLM Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_B)"
!define MUI_PAGE_HEADER_SUBTEXT "Please choose the folder in which to install FlexLM."
!define MUI_PAGE_CUSTOMFUNCTION_PRE SelectFilesB
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE DeleteSectionsINI
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_UNPAGE_FINISH
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
LangString NoSectionsSelected ${LANG_ENGLSH} "You haven't selected any sections!"
LangString MUI_DIRECTORYPAGE_TEXT_TOP_A ${LANG_ENGLSH} "Setup will install \
${PRODUCT_NAME} in the following folder..."
LangString MUI_DIRECTORYPAGE_TEXT_TOP_B ${LANG_ENGLSH} "Setup will install \
${FLEX_LM} in the following folder..."
;--------------------------------
; Settings
!define PROG1_InstDir "$PROGRAMFILES64\${PRODUCT_NAME}\"
!define PROG1_StartIndex ${SEC1}
!define PROG1_EndIndex ${SEC1}
!define PROG2_InstDir "C:\${FLEX_DIR}"
!define PROG2_StartIndex ${SEC3}
!define PROG2_EndIndex ${SEC3}
;--------------------------------
; Function
; StrContains
; This function does a case sensitive searches for an occurrence of a substring in a string.
; It returns the substring if it is found.
; Otherwise it returns null("").
; Written by kenglish_hi
; Adapted from StrReplace written by dandaman32
Var STR_HAYSTACK
Var STR_NEEDLE
Var STR_CONTAINS_VAR_1
Var STR_CONTAINS_VAR_2
Var STR_CONTAINS_VAR_3
Var STR_CONTAINS_VAR_4
Var STR_RETURN_VAR
Function StrContains
Exch $STR_NEEDLE
Exch 1
Exch $STR_HAYSTACK
; Uncomment to debug
;MessageBox MB_OK 'STR_NEEDLE = $STR_NEEDLE STR_HAYSTACK = $STR_HAYSTACK '
StrCpy $STR_RETURN_VAR ""
StrCpy $STR_CONTAINS_VAR_1 -1
StrLen $STR_CONTAINS_VAR_2 $STR_NEEDLE
StrLen $STR_CONTAINS_VAR_4 $STR_HAYSTACK
loop:
IntOp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_1 + 1
StrCpy $STR_CONTAINS_VAR_3 $STR_HAYSTACK $STR_CONTAINS_VAR_2 $STR_CONTAINS_VAR_1
StrCmp $STR_CONTAINS_VAR_3 $STR_NEEDLE found
StrCmp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_4 done
Goto loop
found:
StrCpy $STR_RETURN_VAR $STR_NEEDLE
Goto done
done:
Pop $STR_NEEDLE ;Prevent "invalid opcode" errors and keep the
Exch $STR_RETURN_VAR
FunctionEnd
!macro _StrContainsConstructor OUT NEEDLE HAYSTACK
Push `${HAYSTACK}`
Push `${NEEDLE}`
Call StrContains
Pop `${OUT}`
!macroend
!define StrContains '!insertmacro "_StrContainsConstructor"'
;--------------------------------
; Start sections
Section "MyApp" SEC1
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} $0 "MyApp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is $0"
StrCmp $0 "" notfoundMyApp
StrCpy $MyAppInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 MyAppInstallDir is $MyAppInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
doneMyApp:
${StrContains} $0 "Flex" "$INSTDIR"
StrCmp $0 "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
##All the files in Group 1 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
File MyApp.exe
File ReleaseNotes.txt
File MyCompany_LandingPage_114.bmp
File MyAppLicense.txt
# create a shortcut named "new shortcut" in the start menu programs directory
CreateShortcut "$SMPROGRAMS\${PRODUCT_NAME}.lnk" "$InstDir\${PRODUCT_NAME}.exe"
# Add application to registry
ClearErrors
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Version' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'AppID' "{A0E84732-E2B2-46E5-8CA2-462B8DF92DCD}"
# Add program to Add/Remove programs
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayIcon" "$PROGRAMFILES64\${PRODUCT_NAME}\${PRODUCT_NAME}.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"AppID" "{A0E84732-E2B2-46E5-8CA2-462B8DF92DCD}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayName" "${PRODUCT_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"Publisher" "${COMPANY_NAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \
"UninstallString" "$\"$INSTDIR\uninstaller.exe$\""
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
doneFlex:
# messagebox mb_ok sec1
SectionEnd
Section "FlexLM" SEC3
;MessageBox MB_OK "SEC3 #1 INSTDIR is $INSTDIR"
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} $0 "Pro" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is $0"
StrCmp $0 "" notfoundMyApp
StrCpy $MyAppInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 MyAppInstallDir is $MyAppInstallDir"
Goto doneMyApp
notfoundMyApp:
;MessageBox MB_OK 'Did not find MyApp string'
${StrContains} $0 "Flex" "$INSTDIR"
StrCmp $0 "" notfoundFlex
StrCpy $FlexLmInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #4 FlexLmInstallDir is $FlexLmInstallDir"
Goto doneFlex
notfoundFlex:
;MessageBox MB_OK 'Did not find Flex string'
doneFlex:
##All the files in Group 2 will be installed to the same location, $INSTDIR
SetOutPath "$INSTDIR"
File installs.exe
File lmdown.exe
File lmflex.exe
# define uninstaller name
WriteUninstaller $INSTDIR\uninstaller.exe
doneQI:
# messagebox mb_ok sec3
SectionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecMyApp ${LANG_ENGLISH} "MyAppTM software is an easy-to-use suite of tools."
LangString DESC_SecFlexLM ${LANG_ENGLISH} "FlexSQI contains all the files necessary to implement the FlexLM license server."
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC1} $(DESC_SecMyApp)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC3} $(DESC_SecFlexLM)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
Section "Uninstall"
# Always delete uninstaller first
Delete $INSTDIR\uninstaller.exe
# now delete installed files and registry keys for MyApp
Delete $INSTDIR\config.dat
Delete $INSTDIR\MyApp.exe
Delete $INSTDIR\ReleaseNotes.txt
Delete $INSTDIR\MyCompany_LandingPage_114.bmp
Delete $INSTDIR\MyAppLicense.txt
Delete "$SMPROGRAMS\MyApp.lnk"
DeleteRegKey HKCU "SOFTWARE\${PRODUCT_NAME}"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
DeleteRegKey /ifempty HKCU "Software\Modern UI Test"
# now delete installed files and registry keys for FlexLM
Delete $INSTDIR\installs.exe
Delete $INSTDIR\lmborrow.exe
Delete $INSTDIR\lmflex.exe
# Delete the MyApp and FlexLM directories
RMDir $INSTDIR
SectionEnd
;--------------------------------
; Please don`t modify below here unless you`re a NSIS 'wiz-kid'
## Create $PLUGINSDIR
Function .onInit
InitPluginsDir
SetOutPath $TEMP
File /oname=spltmp.bmp "MyCompany_LandingPage_114.bmp"
splash::show 2000 $TEMP\spltmp
Pop $0 ; $0 has '1' if the user closed the splash screen early,
; '0' if everything closed normally, and '-1' if some error occurred.
Delete $TEMP\spltmp.bmp
FunctionEnd
## If user goes back to this page from 1st Directory page
## we need to put the sections back to how they were before
Var IfBack
Function SelectFilesCheck
StrCmp $IfBack 1 0 NoCheck
;Call ResetFiles
NoCheck:
FunctionEnd
## Also if no sections are selected, warn the user!
Function ComponentsLeave
Push $R0
Push $R1
Call IsPROG1Selected
Pop $R0
Call IsPROG2Selected
Pop $R1
StrCmp $R0 1 End
StrCmp $R1 1 End
Pop $R1
Pop $R0
MessageBox MB_OK|MB_ICONEXCLAMATION "$(NoSectionsSelected)"
Abort
End:
Pop $R1
Pop $R0
FunctionEnd
Function IsPROG1Selected
Push $R0
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
SectionGetFlags 0 $R0 # Get section flags
IntOp $R0 $R0 & ${SF_SELECTED}
StrCmp $R0 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R0 1
Exch $R0
FunctionEnd
Function IsPROG2Selected
Push $R1
StrCpy $R1 ${PROG2_StartIndex} # Group 2 start
IntOp $R1 $R1 + 1
SectionGetFlags 1 $R1 # Get section flags
IntOp $R1 $R1 & ${SF_SELECTED}
StrCmp $R1 ${SF_SELECTED} 0 +3 # If section is selected, done
StrCpy $R1 1
Exch $R1
FunctionEnd
## Here we are selecting first sections to install
## by unselecting all the others!
Function SelectFilesA
${If} ${SectionIsSelected} ${SEC1}
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${Else}
Abort
${EndIf}
# If user clicks Back now, we will know to reselect Group 2`s sections for
# Components page
StrCpy $IfBack 1
# We need to save the state of the Group 2 Sections
# for the next InstFiles page
Push $R0
Push $R1
StrCpy $R0 ${PROG2_StartIndex} # Group 2 start
; Loop:
; IntOp $R0 $R0 + 1
; SectionGetFlags $R0 $R1 # Get section flags
; WriteINIStr "$PLUGINSDIR\sections.ini" Sections $R0 $R1 # Save state
; !insertmacro UnselectSection $R0 # Then unselect it
; StrCmp $R0 ${PROG2_EndIndex} 0 Loop
# Don`t install prog 1?
Call IsPROG1Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG1_InstDir define
StrCpy $INSTDIR "${PROG1_InstDir}"
Pop $R1
Pop $R0
FunctionEnd
## Here we need to unselect all Group 1 sections
## and then re-select those in Group 2 (that the user had selected on
## Components page)
Function SelectFilesB
${If} ${SectionIsSelected} ${SEC3}
;MessageBox MB_OK "SEC1 #3 INSTDIR is $INSTDIR"
${Else}
Abort
${EndIf}
Push $R0
;Push $R1
StrCpy $R0 ${PROG1_StartIndex} # Group 1 start
; Loop:
; IntOp $R0 $R0 + 1
; !insertmacro UnselectSection $R0 # Unselect it
; StrCmp $R0 ${PROG1_EndIndex} 0 Loop
; Call ResetFiles
# Don't install prog 2?
Call IsPROG2Selected
Pop $R0
StrCmp $R0 1 +4
Pop $R1
Pop $R0
Abort
# Set current $INSTDIR to PROG2_InstDir define
StrCpy $INSTDIR "${PROG2_InstDir}"
;Pop $R1
Pop $R0
FunctionEnd
## Here we are deleting the temp INI file at the end of installation
Function DeleteSectionsINI
FlushINI "$PLUGINSDIR\Sections.ini"
Delete "$PLUGINSDIR\Sections.ini"
# FlexLM libs
;MessageBox MB_OK "DeleteSectionsINI #1 MyAppInstallDir is $MyAppInstallDir"
Delete $MyAppInstallDir\installs.exe
Delete $MyAppInstallDir\lmborrow.exe
# QI Pro files
;MessageBox MB_OK "DeleteSectionsINI #1 FlexLmInstallDir is $FlexLmInstallDir"
Delete $FlexLmInstallDir\config.dat
Delete $FlexLmInstallDir\MyApp.exe
Delete $FlexLmInstallDir\ReleaseNotes.txt
Delete $FlexLmInstallDir\MyApp_LandingPage_114.bmp
Delete $FlexLmInstallDir\MyAppLicense.txt
FunctionEnd

How can I define more than one directory variable in NSIS script?

Please note that I have created a single variable without a problem as described here, but when I try to create a second variable, I get this error:
!define: "MUI_DIRECTORYPAGE_VARIABLE" already defined!
Here is what I have setup which works for a single variable:
Var HW_DATA_DIR
!define MUI_DIRECTORYPAGE_VARIABLE $HW_DATA_DIR
// ...
!define HW_DATA "HW-Data"
## This is the title on the MyApp Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_HW)"
;Directory for App files and where config.dat will point to
!define MUI_PAGE_CUSTOMFUNCTION_PRE wel_pre
!define MUI_PAGE_CUSTOMFUNCTION_SHOW dir_pre
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
LangString MUI_DIRECTORYPAGE_TEXT_TOP_HW ${LANG_ENGLSH} "Setup will install \
${HW_DATA} in the following folder..."
## Sections Group 0
Function wel_pre
StrCpy $APP_DIR "C:\${HW_DATA}"
strcmp '$0' '1' noabort
messagebox mb_yesno|mb_defbutton2|mb_iconquestion "Leave MyApp directories at default values of C:\${HW_DATA} and C:\${HW_WORKSPACE}?" idno noabort
strcpy $0 2
abort
noabort:
strcpy $0 1
Functionend
Function dir_pre
GetDlgItem $1 $HWNDPARENT 1037
CreateFont $2 "$(^Font)" "8" "700"
SendMessage $1 ${WM_SETFONT} $2 0
SetCtlColors $1 '0x000000' '0xFFFFFF'
GetDlgItem $1 $HWNDPARENT 1038
CreateFont $2 "$(^Font)" "8" ""
SendMessage $1 ${WM_SETFONT} $2 0
SetCtlColors $1 '0x000000' '0xFFFFFF'
Functionend
!define PROG0_InstDir "C:\${HW_DATA}"
!define PROG0_StartIndex ${PROG0}
!define PROG0_EndIndex ${SEC0}
SectionGroup /e "MyApp" PROG0
Section "Main" SEC0
CreateDirectory "$HW_DATA"
CreateDirectory "$HW_DATA\Plugins"
CreateDirectory "$HW_DATA\Plugins\ComputePlugin"
CreateDirectory "$HW_DATA\Plugins\ExtensionPlugin"
File /oname=$HW_DATA\Plugins\ComputePlugin\computeplugin.xplot.dll computeplugin.xplot.dll
File /oname=$HW_DATA\Plugins\ExtensionPlugin\hwProxyInterface.MyProApp.dll hwProxyInterface.MyProApp.dll
SectionEnd
This is how I am attempting to add a second variable:
Var HW_DATA_DIR
!define MUI_DIRECTORYPAGE_VARIABLE $HW_DATA_DIR
Var HW_WORKSPACE_DIR
!define MUI_DIRECTORYPAGE_VARIABLE $HW_WORKSPACE_DIR
// ...
!define HW_DATA "HW-Data"
!define HW_WORKSPACE "HW-Workspaces"
## This is the title on the MyApp Directory page
!define MUI_DIRECTORYPAGE_TEXT_TOP "$(MUI_DIRECTORYPAGE_TEXT_TOP_HW)"
;Directory for MyApp files and where config.dat will point to
!define MUI_PAGE_CUSTOMFUNCTION_PRE wel_pre
!define MUI_PAGE_CUSTOMFUNCTION_SHOW dir_pre
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
// ...
LangString MUI_DIRECTORYPAGE_TEXT_TOP_HW ${LANG_ENGLSH} "Setup will install \
${HW_DATA} in the following folder..."
## Sections Group 0
Function wel_pre
StrCpy $HW_DATA_DIR "C:\${HW_DATA}"
StrCpy $$HW_WORKSPACE_DIR "C:\${HW_WORKSPACE}"
strcmp '$0' '1' noabort
messagebox mb_yesno|mb_defbutton2|mb_iconquestion "Leave Headwave directories at default values of C:\${HW_DATA} and C:\${HW_WORKSPACE}?" idno noabort
strcpy $0 2
abort
noabort:
strcpy $0 1
Functionend
Function dir_pre
GetDlgItem $1 $HWNDPARENT 1037
CreateFont $2 "$(^Font)" "8" "700"
SendMessage $1 ${WM_SETFONT} $2 0
SetCtlColors $1 '0x000000' '0xFFFFFF'
GetDlgItem $1 $HWNDPARENT 1038
CreateFont $2 "$(^Font)" "8" ""
SendMessage $1 ${WM_SETFONT} $2 0
SetCtlColors $1 '0x000000' '0xFFFFFF'
Functionend
!define PROG0_InstDir "C:\${HW_DATA}"
!define PROG0_StartIndex ${PROG0}
!define PROG0_EndIndex ${SEC0}
SectionGroup /e "MyApp" PROG0
Section "Main" SEC0
CreateDirectory "$HW_DATA"
CreateDirectory "$HW_DATA\Plugins"
CreateDirectory "$HW_DATA\Plugins\ComputePlugin"
CreateDirectory "$HW_DATA\Plugins\ExtensionPlugin"
File /oname=$HW_DATA\Plugins\ComputePlugin\computeplugin.xplot.dll computeplugin.xplot.dll
File /oname=$HW_DATA\Plugins\ExtensionPlugin\hwProxyInterface.MyProApp.dll hwProxyInterface.MyProApp.dll
CreateDirectory "$HW_WORKSPACE_DIR"
SectionEnd
See also
https://nsis.sourceforge.io/Demonstrating_Page%27s_Custom_Functions_Pre_Show_Leave
https://nsis.sourceforge.io/Two_installations_in_one_installer
Does anyone have any suggestions? In my case I happen to need to create a data directory and a workspace directory for a particular application, with default values but also allowing the user to change the DATA directory and the WORKSPACE directory to custom directories in case, say, they need to be on a network drive instead of being on the default C:\ drive. TIA.
The directory variable define is a per-page setting and must be set just before the page macro:
Var foo
Var bar
!define MUI_DIRECTORYPAGE_VARIABLE $foo
insertmacro MUI_PAGE_DIRECTORY
!define MUI_DIRECTORYPAGE_VARIABLE $bar
!insertmacro MUI_PAGE_DIRECTORY

Select section by variable

!include "MUI2.nsh"
!include "FileFunc.nsh"
!include "LogicLib.nsh"
;---------------------------------------------------------------------------
;---------------------------------------------------------------------------
ShowInstDetails show
RequestExecutionLevel admin
;---------------------------------------------------------------------------
Name "Test"
Outfile "Test.exe"
;---------------------------------------------------------------------------
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
;---------------------------------------------------------------------------
!insertmacro MUI_LANGUAGE "English"
;---------------------------------------------------------------------------
LangString DESC_sec1 ${LANG_ENGLISH} "sec1 files"
Section /o "sec1" sec1
SectionEnd
LangString DESC_sec2 ${LANG_ENGLISH} "sec2 files"
Section /o "sec2" sec2
SectionEnd
LangString DESC_sec3 ${LANG_ENGLISH} "sec3 files"
Section /o "sec3" sec3
SectionEnd
Var test
Function .OnInit
StrCpy $test "sec2"
!insertmacro SelectSection $test
FunctionEnd
How to select section at runtime by section name?
In eample always selected 1st section (think its a bug)
But if i rewrite like this
!insertmacro SelectSection ${sec2}
All works fine...
Is there way to select section by name from variable?
some long text some long text some long text some long text
some long text some long text some long text some long text
Sections are accessed by their ID which is a number set at compile time:
Section "Foo"
SectionEnd
Section "Bar" SID_BAR
SectionEnd
Section "Baz"
SectionEnd
Page Components
Page InstFiles
!include Sections.nsh
Function .onInit
!insertmacro UnselectSection ${SID_BAR}
FunctionEnd
If you want to use the display name then you have to manually enumerate the sections:
Section "Foo"
SectionEnd
Section "Bar"
SectionEnd
Section "Baz"
SectionEnd
Page Components
Page InstFiles
!macro GetSectionIdFromName name outvar
Push "${name}"
Call GetSectionIdFromName
Pop ${outvar} ; ID of section, "" if not found
!macroend
Function GetSectionIdFromName
Exch $1 ; Name
Push $2
Push $3
StrCpy $2 -1
ClearErrors
loop:
IntOp $2 $2 + 1
SectionGetText $2 $3
IfErrors fail
StrCmp $3 $1 "" loop
StrCpy $1 $2
Goto done
fail:
StrCpy $1 ""
done:
Pop $3
Pop $2
Exch $1
FunctionEnd
!include Sections.nsh
!include LogicLib.nsh
Function .onInit
StrCpy $1 "Bar" ; The name we are looking for
!insertmacro GetSectionIdFromName $1 $0
${If} $0 != ""
!insertmacro UnselectSection $0
${EndIf}
FunctionEnd

NSIS unattended options

I want to build a NSIS-script, which has three section
section Main
section Minor
section Shared
Shared is invisible and would be installed, if Main or Minor is checked. If I start the installer, every section (Main, Minor) is checked.
Now it should be able to define the section (in silent install). What have I to change, to only install Main or Minor or Both?
Name "Test"
Outfile "Test.exe"
;RequestExecutionLevel ?
!include "Sections.nsh"
!include "LogicLib.nsh"
!include "FileFunc.nsh" ;For GetOptions
Page Components "" "" EnforceSectionDependencies
Page InstFiles
Section /o "Main" SID_MAIN
DetailPrint Main
SectionEnd
Section /o "Minor" SID_MINOR
DetailPrint Minor
SectionEnd
Section "" SID_SHARED
DetailPrint Shared
SectionEnd
!macro CheckSectionSwitch sw sid
${GetOptions} $0 '${sw}' $9
${IfNot} ${Errors}
StrCpy $1 1
!insertmacro SelectSection ${sid}
${EndIf}
!macroend
Function .onInit
${GetParameters} $0
StrCpy $1 0 ;Any section swithes?
ClearErrors
!insertmacro CheckSectionSwitch '/Main' ${SID_MAIN}
!insertmacro CheckSectionSwitch '/Minor' ${SID_MINOR}
${If} $1 = 0
;Set defaults
!insertmacro SelectSection ${SID_MAIN}
!insertmacro SelectSection ${SID_MINOR}
${EndIf}
call EnforceSectionDependencies
FunctionEnd
Function EnforceSectionDependencies
!insertmacro UnselectSection ${SID_SHARED}
${If} ${SectionIsSelected} ${SID_MAIN}
${OrIf} ${SectionIsSelected} ${SID_MINOR}
!insertmacro SelectSection ${SID_SHARED}
${EndIf}
FunctionEnd
You should look at the Section management part of the documentation, notably the SectionSetFlags to change the sections selections.
Also, maybe that the How to control Section selections, while using SubSections & InstTypes example will be useful.

Resources