I was looking at some of the Azure DevOps API documentation and noticed that some of the endpoints mention a properties object of type PropertiesCollection. Unfortunately, the details for that data structure are not particularly helpful, and I couldn't figure out how to use it. Some pages include examples, but none that I could find included an expanded properties object.

To figure out how to use it, I created a simple .NET console application. I added references to the following NuGet packages:

  • Microsoft.TeamFoundationServer.Client
  • Microsoft.VisualStudio.Services.InteractiveClient
  • Microsoft.VisualStudio.Services.Release.Client
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.ReleaseManagement.WebApi.Clients;
using Microsoft.VisualStudio.Services.WebApi;

const string collectionUri = "https://dev.azure.com/organisation";
const string projectName = "MyProject";
const string pat = "YOUR-PAT-HERE";
const int releaseId = 20;

var creds = new VssBasicCredential(string.Empty, pat);

// Connect to Azure DevOps Services
var connection = new VssConnection(new Uri(collectionUri), creds);

using var client = connection.GetClient<ReleaseHttpClient>();

// Get data about a specific release
var release = await client.GetReleaseAsync(projectName, releaseId);

release.Properties.Add("Thing", "hey");

// Send the updated release back to Azure DevOps Services
var result = await client.UpdateReleaseAsync(release!, projectName, releaseId);

Console.WriteLine();

This allowed me to create a property key and value, that I could then examine by querying the item (in this case a 'classic' release), by calling the GET endpoint. eg.

https://vsrm.dev.azure.com/{organization}/{project}/_apis/release/releases/{releaseId}?propertyFilters=Thing&api-version=7.0

Note that you need to specify the propertyFilters parameter. Otherwise the properties` object will not be included in the response.

And in doing that, we can see the JSON data structure!

    "properties": {
        "Thing": {
            "$type": "System.String",
            "$value": "hey"
        }
    }

So, to add a property, you need to add a new key/value pair to the properties object, where the key is the name of the property, and the value is an object with two properties: $type and $value. The $type property is the type of the value, and the $value property is the value itself.

The documentation clarifies the types supported:

Values of type Byte[], Int32, Double, DateType and String preserve their type, other primitives are retuned as a String. Byte[] expected as base64 encoded string.

(I think 'DateType' is a typo, and should be 'DateTime')

Now that we know the shape of the data, I can jump back to PowerShell and use that to add a new property:

$uri = "https://vsrm.dev.azure.com/$($organisation)/$($project)/_apis/release/releases/$($releaseId)?api-version=7.0&propertyFilters=Extra"

$result = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers

if (-not ($result.properties.Extra)) {
    $result.properties | Add-Member -MemberType NoteProperty -Name "Extra" -Value @{
        "`$type" = "System.String"
        "`$value" = "haaaa"
    }
}
$body = $result | ConvertTo-Json -Depth 20

"Updating via PUT"

Invoke-RestMethod -Uri $uri -Method Put -Headers $headers -Body $body -ContentType "application/json"