TFS 2015 clone/import/export build definition between team projects

By Mirek on (tags: build definition, clone, tfs, VSO, categories: tools, infrastructure, code)

While exploring features of the new Visual Studio Team Foundation Server 2015 I found that it is not possible to reuse a build definition created in one team project into another team project. Since this is a feature that I am going to use frequently, well, every time I start a new project, this is a must have functionality. In this post I will give you a complete solution I’ve came up with.

This is by the way quite strange that you have a possibility to save build definition as a template for future use, but such template is then available only in the same team project. So basically it is usefull when you are going to have many build definitions in one team project. But when you want to reuse a build definition in other projects, because for instance you use many build steps, many variables and setting and many of them are the same for all of your projects, then you have to deal with this limitation.
Fortunatelly the Visual Studio Online and TFS 2015 comes with Team Services API which you can access and which allows you to programatically perform various actions on your build  system. It also allows you to manage all build definitions. There are .net libraries created by Microsoft which facilitates working with the api and which I am going to use.
Lets create new console app project in Visual Studio 2015, name it TfsCloneBuildDef and add following nuget pakages

Microsoft.TeamFoundationServer.Client
Microsoft.VisualStudio.Services.Client

When I wrote this post both of above packages are available in stable version of 14.89.0. Now we can implement our application. For the sake of the simplicity I will skip negligible code and focus on the most important part, as you can download whole solution from the attachement of this post. So the core method which will clone the build definition will look like this

   1: var cred = new VssCredentials(new WindowsCredential(new NetworkCredential(username, password)));
   2: var buildClient = new BuildHttpClient(new Uri(collectionURL, UriKind.Absolute), cred);
   3:  
   4: var buildDef = (await buildClient.GetDefinitionAsync(sourceProj, buildDefId)) as BuildDefinition;
   5:  
   6: buildDef.Project = null;
   7: buildDef.Name += "_clone";
   8:  
   9: await buildClient.CreateDefinitionAsync(buildDef, targetProj);

In the first line the credentials to the team server are created. Here you must provide your TFS user and password. Next we build a BuildHttpClient providing the projects collection url and the credentials object. The url should be in form like this one http://mytsfserver:8080/tfs/DefaultCollection. Then in line number 4 we retreive the build definition object from the source project providing the project name and the build definition id. Where to get the build  definition id from? Well, you can easilly read it from the url when you select the build definition in VSO web portal. For instance here: ../tfs/DefaultCollection/TestProject/_build#definitionId=6&_a=completed the definition id is 6.
In the next step we change the name of the build definition so it is clearly seen that this is a clone. We also need to remove the reference to the project. Since build definition contains a reference to the project it would not be possible to import it into a different project. Finally we create new build definition in target project providing the definition object we retreived from previous project.
Important note: the build definition contains more project specific settings which you will need to change manually in the VSO by editing the build definition. This is for instance Repository section

tfscbd01

or the Triggers section

tfscbd02

But we can handle this automatically as well, which I will show you later.
Now we can test our application. Let’s assume I have team project called TestProject with a build definition with name dev and id equal 6. I also have project called TestProject2 which I want to have the same build definition as the former one. Then all I need is to do this

TfsCloneBuildDef.exe http://mytfsserver:8080/tfs/DefaultCollection "user" "pass" "TestProject" "TestProject2" 6

And when I check the build definitions in TestProject2 there is new build definition called dev_clone.  That’s it.

The next step is to export the build definition to a file so we can latter import it. That would be nice so we can have sort of backup for the build definitions or we could even import it in a different build server later on. Since BuildDefinition object is a part of api it is well serializable and we are going to use this property. Lets break the method, which we used to clone the build definitions between two projects, in two methods. One will be the export and he second will be the import part. Also let's use a json serializer to serialize the build definition and save it to a file.

   1: var buildDef = (await buildClient.GetDefinitionAsync(project, buildDefId)) as BuildDefinition;
   2: buildDef.Project = null;
   3: File.WriteAllText(filePath, JsonConvert.SerializeObject(buildDef));

Quite easy, isn’t it? Now we can add an import method as well.

   1: if (!File.Exists(filePath))
   2:     throw new FileNotFoundException("File does not exist!", filePath);
   3: Console.WriteLine($"Importing build definition from file '{filePath}' to '{project}' project.");
   4: var buildDef = JsonConvert.DeserializeObject<BuildDefinition>(File.ReadAllText(filePath));
   5: buildDef.Name = newBuildName;
   6: await buildClient.CreateDefinitionAsync(buildDef, project);
Here we can additionally provide a different name for the build, just in case we already have a build definition with that name. This is also the place where we could adjust the build definition to the new project we import to. Since we have a json file we can easily search its content and replace all occurences of the old project name with the new project name.


Download attachement - 12 KB