nsis File folder parametric directory - nsis

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

Related

How set options for a NSIS uninstaller

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'

Uninstall only installed files NSIS

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

NSIS section sizes doubled due to use of SetOverwrite

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

Sharing function that takes input variables between installer and uninstaller

How can I modifiy the below syntax such that my library function can be used in both the installer and the uninstaller? So far I have only managed to find an example for functions that do not take an input value. A good example of which can be found here:
http://nsis.sourceforge.net/Sharing_functions_between_Installer_and_Uninstaller
I haven't been able to work out the syntax should you want to pass varaibles and still utilise the function for both installer and uninstaller.
/* FILE: MyFunctionLibrary.nsh */
Function MyFunction
!define MyFunction"!insertmacro MyFunctionCall"
!macro MyFunctionCall _VAR1
Push "${_VAR1}"
Call MyFunction
!macroend
Exch $0
MessageBox MB_OK $0
Pop $0
FunctionEnd
/* FILE: MyInstallerScript.nsi */
/*...mui setup stuff...*/
!include "MyFunctionLibrary.nsh"
Section Install SEC01
${MyFunction} "install section"
SectionEnd
Section UnInstall SEC02
${MyFunction} "uninstall section"
SectionEnd
/*...other stuff...*/
There are two things going on here. You need to generate both functions and that is often done with a macro. You can also (optionally) provide a helper macro you use to call the function.
The most important step is creating the functions:
; --- MyFunctionLibrary.nsh ---
!macro DeclareMyFunction un
Function ${un}MyFunction
Exch $0
DetailPrint "MyFunction: parameter=$0"
Pop $0
FunctionEnd
!macroend
; --- MyInstallerScript.nsi ---
!insertmacro DeclareMyFunction ""
Section
Push "Hello World"
Call MyFunction
SectionEnd
!insertmacro DeclareMyFunction "un."
Section Uninstall
Push "Hello World"
Call un.MyFunction
SectionEnd
There is also a less common way you can use by providing just the functions code in the macro:
!macro MyFunctionCode
Exch $0
DetailPrint "MyFunction: parameter=$0"
Pop $0
!macroend
Function MyFunction
!insertmacro MyFunctionCode
FunctionEnd
Function un.MyFunction
!insertmacro MyFunctionCode
FunctionEnd
The macro that helps you call the function just needs to perform x number of Push's and Call YourFunction or un.YourFunction:
!macro DeclareMyFunction un
Function ${un}MyFunction
Exch $0
DetailPrint "MyFunction: parameter=$0"
Pop $0
FunctionEnd
!macroend
!define MyFunction "!insertmacro CallMyFunction"
!macro CallMyFunction Param1
Push "${Param1}"
!ifdef __UNINSTALL__
Call un.MyFunction
!else
Call MyFunction
!endif
!macroend
!insertmacro DeclareMyFunction ""
Section
${MyFunction} "Hello World"
SectionEnd
!insertmacro DeclareMyFunction "un."
Section Uninstall
${MyFunction} "Hello World"
SectionEnd
Some of the details can be abstracted away by using util.nsh:
!include Util.nsh
!macro MyFunction
Exch $0
DetailPrint "MyFunction: parameter=$0"
Pop $0
!macroend
!define MyFunction "!insertmacro CallMyFunction"
!macro CallMyFunction Param1
Push "${Param1}"
!insertmacro CallArtificialFunction MyFunction
!macroend
Section
${MyFunction} "Hello World"
SectionEnd
Section Uninstall
${MyFunction} "Hello World"
SectionEnd

How to process a string in NSIS?

I have a string from the parameters that has a installation path using this code:
${GetParameters} $R0
${if} $R0 != ""
StrCpy $R1 $R0 "" 3
StrCpy $INSTDIR $R1 -1
${endif}
the $INSTDIR contains a path like this:
C:\Program Files (x86)\My Applicatoin
I want to get "My Application" out of it and save it in a variable. I know that I should check the characters backwards until i reach the backslash ( \ ) but I do not know how to implement it in NSIS syntax.
How can I get "My Application" from the folder path using NSIS?
Most string operations can be coded with just StrCpy, StrCmp and StrLen.
A basic version that only checks \ and not / already exists:
!include FileFunc.nsh
StrCpy $0 "C:\Program Files (x86)\My Application"
${GetFileName} $0 $1
DetailPrint $1

Resources