Again on assembly numbering during TFS Build

If you read this post, you can see how to customize a tfs build to modify versioning of the assembly. During that process to find the latest changeset of the repository, to use as revision Number , I used a direct call to tf.exe tool and a custom Regex msbuild task to parse the result to find desired number. This approach have some weak points, first of all you need to know the location of tf.exe tool, moreover we are bound to the format output of the tool, now I want to show you a different way to obtain the same result.

To access functionality of Team Foundation Server you can also use API, and I must admit that I really like this approach instead of relying on calling an external exe tool and parse its output. Here is the full code of a custom task that accomplish the same operation with the use of tfs API.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
namespace DotNetMarche.MsBuildExtensions.TfsBuild
{
    public class SourceControlTask : Task
    {
        [Required]
        public String Operation { get; set; }

        [Required]
        public String TfsUrl { get; set; }

        [Output]
        public Int32 LatestChangeset { get; set; }

        public ICredentials Credentials { get; set; }

        public String TeamProject { get; set; }

        public override bool Execute()
        {
            SourceControlTaskOperation operation;
            try
            {
                operation = (SourceControlTaskOperation)Enum.Parse(typeof(SourceControlTaskOperation), Operation);
            }
            catch (FormatException fex)
            {
                Log.LogError("The operation {0} is not supported;", Operation);
                return false;
            }

            Log.LogMessage("SourceControlTask: Operation{0} requested;");
            TeamFoundationServer tfs;
            if (Credentials == null)
                tfs = new TeamFoundationServer(TfsUrl);
            else
                tfs = new TeamFoundationServer(TfsUrl, Credentials);

            switch (operation)
            {
                case SourceControlTaskOperation.GetLatestChangeset:
                    VersionControlServer vcs = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));

                    LatestChangeset = vcs.GetLatestChangesetId();
                    break;
                default:
                    Log.LogError("The operation {0} is not supported;", Operation);
                    return false;
            }
            return true;
        }
    }

    public enum SourceControlTaskOperation
    {
        GetLatestChangeset = 0,
    }
}

The object that permits to access TFS is the TeamFoundationServer one, that you can find in the C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\Microsoft.TeamFoundation.Client.dll assembly. Since we need also to use version control API we need also a reference to the Microsoft.TeamFoundation.VersionControl.Client located in the same directory. The task has a property called Credentials of type ICredentials used to specify the credentials used to access tfs. Since during a build the thread runs with the credential of the Build Agent there is no need to specify any credentials, but if you want to create a unit test you absolutely need them.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[TestMethod]
public void TestSmokeConnectToServer()
{
    SourceControlTask sut = new SourceControlTask();
    sut.BuildEngine = MockRepository.GenerateStub<IBuildEngine>();
    sut.Operation = "GetLatestChangeset";
    sut.Credentials = new NetworkCredential("alkampfer", "thisisnotmypassword:)");
    sut.TfsUrl = "http://tfsalkampfer:8080/";
    sut.Execute();
    Assert.IsTrue(sut.LatestChangeset > 0);
}

With this test I verify that the task GetLatestChangeset works and returns a value greater than 0, but since this test can be run with an account that have no permission to tfs I need to specify credential in the test. If you  do not like to store credentials in tests, you need to be sure to run this test with a user that have access right to tfs.

After you create a valid TeamFoundationServer object you need to call its GetService method to obtain a reference to desired service, in this example the VersionControlServer service. Once you have reference to it, you can simply call his GetLatestChangesetId() method to retrieve the latest changeset number. Once you have this task you can use in a tfs build project in this way.

1
2
3
4
5
<SourceControlTask
    TfsUrl="$(TeamFoundationServerUrl)"
    Operation="GetLatestChangeset">
    <Output    TaskParameter="LatestChangeset" PropertyName="lastChangeset"/>
</SourceControlTask>

As you can see you can simply specify the url of Team Foundation Server, the operation GetLatestChangeset and finally grab the output value with <output> tag.With the old method you need to call two tasks and if for some reason the output of the tf.exe tool changes (maybe for a new version), you need also to change the regex used to parse the output. Thanks to the api you can create a stronger method to obtain latest changeset number in your build file.

Now you can fire a build, goes to the drop location and verify the File Version attribute of the file

image

As you can see the version number is 135, my actual latest number for version control system of my test TFS server.

alk.

Tags: TeamFoundationServer MsBuild TfsBuild