NSIS - put EXE version into name of installer - nsis

NSIS has a Name variable that you define in the script:
Name "MyApp"
It defines the name of the installer, that gets displayed as the window title, etc.
Is there a way to pull the .NET Version number out of my main EXE and append it to the Name?
So that my installer name would automatically be 'MyApp V2.2.0.0" or whatever?

There might be a very simple way to do this, but I don't know what it is. When I first started using NSIS, I developed this workaround to suit my needs and haven't revisited the problem since to see if there's anything more elegant.
I wanted my installers to have the same version number, description, and copyright info as my main executable. So I wrote a short C# application called GetAssemblyInfoForNSIS that pulls that file info from an executable and writes it into a .nsh file that my installers include.
Here is the C# app:
using System;
using System.Collections.Generic;
using System.Text;
namespace GetAssemblyInfoForNSIS {
class Program {
/// <summary>
/// This program is used at compile-time by the NSIS Install Scripts.
/// It copies the file properties of an assembly and writes that info a
/// header file that the scripts use to make the installer match the program
/// </summary>
static void Main(string[] args) {
try {
String inputFile = args[0];
String outputFile = args[1];
System.Diagnostics.FileVersionInfo fileInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(inputFile);
using (System.IO.TextWriter writer = new System.IO.StreamWriter(outputFile, false, Encoding.Default)) {
writer.WriteLine("!define VERSION \"" + fileInfo.ProductVersion + "\"");
writer.WriteLine("!define DESCRIPTION \"" + fileInfo.FileDescription + "\"");
writer.WriteLine("!define COPYRIGHT \"" + fileInfo.LegalCopyright + "\"");
writer.Close();
}
} catch (Exception e) {
Console.WriteLine(e.Message + "\n\n");
Console.WriteLine("Usage: GetAssemblyInfoForNSIS.exe MyApp.exe MyAppVersionInfo.nsh\n");
}
}
}
}
So if you use that application like so:
GetAssemblyInfoForNSIS.exe MyApp.exe MyAppVersionInfo.nsh
You would get a file named MyAppVersionInfo.nsh that looks something like this (assuming this info is in your executable):
!define VERSION "2.0"
!define DESCRIPTION "My awesome application"
!define COPYRIGHT "Copyright © Me 2010"
At the top of my NSIS script, I do something like this:
!define GetAssemblyInfoForNSIS "C:\MyPath\GetAssemblyInfoForNSIS.exe"
!define PrimaryAssembly "C:\MyPath\MyApp.exe"
!define VersionHeader "C:\MyPath\MyAppVersionInfo.nsh"
!system '"${GetAssemblyInfoForNSIS}" "${PrimaryAssembly}" "${VersionHeader}"'
!include /NONFATAL "${VersionHeader}"
!ifdef VERSION
Name "My App ${VERSION}"
!else
Name "My App"
!endif
!ifdef DESCRIPTION
VIAddVersionKey FileDescription "${DESCRIPTION}"
!endif
!ifdef COPYRIGHT
VIAddVersionKey LegalCopyright "${COPYRIGHT}"
!endif
The first 3 defines set up the file names to use in the !system call to GetAssemblyInfoForNSIS.exe. This system call takes place during your installer's compilation and generates the .nsh file right before you include it. I use the /NONFATAL switch so that my installer doesn't fail completely if an error occurs in generating the include file.

You can do this without .NET by using the GetVersion plugin, but following the same basic logic:
Here is ExtractVersionInfo.nsi:
!define File "...\path\to\your\app.exe"
OutFile "ExtractVersionInfo.exe"
SilentInstall silent
RequestExecutionLevel user
Section
## Get file version
GetDllVersion "${File}" $R0 $R1
IntOp $R2 $R0 / 0x00010000
IntOp $R3 $R0 & 0x0000FFFF
IntOp $R4 $R1 / 0x00010000
IntOp $R5 $R1 & 0x0000FFFF
StrCpy $R1 "$R2.$R3.$R4.$R5"
## Write it to a !define for use in main script
FileOpen $R0 "$EXEDIR\App-Version.txt" w
FileWrite $R0 '!define Version "$R1"'
FileClose $R0
SectionEnd
You compile this once, and then call it from your real installer:
; We want to stamp the version of the installer into its exe name.
; We will get the version number from the app itself.
!system "ExtractVersionInfo.exe"
!include "App-Version.txt"
Name "My App, Version ${Version}"
OutFile "MyApp-${Version}.exe"

Since NSISv3.0 this can be done with !getddlversion without using any third-party software:
!getdllversion "MyApp.exe" ver
Name "MyName ${ver1}.${ver2}.${ver3}.${ver4}"
OutFile "my_name_install_v.${ver1}.${ver2}.${ver3}.${ver4}.exe"

I found a way to do this on the NSIS wiki:
http://nsis.sourceforge.net/Version_Info_manipulations_on_compile-time

You can achieve this using MSBuild.
Just add your .nsi script to project and set this file property
Copy to Output Directory value Copy always or Copy if newer.
Add to your project file (e.g. .csproj or .vbproj) following code (suppose your nsi script has name installer.nsi)
<Target Name="AfterBuild" Condition=" '$(Configuration)' == 'Release'">
<!-- Getting assembly information -->
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="myAssemblyInfo"/>
</GetAssemblyIdentity>
<!-- Compile NSIS installer script to get installer file -->
<Exec Command='"%programfiles(x86)%\nsis\makensis.exe" /DVersion=%(myAssemblyInfo.Version) "$(TargetDir)installer.nsi"'>
<!-- Just to show output from nsis to VS Output -->
<Output TaskParameter="ConsoleOutput" PropertyName="OutputOfExec" />
</Exec>
</Target>
Use $Version variable in your nsi script:
# define installer name
OutFile "MyApp-${Version}.exe"

Call simple VBS script after NSIS compile:
Set ddr = CreateObject("Scripting.FileSystemObject")
Version = ddr.GetFileVersion( "..\path_to_version.exe" )
ddr.MoveFile "OutputSetup.exe", "OutputSetup_" & Version & ".exe"

Since NSIS v3.0a0 you can do it directly in the script, no external tools needed: !getdllversion
Sample code (from the documentation):
!getdllversion "$%WINDIR%\Explorer.exe" Expv_
!echo "Explorer.exe version is ${Expv_1}.${Expv_2}.${Expv_3}.${Expv_4}"

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 do I execute the app right after installation ... with arguments?

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

NSIS AccessControl::GrantOnFile permission failing

I am trying to create and set a directory with NSIS and the accessControl plugin like the following:
CreateDirectory "$APPDATA\${productName}"
; create fileResources directory
CreateDirectory "$APPDATA\${productName}\fileResources"
AccessControl::GrantOnFile "$APPDATA\${productName}\fileResources" "Everyone" "FullAccess"
Pop $0 ; get "Marker" or error msg
StrCmp $0 "Marker" Continue
MessageBox MB_OK|MB_ICONSTOP "Error setting access control for $APPDATA\${productName}\fileResources: $0"
Pop $0 ; pop "Marker"
Continue:
Pop $0
I am receiving the following on $0 what is that response?
I want to make a folder readable and writable by the installed program
I'm guessing that you are building a Unicode installer using NSIS v3 and that you put the wrong plugin in the plugins subdirectory, that is why the result looks chinese.
To install a plugin correctly you need to put the ANSI .dll in NSIS\Plugins\x86-ansi and the Unicode .dll in NSIS\Plugins\x86-unicode.

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

Install from dynamic location

I have 2 versions of the same exe file for my project. The installer is supposed to pick one of the 2 versions depending on some conditions.
In a normal case i would do File executable\myExe.exe. Because i now have 2 versions of the file, i would have to do something like File "${ExeSourcePath}\myExe.exe", and $ExeSourcePath is determined by checking various conditions. When compiling this code i get
File: "${ExeSourcePath}\myExe.exe" -> no files found.
Anyone knows why? I'm only allowed to use fixed paths with the File command or am i doing something wrong?
${ExeSourcePath} is a precompiler define and $ExeSourcePath is a variable used at runtime, the File command can only use precompiler defines.
There are two ways you can handle this:
A) Include both files and decide at runtime based on the users system or choices made during install:
!include LogicLib.nsh
Section
ReadRegStr $0 HKLM "Software\foo\bar" baz
${If} $0 > 5
File "c:\myproject\version2\app.exe"
${Else}
File "c:\myproject\version1\app.exe"
${EndIf}
SectionEnd
B) Only include one file based on command line passed to makensis (/Dusev2 app.nsi) or something on your system:
Section
!define projectroot "c:\myproject"
!searchparse /noerrors /file ....... usev2 ;Or you can use !system etc
!ifdef usev2
File "${projectroot}\version2\app.exe"
!else
File "${projectroot}\version1\app.exe"
!endif
SectionEnd

Resources