Sunday, November 12, 2017

Calling VSTS APIs with PowerShell

Copyright from: blog.devmatter.com

Continuing along with my other various examples of VSTS API calls, I thought I’d include an example on how to call a Visual Studio Team Services REST API using PowerShell.
To provide a concrete example, consider this scenario:
We use VSTS-based builds to build our projects and create artifacts (e.g. web apps/services, binaries, etc.). We then make use of Octopus Deploy to deploy our artifacts into our various environments (e.g. development, test, production, etc.). Once these artifacts have been deployed to the production environment, we’d like to keep them around indefinitely.
As you are likely aware, VSTS has a default retention policy on builds of 10 days with a maximum retention of 30 days. That is, unless you manually switch the retention for a build to “Keep Forever”. This can be accomplished easily within the VSTS web interface. However, we would like to do this programmatically when Octopus has successfully deployed a build to the Production environment. To do that, we plan to create a PowerShell script to manage the API calls. What follows is an example of how we might approach this task.
For this particular script, I will be relying on personal access tokens, or PAT’s to handle authentication with the VSTS REST APIs. For a quick refresher on PAT’s, check out Personal Access Tokens and VSTS APIs.
The first thing our script will do (I’ll show the full script at the end of this article) is create the Base64-encoded Basic authorization header necessary to authenticate with the API call.

Param(
   [string]$vstsAccount = "",
   [string]$projectName = "",
   [string]$buildNumber = "",
   [string]$keepForever = "true",
   [string]$user = "",
   [string]$token = ""
)
 
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
In the above snippet, we have created a set of parameters, along with some default values, that can be passed in when calling this PowerShell script. The code at the bottom of the snippet is simply combining the user name and PAT (separated by a colon) and Base64-encoding it.

$uri = "https://$($vstsAccount).visualstudio.com/DefaultCollection/$($projectName)/_apis/build/builds?api-version=2.0&buildNumber=$($buildNumber)"
Next, we construct the URL to make the desired API call. In this case, we are calling the Build API with a query parameter buildNumber. Notice that buildNumber is one of the input parameters defined above. In our case, we track the build number in Octopus; However, we need the build ID to set the retention. The results of this API call will include the build ID that we need.

$result = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
The above code is where all the magic happens. The PowerShell cmdlet, Invoke-RestMethod, sends an HTTP(S) request to a RESTful web service and returns the results. Here we’re passing in the URL to the API to be called, the method type (e.g. GET) and the authorization header (which includes our PAT). We store the results in the aptly named variable, $result.

if ($result.count -eq 0)
{
     throw "Unable to locate Build ID for Build Number $($buildNumber)"
}
 
$buildId = $result.value[0].id
One of the properties of $result is count. In our case, we only ever expect to get back 1 result. If we get back zero results then we throw an error indicating we couldn’t find the build number specified.
Assuming all went well, we then pull out the Build ID into the $buildId variable. The magic going on here is that PowerShell has automatically parsed the JSON results for us and provided properties allowing us to easily get at the elements we care about. In this case, I care about the id property so that’s all I pull out.

$uri = "https://$($vstsAccount).visualstudio.com/DefaultCollection/$($projectName)/_apis/build/builds/$($buildId)?api-version=2.0"
 
$body = "{keepForever:$($keepForever)}"
 
$result = Invoke-RestMethod -Uri $uri -Method Patch -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body
Next, we have to setup the second API call that takes the Build ID that we just obtained and used it to set the retention to “Keep Forever”. Notice that this second API call uses the PATCH method and also expects some JSON in the message Body.

Write-Output ("Retention set to '$($keepForever)' for Build ID $($buildId) (Build Number: $($buildNumber))")
For completeness, we’ll write out an informational message letting us know what happened.
When I run the script with the appropriate parameters, here is what I see:
image
Here’s the full PowerShell script in all it’s, well, glory…
Param(
   [string]$vstsAccount = "",
   [string]$projectName = "",
   [string]$buildNumber = "",
   [string]$keepForever = "true",
   [string]$user = "",
   [string]$token = ""
)

Write-Verbose "Parameter Values"
foreach($key in $PSBoundParameters.Keys)
{
     Write-Verbose ("  $($key)" + ' = ' + $PSBoundParameters[$key])
}
 
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
 
# Construct the REST URL to obtain Build ID
$uri = "https://$($vstsAccount).visualstudio.com/DefaultCollection/$($projectName)/_apis/build/builds?api-version=2.0&buildNumber=$($buildNumber)"
 
# Invoke the REST call and capture the results
$result = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
 
# This call should only provide a single result; Capture the Build ID from the result
if ($result.count -eq 0)
{
     throw "Unable to locate Build ID for Build Number $($buildNumber)"
}
 
$buildId = $result.value[0].id
 
#
# Now, let's set the Retention to Keep Forever
#
 
# Construct the REST URL to set the retention
$uri = "https://$($vstsAccount).visualstudio.com/DefaultCollection/$($projectName)/_apis/build/builds/$($buildId)?api-version=2.0"
 
# This method requires "keepForever" to be set (and passed in via the message body)
$body = "{keepForever:$($keepForever)}"
 
# Invoke the REST call and capture the results (notice this uses the PATCH method)
$result = Invoke-RestMethod -Uri $uri -Method Patch -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body
 
Write-Output ("Retention set to '$($keepForever)' for Build ID $($buildId) (Build Number: $($buildNumber))")

While this is a relatively simple example it does show how to solve real-world problems. That said, there are improvements that can be made to this script. For example, if one of the API calls fail, you might want to log the details somewhere or you might want to handle the exceptions explicitly. In any case, I hope you find the example useful :-)

No comments: