Determine assembly version during a post-build event

Solution 1:

If (1) you don't want to download or create a custom executable that retrieves the assembly version and (2) you don't mind editing the Visual Studio project file, then there is a simple solution that allows you to use a macro which looks like this:

@(Targets->'%(Version)')

@(VersionNumber)

To accomplish this, unload your project. If the project somewhere defines a <PostBuildEvent> property, cut it from the project and save it elsewhere temporarily (notepad?). Then at the very end of the project, just before the end-tag, place this:

<Target Name="PostBuildMacros">
  <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="Targets" />
  </GetAssemblyIdentity>
  <ItemGroup>
    <VersionNumber Include="@(Targets->'%(Version)')"/>
  </ItemGroup>
</Target>
<PropertyGroup>
  <PostBuildEventDependsOn>
    $(PostBuildEventDependsOn);
    PostBuildMacros;
  </PostBuildEventDependsOn>    
  <PostBuildEvent>echo HELLO, THE ASSEMBLY VERSION IS: @(VersionNumber)</PostBuildEvent>
</PropertyGroup>

This snippet has an example <PostBuildEvent> already in it. No worries, you can reset it to your real post-build event after you have re-loaded the project.

Now as promised, the assembly version is available to your post build event with this macro:

@(VersionNumber)

Done!

Solution 2:

If you prefer scripting these methods might also work for you:

If you are using the post-build event, you can use the filever.exe tool to grab it out of the already built assembly:

for /F "tokens=4" %%F in ('filever.exe /B /A /D bin\debug\myapp.exe') do (
  set VERSION=%%F
)
echo The version is %VERSION%

Get filever.exe from here: http://support.microsoft.com/kb/913111

If you are using the pre-build event, you can take it out of the AssemblyInfo.cs file as follows:

set ASMINFO=Properties\AssemblyInfo.cs
FINDSTR /C:"[assembly: AssemblyVersion(" %ASMINFO% | sed.exe "s/\[assembly: AssemblyVersion(\"/SET CURRENT_VERSION=/g;s/\")\]//g;s/\.\*//g" >SetCurrVer.cmd
CALL SetCurrVer.cmd
DEL SetCurrVer.cmd
echo Current version is %CURRENT_VERSION%

This uses the unix command line tool sed, which you can download from many places, such as here: http://unxutils.sourceforge.net/ - iirc that one works ok.

Solution 3:

As a workaround I've written a managed console application which takes the target as a parameter, and returns the version number.

I'm still interested to hear a simpler solution - but I'm posting this in case anyone else finds it useful.

using System;
using System.IO;
using System.Diagnostics;
using System.Reflection;

namespace Version
{
    class GetVersion
    {
        static void Main(string[] args)
        {
            if (args.Length == 0 || args.Length > 1) { ShowUsage(); return; }

            string target = args[0];

            string path = Path.IsPathRooted(target) 
                                ? target 
                                : Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + Path.DirectorySeparatorChar + target;

            Console.Write( Assembly.LoadFile(path).GetName().Version.ToString(2) );
        }

        static void ShowUsage()
        {
            Console.WriteLine("Usage: version.exe <target>");
        }
    }
}

Solution 4:

This answer is a minor modification of the answer of Brent Arias. His PostBuildMacro worked quite well for me until a version update of Nuget.exe.

In the recent releases, Nuget trims non significant parts of the package version number in order to obtain a semantic version like "1.2.3". For example, the assembly version "1.2.3.0" is formatted by Nuget.exe "1.2.3". And "1.2.3.1" is formatted "1.2.3.1" as expected.

As I need to infer the exact package filename generated by Nuget.exe, I use now this adaptated macro (tested in VS2015):

<Target Name="PostBuildMacros">
  <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
    <Output TaskParameter="Assemblies" ItemName="Targets" />
  </GetAssemblyIdentity>
  <ItemGroup>
    <VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />
  </ItemGroup>
</Target>
<PropertyGroup>
  <PostBuildEventDependsOn>
    $(PostBuildEventDependsOn);
    PostBuildMacros;
  </PostBuildEventDependsOn>    
  <PostBuildEvent>echo HELLO, THE ASSEMBLY VERSION IS: @(VersionNumber)</PostBuildEvent>
</PropertyGroup>

UPDATE 2017-05-24: I corrected the regex in this way: "1.2.0.0" will be translated to "1.2.0" and not "1.2" as previously coded.


And to answer to a comment of Ehryk Apr, you can adapt the regex to keep only some part of the version number. As an example to keep "Major.Minor", replace:

<VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />

By

<VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^([^\.]+)\.([^\.]+)(.*)$&quot;, &quot;$1.$2&quot;))" />

Solution 5:

I think the best thing you can do is look at MSBuild and MsBuild Extension Pack you should be able to edit you solution file so that a post build event occurs and writes to your test file.

If this is too complicated then you could simply create a small program that inspects all assemblies in you output directory and execute it on post build, you could pass in the output directory using the variable name... for example in the post build event...

AssemblyInspector.exe "$(TargetPath)"

class Program
{
    static void Main(string[] args)
    {
        var assemblyFilename = args.FirstOrDefault();
        if(assemblyFilename != null && File.Exists(assemblyFilename))
        {
            try
            {
                var assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFilename);
                var name = assembly.GetName();

                using(var file = File.AppendText("C:\\AssemblyInfo.txt"))
                {
                    file.WriteLine("{0} - {1}", name.FullName, name.Version);
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }
    }
}

You could also pass in the text file location...