Azure Virtual Desktop - Working with registration tokens

Language expression property error in Azure Bicep v0.11.1 or higher

A registration token for an Azure Virtual Desktop host pool is a unique identifier used to join new session hosts to the host pool. You would use it to securely add and manage virtual machines within the host pool, ensuring that only authorized devices are granted access.

When using newer versions of Azure Bicep starting with v0.11.1, it is no longer possible to access the Azure Virtual Desktop (AVD) host pool registration token directly using the resource identifier inside of a Bicep template.

The following code is no longer valid.

resource hp 'Microsoft.DesktopVirtualization/hostPools@2022-04-01-preview' = {
    name: 'test-avd-hp-we-001'
    location: deploymentLocation
    properties: {
        hostPoolType: 'Pooled'
        loadBalancerType: 'BreadthFirst'
        preferredAppGroupType: 'Desktop'
        maxSessionLimit: 10
        startVMOnConnect: true
        registrationInfo: {
            registrationTokenOperation: 'Update'
            expirationTime: dateTimeAdd(expirationTime, 'PT2H')
        }
    }
}

// This is where the problems start
output registrationToken string = hp.properties.registrationInfo.token

The deployment fails with the following error.

{
  "code": "DeploymentOutputEvaluationFailed",
  "message": "Unable to evaluate template outputs: 'registrationToken'. Please see error details and deployment operations. Please see https://aka.ms/arm-common-errors for usage details.",
  "details": [
    {
      "code": "DeploymentOutputEvaluationFailed",
      "target": "registrationToken",
      "message": "The template output 'registrationToken' is not valid: The language expression property 'token' can't be evaluated.."
    }
  ]
}

Introduction

So, why would you want to use this feature at all?
There are several reasons why you might want to access the registration token during an Azure Bicep deployment. In my test lab, I often update the token during deployment and capture it as output for manual dev/test purposes. You can also store the registration token in an Azure KeyVault or pass it back to another module. This is a smart move if you're deploying your session hosts within the same deployment.


Troubleshooting

So let's take a closer look at why it is no longer possible to access the registration token by referencing the host pool resource identifier.
When you add the code shown above to your Azure Bicep project in Visual Studio Code, everything looks fine. There is no error from the Bicep language server that the registration token reference is invalid code.

Let's remember that this code was still valid until the version of Bicep CLI v0.11.1. To find out what happens under the hood we compile our Bicep file into an ARM template.

To create the first ARM template example, I will use the latest Bicep CLI v0.16.2, in which we are experiencing deployment issues and which is the most recent version at the time of writing this article.

Note: I am a big fan of the Bicep CLI embedded in the Azure CLI. It is very easy to list all available versions using the az bicep list-versions command. You can seamlessly switch between available versions using the command az bicep install --version 0.xx.x.

After executing the command az bicep build --file main.bicep, our line representing the registration token output appears as follows:

"outputs": {
    "registrationToken": {
      "type": "string",
      "value": "[reference(resourceId('Microsoft.DesktopVirtualization/hostPools', 'test-avd-hp-we-001'), '2021-07-12').registrationInfo.token]"
    }

Here we can see a reference function was built holding the resource Id of our host pool and an API version 2021-07-12.
Okay, now let's downgrade our Bicep CLI to version v0.10.61 and run the build command again.

To downgrade the Bicep CLI, we will run the command: az bicep install --version 0.10.61.

"outputs": {
    "registrationToken": {
      "type": "string",
      "value": "[reference(resourceId('Microsoft.DesktopVirtualization/hostPools', 'test-avd-hp-we-001')).registrationInfo.token]"
    }
  }

In this example, we can see that our output was built using the same reference and resourceId functions. The difference here is that no API version was specified.

We can even go one step further and validate this behaviour using the Azure Resource Manager REST API. In the first round, we build the following request using the resource id of our host pool and the API version '2021-07-12' provided in the ARM template we built with the latest Bicep CLI.

GET: https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-demo-avd1-we/providers/Microsoft.DesktopVirtualization/hostPools/test-avd-hp-we-001?api-version=2021-07-12

RESPONSE: 
{
  "name": "test-avd-hp-we-001",
  "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourcegroups/rg-demo-avd1-we/providers/Microsoft.DesktopVirtualization/hostpools/test-avd-hp-we-001",
  "type": "Microsoft.DesktopVirtualization/hostpools",
  "location": "westeurope",
  "tags": null,
  "kind": null,
  "systemData": {
    ...
  },
  "properties": {
    ...
    "hostPoolType": "Pooled",
    ...
    "maxSessionLimit": 10,
    "loadBalancerType": "BreadthFirst",
    "validationEnvironment": false,
    "registrationInfo": null,
    ...
  }
}

In the response, the 'registrationInfo' property is null, indicating that we don't receive any registration information from the REST API using this version.

We can still cross-check this by using an older API version for our request. So let's do it.

GET: https://management.azure.com/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-demo-avd1-we/providers/Microsoft.DesktopVirtualization/hostPools/test-avd-hp-we-001?api-version=2021-01-14-preview

RESPONSE:
{
  "name": "test-avd-hp-we-001",
  "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourcegroups/rg-demo-avd1-we/providers/Microsoft.DesktopVirtualization/hostpools/test-avd-hp-we-001",
  "type": "Microsoft.DesktopVirtualization/hostpools",
  "location": "westeurope",
  "tags": null,
  "kind": null,
  "systemData": {
    ...
  },
  "properties": {
    "friendlyName": null,
    "description": null,
    "hostPoolType": "Pooled",
    ...
    "maxSessionLimit": 10,
    "loadBalancerType": "BreadthFirst",
    ...
    "registrationInfo": {
      "resetToken": false,
      "expirationTime": "2023-03-26T21:55:24Z",
      "token": "***",
      "registrationTokenOperation": "None"
    },
    ...
  }
}

Look, we can access the registration token directly again using the older API version.


Solutions

Downgrade to Bicep CLI v0.10.61

Let's not talk about it. The Bicep CLI is under active development and it's always a good idea to use the latest version to benefit from the latest features and fixes.

Create the reference yourself

During our troubleshooting, we discovered that accessing the registration token directly through the resource identifier is no longer possible. Code such as hp.properties.registrationInfo.token will not function with newer versions of the Bicep CLI due to the API version Bicep compiling into our template.

To resolve the issue, simply construct the reference yourself. The code below, for instance, will compile into a valid ARM template:

registrationtoken: reference(hp.id).registrationInfo.token

Now you can proceed with exciting tasks using your registration token, such as storing it as an Azure KeyVault secret.


Closing words

I don't believe it's negative that in recent API versions, accessing the registration token is no longer possible. The registration token is a secret and should be handled accordingly.

In the future, I would like to see a dedicated function to access the token. Similar to what has already been solved for Azure KeyVaults with .GetSecret(). However, this is something that would have to be implemented on the provider side and not in Bicep.

Whenever you run into problems like those described in this blog, it's always a good idea to compile your template into ARM code. There is no serious reason to learn ARM templates in the age of Azure Bicep. However, some problems are easier to solve and understand at the ARM code level.

Inspiration

This blog was written while listening to the album 'Money For Nothing (Remastered 2022)' by Dire Straits. Enjoy it.