Automatically retrieve EXE file version to embed in NSIS installer metadata
Asked Answered
S

2

7

I am attempting to remove one more step in my application's release process by automatically retrieving versioning info from my executable (in this case, a .NET application).

Up to this point, I have been able to get by with a limited knowledge of NSIS, but I am quickly learning that this is not enough.

Step 1: Declare version info in executable

In AssemblyInfo.cs, I declare [assembly: AssemblyVersion("1.0.0.1")]. This successfully makes the version info appear in the compiled executable (under "File version" and "Product version").

Step 2: Retrieve version info from executable

According to this article on "GetFileVersion", importing "FileFunc.nsh" allows you to retrieve version info from an executable.

Code used:

Section
    Var /GLOBAL version
    ${GetFileVersion} "C:\test.exe" $version
    ...
SectionEnd

Step 3: Verify contents of function call

Based on section 5.1.7 of the documentation, I should be able to print to the command line during compile time using the "!echo" command. The difference between printing the contents of a variable (or a constant, etc) still confuses me, so I have tried all four of these options:

!echo $version
!echo "$version"
!echo "${version}"
!echo ${version}

This results in:

$version (InstallScript.nsi:15)
$version (InstallScript.nsi:16)
${version} (InstallScript.nsi:17)
${version} (InstallScript.nsi:18)

Step 4: Declare the installer metadata

Based on section 4.8.3, I should be able to add installer metadata via VIProductVersion and VIAddVersionKey.

VIProductVersion $version 
VIAddVersionKey "FileVersion" "$version"

In the built installer, this adds the string "$version" into the specified fields.


Is there a ToString() equivalent in NSIS? How can I access a variable's contents? Does the print of the variable name mean that it has no contents? How can I verify that GetFileVersion is called correctly, executes correctly, and returns a value?

Stound answered 18/11, 2011 at 20:22 Comment(3)
The reason this won't work is that the ${GetFileVersion} gets executed at runtime. You can't use the contents of a variable in a thing that gets fixed at compile time. You could do it with a preprocessing executable.Electrostriction
@ChrisMorgan: Can you expand this comment and your alternative into an answer?Stound
Possible duplicate of NSIS - put EXE version into name of installerAnhydrite
H
13

Edit: NSIS v3 now includes a !getdllversion preprocessor instruction, you only need the GetVersionLocal workaround if you are still using NSIS v2.

There are plans for a !getdllversionlocal in NSIS 2.47, for now you have to use this workaround:

outfile test.exe
requestexecutionlevel user

!macro GetVersionLocal file basedef
!verbose push
!verbose 1
!tempfile _GetVersionLocal_nsi
!tempfile _GetVersionLocal_exe
!appendfile "${_GetVersionLocal_nsi}" 'Outfile "${_GetVersionLocal_exe}"$\nRequestexecutionlevel user$\n'
!appendfile "${_GetVersionLocal_nsi}" 'Section$\n!define D "$"$\n!define N "${D}\n"$\n'
!appendfile "${_GetVersionLocal_nsi}" 'GetDLLVersion "${file}" $2 $4$\n'
!appendfile "${_GetVersionLocal_nsi}" 'IntOp $1 $2 / 0x00010000$\nIntOp $2 $2 & 0x0000FFFF$\n'
!appendfile "${_GetVersionLocal_nsi}" 'IntOp $3 $4 / 0x00010000$\nIntOp $4 $4 & 0x0000FFFF$\n'
!appendfile "${_GetVersionLocal_nsi}" 'FileOpen $0 "${_GetVersionLocal_nsi}" w$\nStrCpy $9 "${N}"$\n'
!appendfile "${_GetVersionLocal_nsi}" 'FileWrite $0 "!define ${basedef}1 $1$9"$\nFileWrite $0 "!define ${basedef}2 $2$9"$\n'
!appendfile "${_GetVersionLocal_nsi}" 'FileWrite $0 "!define ${basedef}3 $3$9"$\nFileWrite $0 "!define ${basedef}4 $4$9"$\n'
!appendfile "${_GetVersionLocal_nsi}" 'FileClose $0$\nSectionend$\n'
!system '"${NSISDIR}\makensis" -NOCD -NOCONFIG "${_GetVersionLocal_nsi}"' = 0
!system '"${_GetVersionLocal_exe}" /S' = 0
!delfile "${_GetVersionLocal_exe}"
!undef _GetVersionLocal_exe
!include "${_GetVersionLocal_nsi}"
!delfile "${_GetVersionLocal_nsi}"
!undef _GetVersionLocal_nsi
!verbose pop
!macroend

!insertmacro GetVersionLocal "$%windir%\Explorer.exe" MyVer_
VIProductVersion "${MyVer_1}.${MyVer_2}.${MyVer_3}.${MyVer_4}"
VIAddVersionKey "FileVersion" "${MyVer_1}.${MyVer_2}.${MyVer_3}.${MyVer_4}"

page instfiles
section
sectionend

This macro:

  1. Creates a temporary .nsi file
  2. Compiles the temporary .nsi to a temporary executable
  3. Runs the temporary .exe
  4. Deletes the two files (.nsi and .exe)
  5. Returns an array containing the version info of the specified executable.
Hardaway answered 19/11, 2011 at 13:33 Comment(9)
There, I knew you'd do better, @Anders. That's why I didn't bother filling my comment out into an answer :-)Electrostriction
@Anders: Thank you for the answer. I was unable to work on this problem in the interim due to other priorities, but now I'm working on it again. From what I can tell, this macro creates a temporary nsi file, compiles it, runs it, and deletes the files. Is that correct?Stound
@Stound It also includes the result but yes that is correctHardaway
@Anders: Tested and works. I edited in a version of my comment into your post. Please remove or change it if you feel it is inaccurate.Stound
Can someone explain how this is used please?Theisen
@Theisen What do you mean "how this is used"? The example uses it. But NSIS v3 has a command for it now so there is no need for this workaround in the first place anymore...Hardaway
I don't see where a custom .exe path is used in the above. Can you provide a reference to the new way of doing this please?Theisen
@Theisen Look at the top of my answer for NSIS 3. For the macro when using NSIS 2 it is right there: !insertmacro GetVersionLocal "$%windir%\Explorer.exe" MyVer_ , just replace $%windir%\Explorer.exe with whatever path you want. You can change MyVer_ if you want as well...Hardaway
Sorry about this. It's been a hell of a long day... Thanks very much for your time.Theisen
H
10

Updated information:

NSIS now includes !getdllversion command which does the same thing. Usage:

!getdllversion "$%windir%\explorer.exe" expv_
!echo "Explorer.exe version is ${expv_1}.${expv_2}.${expv_3}.${expv_4}"

UPDATE: !getdllversion command is a "Compile Time Command", it's different from the GetDLLVersion instruction. IMO it should have been named as !getdllversionlocal to prevent confusion, since it's similar to GetDLLVersionLocal instruction, not the GetDLLVersion

In the user manual included in NSIS 3.0a1 it says:

5.1.14 !getdllversion

localfilename define_basename

This is similar to GetDLLVersionLocal, only it stores the version number in defines and can therefore be used anywhere, not just inside functions and sections.

!getdllversion "$%windir%\explorer.exe" expv_
!echo "Explorer.exe version is ${expv_1}.${expv_2}.${expv_3}.${expv_4}"
Hestia answered 16/9, 2013 at 14:59 Comment(7)
GetDllVersion runs on the target machine running the installer. He really wants GetDLLVersionLocal, which is evaluated on the building system.Gujral
@EricLaw: No, it's evaulated on the building system; from the NSIS User Manual: This is similar to GetDLLVersionLocal, only it stores the version number in defines and can therefore be used anywhere, not just inside functions and sections. Not: This section does't exist in online manual currently, only in user manual coming with the NSIS installation.Hestia
You're right-- but that's really odd. The NSIS.chm file included in NSIS3.0a has no such information, and says: GetDLLVersionLocal - This is similar to GetDLLVersion, only it acts on the system building the installer. It doesn't really make any sense that GetDllVersion runs on the building system, but testing suggests that it seems to be true.Gujral
GetDLLVersion instruction runs on the target machine, but !getdllversion command runs on the building system. Command should have been named as !getdllversionlocalHestia
@EricLaw, I'm confused after seeing that you opened a ticket about this 4 years ago and thanked after !getdllversion is added in June. sourceforge.net/p/nsis/bugs/913 . Am I missing something ?Hestia
:-) I simply keep forgetting that !getdllversion exists and does something different than getdllversionGujral
I think it's named just !getdllversion because as a compile-time command it's impossible to run it in a non-local environment (the target machine). Maybe that was the reason...Orris

© 2022 - 2024 — McMap. All rights reserved.