Introduction
My team uses TFS (Microsoft Team Foundation Server 2010) to do nightly builds. With TFS, it is very easy to set up nightly builds. However, TFS does not auto-increment the build number or contain any mechanism to do this. This means that every build has the same number, unless a developer changes it. This project is a utility that will increment the build number, every time it is run.
The process for incrementing the build number is:
- Check-out your AssemblyInfo.cs/.vb file
- Increment the lowest digit (1.1.1.1 becomes 1.1.1.2)
- Check-in the file
My team scheduled this utility to run each night before the scheduled build. However, we could have chosen to include it into the build template or workflow. If we ever switch to a “Continuous Integration” process, we will move this utility into the build template (or workflow).
Prerequisites
- To run this, you need to have the TFS client files installed.
- You need permissions to TFS and any TFS path that you will change/increment. If you are running this project (EXE) as a scheduled process, then the scheduler (or task) needs to use an account with those permissions.
Setting Things Up
Project
I created a command line project (with an optional GUI). To do this, I created a “Visual C#“, Windows, “Windows Forms Application”. After the project was created, I went into the Project Properties, under the Application tab, I changed the Output type to “Console Application”. Finally, I changed the Main()
function to detect command-line mode versus UI mode, by testing for command-line arguments:
static void Main(string[] args)
{
if (args == null || args.Length < 1)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
}
else {
if (args[0].Contains("silent"))
Console.WriteLine("Version Revved up to " + TfsRevUp.DoRevUp(
TfsAutoRevUp.Properties.Settings.Default.TfsServerUrl,
TfsAutoRevUp.Properties.Settings.Default.AssemblyInfoFilePath));
else
Console.WriteLine("Use the /silent arg to run in silent mode.\n" +
"Example: TfsRevUp.exe /silent");
}
}
Reference the TFS Assemblies
In the C# project, the following DLLs (TFS assemblies) are needed:
- Microsoft.TeamFoundation.dll
- Microsoft.TeamFoundation.Client.dll
- Microsoft.TeamFoundation.Common.dll
- Microsoft.TeamFoundation.VersionControl.Client.dll
- Microsoft.TeamFoundation.VersionControl.Common.dll
These files can usually be found in the folder: c:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ReferenceAssemblies\v2.0\
Make a Configuration Setting that Points to TFS
I set up this utility to work for one project at a time. The settings went into the app.config file (TfsRevUp.exe.config) so I can change it without recompiling.
Using the Code
All of the work for this utility is in one class, named TfsRevUp
(.cs).
namespace TfsRevUp
{
class TfsRevUp
{
public static string DoRevUp(string TfsServerUrl, string AssemblyInfoFilePath)
{
The values for these two arguments will come from the app.config file (Project Settings, above)
Includes
To accomplish my goals, I need to use two different TFS libraries, at the same time. One is for the TFS server and one is for the TFS client. The names are long, so I used an alias for the client namespace:
using TFS=Microsoft.TeamFoundation.VersionControl.Client;
Screen shot (UI or command line)
- or -
Connect to TFS
TFS is designed to host several services. Source control is only one of them. So I need two connections:
Microsoft.TeamFoundation.Client.TeamFoundationServer tfServer;
TFS.VersionControlServer verServer;
tfServer = new Microsoft.TeamFoundation.Client.TeamFoundationServer(TfsServerUrl);
verServer = tfServer.GetService<TFS.VersionControlServer>();
Create a Workspace
const string cWorkspaceName = "RevUp-TempWorkspace";
TFS.Workspace[] workspaces = verServer.QueryWorkspaces
(cWorkspaceName, verServer.AuthorizedUser, System.Environment.MachineName);
TFS.Workspace wsp;
if (workspaces == null || workspaces.Length < 1)
wsp = verServer.CreateWorkspace(cWorkspaceName, verServer.AuthorizedUser);
else
wsp = workspaces[0];
Map the Workspace to a Local HDD Path
string strLocalPath = System.IO.Directory.GetCurrentDirectory() + "\\Temp\\";
//if the local path (temp) doesn’t exist, create it now
if (!System.IO.Directory.Exists(strLocalPath))
System.IO.Directory.CreateDirectory(strLocalPath);
//map to a local path for editing (unless it already is mapped)
if (!wsp.IsServerPathMapped(AssemblyInfoFilePath))
wsp.Map(AssemblyInfoFilePath, strLocalPath);
Check-out the File
if (!wsp.HasCheckInPermission)
{
return "You do not have permissions to rev-up the project";
}
string strLocalFileName = strLocalPath + "AssemblyInfo.cs";
TFS.PendingSet[] sets = wsp.QueryPendingSets(new string[] { strLocalFileName },
TFS.RecursionType.None, cWorkspaceName, verServer.AuthorizedUser, false);
if (sets.Length < 1)
{
wsp.Get();
wsp.PendEdit(new string[] { strLocalFileName },
TFS.RecursionType.None, null, TFS.LockLevel.CheckOut);
strNewRev = UpdateAssemblyInfoFile(strLocalFileName);
Parse and Up the Rev
Once I’ve gotten a local copy of the Assembly info file, I find the version number, add 1, update the file and save it.
private static string UpdateAssemblyInfoFile(string filename)
{
System.Text.RegularExpressions.MatchCollection matches;
const string cRxMask = "\\\"[0-9]+.[0-9]+.[0-9]+.[0-9]+\\\"";
string newVer = "";
string strFileContent = System.IO.File.ReadAllText(filename);
matches=System.Text.RegularExpressions.Regex.Matches(strFileContent, cRxMask);
foreach (System.Text.RegularExpressions.Match match in matches)
{
newVer = IncrementVer(match.Value);
strFileContent = System.Text.RegularExpressions.Regex.Replace(
strFileContent, match.Value, newVer);
}
System.IO.File.WriteAllText(filename, strFileContent);
return newVer;
}
private static string IncrementVer(string oldVer)
{
string newVer = oldVer.Replace("\"", ""); char[] dot = { '.' };
string[] segments = newVer.Split(dot);
segments[3] = (int.Parse(segments[3]) + 1).ToString("000"); newVer = String.Join(".", segments);
return "\"" + newVer + "\""; }
Check In
wsp.CheckIn(changes, "Daily rev up");
}
Remove the Workspace
verServer.DeleteWorkspace(cWorkspaceName, verServer.AuthorizedUser);
return strNewRev;
}
Plan B
Occasionally, somebody from my team would check-out the AssemblyInfo file and forget to check it in, or this utility would encounter an error, etc. So I added the “if
” block (near the top) to “QueryPendingSets
” and if it found some, it would attempt to re-commit or respond with an error message, that somebody has this file checked-out.
else
{
string re="Previous rev-up is being re-tried, successful";
for (int x = 0; x < sets.Length; x++)
{
if (sets[x].Name == cWorkspaceName)
{
wsp.CheckIn(sets[x].PendingChanges, "Daily rev up retry");
break;
}
else {
re= "Cannot rev-up because someone else has the project checked-out " +
"exclusively";
}
}
return re;
}
Installing/Running
My team’s builds kicked-off at 1 am every day, so I used the Task Scheduler (on the TFS server) to run TfsRevUp a half-hour before, at 12:30 am. In the project, in the file Program.cs I checked for a command-line switch /silent
. It will run the program as a command-line app which will “rev-up” and quit.
Conclusion
That is all it takes to check a file in/out of TFS and increment the build number(s).
Advanced Options
This article has covered a very simple way of incrementing project build numbers for projects in TFS. However, you may want to integrate this with a TFS build or a TFS build workflow/template. Here are a few resources that you can continue reading about to cover more advanced topics that add onto this concept:
Credit
I borrowed some of this code from other projects. These guys did some excellent work and deserve credit for some of the tricky parts of my project.
History