A colleague, Richard Banks, has previously blogged on this topic (http://www.richard-banks.org/2010/07/how-to-versioning-builds-with-tfs-2010.html), using custom activities and modifying the build workflow.
However, I also like the approach taken by John Robbins (http://www.wintellect.com/CS/blogs/jrobbins/archive/2010/06/15/9994.aspx).
John's approach is done entirely within the build file, using some of the new features of MSBuild 4.0, and therefore has no dependencies except what is already on a TFS build server.
Based heavily on John's work, I've created my own build targets that are based on the same core features but tailored to the way I like to work.
The build number is still based on the TFS build number, however rather than a base year of 2001 I pass it in as a property. This avoids the problem that a build on 31 Dec 2012 could be number 1.0.51231.1, whereas the one on 1 Jan 2013 would be 220.127.116.11. By setting the base year to the year your project starts you ensure your build numbers start low and increase.
(If you start reaching the end, after five years or so, you can always reset the base year after changing the minor version).
I also have an option to read the major and minor versions from the existing AssemblyInfo.cs file, rather than having them set in the build script, which I find a useful way to allow me to change the version number.
Like John's script, I only update AssemblyFileVersion, which can help in a multiple-project situation where there are version dependencies on strong names (such as in config files).
However, rather than a central shared version info file, I write the updated version number back into the projects AssemblyInfo.cs file.
The benefit of John's original approach is that you can have a separate project dependency that updates a central file for all builds, whereas with my approach you need to update each project's build (.csproj) file. On the other hand the negative with the original approach is that you need to change the structure of projects to point to the shared file, whereas I keep them self-contained with the original AssemblyInfo.cs file (similar to Richard's approach).
I only made changes to the C# project type, which is where I do most of my work, and so don't support all the project types that John's script does (VB.NET, C++, etc).
The only other output I have implemented is writing straight into a text file, which I find useful to copy to the output directory as a easy way to reference the build. This is particularly useful for web projects, where they are a directory full of .aspx files (and you can also hit the Version.txt file from a browser).
To use the script, include the TFSBuildNumber.targets file somewhere in your solution or project, then copy the example lines from Example.csproj to the .csproj files for projects you want to version.
To do this from within Visual Studio, first you need to right click and unload the project, then right click and open the file for editing. After pasting in the code, save and close the file, then reload the project.
The end of your .csproj file should look something like this (alter the path depending on where you placed the TFSBuildNumber.targets file):
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Import Project="..\TFSBuildNumber.targets" />
<Target Name="BeforeBuild" DependsOnTargets="GetMajorMinorFromCSharpAssemblyInfoFile;WriteProjectTextAssemblyVersionFile;UpdateCSharpAssemblyInfoFile">
One benefit of having the version numbers line up with the TFS build numbers is that given a particular DLL you can check the version number and then easily translate to the particular build it came from, e.g. (with a base year of 2010) the version number 1.0.924.5 comes from TFS build number 20100924.5.
If you want to use this in your project, download the TFSBuildNumber.targets file from the Essential Diagnostics project on CodePlex.