Change build definition branches with PowerShell

Problem:

I have a large number of build definitions I need to point to a different branch.

Solution:

Use the attached PowerShell script.

Explanation:

Today on a call with a customer I was asked how they could point hundreds of builds to a new branch.  My obvious answer was with PowerShell.  With PowerShell we can use the entire TFS API to script whatever we need.  The attached script defines an update-BuildBranch function that takes the following parameters:

$tfsUrl

This is the full URL to your Team Foundation Server collection. https://tfs.visualstudio.com/DefaultCollection/

$teamProject

This is the name of the Team Project that contains the desired build definition.

$buildDefinitionName

This is the name of the Build Definition to update or clone.

$from

This is the portion of the Source Control Folder value to search for. $/TeamProject/Branch

$to

This is the value to replace the $from value with. $/TeamProject/NewBranch

$update

If present the Build Definition is updated. If not a copy of the Build Definition is made. -update

 

function update-BuildBranch

{

    [CmdletBinding()]

    param(

        [string] $tfsUrl,

        [string] $teamProject,

        [string] $buildDefinitionName,

        [string] $from,

        [string] $to,

        [switch] $update

        )

 

    [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")

    [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Build.Client")

 

    $teamProjectCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsUrl)

    $buildServer = $teamProjectCollection.GetService([type]"Microsoft.TeamFoundation.Build.Client.IBuildServer")

    $buildDef = $buildServer.QueryBuildDefinitions($teamProject) | Where-Object { $_.Name -eq $buildDefinitionName }

 

    if($buildDef -eq $null)

    {

        Write-Error "Build Definition was not found"

        return;

    }

 

    if($from.StartsWith("$"))

    {

        $from = "\" + $from;

    }

 

    if($update.IsPresent)

    {

        foreach($mapping in $buildDef.Workspace.Mappings)

        {

            $original = $mapping.ServerItem

            $mapping.ServerItem = $original -replace $from, $to

            Write-Verbose "Updating $original to $($mapping.ServerItem)"

        }

 

        $buildDef.ProcessParameters = $buildDef.ProcessParameters -replace $from, $to;

 

        $buildDef.Save();

    }

    else

    {

        $branch = $to.Substring($to.LastIndexOf("/") + 1);

 

        # Find a name

        $count = 1;

        $newName = "$($buildDef.Name)_$branch"

 

        while(($buildServer.QueryBuildDefinitions($teamProject) | Where-Object { $_.Name -eq $newName }) -ne $null)

        {

            $newName = "$($buildDef.Name)_$branch`_$count"

            $count++;

        }

       

        Write-Verbose "Cloning build to $newName"

 

        $cloneDef = $buildServer.CreateBuildDefinition($teamProject);

 

        $cloneDef.BuildController = $buildDef.BuildController

        $cloneDef.BatchSize = $buildDef.BatchSize;

        $cloneDef.ContinuousIntegrationQuietPeriod = $buildDef.ContinuousIntegrationQuietPeriod;

        $cloneDef.ContinuousIntegrationType = $buildDef.ContinuousIntegrationType;

        $cloneDef.DefaultDropLocation = $buildDef.DefaultDropLocation;

        $cloneDef.Description = $buildDef.Description;

        $cloneDef.Enabled = $buildDef.Enabled;

        $cloneDef.Name = $newName;

        $cloneDef.Process = $buildDef.Process;

        $cloneDef.ProcessParameters = $buildDef.ProcessParameters -replace $from, $to;

        $cloneDef.QueueStatus = $buildDef.QueueStatus;

       

        $cloneDef.TriggerType = $buildDef.TriggerType;

 

        foreach($mapping in $buildDef.Workspace.Mappings)

        {

            $original = $mapping.ServerItem

            $newMapping = $original -replace $from, $to;

            $cloneDef.Workspace.AddMapping($newMapping, $mapping.LocalItem, $mapping.MappingType)

            Write-Verbose "Updating $original to $newMapping"

        }

 

        $cloneDef.Save();

    }

}

 

updateBuildBranch.ps1 (3KB)

Comments (9) -

  • Mark Rainey

    3/13/2015 12:47:29 PM | Reply

    Thank you Donovan!  Exactly what I was looking for.

  • Kurtis Orr

    10/16/2015 7:22:37 PM | Reply

    Donovan,

    This script is exactly what I was looking for!

    I saw you at Ignite in Chicago and you really gave me the push I needed to use powershell to my advantage.

    -Thanks,
    Kurtis Orr

    • Donovan

      1/3/2016 2:57:01 AM | Reply

      Hey Kurtis!  Glad this information was helpful.

  • Rizwan

    3/12/2018 9:01:09 PM | Reply

    Hi, Thanks for posting this blog. This is what I was looking for but I am running into an issue and get following error:
    Build Definition was not found
        + CategoryInfo          : NotSpecified: (Smile [Write-Error], WriteErrorException
        + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,CreateNewBuild.ps1

    It seems that it is not able to create the $buildServer object out of $teamProjectCollection.  This is my code block where I am running into issue:

           # AVRC_ETL/_apis/Release/definitions
       $tfsUrl = "http://server:8080/tfs/XVR/"
       $teamProject = "AVRC_ETL"
       $buildDefinitionName = "MS-BIEngineContent"
       $from =  "$/AVRC_ETL/ETL_DEV"
       $to = "$/AVRC_ETL/ETL_DEV/Dataset"
      
        [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")

        [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Build.Client")



        $teamProjectCollection = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tfsUrl)
        write-output $teamProjectCollection

        $buildServer = $teamProjectCollection.GetService([type]"Microsoft.TeamFoundation.Build.Client.IBuildServer")
        write-output $buildServer
        
        $buildDef = $buildServer.QueryBuildDefinitions($teamProject) | Where-Object { $_.Name -eq $buildDefinitionName }

    # Write-Output $teamProject
    # write-Output $teamProjectCollection
    # Write-Output $buildDefinitionName
      write-Output $buildServer
       Write-Output "DEF: " $buildDef

        if($buildDef -eq $null)

        {

            Write-Error "Build Definition was not found"

            return;

        }

    • Donovan

      3/13/2018 10:58:16 PM | Reply

      In the line
      $tfsUrl = "http://server:8080/tfs/XVR/"
      is XVR your collection name or project name?

      • Rizwan

        3/15/2018 3:03:35 PM | Reply

        XVR is the name of collection. So I see, I am passing collection name in the URL but also trying to get the $teamProjectCollection by using the ($tfsUrl) which already had collection in it.. Am I right? So I should remove the collection name from the $tfsUrl?

  • William

    6/8/2018 12:48:08 PM | Reply

    Does this work for TFS 2017?  I am thinking this is for the old build engine not the new build engine from TFS 2015?

    • Donovan

      6/9/2018 6:24:08 PM | Reply

      Is for the XAML bested builds. For the new build system their is a REST API.  https://cda.ms/wS

    • Kurtis Orr

      6/10/2018 2:46:26 AM | Reply

      Hi William,

      The best way I found for this in with the new build system is to make a custom variable $(BranchNumber) and have that be in the path for the source settings. Then when you do the REST API calls, you go through the builds and edit the JSON to change the value of $(BranchNumber) to whatever you'd like (We query what branch number you want in the beginning, so it it just replaces the string). That way you only have to change 1 value to edit all the source strings. Cheers!

Add comment

Loading