Files for both applications are installed in both destination folders - nsis

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

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

creating signed uninstaller using embedded signing rather than separate uninstaller script

In order to create a signed uninstaller, I cannot create this using the usual NSIS WriteUninstaller command, because the usually script will create the uninstaller and embed it inside the installer, so there is no chance to sign the uninstaller. For details please see https://nsis-dev.github.io/NSIS-Forums/html/t-245688.html.
This means that I would have to create a separate uninstaller script, sign that, and then embed it in the installer using a normal File command. This works in deleting the files; however, since the uninstaller executable that is running, it cannot delete itself, and leaves the uninstaller.exe and the MyApp directory behind. Here is my uninstaller script:
!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 "FlexSMyApp"
!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$\""
!ifndef INNER
SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
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
doneMyApp:
# 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} "FlexSMyApp 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
; ...
Function .onInit
!ifdef INNER
; If INNER is defined, then we aren't supposed to do anything except write out
; the uninstaller. This is better than processing a command line option as it means
; this entire code path is not present in the final (real) installer.
SetSilent silent
WriteUninstaller "$%TEMP%\uninstaller.exe"
Quit ; just bail out quickly when running the "inner" installer
!endif
## Create $PLUGINSDIR
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
; ...
Section "Files" ; or whatever
; ...
; where you would normally put WriteUninstaller ${INSTDIR}\uninstaller.exe put instead:
;!ifndef INNER
; SetOutPath $INSTDIR
; this packages the signed uninstaller
; File $%TEMP%\uninstaller.exe
;!endif
; ...
SectionEnd
!ifdef INNER
Section "Uninstall"
; your normal uninstaller section or sections (they're not needed in the "outer"
; installer and will just cause warnings because there is no WriteUninstaller command)
# 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
!endif
;--------------------------------
; Please don`t modify below here unless you`re a NSIS 'wiz-kid'
## 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
Does anyone have any suggestions?
UPDATE:
Thanks #Anders, I had somehow deleted the SelectFilesCheck by mistake. Now, if I use this code:
!ifdef INNER
!echo "Inner invocation" ; just to see what's going on
OutFile "$%TEMP%\tempinstaller.exe" ; not really important where this is
SetCompress off ; for speed
!else
!echo "Outer invocation"
; Call makensis again against current file, defining INNER. This writes an installer for us which, when
; it is invoked, will just write the uninstaller to some location, and then exit.
!makensis '/DINNER "${__FILE__}"' = 0
; So now run that installer we just created as %TEMP%\tempinstaller.exe. Since it
; calls quit the return value isn't zero.
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2
; That will have written an uninstaller binary for us. Now we sign it with your
; favorite code signing tool.
nsExec::ExecToStack 'cmd /c "SMyApp-SignTool.bat $%TEMP%\uninstaller.exe"'
; Good. Now we can carry on writing the real installer.
OutFile ${SETUP_NAME}
SetCompressor /SOLID lzma
!endif
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 $QiProInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 QiProInstallDir is $QiProInstallDir"
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"
# specify files to go in output path
File config.dat
File MyApp.exe
File ReleaseNotes.txt
File MyApp_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}" 'Contact' "https://www.mycompany.com/contact"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'DisplayName' "${PRODUCT_NAME}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'DisplayVersion' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'AppID' "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'HelpLink' "http://www.mycompany.com/myapp/HelpDocs/index.htm"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'URLInfoAbout' "https://www.mycompany.com/myapp"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'InstallLocation' "$MyAppInstallDir"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Publisher' "${COMPANY_NAME}"
# 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" "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
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$\""
# Delete splash page image
Delete $INSTDIR\MyApp_LandingPage_114.bmp
!ifndef INNER
;SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
Then I get these errors:
!makensis: returned 0
!system: "set __COMPAT_LAYER=RunAsInvoker&"C:\Users\moorer\AppData\Local\Temp\tempinstaller.exe""
!system: returned 2
Error: Can't add entry, no section or function is open!
Error in script "C:\Project\MsiPackaging\Installer\MyAppInstaller.nsi" on line 169 -- aborting creation process
But if I try to put it in a section like this:
;--------------------------------
; Start sections
Section "MyApp" SEC1
!ifdef INNER
!echo "Inner invocation" ; just to see what's going on
OutFile "$%TEMP%\tempinstaller.exe" ; not really important where this is
SetCompress off ; for speed
!else
!echo "Outer invocation"
; Call makensis again against current file, defining INNER. This writes an installer for us which, when
; it is invoked, will just write the uninstaller to some location, and then exit.
!makensis '/DINNER "${__FILE__}"' = 0
; So now run that installer we just created as %TEMP%\tempinstaller.exe. Since it
; calls quit the return value isn't zero.
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2
; That will have written an uninstaller binary for us. Now we sign it with your
; favorite code signing tool.
nsExec::ExecToStack 'cmd /c "SQI-SignTool.bat $%TEMP%\uninstaller.exe"'
; Good. Now we can carry on writing the real installer.
OutFile ${SETUP_NAME}
SetCompressor /SOLID lzma
!endif
;MessageBox MB_OK "SEC1 #1 INSTDIR is $INSTDIR"
${StrContains} $0 "MyApp" "$INSTDIR"
;MessageBox MB_OK "SEC1 #2 0 is $0"
StrCmp $0 "" notfoundQI
StrCpy $QiProInstallDir "$INSTDIR"
;MessageBox MB_OK "SEC1 #3 QiProInstallDir is $QiProInstallDir"
Goto doneQI
notfoundQI:
;MessageBox MB_OK 'Did not find QI string'
doneQI:
${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"
# specify files to go in output path
File config.dat
File MyApp.exe
File ReleaseNotes.txt
File MyApp_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}" 'Contact' "https://www.mycompany.com/contact"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Company Name' "${COMPANY_NAME}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'DisplayName' "${PRODUCT_NAME}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'DisplayVersion' "${PRODUCT_VERSION}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'AppID' "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'HelpLink' "http://www.mycompany.com/myapp/HelpDocs/index.htm"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'URLInfoAbout' "https://www.mycompany.com/myapp"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'InstallLocation' "$MyAppInstallDir"
WriteRegStr HKCU "SOFTWARE\${PRODUCT_NAME}" 'Publisher' "${COMPANY_NAME}"
# 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" "{K2C40732-E2B2-46E5-8CA2-464L9DF92DCD}"
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$\""
# Delete splash page image
Delete $INSTDIR\MyApp_LandingPage_114.bmp
!ifndef INNER
;SetOutPath $INSTDIR
; this packages the signed uninstaller
File $%TEMP%\uninstaller.exe
!endif
doneFlex:
# messagebox mb_ok sec1
SectionEnd
Then I get this error:
Error: command OutFile not valid in Section
Error in script "MyAppInstaller.nsi" on line 157 -- aborting creation process
!makensis: returned 1, aborting
Error in script "C:\Projects\MsiPackaging\Installer\MyAppInstaller.nsi" on line 165 -- aborting creation process
Do you have any suggestions? TIA.
The signing code from the Wiki does not use StrCpy at all, it must be from your StrCpy $IfBack 1 line. Maybe you forgot Var IfBack above the function.
The code from the wiki should work but here is an alternative version:
#TODO: RequestExecutionLevel
#TODO: InstallDir
!include MUI2.nsh
!macro WriteSignedUninstaller Destination
!makensis '"/DGENRATINGUNINST=$%TEMP%\Uninst.exe" "${__FILE__}" "/XOutfile `$%TEMP%\tempinstaller.exe`"' = 0 ; Create fake installer
!system 'set __COMPAT_LAYER=RunAsInvoker&"$%TEMP%\tempinstaller.exe"' = 2 ; Run fake installer to generate the uninstaller
!system 'SIGNCODE <signing options> "$%TEMP%\Uninst.exe"' = 0 ; Change this line. As a demonstration, use !system 'echo Dummy >> "$%TEMP%\Uninst.exe"'
File "/oname=${Destination}" "$%TEMP%\Uninst.exe"
!macroend
!macro DeclareLanguages
!insertmacro MUI_LANGUAGE English
!macroend
!ifndef GENRATINGUNINST
# Installer:
############
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro DeclareLanguages
Section
SetOutPath $InstDir
!insertmacro WriteSignedUninstaller "$InstDir\Uninst.exe"
; File MyApp.exe
SectionEnd
!else
# Uninstaller:
##############
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro DeclareLanguages
!verbose push 2
SilentInstall Silent
Section
WriteUninstaller "${GENRATINGUNINST}"
Quit
SectionEnd
!verbose pop
Section -Uninstall
; Delete "$InstDir\MyApp.exe"
Delete "$InstDir\Uninst.exe"
RMDir "$InstDir"
SectionEnd
!endif # EOF
Thanks for the feedback, #Anders. In the end, I found this SelfDel plugin, which allows me to go with the separate installer and uninstaller scripts while still deleting the uninstaller:
https://nsis.sourceforge.io/SelfDel_plug-in
Name `SelfDel plug-in example`
OutFile Example.exe
XPStyle on
; Administrator privileges required.
RequestExecutionLevel admin
Page InstFiles
Section
;SelfDel::Del /RMDIR
;SelfDel::Del /REBOOT
;SelfDel::Del /SHUTDOWN
SelfDel::Del
SetAutoClose true
SectionEnd
UPDATE:
When I use use SelfDel it deletes the uninstaller.exe but does not delete the original app directory even if I use SelfDel::Del /RMDIR so I ended up going with Anders answer above.

Installing two apps but second app gets first app name appended on browse for directory [duplicate]

This question already has an answer here:
NSIS Directory Page after Browse for install dir "${PRODUCT_NAME}" is added after selection
(1 answer)
Closed 2 years ago.
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 get to the second license manager app, where I click to browse for a custom folder, and create a new custom folder, when I press OK on the browse it then returns with this custom folder but then appends the application name after that. So for example when I return from the browse for custom directory dialog, it would not return with, say:
C:\Tools\FlexLM
It would instead return with the following even though I didn't add the "MyApp" portion:
C:\Tools\FlexLM\MyApp
Please find the full source 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
;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 QI Pro 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
Does anyone have any suggestions? TIA.
UPDATE:
Please note that I think the issue might be in this file:
~\NSIS\Contrib\Modern UI 2\Pages\Directory.nsh
Where I see this code:
!macro MUI_FUNCTION_DIRECTORYPAGE PRE SHOW LEAVE
Function "${PRE}"
!insertmacro MUI_PAGE_FUNCTION_CUSTOM PRE
!insertmacro MUI_HEADER_TEXT_PAGE $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_DIRECTORY_TITLE) $(MUI_${MUI_PAGE_UNINSTALLER_PREFIX}TEXT_DIRECTORY_SUBTITLE)
FunctionEnd
Function "${SHOW}"
;Get control handles
FindWindow $mui.DirectoryPage "#32770" "" $HWNDPARENT
GetDlgItem $mui.DirectoryPage.Text $mui.DirectoryPage 1006
GetDlgItem $mui.DirectoryPage.DirectoryBox $mui.DirectoryPage 1020
GetDlgItem $mui.DirectoryPage.Directory $mui.DirectoryPage 1019
GetDlgItem $mui.DirectoryPage.BrowseButton $mui.DirectoryPage 1001
GetDlgItem $mui.DirectoryPage.SpaceRequired $mui.DirectoryPage 1023
GetDlgItem $mui.DirectoryPage.SpaceAvailable $mui.DirectoryPage 1024
!ifdef MUI_DIRECTORYPAGE_BGCOLOR
!insertmacro MUI_DEFAULT MUI_DIRECTORYPAGE_TEXTCOLOR ""
SetCtlColors $mui.DirectoryPage.Directory "${MUI_DIRECTORYPAGE_TEXTCOLOR}" "${MUI_DIRECTORYPAGE_BGCOLOR}"
!endif
!insertmacro MUI_PAGE_FUNCTION_CUSTOM SHOW
FunctionEnd
Function "${LEAVE}"
!insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE
FunctionEnd
!macroend
But when I look at the above it still is not clear to me how the MyApp name could be added to the end of the directory string.
Found the solution here:
NSIS Directory Page after Browse for install dir "${PRODUCT_NAME}" is added after selection
So in my case I needed to add a :
InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}\"

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

NSIS uninstaller does not app files if not installed in program files

Hi the below program fails to delete app files if not in program files, I mean if user installs in some other location. After uninstall the registry values are deleted but not the files in the app folder. Any help would be much appreciated.
RequestExecutionLevel admin
!define MULTIUSER_EXECUTIONLEVEL Admin
!define MULTIUSER_INSTALLMODE_COMMANDLINE
!include MultiUser.nsh
!include FileFunc.nsh
!ifndef PRODUCT_VERSION
!define PRODUCT_VERSION "0.0.0.0"
!endif
!ifndef BUILD_TARGET
!define BUILD_TARGET "Release"
!endif
!define OUT_FILE "PlaytestMateSetup.exe"
!if ${BUILD_TARGET} == "Debug"
!define PRODUCT_NAME "Playtest Mate 2.0 Debug"
!define PRODUCT_WEB_SITE "https://127.0.0.1:3000/"
!define MUI_ICON "..\PlaytestMate\Resources\Icons\IconRed.ico"
!endif
!if ${BUILD_TARGET} == "ReleaseTest"
!define PRODUCT_NAME "Playtest Mate 2.0 Staging"
!define PRODUCT_WEB_SITE "https://ca1-ptmstg01.ad.ea.com/"
!define MUI_ICON "..\PlaytestMate\Resources\Icons\IconBlue.ico"
!endif
!if ${BUILD_TARGET} == "Release"
!define PRODUCT_NAME "Playtest Mate 2.0"
!define PRODUCT_WEB_SITE "https://ca1-ptmprd01.ad.ea.com/"
!define MUI_ICON "..\PlaytestMate\Resources\Icons\IconBlack.ico"
!endif
!define PRODUCT_PUBLISHER "EA Digital Illusions CE AB"
!define RUN_REG_KEY "SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
!define PRODUCT_UNINST_KEY "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_INST_KEY "SOFTWARE\${PRODUCT_NAME}"
;BUILD_DIR and CERT_PATH have to be in relation to the .nsi file
!define BUILD_DIR "..\..\Bin\${BUILD_TARGET}"
; PTM-1089
; CERT settings
!define CERT_FILE "ea_root.cer"
!define CERT_PATH "3rdparty\${CERT_FILE}"
; end PTM-1089
!include "MUI2.nsh"
; 64 bit support
!include x64.nsh
; MUI Settings
!define MUI_ABORTWARNING
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
; Welcome page
!insertmacro MUI_PAGE_WELCOME
; Directory page
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE "DirectoryLeave"
; Instfiles page
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
; Finish page
Function DirectoryLeave
# Show message box then take the user back to the Directory page.
MessageBox MB_OK|MB_ICONEXCLAMATION "PTM consumes huge space because of the build sizes. We recommend to use a drive have maximum space other than C Drive"
FunctionEnd
Function createDesktopShortcut
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\PlaytestMate.exe" "-shortcut"
FunctionEnd
!define MUI_FINISHPAGE_SHOWREADME ""
!define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Desktop Shortcut"
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION createDesktopShortcut
; Option to run PTM after install
!define MUI_FINISHPAGE_RUN "$INSTDIR\PlaytestMate.exe"
!insertmacro MUI_PAGE_FINISH
; Uninstaller pages
!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ModifyUnWelcome
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.LeaveUnWelcome
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_INSTFILES
; Language files
!insertmacro MUI_LANGUAGE "English"
; MUI end ------
Name "${PRODUCT_NAME}"
OutFile "${OUT_FILE}"
VIProductVersion ${PRODUCT_VERSION}
VIAddVersionKey ProductName "${PRODUCT_NAME} Installer"
VIAddVersionKey CompanyName "${PRODUCT_PUBLISHER}"
VIAddVersionKey FileDescription "Installs ${PRODUCT_NAME}"
VIAddVersionKey FileVersion ${PRODUCT_VERSION}
VIAddVersionKey ProductVersion ${PRODUCT_VERSION}
VIAddVersionKey OriginalFilename "${OUT_FILE}"
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
ShowInstDetails show
ShowUnInstDetails show
Function .onInit
!insertmacro MULTIUSER_INIT
ReadRegStr $R0 HKLM "${PRODUCT_UNINST_KEY}" "QuietUninstallString"
StrCmp $R0 "" done uninst
;Run the uninstaller
uninst:
ClearErrors
Exec '$R0' ; instead of the ExecWait line
done:
${If} ${RunningX64}
; disable registry redirection (enable access to 64-bit portion of registry)
SetRegView 64
; change install dir
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCT_NAME}"
${Else}
MessageBox MB_OK "${PRODUCT_NAME} needs 64-bit Windows"
Abort
${EndIf}
UserInfo::GetAccountType
pop $0
${If} $0 != "admin" ;Require admin rights on NT4+
MessageBox mb_iconstop "${PRODUCT_NAME} requires Administrator privileges"
SetErrorLevel 740 ;ERROR_ELEVATION_REQUIRED
Quit
${EndIf}
FunctionEnd
Function .onInstSuccess
RMDir /r "$LOCALAPPDATA\${PRODUCT_NAME}"
IfSilent startlauncher finished
startlauncher:
Exec '"$INSTDIR\PlaytestMate.exe"'
finished:
FunctionEnd
Section "MainSection" SEC01
Call EnsureNotRunning
Delete "$INSTDIR\*.*"
SetOutPath "$INSTDIR"
SetOverwrite try
File "${BUILD_DIR}\Builds.Core.dll"
File "${BUILD_DIR}\Builds.Core.Service.Platform.Origin.dll"
File "${BUILD_DIR}\EngineIoClientDotNet.dll"
File "${BUILD_DIR}\INIFileParser.dll"
File "${BUILD_DIR}\log4net.config"
File "${BUILD_DIR}\log4net.dll"
File "${BUILD_DIR}\log4net.xml"
File "${BUILD_DIR}\Newtonsoft.Json.dll"
File "${BUILD_DIR}\Newtonsoft.Json.xml"
File "${BUILD_DIR}\PlaytestMate.Core.dll"
File "${BUILD_DIR}\PlaytestMate.Core.dll.config"
File "${BUILD_DIR}\PlaytestMate.exe"
File "${BUILD_DIR}\PlaytestMate.exe.config"
File "${BUILD_DIR}\SocketIoClientDotNet.dll"
File "${BUILD_DIR}\WebSocket4Net.dll"
; PTM-1089
File "${CERT_PATH}"
; end PTM-1089
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\PlaytestMate.exe" "-shortcut"
CreateShortCut "$INSTDIR\Show log files.lnk" "$WINDIR\explorer.exe" "%USERPROFILE%\Documents\PlaytestMate" "$WINDIR\system32\imageres.dll" 172
; PTM-1089 you can remove this once we get a proper cert
Push "$INSTDIR\${CERT_FILE}"
Call AddCertificateToStore
Pop $0
${If} $0 != success
MessageBox MB_OK "EA Root Certificate import failed: $0"
${EndIf}
; end PTM-1089
SectionEnd
Section -AdditionalIcons
WriteIniStr "$INSTDIR\${PRODUCT_NAME}.URL" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Website.lnk" "$INSTDIR\${PRODUCT_NAME}.url"
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe"
SectionEnd
Section -Post
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\PlaytestMate.exe"
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
WriteRegStr HKLM "${PRODUCT_INST_KEY}" "InstallPath" "$INSTDIR\PlaytestMate.exe"
WriteRegStr HKLM "${PRODUCT_INST_KEY}" "Version" "${PRODUCT_VERSION}"
; estimated size
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
WriteRegDWORD HKLM "${PRODUCT_UNINST_KEY}" "EstimatedSize" "$0"
WriteRegDWORD HKLM "${PRODUCT_UNINST_KEY}" "VersionMajor" 2
WriteRegDWORD HKLM "${PRODUCT_UNINST_KEY}" "VersionMinor" 0
; Runs PTM on Windows start-up
WriteRegStr HKLM "${RUN_REG_KEY}" "PlaytestMate" "$INSTDIR\PlaytestMate.exe"
WriteUninstaller "$INSTDIR\uninstall.exe"
SectionEnd
!macro NOTRUNNINGMACRO un
Function ${un}EnsureNotRunning
${DisableX64FSRedirection}
SetOutPath "$INSTDIR"
File ".\killProcess.ps1"
nsExec::ExecToStack 'Powershell.exe -inputformat none -noprofile ".\killProcess.ps1"'
${EnableX64FSRedirection}
Sleep 500
performCheck:
System::Call 'kernel32::OpenMutex(i 0x100000, b 0, t "Global\{${BUILD_TARGET} }") i .R0'
IntCmp $R0 0 backupCheck isRunning isRunning
backupCheck:
System::Call 'kernel32::OpenMutex(i 0x100000, b 0, t "Global\{0}") i .R0'
IntCmp $R0 0 notRunning isRunning isRunning
isRunning:
System::Call 'kernel32::CloseHandle(i $R0)'
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "${PRODUCT_NAME} is running. Please close it first" /SD IDCANCEL IDOK performCheck
Abort
notRunning:
FunctionEnd
!macroend
!insertmacro NOTRUNNINGMACRO ""
!insertmacro NOTRUNNINGMACRO "un."
Function un.onUninstSuccess
HideWindow
IfSilent finished
MessageBox MB_ICONINFORMATION|MB_OK "${PRODUCT_NAME} was successfully removed from your computer."
finished:
FunctionEnd
Function un.onInit
!insertmacro MULTIUSER_UNINIT
${If} ${RunningX64}
; disable registry redirection (enable access to 64-bit portion of registry)
SetRegView 64
; change install dir
StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCT_NAME}"
${EndIf}
FunctionEnd
Var keepUserSettingsCheckbox
Var keepUserSettingsBool
Function un.ModifyUnWelcome
${NSD_CreateCheckbox} 120u -18u 50% 12u "Remove user settings"
Pop $keepUserSettingsCheckbox
SetCtlColors $keepUserSettingsCheckbox "" ${MUI_BGCOLOR}
${NSD_Check} $keepUserSettingsCheckbox ; Check it by default
FunctionEnd
Function un.LeaveUnWelcome
${NSD_GetState} $keepUserSettingsCheckbox $0
${If} $0 <> 0
StrCpy $keepUserSettingsBool "True"
${Else}
StrCpy $keepUserSettingsBool "False"
${EndIf}
FunctionEnd
Function un.GetMyDocs
ReadRegStr $0 HKCU \
"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" \
Personal
FunctionEnd
Section Uninstall
Call un.EnsureNotRunning
RMDir /r "$INSTDIR"
RMDir /r "$INSTDIR\crashDumps"
Delete "$SMPROGRAMS\${PRODUCT_NAME}\*.*"
RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}"
RMDir /r "$PROGRAMFILES64\${PRODUCT_NAME}"
StrCmp $keepUserSettingsBool "True" 0 +11
Delete "$LOCALAPPDATA\${PRODUCT_NAME}\*.*"
RMDir /r "$LOCALAPPDATA\${PRODUCT_NAME}"
Call un.GetMyDocs
RMDir /r "$0\${PRODUCT_NAME}"
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
DeleteRegKey HKLM "${PRODUCT_UNINST_KEY}"
DeleteRegKey HKLM "${PRODUCT_INST_KEY}"
DeleteRegValue HKLM "${RUN_REG_KEY}" "${PRODUCT_NAME}"
SetAutoClose true
SectionEnd
With just a quick glance at your document here. Maybe try using this were applicable.. Don't quote me on this; I'm tired and should get some sleep.. Lol. Good luck.
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "UninstallString" `"$INSTDIR\uninstall.exe"`
WriteRegStr HKLM "${PRODUCT_UNINST_KEY}" "QuietUninstallString" `"$INSTDIR\uninstall.exe" /S`

Resources