I've inherited an installer script, and I'm annoyed by it claiming to require twice as much space as it actually needs.
I found that this was due to how each section is using SetOverwrite (because they're re-used for a repair install?). I understand that it's necessary to keep the duplicated File commands in each of the If/Else blocks because of how SetOverwrite works (see below), but I have confirmed that it results in a doubling of the automatic section size calculations.
${MementoSection} $(APP_Section_SecHelp) SecHelp
SetDetailsPrint textonly
DetailPrint $(APP_DetailPrint_SecHelp)
SetDetailsPrint listonly
SetOutPath $INSTDIR
SectionIn 1 2
${If} $SetOverwriteOn == TRUE
SetOverwrite ifnewer
File /r "${APP_SOURCE_DIR}\Help"
${Else}
SetOverwrite off
File /r "${APP_SOURCE_DIR}\Help"
${EndIf}
SectionGetFlags ${SecHelp} $SecHelp_GetFlag
${MementoSectionEnd}
Is this a bad design pattern that I should change? Do I need to add a hack to call SectionGetSize, divide by 2 and call SectionSetSize?
Personally I would just use SetOverwrite ifnewer for both but if you absolutely want to do it your way then using SectionSetSize is one option.
Another thing you could do is to put the repair File /r instruction in a separate function that you call from the section. The downside of a function is that the progressbar does not move much during the extraction.
A third alternative is to put some of the repair tasks in a separate section that is unchecked by default and you enable it when you are in repair mode.
Edit: Here is a example that uses SectionSetSize:
!include LogicLib.nsh
InstallDir $Temp
Page Components InitComponentsPage
Page Directory
Page InstFiles
!macro ModifySectionHack SID TEMPVAR
SectionGetSize ${SID} ${TEMPVAR}
IntOp ${TEMPVAR} ${TEMPVAR} / 2
SectionSetSize ${SID} ${TEMPVAR}
!macroend
Function InitComponentsPage
StrCpy $0 0
loop:
ClearErrors
SectionGetFlags $0 $1 ; The error flag is set if we try to access a section that does not exist
IfErrors done
!insertmacro ModifySectionHack $0 $1
IntOp $0 $0 + 1
Goto loop
done:
FunctionEnd
Section "Foo"
InitPluginsDir ; Need a place to extract to for this example
SetOutPath $PluginsDir
${If} 1 <> 2 ; Don't really care about the result
SetOverwrite ifnewer
File "${NSISDIR}\bin\makensis.exe" ; ~400kb
${Else}
SetOverwrite off
File "${NSISDIR}\bin\makensis.exe"
${EndIf}
SectionEnd
Section "Bar"
InitPluginsDir ; Need a place to extract to for this example
SetOutPath $PluginsDir
${If} 1 <> 2 ; Don't really care about the result
SetOverwrite ifnewer
File "${NSISDIR}\nsis.exe" ; ~700kb
${Else}
SetOverwrite off
File "${NSISDIR}\nsis.exe"
${EndIf}
SectionEnd
This loops through all the sections and adjusts all of them but I'm not sure if that is such a good idea. If this was my script then I would manually call ModifySectionHack on each section that has the SetOverwrite problem:
!include LogicLib.nsh
InstallDir $Temp
Page Components
Page Directory
Page InstFiles
!macro ModifySectionHack SID TEMPVAR
SectionGetSize ${SID} ${TEMPVAR}
IntOp ${TEMPVAR} ${TEMPVAR} / 2
SectionSetSize ${SID} ${TEMPVAR}
!macroend
Section "Foo" SID_FOO
InitPluginsDir ; Need a place to extract to for this example
SetOutPath $PluginsDir
${If} 1 <> 2 ; Don't really care about the result
SetOverwrite ifnewer
File "${NSISDIR}\bin\makensis.exe" ; ~400kb
${Else}
SetOverwrite off
File "${NSISDIR}\bin\makensis.exe"
${EndIf}
SectionEnd
Section "Bar"
${If} 1 = 2
File /r "${NSISDIR}\stubs"
${EndIf}
SectionEnd
Section "Baz" SID_BAZ
InitPluginsDir ; Need a place to extract to for this example
SetOutPath $PluginsDir
${If} 1 <> 2 ; Don't really care about the result
SetOverwrite ifnewer
File "${NSISDIR}\nsis.exe" ; ~700kb
${Else}
SetOverwrite off
File "${NSISDIR}\nsis.exe"
${EndIf}
SectionEnd
Function .onInit
; Adjust the section size for the sections that use SetOverwrite:
!insertmacro ModifySectionHack ${SID_FOO} $1
!insertmacro ModifySectionHack ${SID_BAZ} $1
FunctionEnd
Related
Is there a way to make the NSIS installer skip certain dialogs?
It has these command-line arguments,
/S, /NCRC and /D=dir
Although /S and /NCRC can used for silent and unattended modes, is there are command-line arguments to make the installer skip certain dialogs in the installer and show the rest of the dialog? For example. skip the Welcome dialog and the next two dialogs and go to the fourth one.
/S, /NCRC and /D= are the only installer parameters with built-in support, anything else you have to handle yourself.
Pages can be skipped by calling Abort in the page pre callback. It is also possible to jump forward a specific number of pages. The GetOptions macro can be used to parse the command line.
OutFile Test.exe
RequestExecutionLevel user
InstallDir $Temp
!include LogicLib.nsh
!include FileFunc.nsh
Page License LicPre
Page Components CmpPre
Page Directory "" DiShow
Page InstFiles
Var SkippedL
Var SkippedC
!macro AbortIfCmdlineParam Switch Var
${GetParameters} $0
ClearErrors
${GetOptions} $0 "${Switch}" $0
${IfNot} ${Errors}
${If} ${Var} = 0
StrCpy ${Var} 1
Abort
${EndIf}
${EndIf}
!macroend
Function LicPre
!insertmacro AbortIfCmdlineParam "/SkipL" $SkippedL
FunctionEnd
Function CmpPre
!insertmacro AbortIfCmdlineParam "/SkipC" $SkippedC
FunctionEnd
Function DiShow
# Disable back button if both pages skipped, this is optional
${If} $SkippedL <> 0
${AndIf} $SkippedC <> 0
GetDlgItem $0 $hwndparent 3
EnableWindow $0 0
${EndIf}
FunctionEnd
Section
SectionEnd
Run as Test /SkipL /SkipC to skip both.
Or:
OutFile Test.exe
RequestExecutionLevel user
InstallDir $Temp
!include LogicLib.nsh
!include FileFunc.nsh
Page License "" LicShow
Page Components
Page Directory
Page InstFiles
Function LicShow
Var /Global HasSkipped
${GetParameters} $0
ClearErrors
${GetOptions} $0 "/Skip=" $0
${IfNot} ${Errors}
${AndIf} $0 < 4 ; Don't let user skip InstFiles
${AndIf} $HasSkipped = 0
StrCpy $HasSkipped 1
SendMessage $HWNDPARENT 0x408 $0 ""
${EndIf}
FunctionEnd
Section
SectionEnd
...and run as Test /Skip=2.
I have an NSIS installer with some options which works very well. But my "--quiet" option doesn't work for the uninstaller.
uninst:
ClearErrors
${getOPtions} $CMDLINE "--quiet" $0
${IfNot} ${Errors}
StrLen $2 "\Uninstall.exe /S"
${Else}
StrLen $2 "\Uninstall.exe"
${EndIf}
StrCpy $3 $0 -$2 # remove "\Uninstall.exe"
ExecWait '$0 _?=$3' ;Do not copy the uninstaller to a temp file`
GetOptions expects you to call GetParameters to get the parameters, not $CmdLine:
!include FileFunc.nsh
!include LogicLib.nsh
...
${GetParameters} $1
ClearErrors
${GetOptions} $1 "--quiet" $2
${IfNot} ${Errors}
MessageBox mb_ok "Quiet mode"
${EndIf}
ExecWait "\Uninstall.exe /S" is never going to work, \Uninstall.exe means Uninstall.exe in the root of the current drive. You must use the full path and it should look something like this:
StrCpy $0 "$ProgramFiles\my app" ; TODO: Get the old install path
StrCpy $1 "/S" ; TODO: Set optional parameters
ExecWait '"$0\Uninstall.exe" $1 _?=$0'
I want to find a way to uninstall only installed files.
I tried http://nsis.sourceforge.net/Uninstall_only_installed_files but I have around 10.000 files nested in many directories, therefore specifying each file separately in a nsis script is not a solution.
You can use the !system command to execute external tools at compile-time. This external tool/script should generate include files with install and uninstall instructions.
You can also use NSIS on the fly to generate the instructions but it is a bit messy when you put all of it in a single script:
; Generate some files for this example:
!system 'mkdir "$%temp%\testdir\subdir\sub2\sub3"'
!appendfile "$%temp%\testdir\file1.txt" ""
!appendfile "$%temp%\testdir\subdir\file2.txt" ""
!appendfile "$%temp%\testdir\subdir\sub2\file3.txt" ""
!appendfile "$%temp%\testdir\subdir\sub2\sub3\file4.txt" ""
!macro GENFILELIST_Enum SourceDir InstDir
Push "${SourceDir}"
Push "${InstDir}"
Call GENFILELIST_Enum
!macroend
### Start editing here ###
!macro GeneratePages
OutFile "test.exe"
Name "Test"
RequestExecutionLevel admin
InstallDir "$ProgramFiles\MyTestApp"
Page Directory
Page InstFiles
UninstPage UninstConfirm
UninstPage InstFiles
!macroend
!macro GENFILELIST
!insertmacro GENFILELIST_Enum "$%temp%\testdir" "" ; Files to install
!macroend
!macro GenerateInstallSections FileInstructions
Section
SetOutPath $InstDir
!include "${FileInstructions}"
WriteUninstaller "$InstDir\Uninst.exe"
SectionEnd
!macroend
!macro GenerateUninstallSections FileInstructions
Section Uninstall
SetOutPath $InstDir
!include "${FileInstructions}"
SetOutPath $Temp
Delete "$InstDir\Uninst.exe"
RmDir $InstDir
SectionEnd
!macroend
### Stop editing here ###
!ifndef GENFILELIST_IN
!tempfile GENFILELISTAPP
!tempfile GENFILELIST_IN
!tempfile GENFILELIST_UN
!appendfile "${GENFILELIST_IN}" '!define GENFILELIST_UN "${GENFILELIST_UN}"$\n'
!appendfile "${GENFILELIST_IN}" 'OutFile "${GENFILELISTAPP}"$\n'
!system '"${NSISDIR}/makensis" "/DGENFILELIST_IN=${GENFILELIST_IN}" "${__FILE__}"' = 0
!system '"${GENFILELISTAPP}" /S' = 0
!delfile "${GENFILELISTAPP}"
!insertmacro GeneratePages
!insertmacro GenerateInstallSections "${GENFILELIST_IN}"
!insertmacro GenerateUninstallSections "${GENFILELIST_UN}"
!delfile "${GENFILELIST_IN}"
!delfile "${GENFILELIST_UN}"
!else
!include LogicLib.nsh
!include "${GENFILELIST_IN}"
!delfile "${GENFILELIST_IN}"
!macro GENFILELIST_Append file string tmpvar
FileOpen ${tmpvar} "${file}" a
FileSeek ${tmpvar} 0 END
FileWrite ${tmpvar} '${string}'
FileClose ${tmpvar}
!macroend
!define DOLLAR "$$"
Function GENFILELIST_Enum
System::Store S
Pop $9 ; Relative path
Pop $8 ; Base path
${IfThen} $9 == "" ${|} StrCpy $9 "${DOLLAR}OutDir" ${|}
FindFirst $0 $1 "$8\*"
loop:
StrCmp $1 "" stop
StrCmp $1 "." next
StrCmp $1 ".." next
StrCpy $3 $1
${If} ${FileExists} "$8\$1\*"
!insertmacro GENFILELIST_Append "${GENFILELIST_IN}" 'SetOutPath "${DOLLAR}OutDir\$1"$\n' $2
Push $8\$1
Push $9\$1
Call GENFILELIST_Enum
${Else}
!insertmacro GENFILELIST_Append "${GENFILELIST_IN}" 'File "$8\$1"$\n' $2
!insertmacro GENFILELIST_Append "${GENFILELIST_UN}" 'Delete "$9\$1"$\n' $2
${EndIf}
next:
FindNext $0 $1
Goto loop
stop:
FindClose $0
!insertmacro GENFILELIST_Append "${GENFILELIST_UN}" 'RmDir "$9"$\n' $2
System::Store L
FunctionEnd
RequestExecutionLevel user
Section
!insertmacro GENFILELIST
SectionEnd
!endif
I wish made a function to add some files into some folder, 'cause I need to add more files.
here is my code of the function:
Function "addElement"
DetailPrint $0
CreateDirectory $INSTDIR\data\Element\$0
SetOutPath $INSTDIR\data\Element\$0
File /r "${binFolder}\data\Element\$0\*.*"
FunctionEnd
and here I call it:
strcpy $0 "Element_1"
call "addElement"
strcpy $0 "Element_2"
call "addElement"
strcpy $0 "Element_3"
call "addElement"
the nsis gives this error:
at the line File /r... gives -> no files found.
$0 is a variable and variables are used at run-time, the File instruction needs to know the filename at compile-time!
Replace the function with a macro:
!macro addElement fname
DetailPrint "${fname}"
CreateDirectory "$INSTDIR\data\Element\${fname}"
SetOutPath "$INSTDIR\data\Element\${fname}"
File /r "${binFolder}\data\Element\${fname}\*.*"
!macroend
...
Section
!insertmacro addElement foo
!insertmacro addElement bar
!insertmacro addElement baz
I want to build a NSIS-script, which has three section
section Main
section Minor
section Shared
Shared is invisible and would be installed, if Main or Minor is checked. If I start the installer, every section (Main, Minor) is checked.
Now it should be able to define the section (in silent install). What have I to change, to only install Main or Minor or Both?
Name "Test"
Outfile "Test.exe"
;RequestExecutionLevel ?
!include "Sections.nsh"
!include "LogicLib.nsh"
!include "FileFunc.nsh" ;For GetOptions
Page Components "" "" EnforceSectionDependencies
Page InstFiles
Section /o "Main" SID_MAIN
DetailPrint Main
SectionEnd
Section /o "Minor" SID_MINOR
DetailPrint Minor
SectionEnd
Section "" SID_SHARED
DetailPrint Shared
SectionEnd
!macro CheckSectionSwitch sw sid
${GetOptions} $0 '${sw}' $9
${IfNot} ${Errors}
StrCpy $1 1
!insertmacro SelectSection ${sid}
${EndIf}
!macroend
Function .onInit
${GetParameters} $0
StrCpy $1 0 ;Any section swithes?
ClearErrors
!insertmacro CheckSectionSwitch '/Main' ${SID_MAIN}
!insertmacro CheckSectionSwitch '/Minor' ${SID_MINOR}
${If} $1 = 0
;Set defaults
!insertmacro SelectSection ${SID_MAIN}
!insertmacro SelectSection ${SID_MINOR}
${EndIf}
call EnforceSectionDependencies
FunctionEnd
Function EnforceSectionDependencies
!insertmacro UnselectSection ${SID_SHARED}
${If} ${SectionIsSelected} ${SID_MAIN}
${OrIf} ${SectionIsSelected} ${SID_MINOR}
!insertmacro SelectSection ${SID_SHARED}
${EndIf}
FunctionEnd
You should look at the Section management part of the documentation, notably the SectionSetFlags to change the sections selections.
Also, maybe that the How to control Section selections, while using SubSections & InstTypes example will be useful.