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.

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.

Visual Studio - Selecting the AssemblyInfo.cs file in the project

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.

Visual Studio - Adding a TextTemplate item called VersionAutoIncrementer.tt

After you add this file, run it to generate the initial VersionAutoIncrement.cs file:

  • In .NET Framework projects, it runs automatically when you add it.
  • In .NET Core projects, hit Ctrl-S to run it.

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

Visual Studio prompt warning you about running a text template file

Note: If you don’t run it initially in a .NET Core project, the first Build seems to ignore the generated .cs file and it will put the assembly version as 0.0.0.0. Thanks to commenter krs for pointing this out.

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.

In this example, it’s getting the current date/time, making calculations with it, and then sticking the calculations in the version string.

<#@ 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 (or to a props 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 (v16.0). As a commenter pointed out, you can use the variable $(VisualStudioVersion) in the path if you want.

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 (v16.0). As a commenter pointed out, you can use the variable $(VisualStudioVersion) in the path if you want.

6 – Build the project

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

Visual Studio - Assembly version source file generated by the text template

Comments are closed.