Have you ever used Azure CLI? Azure CLI is probably the best way to interact with Microsoft Azure and to automate recurring tasks in the context of Microsoft’s public cloud. However, I often see people writing fundamental shell scripts to execute more advanced queries or filter simple lists of elements returned from Azure CLI.

Although using shell scripts is an excellent way to get things done, it’s more time-consuming compared to learning all the capabilities that Azure CLI brings to the table. Microsoft provides some good documentation on how to write fundamental queries with Azure CLI. However, more advanced, real-world queries are not documented. This is why people start tinkering with shell scripts instead of using built-in capabilities. So let’s quickly catch up with fundamental JMESPath queries that every Azure CLI user should know.

Projecting a property from an object

Let’s say you want to get the resource identifier of an Azure Container Registry instance to attach that particular instance to an Azure Kubernetes Service (AKS). You can do so by combining --query and -otsv:

# store ACR resource identifier in acrId
acrId=$(az acr show -n myacr --query "id" -otsv)

echo $acrId
# /subscriptions/<SUB_ID>/resourceGroups/rg-sample/providers/Microsoft.ContainerRegistry/registries/myacr

Note that this command lacks the -g (--resource-group) flag. It’s not required for az acr.

Project a property from objects in an array

Now let’s grab all resource identifiers of storage accounts within a certain Resource Group:

az storage account list -g rg-sample --query "[].{myId:id}" -ojson

The query consists of multiple parts. First, we specify that we expect the command to return an array that we want to do further processing on [] to project properties from objects in that array; we specify an alias of myId which should be set to the id property of every object.

[
    {
        "myId": ""
    },
    {
        "myId": ""
    }
]

Project multiple properties from objects in an array

We can extend the previous example to grab also the name of the storage account and the stock keeping unit (sku):

az storage account list -g rg-sample \
  --query "[].{myId:id,name:name,sku:sku.name}" \
  -ojson

Take a look at sku. We use dot-notation (.) to access nested properties. Which will return the list of all storage accounts in rg-sample as JSON:

[
  {
    "myId": "",
    "name": "",
    "sku": ""
  },
  {
    "myId": "",
    "name": "",
    "sku": ""
  }
]

Filtering arrays

Filtering arrays in Azure CLI can become more complicated depending on the scenario. Microsoft provides some samples on filtering arrays using operators and built-in functions using string fields.

However, in reality, there is more than just strings :). Again, let’s review basic filtering:

az storage account list -g rg-samples \
  --query "[?accessTier == 'Hot']" \
  -otable

We extend our intention to process an array [] by specifying the filter condition, which must evaluate as true. The condition is introduced by ? followed by the actual condition.

Use logical operators in filters

In the previous example, we queried for all Storage Accounts with accessTier set to Hot, let’s extend the query by using the logical and operator && to filter also on the sku.tier property:

az storage account list -g rg-samples \
  --query "[?accessTier == 'Hot' && sku.tier == 'Standard']" \
  -otable

Combine filters and projections

Obviously, we can combine filters and projections in a single query:

az storage account list -g rg-samples \
  --query "[?accessTier == 'Hot' && sku.tier == 'Standard]{myId:id,name:name,sku:sku.name}" \
  -otable

Filter arrays (booleans)

To filter arrays on fields of type boolean, we must escape the desired field value. See the following query, escaping false by using a combination of ````` (backslash + back-tick):

az storage account list -g rg-samples \
  --query "[?allowBlobPublicAccess==\`false\`]" \
  -otable

We can simplify the query if we want to check if the boolean property is true by removing the comparison:

az storage account list -g rg-samples \
  --query "[?allowBlobPublicAccess]" \
  -otable

Filter arrays (numbers)

Filtering arrays on fields of type number works the same way as filtering on fields of type boolean. Again, we must use proper escaping syntax. For demonstration purposes, let’s examine some information about the secure score calculated by Microsoft Defender for Cloud:

az security secure-score-controls list \
  --query "[?unhealthyResourceCount>=\`1\`]" \
  -otable

This works also for floating-point numbers:

az security secure-score-controls list \
  --query "[?current>=\`3.3\`]"

Sorting arrays by a property

We can sort arrays using the built-in sort_by function. For example, let’s sort all secure score control details returned from the previous query by the number of healthy resources (healthyResourceCount):

az security secure-score-controls list \
  --query "sort_by([? current  >= \`3.30\`], &healthyResourceCount)" \
  -otable

You’ve to prefix the desired property (healthyResourceCount) with & to tell Azure CLI that the desired property is a nested in every object of the array.

Working with nested arrays

Sometimes, requirements are a bit more sophisticated. Depending on the actual resource type, received responses may become pretty big. Required information is always in some nested array or property (at least for me :D). Luckily, we can extend our JMESPath queries to support more complex situations.

Project properties from nested arrays

Have you ever wondered which permissions are associated with a given principal in an instance of Azure Key Vault? You can get information like this quickly by using proper JMESPath queries as shown here:

az keyvault show -n kv-sample -g rg-sample \
  --query "properties.accessPolicies[*].{objectId:objectId,permissions:permissions}[]" \
  -ojson

This query will print all object identifiers and their associated permissions in the format of

[
  {
    "objectId": "",
    "permissions": 
    {
      "certificates": [],
      "keys": [],
      "secrets": [],
      "storage": []
    }
  },
  {
    "objectId": "",
    "permissions": 
    {
      "certificates": [],
      "keys": [],
      "secrets": [],
      "storage": []
    }
  }
]

Filter and project properties from nested arrays

We can take the previous sample further and ask Azure CLI to show only object identifiers and their permissions if the underlying principal can delete secrets from Azure Key Vault. To do so, we add a filter that uses the JMESPath built-in function contains.

az keyvault show -n kv-sample -g rg-sample \
  --query "properties.accessPolicies[*].{objectId:objectId,permissions:permissions}[? contains(permissions.secrets, 'Delete')]" \
  -ojson

Again, we will receive the same response structure. However, we’ll only receive access policies with the Delete permission set for secrets this time.

Recap

As you can see, JMESPath is quite powerful. It can do more than just projecting a single property from a JSON object. JMESPath offers more than I’ve covered in this article. However, I wanted to document some practical examples which are not part of the official Azure CLI documentation.

Don’t get me wrong! I’m not saying stop using shell scripts. Quite the reverse! Script everything, but know the capabilities of the tools you are using. Using more advanced JMESPath queries allows you to address requirements without increasing complexity.