Copying a TFS Build Definition

I found myself in a situation recently where I needed the ability to copy a build definition in TFS on a fairly consistent basis.  While this is a feature that has been requested before, unfortunately it hasn’t yet made it into the product.  So I thought I would see if I could create a Visual Studio extension that would provide this functionality in reasonably short order.

I started off researching how I might use the TFS API to perform the copy.  Of course, I couldn’t find much about how to create the copy, other than the various posts asking about or requesting this functionality.  What I did find though was a blog post by Jakob Ehn on creating a build definition programmatically, which served as the basis for how I would perform the copy.

You start by getting a reference to the build server via a call to the GetService method on the TfsTeamProjectCollection instance as shown in the code snippet below.  Once we have the build server reference we can then load the build definition that we’re making a copy of.  The key to copying the build definition is making sure that you pick up all the original build attributes.  That means iterating over all of the workspace mappings and process parameters, among the other build definition settings, as shown.  Once we’re done creating the copy, simply call Save() on the new build definition to commit the changes to TFS.

  1: private void CopyBuildDefinition(string teamProject, string buildDefinitionName)
  2: {
  3:     // Get a reference to the build service
  4:     IBuildServer buildServer = (IBuildServer)_tfsServer.GetService(typeof(IBuildServer));
  5:  
  6:     // Load the build definition to be copied
  7:     var buildDefinitionToCopy = buildServer.GetBuildDefinition(teamProject, buildDefinitionName);
  8:     if (buildDefinitionToCopy != null)
  9:     {
  10:         // Copy General Info
  11:         var newBuildDefinition = buildServer.CreateBuildDefinition(teamProject);
  12:         newBuildDefinition.Name = GetNewBuildName(buildServer, teamProject, buildDefinitionName);
  13:         newBuildDefinition.Description = buildDefinitionToCopy.Description;
  14:  
  15:         // Copy Triggers
  16:         newBuildDefinition.ContinuousIntegrationType = buildDefinitionToCopy.ContinuousIntegrationType;
  17:         newBuildDefinition.ContinuousIntegrationQuietPeriod = buildDefinitionToCopy.ContinuousIntegrationQuietPeriod;
  18:         newBuildDefinition.Schedules.AddRange(buildDefinitionToCopy.Schedules);
  19:  
  20:         // Copy Workspace mappings
  21:         foreach (IWorkspaceMapping mapping in buildDefinitionToCopy.Workspace.Mappings)
  22:         {
  23:             newBuildDefinition.Workspace.AddMapping(mapping.ServerItem, mapping.LocalItem, mapping.MappingType, mapping.Depth);
  24:         }
  25:  
  26:         // Copy Build defaults
  27:         newBuildDefinition.BuildController = buildDefinitionToCopy.BuildController;
  28:         newBuildDefinition.DefaultDropLocation = buildDefinitionToCopy.DefaultDropLocation;
  29:  
  30:         // Copy Process parameters
  31:         newBuildDefinition.Process = buildDefinitionToCopy.Process;
  32:         var processParams = WorkflowHelpers.DeserializeProcessParameters(buildDefinitionToCopy.ProcessParameters);
  33:         var copyProcessParams = WorkflowHelpers.DeserializeProcessParameters(newBuildDefinition.ProcessParameters);
  34:         foreach (KeyValuePair<string, object> entry in processParams)
  35:         {
  36:             copyProcessParams.Add(entry.Key.ToString(), entry.Value);
  37:         }
  38:         newBuildDefinition.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(copyProcessParams);
  39:  
  40:         // Copy Retention policies
  41:         newBuildDefinition.RetentionPolicyList.Clear();
  42:         newBuildDefinition.RetentionPolicyList.AddRange(buildDefinitionToCopy.RetentionPolicyList);
  43:  
  44:         // Save copy
  45:         newBuildDefinition.Save();
  46:     }
  47: }

One of the tricks of creating the copy is coming up with a meaningful name for the new build definition.  You’ll see in the code snippet above at line 12 a call to GetNewBuildName().  I decided to take an approach similar to how Visual Studio creates copies of files within a project.  That is, I look at the name of the build definition being copied and create the new build definition with a name of “Copy (n) of buildDefinitionName”.  The implementation of this approach is shown in the following code snippet.

  1: private string GetNewBuildName(IBuildServer buildServer, string teamProject, string buildDefinitionName)
  2: {
  3:     string newBuildName = DEFAULT_PREFIX + buildDefinitionName;
  4:  
  5:     IBuildDefinitionSpec buildDefinitionSpec = buildServer.CreateBuildDefinitionSpec(teamProject);
  6:     buildDefinitionSpec.Name = "Copy*of " + buildDefinitionName;
  7:     IBuildDefinitionQueryResult results = buildServer.QueryBuildDefinitions(buildDefinitionSpec);
  8:  
  9:     if (results.Definitions.Length >= 1)
  10:     {
  11:         var buildNames = from buildDef in results.Definitions
  12:                          orderby buildDef.Name
  13:                          select buildDef.Name;
  14:  
  15:         if (buildNames.Contains(newBuildName))
  16:         {
  17:             for (int inx = 2; inx < 50; inx++)
  18:             {
  19:                 newBuildName = String.Format("Copy ({0}) of {1}", inx, buildDefinitionName);
  20:                 if ( !buildNames.Contains( newBuildName ) )
  21:                     break;
  22:             }
  23:         }
  24:     }
  25:  
  26:     return newBuildName;
  27: }

That’s all there is to it.  The completed Visual Studio extension is available for install from the Extension Manager in Visual Studio (search for "copy build" in the online gallery) or you can download it directly from the Visual Studio Gallery.