How do I execute the app right after installation ... with arguments? - nsis

My installation EXE should unzip itself in a temp folder, then execute an app therein (with passing args), return and delete the temp folder again.
How do I write the nsi script?

It sounds to me like you are trying to create a portable application. Portable applications are always better when the original author adds support for it because it can handle registry and other configuration files correctly.
If you still want to create a launcher application you can do something like this:
OutFile "MyLauncher.exe"
RequestExecutionLevel User
SilentInstall Silent
SetCompressor LZMA
!include FileFunc.nsh
!insertmacro GetParameters
Section
${GetParameters} $1
InitPluginsDir
SetOutPath $PluginsDir
File "c:\myfiles\MyApp.exe"
File /r "c:\myfiles\otherfiles\*.*" ; If you need to include other files required by the application
ExecWait '"$PluginsDir\MyApp.exe" /param1 "pa ra m2" /param3 $1' $0 ; $1 contains the parameters passed to your launcher, remove it if you don't want to pass those arguments
SetErrorLevel $0
SetOutPath $Temp ; Don't lock $PluginsDir so it can be deleted automatically by the installer
SectionEnd

Answering my own question.
The required nsi script skeleton should look like this:
# The name of the installer (arbitrary)
Name "hello"
# The name of the installation file
OutFile "hello.exe"
# where put the installation - other options would be $TEMP, etc.
InstallDir $DESKTOP
RequestExecutionLevel user # no Windows UAC popup please!
SilentInstall silent # completely silent install
SetCompressor /SOLID /FINAL lzma # max compression for inst. file
# The stuff to install
Section ""
SetOutPath $INSTDIR # where to install (overwritable by user!)
File /r D:\...\... # where the install material lives
SectionEnd
# this function auto-runs after installation is fine
Function .onInstSuccess
# parameter are passed through via $CMDLINE
ExecWait '"$OUTDIR\hello.dist\hello.exe" $CMDLINE'
RMDir /r "$OUTDIR\hello.dist" # remove install folder again
FunctionEnd

Related

Generate installation logs

I am using special logging build to generate installation logs.
I have observed the logs are not generating when called another installer from installer script.
For ex -
ExecWait '"$INSTDIR\installer1.exe" /S _?=$INSTDIR'
The log is generating for main installer but not for installer1.exe
The installer1.exe contains lots of components and I need to print the logs for the same. I have tried enabling logset on in the installer1 script but no luck.
Tried using dumplog but it doesn't work with silent installation.
Any help would be appreciated!
Sample code from Main Installer script --
InstallDir "C:\MyFolder"
Name "${PRODUCT_NAME_VERSION}"
OutFile "${OUT_FILE}"
Section "Test"
SetOutPath $INSTDIR
LogSet on
ExecWait '"$EXEDIR\Packages\installer1.exe" /S /INST=$INSTDIR' $0
SectionEnd
Sample code from sub-installer script ---
InstallDir "C:\MyFolder"
Section "-Demo"
SetOutPath $INSTDIR
LogSet on
LogText "Print something"
SetOutPath $INSTDIR\ExternalFolder\Demo
File /nonfatal /a /r $INSTDIR\ExternalFolder\Demo\Test
ExecWait '"$INSTDIR\ExternalFolder\Demo\Test\TestSetup.exe" /silent '
SectionEnd
The sub-installer (installer1.exe) is pre-compiled and kept the exe in $EXEDIR\Packages\installer1.exe The patch is valid.
_?= is special syntax that is only supported by NSIS uninstallers, installers use /D=.
ExecWait '"$InstDir\installer.exe" /S /D=$InstDir'
Logging of course has to be enabled in this sub-installer as well.
/D= overrides the InstallDir attributes, forcing $InstDir to the specified path before .onInit is executed.
InstallDir $INSTDIR does not make sense, use something like InstallDir "$ProgramFiles\MyApp"

How to move a subdirectory with its contents to another directory with NSIS

this is the first time I work with NSIS, I want to edit an script NSIS to allow me:
If first instalation -> create the directory "Affaires" in the location "C:\Users\ali.ait-mouhoub.ext\AppData".
If it is an update (So the directory "Affaires" it already exists in "C:\N.O.E") -> move the directory "Affaires" with its contents to "C:\Users\ali.ait-mouhoub.ext\AppData".
The current script creates the directory "Affaires" in "C:\N.O.E".
Can you please help me modify my script to meet my needs?
If the both the new and old locations are on the same volume then you can use Rename:
Section
Rename "$InstDir\Stuff" "$InstDir\OldStuff"
SectionEnd
If the new location might be on a different volume then you have to copy+delete:
!include LogicLib.nsh
Section
ClearErrors
CopyFiles /Silent "$InstDir\Stuff" "x:\Backup"
${If} ${Errors}
MessageBox MB_ICONSTOP "Unable to move!"
${Else}
RMDir /R "$InstDir\Stuff"
${EndIf}
SectionEnd

NSIS - Adding other type of files

I have a NSIS generated-installer, however, I would like to add other files to the installer at compile time. For example, I want to add a batch file to the installer, so when the .exe file is ran, I can call that batch file within the .exe without needing to copy the actual batch file to the output location. This means the batch file will actually exist within the installer.
I hope this makes sense and thanks.
A batch file has to be extracted somewhere before it can be executed. $PluginsDir is a good choice because it is deleted when the installer ends.
Section
SetOutPath $Instdir
File myfiles\*
InitPluginsDir
SetOutPath $PluginsDir
File file.bat
nsExec::Exec '"cmd.exe" /c if 1==1 "$PluginsDir\file.bat"'
Pop $0 ; Exit code
SetOutPath $Temp ; Don't hold lock on pluginsdir
SectionEnd

Signing NSIS Uninstaller from Linux or Mac

I am porting our NSI installer to Linux and Mac instead of Windows to better integrate with our Maven build system.
We need to sign our installer and uninstaller. This was done as suggested at http://nsis.sourceforge.net/Signing_an_Uninstaller, but I just realized that it tries to run the tempinstaller to force it to produce the uninstaller.exe which can then be signed.
Obviously this trick doesn't work too well on *Nix systems and make this part of the process non-portable.
Does anyone has a better solution. I'm no expert at NSIS and wondering if there is a clever way to get the uninstall.exe so that it can be signed?
I don't think there is a real solution to this.
The installer and uninstaller uses the same exe code and only checks a flag (FH_FLAGS_UNINSTALL in firstheader) on startup to see if it is a uninstaller. Just flipping this bit is not enough though, the program would fail the CRC check and even if you bypass that the uninstaller data is compressed so you would have to decompress that to the correct location in the file. To actually accomplish this you would have to write a custom tool. You can see this operation in the NSIS source in exec.c if you search for EW_WRITEUNINSTALLER.
We need to sign our installer and uninstaller. This was done as suggested at http://nsis.sourceforge.net/Signing_an_Uninstaller, but I just realized that it tries to run the tempinstaller to force it to produce the uninstaller.exe which can then be signed. [...] this trick doesn't work too well on *Nix systems and make this part of the process non-portable.
If you exploit a stub installer for uninstall operations (no payload), this appears to be possible.
It will spawn an uninstall.exe process from the $TEMP folder, which is then capable of deleting $INSTDIR.
This script will create a stub (un)installer which can then be Authenticode Signed. It will compile on Windows, MacOS and Linux.
Caveats:
You'll have to manually bundle this into the installer (trivial)
You'll have to manage your own uninstall registry entries (trivial)
The look and feel may not match NSIS's default for uninstallers
You'll see the installer open twice (first from $INSTDIR, second from $TEMP). This is a child process which allows uninstall.exe to delete itself, similar to how NSIS does it in the Section "Uninstall".
You'll need a secondary .nsi script dedicated to uninstall operations, cumbersome if you have a lot of shared logic between your install/uninstall sections.
Worse, you'll have to AVOID the "Uninstall" section title, as you'll be placed into the same problem as the OP when that bytecode is generated.
When explicitly running from $TEMP some relative file logic will be incorrect. The example passes these back in as a $DELETE_DIR, $DELETE_EXE respectively.
The code:
!include MUI2.nsh
!include x64.nsh
!include LogicLib.nsh
!include FileFunc.nsh
!include WinMessages.nsh
!define MUI_PRODUCT "My App"
!define MUI_VERSION "1.0.0"
; Masquerade the title
!define MUI_PAGE_HEADER_TEXT "Uninstall My App"
!define MUI_PAGE_HEADER_SUBTEXT "Remove My App from your computer"
!define MUI_INSTFILESPAGE_FINISHHEADER_TEXT "Uninstallation Complete"
!define MUI_INSTFILESPAGE_FINISHHEADER_SUBTEXT "Uninstall was completed successfully."
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
!insertmacro GetParameters
RequestExecutionLevel admin
CRCCheck On
OutFile "uninstall.exe"
Name "Uninstall"
Var /GLOBAL RESPAWN
Var /GLOBAL DELETE_DIR
Var /GLOBAL DELETE_EXE
Section
; Masquerade as uninstall
SendMessage $HWNDPARENT ${WM_SETTEXT} 0 "STR:Uninstall"
${GetParameters} $0
${GetOptions} "$0" "/RESPAWN=" $RESPAWN
${GetOptions} "$0" "/DELETE_DIR=" $DELETE_DIR
${GetOptions} "$0" "/DELETE_EXE=" $DELETE_EXE
${If} $RESPAWN != ""
; We're running from $TEMP; Perform the uninstall
!define yay "We're running from $EXEPATH, yay, we can remove the install directory!$\n$\n"
!define myvars "$\tRESPAWN$\t$RESPAWN$\n$\tDELETE_EXE$\t$DELETE_EXE$\n$\tDELETE_DIR$\t$DELETE_DIR"
MessageBox MB_OK "${yay}${myvars}"
; Your uninstall code goes here
; RMDir /r $DELETE_DIR\*.*
; Delete "$DESKTOP\${MUI_PRODUCT}.lnk"
; Delete "$SMPROGRAMS\${MUI_PRODUCT}\*.*"
; RmDir "$SMPROGRAMS\${MUI_PRODUCT}"
; Delete Uninstaller And Unistall Registry Entries
; DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\${MUI_PRODUCT}"
; DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${MUI_PRODUCT}"
; Remove the old version of ourself
ClearErrors
Delete $DELETE_EXE
IfErrors 0 +3
MessageBox MB_OK "File could NOT be deleted: $DELETE_EXE"
Goto +2
MessageBox MB_OK "File was successfully deleted: $DELETE_EXE"
; Remove ourself from $TEMP after reboot
Delete /REBOOTOK $EXEPATH
; ${If} ${RunningX64}
; ${EnableX64FSRedirection}
; ${EndIf}
SetDetailsPrint textonly
DetailPrint "Completed"
${Else}
; We're NOT running from $TEMP, copy to temp and respawn ourself
GetTempFileName $0
CopyFiles "$EXEPATH" "$0"
Exec '"$0" /RESPAWN=1 /DELETE_DIR="$EXEDIR" /DELETE_EXE="$EXEPATH"'
Quit
${EndIf}
SectionEnd
Function .onInit
; ${If} ${RunningX64}
; SetRegView 64
; ${DisableX64FSRedirection}
; ${EndIf}
FunctionEnd

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

Resources