67

Let's say I wanted to create a static text file which ships with each release. I want the file to be updated with the version number of the release (as specified in AssemblyInfo.cs), but I don't want to have to do this manually.

I was hoping I could use a post-build event and feed the version number to a batch file like this:

call foo.bat $(AssemblyVersion)

However I can't find any suitable variable or macro to use.

Is there a way to achieve this that I've missed?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Winston Smith
  • 21,585
  • 10
  • 60
  • 75
  • read all the answers for the one that's most appropriate to you. The 'PostBuildEventDependsOn' one is the highest voted and simplest. (watch out for the special '25' Unicode trick when pasting' – OzBob Sep 21 '15 at 07:43

13 Answers13

105

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!

Riegardt Steyn
  • 5,431
  • 2
  • 34
  • 49
Brent Arias
  • 29,277
  • 40
  • 133
  • 234
  • 1
    It outputs THE ASSEMBLY VERSION IS: (Version) for me – Poul K. Sørensen Oct 23 '13 at 14:32
  • 1
    It did for me as well. Problem was that '25' sneaked in via the post build editor in visual studio, you will see it if you text-edit csproj @(Targets->'%25(Version)'). It works like a charm when 25 is removed... – jmelhus Dec 03 '13 at 18:25
  • Excellent, after finding the 25 that kept sneaking in (WTH is that about anyway?) I've got a nicely automated package build. Now I just need to make it conditionally fire only when building in Release mode and we're golden. Thanks! – Mike Devenney Jan 28 '14 at 19:23
  • 1
    @MikeDevenney ... if "$(ConfigurationName)" == "Release" goto :release {new line} :debug {some code unique to debug mode} goto :end {new line} :release {some code unique to release mode} :end {some finalisation code} – Riegardt Steyn Jul 01 '14 at 10:35
  • 1
    @BrentArias - This will re-insert a "%25" escape character for the percentage sign, every time you edit your post-build events in Visual Studio. I have edited your post to introduce a simple work-around (hope you don't mind). – Riegardt Steyn Jul 01 '14 at 13:25
  • @Heliac: No, I don't mind the change. However, as long as you are going to take that approach, it would make more sense to assign `@(Targets->'%(Version)')` to a `$(VersionNumber)` in a PropertyGroup. – Brent Arias Jul 01 '14 at 19:53
  • 1
    I'm trying to use this to get `Major.Minor` only, or `Major` and `Minor` separately. Nothing I try seems to be working. How can I get these properties separately? – Ehryk Apr 02 '15 at 01:23
  • Thanks! How can I change this to return the FileVersion? – Matt Fitzmaurice Apr 08 '15 at 05:10
  • @Matt: The "GetAssemblyIdentity" MSBuild task does not provide any other property than the "Version" which we've already seen. However, I wonder if that property is implementing in terms of "return AssemblyInformationalVersion ?? AssemblyFileVersion ?? AssemblyVersion". If yes, then it already is returning "FileVersion" under the right circumstances. – Brent Arias Apr 19 '15 at 20:34
  • 1
    Thanks for that - awesome approach. As an addendum - there's no need to cut/paste your existing build script, as long as you're ok to insert the `` block yourself before it. Now to just get the major/minor numbers... – tobriand May 07 '15 at 13:57
  • This is a great answer. It helped me improve all of my post-build scripts – Bassie Sep 19 '16 at 14:44
  • @Ehryk: probably late, but see my answer based on a regex to keep only `Major.Minor` part. – Eric Boumendil May 04 '17 at 13:43
  • @BrentArias you mention using `$(VersionNumber)` in a property group - how do you do that with this? The @(VersionNumber) does not work for me, just leaves it blank. Your default post-build works however... – Mgamerz Jun 04 '17 at 21:30
  • @Mgamerz Perhaps you have a build or post-build step that moves the target assemblies to a new location before this `GetAssemblyIdentity` task has a chance to run? – Brent Arias Jun 05 '17 at 16:31
  • Nope, I don't move any files. I am calling 7z.exe on the folder to pack up a release for me. In the parameters for 7z I use a filename e.g. `AddonMaker_@(VersionNumber).7z` and a file named `AddonMaker_.7z` is created instead. I removed your post build (the `PostBuildEventDependsOn` container) - I assume I am supposed to do that to remove your example output and make my post build work normally. `Edit:` Derp. I think I see the problem. I need to replace your `PostBuildEvent` line with mine - not substitute yours into mine. I'm a goof. – Mgamerz Jun 06 '17 at 19:13
  • This is a great solution for me! – Rick the Scapegoat Aug 01 '17 at 21:08
  • 4
    This should be the accepted answer, as it will not break the build for external (automated) building systems like Azure DevOps. In fact; anyone can build it without having to install some tooling first. – Kornelis Feb 20 '19 at 09:48
17

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.

Tuinstoelen
  • 1,221
  • 10
  • 8
  • I use Pre-Build event. Now, I have Properties\AssemblyInfo.cs or link to %SolutionDir%\GlobalAssemblyInfo.cs. How can I detect if AssemblyVersion is in Properties\AssemblyInfo.cs or in %SolutionDir%\GlobalAssemblyInfo.cs, and then get the version...?? – Kiquenet Nov 11 '11 at 08:54
  • Perhaps you can use the "IF [NOT] EXIST filename command" function? Type "if /?" on a command prompt for documentation and some samples. – Tuinstoelen Nov 11 '11 at 13:24
12

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>");
        }
    }
}
Even Mien
  • 44,393
  • 43
  • 115
  • 119
Winston Smith
  • 21,585
  • 10
  • 60
  • 75
  • Yeah, I did find this useful, thanks. I don't want to use unix tools and I don't want to use MSBuild, so I used code similar to that provided here by Winston and Rohan. In the postbuild event of a project I invoke this code with 2 arguments, the TargetPath of the assembly and the path to the setup project where the assembly is packaged. In the post build event of the setup, I call msiinfo to move the version data into the MSI so that consumers can see the version they're loading. Thanks. – TonyG Aug 06 '11 at 00:53
  • This should probably be the accepted solution, since filever seems to be getting the file version and not the internal assembly version. I'm using assembly version for my app because it's easily auto-incremented. – transistor1 Oct 17 '12 at 19:46
  • This command line is pretty handy. I can use it to update the version number of an installer to match the version of the app it is installing. One line can put the version number into an environment variable – Chris Miller Sep 05 '13 at 16:45
  • There is absolutely no need to go messing with the input path. Relative paths simply _work_, you know. If they choose to use relative directories when not in the folder the exe file is from, that should work as intended. Anyway, I needed the version number for automatically parsing in a readme file, and I went more or less this way, with a small external app to get the version for me. The best part is that I could just take the version formatting code from my main program and just copy it into this app :) – Nyerguds Feb 06 '16 at 14:24
12

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;))" />
Eric Boumendil
  • 2,318
  • 1
  • 27
  • 32
4

I don't know Why but Brent Arias macro not worked for me (@(VersionNumber) always was empty) :( .Net6 VS2022. I ended up with slightly modified version:

<Target Name="GetVersion" AfterTargets="PostBuildEvent">
    <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
        <Output TaskParameter="Assemblies" ItemName="AssemblyInfo" />
    </GetAssemblyIdentity>
    <PropertyGroup>
        <VersionInfo>%(AssemblyInfo.Version)</VersionInfo>
    </PropertyGroup>
    <!--And use it after like any other variable:-->
    <Message Text="VersionInfo = $(VersionInfo)" Importance="high" />
</Target>
Kirsan
  • 254
  • 3
  • 5
2

Unless I'm missing something, this is a lot simpler. Put this in your pre or post-build scripts:

FOR /F delims^=^"^ tokens^=2 %%i in ('findstr /b /c:"[assembly: AssemblyVersion(" $(ProjectDir)\Properties\AssemblyInfo.cs') do (set version=%%i)
echo Version: %version%
stigzler
  • 793
  • 2
  • 12
  • 29
  • 1
    Upvoted, but there is an important space character missing in the script right before '$(ProjectDir)' but after the closing quotation mark. (I am unable to edit myself because the system says I had to edit at least 6 characters.) – Lynax Mar 17 '23 at 14:09
  • Congratulations my erudite friend! You have just won the prize as the most assiduous-missing-space-spotter of 2023. You have surpassed the humdrum moribund everyday rumblings of your peers. With skills like that, you will surely go far! – stigzler Mar 17 '23 at 22:11
1

From that what I understand...

You need a generator for post build events.

1. Step: Writing a Generator

/*
* Author: Amen RA
* # Timestamp: 2013.01.24_02:08:03-UTC-ANKH
* Licence: General Public License
*/
using System;
using System.IO;

namespace AppCast
{
    class Program
    {
        public static void Main(string[] args)
        {
            // We are using two parameters.

            // The first one is the path of a build exe, i.e.: C:\pathto\nin\release\myapp.exe
            string exePath = args[0];

            // The second one is for a file we are going to generate with that information
            string castPath = args[1];

            // Now we use the methods below
            WriteAppCastFile(castPath, VersionInfo(exePath));
        }


        public static string VersionInfo(string filePath)
        {
            System.Diagnostics.FileVersionInfo myFileVersionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(filePath);
            return myFileVersionInfo.FileVersion;
        }


        public static void WriteAppCastFile(string castPath, string exeVersion)
        {
            TextWriter tw = new StreamWriter(castPath);
            tw.WriteLine(@"<?xml version=""1.0"" encoding=""utf-8""?>");
            tw.WriteLine(@"<item>");
            tw.WriteLine(@"<title>MyApp - New version! Release " + exeVersion + " is available.</title>");
            tw.WriteLine(@"<version>" + exeVersion + "</version>");
            tw.WriteLine(@"<url>http://www.example.com/pathto/updates/MyApp.exe</url>");
            tw.WriteLine(@"<changelog>http://www.example.com/pathto/updates/MyApp_release_notes.html</changelog>");
            tw.WriteLine(@"</item>");
            tw.Close();
        }
    }
}

2. Step: Using it as a post build command in our IDE

After the application is running satisfyingly for you:

In your development IDE, use the following command line for post build events.

C:\Projects\pathto\bin\Release\AppCast.exe "C:\Projects\pathto\bin\Release\MyApp.exe" "c:\pathto\www.example.com\root\pathto\updates\AppCast.xml"
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Amen Ra
  • 19
  • 1
1

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...

Rohan West
  • 9,262
  • 3
  • 37
  • 64
1

I've started adding a separate project that builds last and adding a post build event to that project that runs itself. Then I just perform my post build steps programmatically in there.

It makes it a lot easier to do stuff like this. Then you can just inspect the assembly attributes of whatever assembly you want. So far it's working pretty awesome.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Joshua Evensen
  • 1,544
  • 1
  • 15
  • 33
1

It should be noted that using the modernized (VS2017+) .csproj formatting and VS2022, $(AssemblyVersion) as in the original post can now be used directly.

  • Hmm. On VS 2022 Community here and it didn't work for me. Also, not in the Macros list in the Build Events window... – stigzler Dec 26 '22 at 22:22
0

I needed exactly this for automatically putting the number in the readme file in the output folder. In the end, as Winston Smith showed, a small external tool is a very good solution for that, and it has the advantage you can format it however you want.

This app outputs the formatted version to the console. I used it in my post-build events to build the readme file by calling it with >> to redirect its output to the readme file.

public class GetVerNum
{
    static void Main(String[] args)
    {
        if (args.Length == 0)
            return;
        try
        {
            FileVersionInfo ver = FileVersionInfo.GetVersionInfo(args[0]);
            String version = "v" + ver.FileMajorPart.ToString() + "." + ver.FileMinorPart;
            if (ver.FileBuildPart > 0 || ver.FilePrivatePart > 0)
                version += "." + ver.FileBuildPart;
            if (ver.FilePrivatePart > 0)
                version += "." + ver.FilePrivatePart;
            Console.Write(version);
        }
        catch { }
    }
}

My post-build events:

<nul set /p dummyset=My Application > "$(ProjectDir)\Readme\readme-header.txt"
"$(ProjectDir)\Readme\GetVersionNumber.exe" "$(TargetPath)" >>"$(ProjectDir)\Readme\readme-header.txt"
echo  by Nyerguds>>"$(ProjectDir)\Readme\readme-header.txt"
echo Build date: %date% %time% >> "$(ProjectDir)\Readme\readme-header.txt"
echo.>>"$(ProjectDir)\Readme\readme-header.txt"
copy /b "$(ProjectDir)\Readme\readme-header.txt" + "$(ProjectDir)\Readme\readme-body.txt" "$(TargetDir)\$(ProjectName).txt"

I put all the readme generating related stuff in the \Readme\ folder of my project; the app containing the above code, and the "readme-body.txt" containing the actual readme stuff.

  • First line: create the "readme-header.txt" file in the \Readme\ folder of my project, and put the program name inside it. (The <nul set /p dummyset= is a trick I found here: Windows batch: echo without new line). You could also store this string in another text file and just copy that to "readme-header.txt" instead.
  • Second line: run the version number retrieving app with the freshly-generated exe file as parameter, and add its output to the header file.
  • Third line: add any other stuff (in this case, credits) to the header file. This also adds a line break to the end.

These three together give you a "readme-header.txt" file with "My Application v1.2.3 by Nyerguds", followed by a line break, in it. Then I add the build date and another open line, and copy the header file and the readme body file together to one file in the final build folder. Note that I specifically use binary copy, otherwise it gives odd results. You do have to make sure the body file contains no UTF-8 byte order mark at the start, or you get weird bytes in your final file.

Community
  • 1
  • 1
Nyerguds
  • 5,360
  • 1
  • 31
  • 63
0

If you have a library project you can try to use WMIC utility (available in windows). Here is an example. Good thing - you don't need to use any external tools.

SET pathFile=$(TargetPath.Replace("\", "\\"))

FOR /F "delims== tokens=2" %%x IN ('WMIC DATAFILE WHERE "name='%pathFile%'" get  Version /format:Textvaluelist')  DO (SET dllVersion=%%x)
echo Found $(ProjectName) version %dllVersion%
DolceVita
  • 2,090
  • 1
  • 23
  • 35
0

I looked for the same feature and i found the solution on MSDN. https://social.msdn.microsoft.com/Forums/vstudio/de-DE/e9485c92-98e7-4874-9310-720957fea677/assembly-version-in-post-build-event?forum=msbuild

$(ApplicationVersion) did the Job for me.

Edit:

Okay I just saw the Problem $(ApplicationVersion) is not from AssemblyInfo.cs, its the PublishVersion defined in the project Properties. It still does the job for me in a simple way. So maybe someone needs it too.

Another Solution:

You can call a PowerShell script on PostBuild, here you can read the AssemblyVersion directly from your Assembly. I call the script with the TargetDir as Parameter

PostBuild Command:

PowerShell -ExecutionPolicy Unrestricted $(ProjectDir)\somescript.ps1 -TargetDir $(TargetDir)

PowerShell Script:

param(
    [string]$TargetDir
)

$Version = (Get-Command ${TargetDir}Example.exe).FileVersionInfo.FileVersion

This way you will get the Version from the AssemblyInfo.cs

Momo
  • 456
  • 5
  • 22