Auto-increment build numbers in Visual Studio

You need to auto-increment your build numbers in order to easily tell which code you’re working with.

In this article I’ll explain how to auto-increment your build numbers in Visual Studio. I’ll be using text templating to generate the Assembly Version.

Update (2021-3-27): I added information about how to handle this .NET Core projects.

1 – Choose a versioning scheme

I’m going to be using the version scheme: <Major Version>.<Minor Version>.<Days Since Project Started>.<Minutes Since Midnight>. You should use whatever makes sense for you.

The one rule you must adhere to is the numbers must be <= 65534 (because they are 16-bit). If you generate a number greater than 65534, you’ll get a build error:

CS7034 The specified version string does not conform to the required format - major[.minor[.build[.revision]]]
Code language: plaintext (plaintext)

There are 86400 seconds per day and 1440 minutes per day. This is why I chose Minutes Since Midnight instead of Seconds Since Midnight. Since 86400 > the limit of 65534, using seconds would sometimes result in the build error shown above. By using minutes, this cannot happen.

2 – Comment out the assembly version properties

Open AssemblyInfo.cs and comment out AssemblyVersion and AssemblyFileVersion.

Note: This step doesn’t apply to .NET Core projects, unless you added an AssemblyInfo.cs file manually.

3 – Add a Text Template file

A Text Template is used to generate code. We’ll be using this to generate the assembly version.

After you add this file you’ll get a warning prompt. Since you’re the one adding this file, you can check the box and click OK.

4 – Update the Text Template to generate the AssemblyVersion property

There’s two parts to the text template:

  1. Specifying the template using placeholder variables.
  2. Populating the placeholder variables.
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> using System.Reflection; [assembly: AssemblyVersion("<#= this.Major #>.<#= this.Minor #>.<#= this.DaysSinceProjectStarted #>.<#= this.MinutesSinceMidnight #>")] <#+ int Major = 1; int Minor = 0; static DateTime ProjectStartedDate = new DateTime(year: 2020, month: 3, day: 12); int DaysSinceProjectStarted = (int)((DateTime.UtcNow - ProjectStartedDate).TotalDays); int MinutesSinceMidnight = (int)DateTime.UtcNow.TimeOfDay.TotalMinutes; #>
Code language: C# (cs)

5 – Update .csproj to run the text template every time it builds

You have to add a few a few properties to the .csproj file to make it run the transform every time it builds. Check the appropriate section below based on if you’re using .NET Framework or .NET Core.

Updating the .csproj in a .NET Framework project

  1. Edit your .csproj in Notepad.
  2. Make two changes:
    • Import Microsoft.TextTempatings.targets.
    • Add the TransformOnBuild, OverwriteReadOnlyOutputFiles, and TransformOutOfDateOnly properties to each build configuration.

The .csproj below shows these highlighted changes:

<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{221097A0-A3F4-45CC-A6C0-B13455C6EAFE}</ProjectGuid> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>AutoIncrementingBuild</RootNamespace> <AssemblyName>AutoIncrementingBuild</AssemblyName> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <Deterministic>true</Deterministic> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>bin\Debug\</OutputPath> <DefineConstants>DEBUG;TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <TransformOnBuild>true</TransformOnBuild> <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles> <TransformOutOfDateOnly>false</TransformOutOfDateOnly> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> <DefineConstants>TRACE</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <TransformOnBuild>true</TransformOnBuild> <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles> <TransformOutOfDateOnly>false</TransformOutOfDateOnly> </PropertyGroup> <ItemGroup> <Reference Include="System" /> <Reference Include="System.Core" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Net.Http" /> <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="VersionAutoIncrement.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>VersionAutoIncrement.tt</DependentUpon> </Compile> </ItemGroup> <ItemGroup> <Content Include="VersionAutoIncrement.tt"> <Generator>TextTemplatingFileGenerator</Generator> <LastGenOutput>VersionAutoIncrement.cs</LastGenOutput> </Content> </ItemGroup> <ItemGroup> <Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v16.0\TextTemplating\Microsoft.TextTemplating.targets" /> </Project>
Code language: HTML, XML (xml)

Note: This is specifically adding the VS2019 path.

Updating the .csproj in a .NET Core project

  1. Click on the project so it opens the .csproj file for editing.
  2. Add the following properties
    1. Import Microsoft.TextTempatings.targets.
    2. Disable GenerateAssemblyInfo.
    3. Add the TransformOnBuild, OverwriteReadOnlyOutputFiles, and TransformOutOfDateOnly.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp3.1</TargetFramework> </PropertyGroup> <Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v16.0\TextTemplating\Microsoft.TextTemplating.targets" /> <PropertyGroup> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <TransformOnBuild>true</TransformOnBuild> <OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles> <TransformOutOfDateOnly>false</TransformOutOfDateOnly> </PropertyGroup> <ItemGroup> <None Include="VersionAutoIncrement.cs"> <DesignTime>True</DesignTime> <AutoGen>True</AutoGen> <DependentUpon>VersionAutoIncrement.tt</DependentUpon> </None> </ItemGroup> <ItemGroup> <None Update="VersionAutoIncrement.tt"> <Generator>TextTemplatingFileGenerator</Generator> <LastGenOutput>VersionAutoIncrement.cs</LastGenOutput> </None> </ItemGroup> <ItemGroup> <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" /> </ItemGroup> </Project>
Code language: HTML, XML (xml)

If you don’t disable GenerateAssemblyInfo, then you’ll get a compile time error saying there’s a duplicate AssemblyVersion.

Note: This is specifically adding the VS2019 path.

6 – Build the project

When you build the project it’ll execute the text template. This generates a source file with the AssemblyVersion property.

37 thoughts on “Auto-increment build numbers in Visual Studio”

  1. To me, this is totally not working!
    Yes, the VersionAutoIncrementer.cs file is created, but it is stuck in its original version number.
    E.g. stuck in:

    using System.Reflection;
    [assembly: AssemblyVersion(“8.0.220.927”)]

    Reply
    • Hi Bjorn,

      Good catch. I updated the page to include instructions for how to update your .csproj file to make it run the text templating transform on every build.

      Please take a look at the step I added to the page – 5 – Update .csproj to run the text template every time it builds.

      Let me know if this helps.

      Reply
  2. I have created the template for shared assembly info.tt its updating version number only once for the first build or rebuild.
    If I change some code and try to build the whole project it’s not reflecting the version number.

    Reply
      • Hi Mak,
        Yes, I did. if I do changes related to that project then the revision number getting updated.if anything other than that project it’s not updating.

        Below is my code in SharedAssembyInfo.tt

        using System.Reflection;

        [assembly: AssemblyConfiguration(“”)]
        [assembly: AssemblyCompany(“GRIDSMART Technologies, Inc.”)]
        [assembly: AssemblyCopyright(“Copyright © GTI 2019”)]
        [assembly: AssemblyTrademark(“”)]
        [assembly: AssemblyCulture(“”)]
        [assembly: AssemblyVersion(“…”)]

        And followed all the steps included the below line in .csproj

        Reply
        • The text template only runs if the project is built. If there’s no changes in the project, then clicking “Build” in Visual Studio won’t build that project. Therefore it won’t run the text template.

          I’m not aware of a way to make “Build” build all projects, even if there are no changes.

          However, since it sounds like you want to run the text template every time, regardless of changes to the project, it really sounds like you want to use “Rebuild” instead of “Build” every time. That’s the simplest solution. There may be other ways to force VS to build your unchanged project, but “Rebuild” definitely makes it do that.

          Reply
    • Hi Chris,

      That’s unusual that the Text Template is missing. What version of Visual Studio are you using? Which .NET version? and which language (C# or VB)?

      Do you have the Text Template Transformation component installed?

      Update (2021-3-27): I just ran into this when using a specific project type. You can just add the Text File, change the extension to .tt, and paste in the text templating code (use the code shown in this article as a starting point). VS updates the .csproj the same way as if you had added a Text Template new item. I think this is a bug in VS, where it’s not showing the Text Template item as an option, even though it’s installed.

      Reply
    • Hi Sid,

      Thanks for your question. I realized I didn’t have a section showing how to update a .NET Core .csproj. So I just added this section: Updating the .csproj in a .NET Core project

      Regarding your question about Blazor .NET Core specifically, the instructions in the article (including the new .NET Core section) work fine. I just tried it out by creating a new Blazor app targeting .NET Core. I added the VersionAutoIncrement.tt and .csproj updates to all three projects (Shared/Client/Server). Then rebuilt the solution and it ran all the transforms and updated all of their assembly versions. Note: I haven’t used Blazor before, so I’m not sure if updating the version in all three projects would matter.

      A few things:
      1. When I tried to add a new Text Template in the Client/Server projects, “Text Template” was missing from this list. I just picked a Text File and changed the extension to .tt and put the text templating code (from this article) and it was fine. It must be a bug in VS causing it to not show Text Template in this list of new items.

      2. The text template runs for each project separately. In this article, it shows how to put the TotalMinutes as part of the version. This means if you are building multiple projects, it’s possible for the final assembly versions to be different between different assemblies built together.

      Please try it out and let me know how it goes.

      Reply
  3. Hi Mak,

    Thanks. This is exactly what i was looking for ! It works well with .Net 5 (windows form app)
    I have replaced the date with an increment counter at each build
    So I manually manage major, minor, revision and build is automatically increase by 1

    Bye

    Reply
  4. Is there a way to reference the generated version number in an HTML page (i.e. layout page) to display the version number? Since I’ve never worked with templates or generated files, I can’t figure out the proper way to reference the generated cs file to access the assembly parameter.

    Reply
    • Hi Chris,

      The generated assembly version can be accessed programmatically at runtime by calling System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;

      What type of project are you referring to?

      I just created an ASP.NET MVC project (with the default controllers and views) and got it to display the generated assembly version by doing this:
      1. In Controllers > HomeController.Index() I put ViewBag.Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
      2. In Views > Home > Index.cshtml, I put <p>@ViewBag.Version</p>
      3. Launch the homepage. The version appears at the bottom.

      I hope this helps. If not, please let me know what type of project you are doing exactly.

      Reply
      • It is a .NET MVC project. It’s just passing 0.0.0.0 for me. The actual version in the generated .cs file is working as intended, but it’s not being referenced properly for some reason. AssemblyInfo lines are commented out. Template is created and version is being generated on build. Is there a reference to the generated file that I’m not making somewhere?

        I’m referencing it directly in the Layout vs. sending it from the controller:

        Version: @System.Reflection.Assembly.GetExecutingAssembly().GetName().Version

        is returning

        Version: 0.0.0.0

        But in the generated version file I see: [assembly: AssemblyVersion(“1.0.693.1116”)]

        Reply
        • Try using GetEntryAssembly() instead of GetExecutingAssembly(), like this:
          <p>@System.Reflection.Assembly.GetEntryAssembly().GetName().Version</p>

          When you call @System.Reflection.Assembly.GetExecutingAssembly().GetName().Version directly in the view (instead of populating ViewBag.Version from the controller), the executing assembly is YourAssembly.Views.dll instead of YourAssembly.dll. This is why it’s showing version 0.0.0.0 (the version of YourAssembly.Views.dll) instead of 1.0.693.1116 (the version of YourAssembly.dll).

          Reply
          • This got me closer, but causes a null reference error due to an issue between managed/unmanaged code. It did lead me on the correct path to locate a solution. To get it working directly in the layout page I used:

            typeof([application-namespace].MvcApplication).Assembly.GetName().Version

            This is great work and thanks for all your help in getting this rolling.

          • I’m glad you were able to solve it! Thanks for sharing what worked for you – I’m sure it’ll help others who run into the same scenario.

  5. Hello,
    What is the VB version of this code.
    typeof([application-namespace].MvcApplication).Assembly.GetName().Version

    I get the NullReferenceException as well for a web application in visual studio 2019 in visual basic.

    I appreciate all the work you guys done and thank you for your help.

    Reply
    • Hi Hasan,

      I got this working in an ASP.NET Web App using VB.NET with the following:

      @(GetType(YourProjectNamespace.MvcApplication).Assembly.GetName().Version)

      Reply
  6. Hi Mak
    Thank you for the great instructions, it worked exactly as you said. Do you know how I could reference and use the version created during the build in Wix toolset’s .wxs file?

    Reply
    • Let’s say you’re auto-generating build numbers for a file called ASPNETVersion.dll. And you’re including this file in the WSX file with the following definition:

      <File Id=”MyDLL” Name=”ASPNETVersion.dll” Source=”C:\Projects\ASPNETVersion.dll” />

      To set the installer version to this DLL’s file version, do this:

      <Product Id=”*” Name=”Wix” Language=”1033″ Version=”!(bind.fileVersion.MyDLL)” Manufacturer=”Test” UpgradeCode=”23efaa51-e3b8-46d6-85bb-888f84123ef6″>

      Reply
  7. Hi Mak
    is it possible to include a prompt that asks “do you want to increment the build version?” and then based on the choice increments the assembly version or leaves it unchanged? I have a build configuration for which I sometimes need to autoincrement but at other times I want to leave the versions unchanged.

    Thanks!

    Reply
    • Are there any conditions you can check instead of prompting the user? Let’s say you want to always auto-increment when doing a Release build. In that case, the simplest option is to make the transformation step conditional on the build configuration. To do that, in your .csproj file, move the TransformOnBuild property to its own PropertyGroup and add a condition based on the build configuration:

      <PropertyGroup Condition="'$(Configuration)'=='Release'">
      <TransformOnBuild>true</TransformOnBuild>
      </PropertyGroup>

      Note: This makes all transformations conditional. So if you have more T4 templates than just the one for auto-incrementing, you may not want to do this.

      If there’s no conditions and it’s truly up to the user to decide each time, you may want to consider running the T4 template manually when needed instead of running it automatically with a prompt.

      (paraphrased from our email conversation about this)

      Reply
  8. Thinking about doing this for a Xamarin project. I have a couple of questions, 1…do you think it would work for that type of project. 2…My ideal would be for this to auto increment when I am making changes and building locally, then after committing my changes and kicking off an azure pipeline I would prefer it not increment there. The reason I would want that is so when a QA and a PROD version are each built by the pipeline, they would have the same version number. Any thoughts on how that could work?

    Reply
    • Hi Rob,

      1. This works with Xamarin projects. I created a Xamarin project, followed the steps in this article, and then output the assembly version to a textbox. When running in the Android emulator, it showed the auto-generated version number in the textbox.
      var txtVersion = FindViewById<Android.Widget.TextView>(Resource.Id.appversion);
      txtVersion.Text = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();

      Note: This doesn’t update the verisonCode/versionName properties in AndroidManifest.xml.

      2. Let’s say you’re using the Debug configuration for local builds. You can make the auto-increment only run for this build configuration by making the TransformOnBuild property conditional on the build configuration (like this in the .csproj file):

      <PropertyGroup Condition="'$(Configuration)'=='Debug'">
      <TransformOnBuild>true</TransformOnBuild>
      </PropertyGroup>

      This makes ALL transformations conditional, so if you have any other T4 templates, be aware.

      Reply
  9. I used your solution for a DotNetFramework project a while back, I recently upgraded this project to DotNet 6.0 using VS 2022.
    I am running into an issue with importing the Microsoft.TextTemplating.targets.

    Has anyone successfully used this with Dotnet 6.0 is VS 2022?

    Reply
    • Hi Brad,

      I tested out the instructions in VS2022 and it works. I tried your scenario – upgrading a project to .NET 6 and VS2022. I was getting an error – “Microsoft.TextTemplating.targets was not imported… because the file doesn’t exist” – because I needed to update the path to v17.0 (for VS2022).

      1. Edit the .csproj file
      2. Update <Import Project=”$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v17.0\TextTemplating\Microsoft.TextTemplating.targets” />

      Reply
        • Thanks Vivien. That’s a good suggestion.

          You can put that in the path (instead of hardcoding the version), which helps future-proof it a little bit:
          $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TextTemplating\Microsoft.TextTemplating.targets”

          Reply
  10. Thank you for that! I was looking for easy way to increment versions of my project, and after long time(!) of looking for good solution for that – I have finally find it here.
    I really appreciate that. Thank you once again

    Reply
  11. I want to just increment the revision by one each build, the other three I would manually control – how would I do this?

    FYI – using VS22

    thanks.

    Reply
    • To do that, you’d have to read the current revision number from text template output file (ex: VersionAutoIncrement.cs) and increment it. You could use regex to parse out the revision number from the AssemblyVersion string, but I think it’s simpler to put the revision number in a comment at the top of the file – that makes it easier to extract.

      1. Host.TemplateFile gives you the full text template file path. You can use this to figure out the text template output file.
      2. Read in the file. If you’re using the technique I mentioned, you can just read the first line.
      3. Convert the revision number to an integer and increment it by 1
      4. The build numbers use unsigned 16-bit integers (UInt16 / ushort). So watch out for the upper limit (65534). If you hit the upper limit, you’ll get a compile error. That’s why i’m using UInt16 below. When that goes over the limit, it’ll get an overflow exception, which will end up resetting the revision to 1. You can try to deal with this programmatically, but this would only happen after 65k builds, so personally I’d just keep it simple and tweak the numbers manually to fix it.

      Here’s a full example of a text template that increments the revision by 1:

      <#@ template debug="false" hostspecific="true" language="C#" #>
      <#@ output extension=".cs" #>
      <#@ assembly name="System.Core" #>
      <#@ import namespace="System.IO" #>
      <#@ import namespace="System.Linq" #>
      <#

          try
          {
              string currentDirectory = Path.GetDirectoryName(this.Host.TemplateFile);
              string fullPath = Path.Combine(currentDirectory, "VersionAutoIncrement.cs");

              string currentRevisionNumber = File.ReadLines(fullPath).First().Replace("//", "");
          
              Revision = Convert.ToUInt16(currentRevisionNumber);
              Revision++;
          }
          catch( Exception )
          {
              //Throws the first time since the output file doesn’t exist yet
          }
      #>
      //<#= this.Revision #>
      using System.Reflection;
      [assembly: AssemblyVersion("1.0.0.<#= this.Revision #>")]

      <#+
          UInt16 Revision = 1;
      #>

      Reply
  12. I have got this working in a VS 2022 / .Net Core 6 project, which is great. Just wondering if it’s possible to detect if a specific Publish setting is running, as I would like to not update the revision for one of them.

    Reply
    • You can conditionally run the text templates. Let’s say you want to only run the auto-increment for Release builds. You can do this:
      <PropertyGroup Condition="'$(Configuration)'=='Release'">
      <TransformOnBuild>true</TransformOnBuild>
      </PropertyGroup>

      (note: paraphrased from email exchange)

      Reply

Leave a Comment