Starting a release using the MS Release Management REST API

I’ll show how to use the MS Release Management REST API from Powershell to start a new Release, so that you can have a build script (or any external process, really) start a release without relying on the TFS-Release Management integration.

Our goal is to end up with a powershell script that starts a new release based on:
– The release template name
– The build name
– And the name of the target stage to deploy to

We’ll be using MS Release Management 2013, Update 4 with the vNext templates doing an on-premise deployment. The components should be configured as “builds externally”.

This post exands on what Anand Gaurav has written on this subject in Trigger Release from build with Release Management for Visual Studio 2013.

Overview

MS Release Management provides a set of REST API endpoints that are used by the MSRM Client. These are hosted in the Release Management website in IIS and can be reached via:

http://<rm server>:1000/account/releaseManagementService/_apis/releaseManagement/<servicename>

The most important of these for our purposes is the Orchestrator service, as it allows us to actually create the release via the InitiateRelease operation. The signature of this operation is as follows:

../OrchestratorService/InitiateRelease?releaseTemplateName=[release template name]&deploymentPropertyBag=[property bag]&api-version=3.0

…where the property bag is a url-encoded JSON dictionary with the details about this release. The following diagram shows how the MSRM dialog maps to the InitiateRelease parameters:

InitiateRelease

As you can see, it requires an internal identifier (the TargetStageId), as well as the names and builds of all Components so MSRM knows which packages to deploy. Hardcoding these values is one option, but luckily, all this information can also be obtained through the REST API, which is what I’m going to demonstrate below.

Starting a release from Powershell

To start off with, we’ll define variables for who will request the release and what we want to release:

$credentials = Get-Credential -Message "Enter credentials" -UserName "RMSERVERleon"
$rmServer = "localhost"
$releaseTemplateName = "Acme"
$targetStageName = "Acceptance stage"
$build = "v1.0 build 56"

Then we define the endpoint urls:

$baseEndpointUrl = "http://$($rmServer):1000/account/releaseManagementService/_apis/releaseManagement"
$configurationServiceUrl = "$baseEndpointUrl/ConfigurationService"
$releaseDefinitionServiceUrl = "$baseEndpointUrl/ReleaseDefinitionService"
$orchestratorServiceUrl = "$baseEndpointUrl/OrchestratorService"

Our first call to MSRM will be to translate the calling user to an internal UserId, e.g. “9004”.

$escapedUsername = [System.Uri]::EscapeDataString($credentials.UserName)
$responseXml = Invoke-RestMethod -Method Post -Credential $credentials `
    -Uri "$configurationServiceUrl/GetUserByUserName?userName=$escapedUsername&api-version=3.0"
$userId = $responseXml.Result.User.Id

Next, we’ll ask MSRM for all active ReleaseDefinitions that this user can release, and use it to translate the $releaseTemplateName into an ApplicationVersionId and a ReleasePathId. An example response of this method is included after the code fragment.

$body = "<Filter StatusId='2' IsDeleted='0' UserId='$userId' />"
$responseXml = Invoke-RestMethod -Method Post -Credential $credentials `
    -Uri "$releaseDefinitionServiceUrl/ListReleaseDefinitions?api-version=3.0" -Body $body

$applicationVersionElt = $responseXml.ApplicationVersionList.ApplicationVersion | 
    Where-Object { $_.Name -eq $releaseTemplateName }
$applicationVersionId = $applicationVersionElt.Id
$releasePathId = $applicationVersionElt.ReleasePathId
The ListReleaseDefinitions response

The ListReleaseDefinitions response

Using the $applicationVersionId, we can get a list of all Components that are used in this release template:

$body = "<Filter ApplicationVersionId='$applicationVersionId' LockRequested='1' LockRequestedById='$userId' />"
$responseXml = Invoke-RestMethod -Method Post -Credential $credentials `
    -Uri "$configurationServiceUrl/GetApplicationVersion?api-version=3.0" -Body $body
$componentNames = $responseXml.ApplicationVersion.Components.Component.Name
The GetApplicationVersion response

The GetApplicationVersion response

Because we need the Id of the Stage we want to target, let’s request the Release Path details and look it up by name:

$responseXml = Invoke-RestMethod -Method Post -Credential $credentials `
    -Uri "$configurationServiceUrl/GetReleasePath?id=$releasePathId&api-version=3.0"
$targetStageElt = $responseXml.ReleasePath.Stages.Stage | 
    Where-Object { $_.StageTypeName -eq $targetStageName }
$targetStageId = $targetStageElt.Id
The GetReleasePath response

The GetReleasePath response

Now we have all the required information to compose the JSON property bag:

$releaseName = "Release: $([DateTime]::Now.ToString('G'))"
$genericProperties = 
@(
    """ReleaseName"":""$releaseName""",
    """ReleaseBuild"":null",
    """ReleaseBuildChangeset"":null",
    """TargetStageId"":""$targetStageId"""
)
$componentProperties = ($componentNames | ForEach-Object { """$($_):Build"":""$build""" })
$json = "{" + ($genericProperties + $componentProperties -join ",`n") + "}"

Finally, we can call InitiateRelease with this information (but be sure that the passed credentials have permission to start a release). If all went well, it will start the Release and return a release Id with which we can monitor progress.

$escapedReleaseTemplateName = [System.Uri]::EscapeDataString($releaseTemplateName)
$propertyBag = [System.Uri]::EscapeDataString($json)
$releaseId = Invoke-RestMethod -Method Post -Credential $credentials `
    -Uri "$orchestratorServiceUrl/InitiateRelease?releaseTemplateName=$escapedReleaseTemplateName&deploymentPropertyBag=$propertyBag&api-version=3.0"

By the way, I have no idea why the propertyBag is passed on the url instead of in the HTTP body, as it can produce some nasty errors when the query string gets too long.

For good measure, lets verify that the release is actually in progress:

$statusId = Invoke-RestMethod -Credential $credentials `
    -Uri "$orchestratorServiceUrl/ReleaseStatus?releaseId=$releaseId"
$statusMapping = @("?", "NotStarted", "InProgress", "Released", "Stopped", "Rejected", "Abandoned" )
$statusMapping[$statusId]

That’s all there is to it, really!