Having InstallDir within IF ELSE block - nsis

I try to have the following code from
; The default installation directory
InstallDir $PROGRAMFILES\${PRODUCT_NAME}
to
!include x64.nsh
${If} ${RunningX64}
; The default installation directory
InstallDir $PROGRAMFILES\${PRODUCT_NAME}
${Else}
; The default installation directory
InstallDir $PROGRAMFILES64\${PRODUCT_NAME}
${EndIf}
I get the following error :-
!insertmacro: _If
Error: Can't add entry, no section or function is open!
Error in macro _RunningX64 on macroline 2
Error in macro _If on macroline 9
Error in script "C:\Users\yccheok\Desktop\mysoftware.nsi" on line 17 -- aborting creation process
Is there way I can set the value for InstallDir, within if else block?

If you need a dynamic $InstDir you should not use InstallDir at all but set $InstDir in .onInit:
Installdir ""
!include LogicLib.nsh
!include x64.nsh
Function .onInit
${If} $InstDir == "" ; /D= was not used on the command line
${If} ${RunningX64}
StrCpy $InstDir "c:\foo"
${Else}
StrCpy $InstDir "c:\bar"
${EndIf}
${EndIf}
FunctionEnd
Your current if else block does not make any sense because you are selecting the 32 bit program files on x64 and the 64 bit program files on x86! It is OK to use $PROGRAMFILES64 on x86 so if you always want the "real" program files you can use $PROGRAMFILES64 for all platforms...

Related

How to make NSIS section mandatory depending on condition

I have a section in my installer I am attempting to make mandatory depending on some condition (the presence of a registry value) but I am not getting the behaviour I am expecting.
I am attempting to use the following command to make the section ticked and locked:
SectionIn RO
I also have a registry value that definitely exists but both of the following code chunks cause the section to be locked and ticked...
ClearErrors
ReadRegStr $0 HKCU "Software\Test" "TestValue"
${If} ${Errors}
SectionIn RO ; registry key not found
${Else}
; do nothing
${EndIf}
ClearErrors
ReadRegStr $0 HKCU "Software\Test" "TestValue"
${If} ${Errors}
; do nothing
${Else}
SectionIn RO ; registry key was found
${EndIf}
So it seems like either both control paths are being executed or the SectionIn command is trancending the if logic.
I can't seem to find much documentation on this particular command but I am a bit stumped. Any ideas?
SectionIn is an attribute and cannot be changed at run-time.
You should use the section helper macros in .onInit:
Section "blah" S_1
SectionEnd
!include LogicLib.nsh
!include Sections.nsh
Function .onInit
...
${If} ${Errors}
!insertmacro SetSectionFlag ${S_1} ${SF_RO}
${EndIf}
FunctionEnd

How to load the files based on the locale and generate the installer using NSIS?

My requirement is, I have specific files (Dlls, chms, etc) for each Locale (Language). I need to load those files based on the locale and generate the installer.
And while uninstalling i should uninstall those files as well from the traget directory.
Here what i am doing is in .onInit function, using GetUserDefaultUILanguage() i am getting the locale and checking this locale and loading the files under this locale.
is it the correct way? Please provide any suggestions for this code.
And also do we need to use ;Pages section before the ;Languages section?
Because i am getting a warning to use ;Pages section before the ;Languages section when i am compiling.
Below is the code snippet i have written:
; LocaleDlls.nsi
;
;
; It will install LocaleDlls.nsi into a directory that the user selects.
;--------------------------------
!include LogicLib.nsh
!include "MUI2.nsh"
; The name of the installer in the path C:\Program Files\LocaleDlls
Name "LocaleDlls"
; The file to write in the path E:\Source\NULLSOFT\src
OutFile "LocaleDlls.exe"
; The default installation directory in the path C:\Program Files\LocaleDlls
InstallDir $PROGRAMFILES\LocaleDlls
; Registry key to check for directory (so if you install again, it will
; overwrite the old one automatically) It shows the path the path C:\Program Files\LocaleDlls
InstallDirRegKey HKLM "Software\NSIS_LocaleDlls" "Install_Dir"
; Request application privileges for Windows Vista
RequestExecutionLevel admin
;--------------------------------
; Pages
Page components
Page directory
Page instfiles
UninstPage uninstConfirm
UninstPage instfiles
;Pages
; Do we need to use PAGE macros before giving LANGUAGE as when compiling we are getting an error.
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English" ; The first language is the default language
!insertmacro MUI_LANGUAGE "PortugueseBR"
;--------------------------------
;Installer Functions
Function .onInit
System::Call 'kernel32::GetUserDefaultUILanguage() i.r10'
MessageBox MB_OK "Return value = $R0"
StrCpy $Language ${LANG_PORTUGUESEBR}
MessageBox MB_OK "Return value = $Language"
${If} $Language P= 1046
MessageBox MB_OK "Current Locale is Portuguese... Loading Portuguese Files"
${EndIf}
File E:\Source\NULLSOFT\src\EngPortuguese\Portuguese\AllowStandby.reg
File E:\Source\NULLSOFT\src\EngPortuguese\Portuguese\Test.chm
File E:\Source\NULLSOFT\src\EngPortuguese\Portuguese\Testdlg.dll
File E:\Source\NULLSOFT\src\EngPortuguese\Portuguese\resource.dll
FunctionEnd
;--------------------------------
; The stuff to install
Section "LocaleDlls (required)"
SectionIn RO
; Set output path to the installation directory. Here is the path C:\Program Files\LocaleDlls
SetOutPath $INSTDIR
; Give the File path
System::Call 'KERNEL32::AddDllDirectory(w "$INSTDIR")' ; Tell Windows we trust all .DLLs in this directory
System::Call 'KERNEL32::LoadLibrary(t "$INSTDIR\testdlg.dll.dll")p.r8 ?e'
Pop $7 ; Get ?e result
${IfThen} $8 P= 0 ${|} MessageBox MB_ICONSTOP "Failed to load pchuteres.dll, error $7" ${|}
${If} $8 P<> 0
MessageBox MB_OK 'Successfully loaded "$INSTDIR\testdlg.dll.dll" # $8'
${EndIf}
; Do the install
; Write the installation path into the registry
WriteRegStr HKLM SOFTWARE\NSIS_DllTesting "Install_Dir" "$INSTDIR"
; Write the uninstall keys for Windows
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\LocaleDlls" "DisplayName" "NSIS LocaleDlls"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\LocaleDlls" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\LocaleDlls" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\LocaleDlls" "NoRepair" 1
WriteUninstaller "uninstall.exe"
SectionEnd
; Optional section (can be disabled by the user)
Section "Start Menu Shortcuts"
CreateDirectory "$SMPROGRAMS\LocaleDlls"
CreateShortcut "$SMPROGRAMS\LocaleDlls\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0
CreateShortcut "$SMPROGRAMS\LocaleDlls\LocaleDlls (MakeNSISW).lnk" "$INSTDIR\LocaleDlls.nsi" "" "$INSTDIR\LocaleDlls.nsi" 0
SectionEnd
;--------------------------------
; Uninstaller
Section "Uninstall"
; Remove registry keys
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\LocaleDlls"
DeleteRegKey HKLM SOFTWARE\NSIS_LocaleDlls
; Remove files and uninstaller
Delete $INSTDIR\LocaleDlls.nsi
Delete $INSTDIR\uninstall.exe
; Remove shortcuts, if any
Delete "$SMPROGRAMS\LocaleDlls\*.*"
; Remove directories used
RMDir "$SMPROGRAMS\LocaleDlls"
RMDir "$INSTDIR"
SectionEnd
;--------------------------------
;Uninstaller Functions
Function un.onInit
!insertmacro MUI_UNGETLANGUAGE
FunctionEnd
You don't need to call GetUserDefaultUILanguage, NSIS calls GetUserDefaultUILanguage to try to set the default $language. If it cannot find a matching language, the first language specified in the .NSI is used. All of this happens before .onInit is called and you don't have to do anything. You can however change $language in .onInit if you are not happy with the language NSIS has chosen. You can also use !insertmacro MUI_LANGDLL_DISPLAY to display a language selection dialog.
Pages need to be inserted before the language when using MUI because the language macro needs to know which strings are required by the pages. It is important that you use the MUI page macros MUI_PAGE_* and not the native page:
!insertmacro MUI_PAGE_WELCOME
Page Custom MyPage ; There is no MUI_PAGE_* macro for this
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English" ; Must come after all MUI_PAGE_* macros.

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

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

Is it possible to conditionally add a file/folder to NSIS installer

Is it possible to conditionally add a file/folder and installation option to a NSIS installer?
My idea is that if the folder Foo exists at a given location it should be added to the installer and the option to install Foo should be added to the installer as well. But if the folder Foo does not exist, the NSIS script should just create the installer but leave Foo and the option to select Foo out of it.
You can try to include a file with /NONFATAL. If it exists, it will be included by the compiler. In runtime, you can check if installer was able to extract it.
File /NONFATAL "file.zip"
${If} ${FileExists} "$OUTDIR\file.zip"
...
${EndIf}
In NSIS 2 File /NONFATAL /R "c:\foo" is the best you can do without external tools and you need a little hack to hide the section when there are no files:
!include LogicLib.nsh
Page Components
Page InstFiles
Section "Main"
SetOutPath $InstDir
# File "C:\myfiles\myapp.exe"
SectionEnd
Section "Install Foo" SID_FOO
SetOutPath $InstDir
File /NONFATAL /r "C:\myfiles\foo\*.*"
SectionEnd
Function .onInit
SectionGetSize ${SID_FOO} $0
StrCmp $0 0 "" +3
SectionSetFlags ${SID_FOO} 0 ; Force all flags off including the checkmark
SectionSetText ${SID_FOO} "" ; Hide the section because its size is 0
FunctionEnd
If this is unacceptable you can use !system and get a little help from cmd.exe to check if something exists:
!tempfile INCEXIST
!system 'if exist "C:\myfiles\foo\*.*" echo !define HAVE_FOO > "${INCEXIST}"'
!include "${INCEXIST}"
!delfile "${INCEXIST}"
!ifdef HAVE_FOO
Section "Install Foo"
SetOutPath $InstDir
File /r "C:\myfiles\foo\*.*"
SectionEnd
!endif
In NSIS 3 !if supports a /FileExists switch:
!if /FileExists "C:\myfiles\foo\*.*"
Section "Install Foo"
SetOutPath $InstDir
File /r "C:\myfiles\foo\*.*"
SectionEnd
!endif
Example to replace file what depends on running service and exists or not at targget location
IfFileExists "$SYSDIR\my_file.dll" exist notexist
exist:
ExecWait 'net stop desired_service'
SetOutPath $SYSDIR
SetOverwrite on
File "/oname=$SYSDIR\my_file.dll" "Path to my file\my_file.dll"
ExecWait 'net start desired_service'
notexist:
.....what you want to do if doesn't exists

Set value of InstallDir in a function, or set auto populate value somehow?

I'm creating an installer using NSIS. This installer actually installs two programs in two different directories in the same installer. I am doing this using the modern user interface (MUI) pages and simply calling MUI_PAGE_DIRECTORY twice specifying different starting parameteres, and capturing the directory in the LEAVE macro. What I'm wondering is, can I somehow call InstallDir in a function, or set the auto directory populate value in a function? Or possibly even call a function after the browse button has been returned from?
The reason I want to do this is so when the user clicks the browse button in either of the two directory pages, after they select a directory, the name of the finnal directory specifed in InstallDir will be appended.
For example:
InstallDir value for program 1: c:\client
InstallDir value for program 2: c:\program files\server
user clicks browse on program 1 and chooses c:\temp the resulting path is c:\temp\client
user clicks browse on program 2 and chooses c:\whatever the resulting path is c:\whatever\server
For reference here are the code snipits of what I have that works, but does not deal with the auto append browse button behaviour:
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ClientDirectoryLeave
!insertmacro MUI_PAGE_DIRECTORY
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ServerDirectoryLeave
!insertmacro MUI_PAGE_DIRECTORY
; Setup the page display for the client install page
Function ShowPageClient
!insertmacro MUI_HEADER_TEXT "Client" "Client"
!insertmacro MUI_INNERDIALOG_TEXT 1006 "Client"
; setup intal directory
Push $0
StrCpy $0 $PROGRAMFILES 2 #
; CLIENT_FOLDER_NAME is defined as a folder, but this would basicaly
; result in C:\Client as the first 2 characters of $PROGRAMFILES
; is the hard drive with program files installed on it
StrCpy $INSTDIR "$0\${CLIENT_FOLDER_NAME}"
Pop $0
; set the inital value of the directory text box
!insertmacro MUI_INNERDIALOG_TEXT 1019 $INSTDIR
; find and disable the directory selection box
; We do not want users to type in this box
FindWindow $R0 "#32770" "" $HWNDPARENT
GetDlgItem $R1 $R0 1019 ;Text Box
EnableWindow $R1 0
FunctionEnd
; Setup the page display for the server install location page
Function ShowPageServer
!insertmacro MUI_HEADER_TEXT "Server" "Server"
!insertmacro MUI_INNERDIALOG_TEXT 1006 "Server"
; setup intal directory
; SERVER_FOLDER_NAME is defined as a folder, but this would basicaly
; result in C:\Program Files\Server
StrCpy $INSTDIR "$PROGRAMFILES\${SERVER_FOLDER_NAME}"
; set the inital value of the directory text box
!insertmacro MUI_INNERDIALOG_TEXT 1019 $INSTDIR
; find and disable the directory selection box
; We do not want users to type in this box
FindWindow $R0 "#32770" "" $HWNDPARENT
GetDlgItem $R1 $R0 1019 ;Text Box
EnableWindow $R1 0
FunctionEnd
Note: I can make the browse button work for one of the directory pages, but then when I'm on the second page, the auto populate actual auto populates incorrectly
The appended folder name is constant and set at compile time, there is a bug report related to this.
My advice is to abandon the append feature and let the user have full control over the two destinations:
Name "NSIS Test"
InstallDir ""
!include MUI.nsh
Var DirClient
Var DirServer
Function .onInit
;Set default destinations
StrCpy $DirClient "$ProgramFiles\$(^Name)\Client"
StrCpy $DirServer "$ProgramFiles\$(^Name)\Server"
FunctionEnd
!macro ConfigureMyDirPage type var
!define MUI_DIRECTORYPAGE_VARIABLE ${var}
!define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install $(^NameDA) ${type}"
!define MUI_DIRECTORYPAGE_TEXT_TOP "Setup will install $(^NameDA) ${type} in the following folder. To install in a different folder, click Browse and select another folder. $_CLICK"
!define MUI_DIRECTORYPAGE_TEXT_DESTINATION "${type} $(^DirSubText)"
!macroend
!insertmacro ConfigureMyDirPage "Client" $DirClient
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro ConfigureMyDirPage "Server" $DirServer
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
Section
DetailPrint DirClient=$DirClient
DetailPrint DirServer=$DirServer
SectionEnd
Okay I finally figured it out. Basically there is a function that is called to "verify" the path after browse button is clicked. I tied into this function an appended the directory manual if needed. To do this I created a new variable and set it in a function called when the page is displays as follows:
; Client Directory
!define MUI_PAGE_CUSTOMFUNCTION_SHOW ShowPageClient
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ClientDirectoryLeave
!insertmacro MUI_PAGE_DIRECTORY
; Setup the page display for the client install page
Function ShowPageClient
; setup intal directory
Push $0
StrCpy $0 $PROGRAMFILES 2 #
StrCpy $INSTDIR "$0\${CLIENT_FOLDER_NAME}"
Pop $0
!insertmacro MUI_INNERDIALOG_TEXT 1019 $INSTDIR
FindWindow $R0 "#32770" "" $HWNDPARENT
GetDlgItem $R1 $R0 1019 ;Text Box
EnableWindow $R1 0
; Setup the client or server variable to indicate that
; we're in the client install part to signal the .onVerifyInstDir function
; to put the correct directory
StrCpy $CLIENT_OR_SERVER "client"
FunctionEnd
The function that is called after browse is .onVerifyInstDir so that is where I check the CLIENT_OR_SERVER variable and set the path appropriately
; This function is special and is called any time a
; path is validated on returning from the browse button
; need to append the correct directory if it does not already exists
; in the install directory path
Function .onVerifyInstDir
; save the current $0 register, as it is used in this function
Push $0
${If} $CLIENT_OR_SERVER == "client"
; in the client stage so directory must contain CLIENT_FOLDER_NAME
${StrContains} $0 "${CLIENT_FOLDER_NAME}" "$INSTDIR"
${If} $0 == ""
; the install dir does not contain the folder so append it
StrCpy $INSTDIR "$INSTDIR\${CLIENT_FOLDER_NAME}"
${EndIf}
${Else}
; in the server stage so directory must contain SERVER_FOLDER_NAME
${StrContains} $0 "${SERVER_FOLDER_NAME}" "$INSTDIR"
${If} $0 == ""
; the install dir does not contain the folder so append it
StrCpy $INSTDIR "$INSTDIR\${SERVER_FOLDER_NAME}"
${EndIf}
${EndIf}
; pop the saved register value
Pop $0
FunctionEnd
Couple notes:
the StrContains function I used was found here:
http://nsis.sourceforge.net/StrContains
Further reference to the .onVerifyInstDir function can be found here:
http://nsis.sourceforge.net/Docs/Chapter4.html#4.7.2.1.10

Resources