How to manage assembly version numbers in .NET using CUSTOM MS BUILD TASKS?

What is an assembly version number?

Every .NET assembly is identified by a version number. It is stored in its manifest along with assembly name, public key and other dependency information. This information is used to enforce policy restrictions and to differentiate it from other assemblies. Version information is stored in AssemblyInfo.cs file in the following format.

[assembly: AssemblyVersion(“1.0.0.0”)]

. . .

: typically you would increment this for any major releases such as adding new features, major upgrades to any components

: you would increment it for enhancements to existing functionality.

: this has to be incremented everytime the solution is BUILT.

: this would be incremented when you make any releases for bug fixes or for multiple released to QA env.

How to increment Version numbers automatically?

You could change the version number format in such a way that Visual studio would increment the number itself by specifying “*” instead of the actual number.

[assembly: AssemblyVersion(“1.0.0.*”)]

In this case, revision number is incremented everytime the library is compiled.

CONS:

When you do a deployment, you always mark the product with the release number. If you use autoincrement then the version number of the assembly wouldn’t match with the system build number.

A new version of an assembly is generated each time it is built regardless of whether any changes have been made to the assembly. For strongly named assemblies, this means that all clients of that assembly must also be rebuilt to point to the correct version. However, if the build process rebuilds the whole system this should not be an issue.

Solution:

In this article I would describe to how better control the version number generation using MS BUILD TASKS.

1) Create a new “Class Library” project.

2) Add references to Microsoft.Build.Framework, Microsoft.Build.Tasks, & Microsoft.Build.Utilities.v3.5

3) Create a new class CustomBuildTask. Extend it from Task

4) Create a variable FileName. This would be set the by consuming applications to the name of the file where assembly version information is stored (typically AssemblyInfo.cs)

string m_sFileName; [Required] public string File { get { return m_sFileName; } set { m_sFileName = value; } }

5) Create some class variables to identify if it was supposed to increment major or minor or build or revision. These would again be set by client as part of their build definition. I would go in detail later about how to do that.

bool m_bIncrementBuild = false; bool m_bIncrementMinor = false; public bool IncrementBuild { get { return m_bIncrementBuild; } set { m_bIncrementBuild = value; } } /// <summary> /// Task argument /// </summary> public bool IncrementMinor { get { return m_bIncrementMinor; } set { m_bIncrementMinor = value; } }

6) Override the execute method of TASK CLASS to write our own custom logic to increment the version number. We read the contents of the file contents. We use regular expressions to search for the version number and increment the same. We finally write all the contents back to the file.

Regex reg = new Regex ( "^(?'Part1'\\s*\\[\\s*assembly\\s*:.*Assembly.*\\s*\\(\\s*\")" + "(?'Major'\\d+)\\.(?'Minor'\\d+)\\.(?'Build'\\d+)\\.(?'Reves'\\d+)" + "(?'Part2'\"\\s*\\)\\s*].*)" ); public override bool Execute() { if (System.IO.File.Exists(m_sFileName)) { System.Text.RegularExpressions.MatchCollection matches; string[] content = ReadFileContent(m_sFileName);; decimal[] versionparts = new decimal[4]; for (int index = 0; index < content.Length; index++) { matches = reg.Matches(content[index]); if (matches.Count >= 1) { try { checked { versionparts[0] = decimal.Parse(matches[0].Groups["Major"].Value); versionparts[1] = decimal.Parse(matches[0].Groups["Minor"].Value); versionparts[2] = decimal.Parse(matches[0].Groups["Build"].Value); versionparts[3] = decimal.Parse(matches[0].Groups["Reves"].Value); } if(m_bIncrementBuild) versionparts[2] += 1; if (m_bIncrementMinor) versionparts[1] += 1; content[index] = string.Format ("{0}{1}.{2}.{3}.{4}{5}", matches[0].Groups["Part1"].Value, versionparts[0], versionparts[1], versionparts[2], versionparts[3], matches[0].Groups["Part2"].Value ); } catch { throw; } } } WriteToFile(content, m_sFileName); } return true; } private string[] ReadFileContent(string filename) { List<string> content = new List<string>(); string line; using (StreamReader sr = new StreamReader(filename)) { while ((line = sr.ReadLine()) != null) { if (line != null && line != "

") content.Add(line); } } return content.ToArray(); } private void WriteToFile(string[] content, string filename) { StreamWriter sw = null; try { sw = new StreamWriter(filename); StringBuilder sb = new StringBuilder(); for (int index = 0; index < content.Length; index++) { if (index == content.Length - 1) { sb.Append(content[index].Trim(new char[] { '\r' })); } else { sb.AppendLine(content[index].Trim(new char[] { '\r' })); } } sw.Write(sb.ToString()); } catch { throw; } finally { if (sw != null) { sw.Flush(); sw.Close(); sw = null; } } }

7) We have our custom build task ready. Now let’s find out how to use it in of our projects.

Create a new project of type “Console Application”.

8) Right Click on the project and say “Unload Project”

9) Right click on the unloaded project and click “Edit ***.Proj”

10) Add the following line right under the project element. We are mapping the task that is referenced in a TASK element to the assembly that contains the task implementation.

11) Since we want the version number to be incremented before the build, add the following Before BUILD event. As you can see, we can set the various properties of task depending upon our requirement. Here I am saying when the configuration selected is DEBUG or LOCAL, increment the minor version.

ProjectDir)\Properties\AssemblyInfo.cs” IncrementMinor=”true” Condition=”‘$(Configuration)’ == ‘Debug’ Or ‘$(Configuration)’ == ‘LOCAL'”>