Invoke TFS 2015 REST API from build- and release-tasks

I ran into a technical challenge writing a custom task for TFS 2015, which I solved by calling the TFS 2015 REST API from my build- and release task. I’ll try to explain why and how I did that in this post. There might be other options as well, please feel free to share them with me.

Background of the problem
I was writing a task for Release Management where I needed to update a file that had already been published to my artifact file share during the build. This XML file is intended to contain all kinds of information regarding the build but I wanted to expand its use with Release-information as well. That way we can easily track build-and-release information from the central deployment-repository. I found that there is no fixed variable available during the release which contains the artifact location! I assume that will be exposed in a next version, but I did not want to wait for it so this is what I came up with.

The solution for my problem
As mentioned: I want to update a file on the artifact share, but as we will have 400+ build definitions soon, each of the developers could potentially enter other artifact paths for their builds, so I am not able to just “assume” the path where their artifacts will be written to (although we do have solid agreements of the file-and-folder structure). I read the documentation for TFS 2015 REST API and after some attempts on my own test-environment I finally found the REST-method that would provide me with the exact artifact information. After I managed to find the specific details directly in the browser, I only needed to adjust my existing task to automate it.

1. Update the task to connect to the API
The API-path that I was specifically interested in: http://{tfslocation}/{teamProjectName}/_apis/build/builds/{buildId}/artifacts. This request will provide me with a list of all artifacts that the build created. In my case I need to find an artifact based on a fixed filename, so that should not be too hard. I did find it difficult to connect without having to enter a fixed username and password combination though. I found a few examples on the web about authentication on the Invoke-RestMethod function, which worked fine technically, but they were all using a username and password string: that’s not something I’d like to use in a business-environment. After several attempts, these two method worked, and I chose to use the latter one.

$apiPath = "{0}{1}/_apis/build/builds/{2}/artifacts" -f ($Env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI, $Env:SYSTEM_TEAMPROJECT, $Env:BUILD_BUILDID)
Write-Host "Now calling REST API to get Artifact-details: $apiPath" 
			
# First option: add the credentials to the header
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f "aUserName", "aSuperSecretPassword")))
$jsonFromTfs = Invoke-RestMethod -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Uri $apiPath
			
# Second option: UseDefaultCredentials, will authenticate with the account that runs the task.
$jsonFromTfs = Invoke-RestMethod -Uri $apiPath -UseDefaultCredentials

2. Use build variables to get the exact data
As you could already see in the PowerShell-example above, there are some variables that you can use to create the exact path to your TFS-instance. This way, even when you would migrate to another TFS, or if hostnames would change, etc. the script still works. The Uri, the Projectname en BuildId were the ones I needed for this specific REST-request.

3. Adjust the task so it can run in both Build and Release
I discovered that the on-premise TFS 2015 (we run on Update 3) does not seem to take the “visiblity” property of a task into account, meaning that a task can have that property set to Build or Release or both, and it will still be shown in the Add-task dialog in both the Build and Release pages. Therefor I decided to make my tasks compatible with both scenarios. To do so I simply check the HOSTTYPE-variable as illustrated below.

if ($Env:SYSTEM_HOSTTYPE -eq "build") { Write-Host "Task runs in BUILD" } 
if ($Env:SYSTEM_HOSTTYPE -eq "release")  { Write-Host "Task runs in RELEASE" }

Working PowerShell-snippet
The following snippet works for me. Of course there is more to it then just this code, but it probably makes clear how I call the API and read the data that it returns. In my case I would open the filename that the API returns, read its data and add more data along the way, but that is not what this post is about. If you’re interested in the task, just let me know.

$apiPath = "{0}{1}/_apis/build/builds/{2}/artifacts" -f ($Env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI, $Env:SYSTEM_TEAMPROJECT, $Env:BUILD_BUILDID)
Write-Host "Now calling REST API to get Artifact-details: $apiPath" 

$jsonFromTfs = Invoke-RestMethod -Uri $apiPad -UseDefaultCredentials

if ($jsonFromTfs.value[0] -ne $null)
{
	$artifactName = $jsonFromTfs.value[0].name
	$artifactPath = $jsonFromTfs.value[0].resource.data
	$artifactTotal = "{0}{1}\{2}" -f ($artifactPath, $artifactName, $artifactFile)
				
	Write-Host "We found the paths, now see if the file can be located."
	if (Test-Path $artifactTotal)
	{
		Write-Host "File was found! Now update Release-information ($artifactTotal)"
	}
}

If you need additional information about these tasks, don’t hesitate to contact me. Hope this helps other DevOps out there as well.

3 thoughts on “Invoke TFS 2015 REST API from build- and release-tasks

  1. Sajan says

    Hi,

    I’m trying to add a new environment to the existing release def as like in example dev exists and want to add QA to the environment and update values respectively for QA (servername etc) How can i do that in powershell? Any help is appreciated.

    Thanks!

    Reply

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

Deze site gebruikt Akismet om spam te verminderen. Bekijk hoe je reactie-gegevens worden verwerkt.