How to move build definitions in TFS to other projects using the REST API

The new build system in TFS or VSTS supports saving build definitions as templates. The problem is, that this only works inside a single TFS project. If you want to share your definition with other projects you have to use the REST API.

First you have to extract your current build definition. Get a list of all build definitions and check the ids.

$url = "https://<server>/tfs/DefaultCollection/ProjectName/_apis/build/definitions?api-version=2.0"
(Invoke-RestMethod -Uri $url -Method Get -UseDefaultCredentials -ContentType application/json).value

Then use this ID to get the full definition.

$url = "https://<server>/tfs/DefaultCollection/ProjectName/_apis/build/definitions/18"
$json = Invoke-RestMethod -Uri $url -Method Get -UseDefaultCredentials -ContentType application/json
$json

Now save the json to a file and remove all the clutter like _links, revision etc. I’ve marked all the sections that you can remove in the following picture.

image

Then search for your project name and replace it with __ProjectName__.

That’s it. You can now use the following PowerShell to create the build definition in any project. The script loads the json for the build from a file, replaces the project name with the parameter and then creates the build definition in the project.

$json = Get-Content .\Build.json
$json = $json.Replace("__ProjectName__", $ProjectName) 

$url = "https://<server>/tfs/DefaultCollection/$ProjectName/_apis/build/definitions?api-version=2.0"
Invoke-RestMethod -Uri $url -Method Post -UseDefaultCredentials -Body $json -ContentType application/json

See this post if you want to use the script with VSTS. In this case you have to create a personal Access token.

Update: There is now a ExportImportBuildDefinition Extension available in the market place from Utkarsh Shigihalli. You can still use the REST API if you want to template your builds or move them from TFS to VSTS or vice versa.

11 comments

  1. Hi Michael,

    I am trying to update the build definition which is very similar to creating the build definition. I am using PUT to update the build definition as per the documentation – https://www.visualstudio.com/integrate/api/build/definitions.

    It is not working for me – It throws an error –
    Invoke-RestMethod : {“$id”:”1″,”innerException”:null,”message”:”The collection must contain at least one
    element.\r\nParameter name: definition.Options.Inputs”,”typeName”:”System.ArgumentException, mscorlib,
    Version=4.0.0.0, Culture=neutral,
    PublicKeyToken=b77a5c561934e089″,”typeKey”:”ArgumentException”,”errorCode”:0,”eventId”:0}
    At C:\vnext\getbd.ps1:17 char:1
    + Invoke-RestMethod -uri “http://alm:8080/tfs/boc_projects/Build/_apis/bu …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExc
    eption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

    Unable to figure out the cause for this issue. Appreciate your help on this!

    Thanks!

      1. Hi,
        I have the same problem: getting an existing build definition using REST API then trying to post it back with PUT.

        Narrowing down the problem it seems something wrong with the “options” object from the returned json; I can update the build definition only by removing it.

  2. Can be automated like this:

    Set-StrictMode -Version Latest

    ## DECLARATIONS ##
    $Server = “http://tfs:8080/tfs”;
    $ProjectSource = “ProjectSrc”;
    $ProjectDestination = “ProjectDst”;

    ## IMPLEMENTATION ##
    $Url = “$Server/DefaultCollection/$ProjectSource/_apis/build/definitions?api-version=2.0&type=build”;
    $BuildDefinitions = Invoke-RestMethod -Uri $Url -Method Get -UseDefaultCredentials -ContentType application/json;

    foreach ($BuildDefinition in $BuildDefinitions.value) {
    foreach ($id in $BuildDefinition.id) {
    $urlget = “$Server/DefaultCollection/$ProjectSource/_apis/build/definitions/$id”;
    $json = Invoke-RestMethod -Uri $urlget -Method Get -UseDefaultCredentials -ContentType application/json;
    $json.PSObject.Properties.Remove(‘_links’);
    $json.PSObject.Properties.Remove(‘createdDate’);
    $json.PSObject.Properties.Remove(‘authoredBy’);
    $json.PSObject.Properties.Remove(‘uri’);
    $json.PSObject.Properties.Remove(‘revision’);
    $json.PSObject.Properties.Remove(‘id’);
    $json.PSObject.Properties.Remove(‘url’);
    $json.PSObject.Properties.Remove(‘project’);
    $postJson = ($json | ConvertTo-Json -Depth 3).Replace($ProjectSource, $ProjectDestination);
    $urlpost = “$Server/DefaultCollection/$ProjectDestination/_apis/build/definitions?api-version=2.0”
    $response =
    try {
    Invoke-RestMethod -Uri $urlpost -Method Post -UseDefaultCredentials -Body $postJson -ContentType application/json
    }
    Catch
    {
    $result = $_.Exception.Response.GetResponseStream()
    $reader = New-Object System.IO.StreamReader($result)
    $reader.BaseStream.Position = 0
    $reader.DiscardBufferedData()
    $responseBody = $reader.ReadToEnd();
    [System.Tuple]::Create($_.Exception.Response ,$responseBody)
    };
    if ([int]$response.Item1.StatusCode -ne 200) {
    $responseStatusCode = [int]$response.Item1.StatusCode;
    $responseMessage = ($response.Item2 | ConvertFrom-Json).message;
    Write-Error “Failed to import build with id $id.
    Response status code: $responseStatusCode.
    Message: $responseMessage.”;
    }
    }
    }

    1. Good information, John (and Mike also, for the original blog info). Tried your code out and with just a few modifications, it worked well for me.
      Thanks!
      -Walter

  3. I am trying to use this approach to create a build in another project which I can get to work if I also remove the queue section, however my build now does not have a queue unless I manually add it.
    Is there a way to get a list of Agent Queues for my new project as I have noticed the following in the Build Definition json’s:
    Original Project:
    „id“: 46,
    „name“: „Hosted VS2017“,
    „url“: https://{account}.visualstudio.com/_apis/build/Queues/46
    New Project:
    „id“: 142,
    „name“: „Hosted VS2017“,
    „url“: https://{account}.visualstudio.com/_apis/build/Queues/142
    However when I query https://{account}.visualstudio.com/_apis/build/Queues neither 46 or 142 are listed?
    Any help would be appreciated.

    1. Have you tried specifying the queue only by name?
      “queue”: {
      “name”: “Hosted VS2017”,
      “pool”: {
      “id”: 4,
      “name”: “Hosted VS2017”,
      “isHosted”: true
      }
      },
      I’m still experimenting – but my definition has another problem.

      1. Yes I have tried that but it still complains. I do now have a solution which is clunky but works.

        Essentially, if you call https://{account}.visualstudio.com/{project}/_apis/distributedtask/queues you get back the specific instances of each Agent Queue for the specified project.

        After parsing you get then set the queue you need base on this data.

        like I said clunky but works.

  4. I do now have another problem when updating a project I keep getting the following error:

    {“The project update is invalid.\r\nParameter name: projectUpdate”}

    Below is my code

    ProjectHttpClient vssProjectClient = vssConnection.GetClient();

    TeamProject teamProject = GetProject(projectGuid, false, false);

    teamProject.Name = “New project name”;

    OperationReference vssOperation = vssProjectClient.UpdateProject(teamProject.Id, projectUpdate: teamProject).Result;

    Any help would be appreciated.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s