Get started with Azure Resource Graph and PowerShell

Using Azure PowerShell against one resource group is easy, but things can get more complexes when you need to query resources over multiple resources groups or subscriptions. You can try to connect to subscriptions one by one, but there is a better option, Azure Resource Graph.

Azure Resource Graph is an exploration tool designed to query different kinds of resources across an Azure tenant efficiently. The query language used by Resource Graph is the same you use in Azure Data Explorer or Azure Monitor, Kusto Query Language. This language is similar to the SQL language, and it supports concepts like tables, columns, filtering, grouping, sorting, join …

You will not have the same result as in an Azure PowerShell Query, but you will get the result faster across your subscriptions or a subset of subscriptions. This performance comes with a drawback, you will not get a collection of objects like with PowerShell with full detail for each object, but a tabular result with a limited number of data.
Azure Resource Graph lets you query several tables, the most used is ‘Resources’ to query your resources. But there are several other tables.

  • ResourceContainers, for subscriptions and resource groups
  • AdvisorResources, to query data from Azure Advisor
  • AlertsManagementResources, to query data from Azure Monitor Alerts
  • ExtendedLocationResources, for extended locations used in Azure Arc
  • GuestConfigurationResources, for Azure Policy Guest configuration extension in VM
  • KubernetesConfigurationResources, for Kubernetes Source Control Configuration
  • PatchAssessmentResources, for VM patch management
  • PatchInstallationResources, for VM patch installation
  • PolicyResources, for Azure Policy data
  • RecoveryServicesResources, for IaaS backup and DR data
  • ServiceHealthResources, for Azure Service Health Issues and Events

You can have the complete list here

To query a table, you can use the Azure Portal, you can also use libraries and packages for .Net, Java, JavaScript, Go, Python, Ruby … or you can use the Rest API.
This last method was the only way to use Azure Resource Graph with PowerShell. And you had to authenticate to the Rest API event if you already had a token from the AZ PowerShell module.
But now, there is a module to query Azure Resource Graph with the token used by Azure PowerShell.
This module is not, yet, integrated into the Azure PowerShell. You have to install it from the PowerShell gallery.

Install-Module -Name Az.ResourceGraph -Scope CurrentUser

You should get the 0.10.0 version.

The module contains five commands:

  • Search-AzGraph
  • Get-AzResourceGraphQuery
  • New-AzResourceGraphQuery
  • Remove-AzResourceGraphQuery
  • Update-AzResourceGraphQuery

Only the first one lets you query Azure Resource Graph.

Let's try our first query. Imagine you were asked to list all VM deployed across your tenant. In the Azure Portal, the query will look like

resources
| where type =~ 'Microsoft.Compute/virtualMachines'

Remember, KQL is case sensitive

In PowerShell

Search-AzGraph -Query "resources | where type =~ 'Microsoft.Compute/virtualMachines'"

You will get an object with two properties a string, SkipToken, and a generic IList object, data. This last object contains the result of the query.

(Search-AzGraph -Query "resources | where type =~ 'Microsoft.Compute/virtualMachines'").data

By default, the Search-AzGraph cmdlet performs a search across subscriptions you have access to. But you can limit it to one subscription by changing your PowerShell profile.

$PSDefaultParameterValues=@{"Search-AzGraph:Subscription"= $(Get-AzSubscription).ID}

Or by using the -Subscription parameter, you can use an array containing subscription IDs you want to query.

Search-AzGraph -Query "Resources" -Subscription @('xxxxx-xxxx-xxxx-xxxx','xxxxx-xxxx-xxxx-xxxx'')

The Query parameter is used for the Azure Resource Graph Query. As we need to use simple quotes for Azure Graph we need to enclose it with double-quotes.
By default, the result of a query is limited to 100 rows. If you need to retrieve more results you will need to use the -First parameter. It takes an integer between 1 and 1000 to limit the number of returned rows.

You can also use the parameter -Skip with an integer to omit the x first rows.

You can also use the limit keyword in the KQL query if you want to limit the result up to 100

resources | limit 10

The query parameter contains the query you want to run against the Azure Resource Graph API. It a string it should surrender by a doubled quote because KQL will use simple quotes.

In case of an error in your query or the KQL syntax, you will have a message like this.

Search-AzGraph -Query "Resource"
Search-AzGraph: {
  "error": {
    "code": "BadRequest",
    "message": "Please provide below info when asking for support: timestamp = 2021-07-04T09:35:16.1689113Z, correlationId = a9447f06-5912-46cd-8907-7b430fcd8a98.",
    "details": [
      {
        "code": "DisallowedLogicalTableName",
        "message": "Table Resource is invalid, unsupported or disallowed."
      }
    ]
  }
}

This error is reported by the Resource Graph API and not by PowerShell itself, so you cannot use a Try/Catch block to manage this exception. As you can see the error message is formatted in JSON.

To manage Azure Graph Resource error as a standard PowerShell exception that works with a try/catch block, there are several steps.

First, we need to create a custom exception to manage the error and later retrieve the error code and the error detail. You can do that by extending the Exception class. In other words, create an inherited class from the Exception class.

class AzResourceGraphException : Exception {
    [string] $additionalData

    AzResourceGraphException($Message, $additionalData) : base($Message) {
        $this.additionalData = $additionalData
    }
}

The constructor is derived from the Exception Class and adds the property additionalData. This property will store the complete error message found in the Resource Graph error.

To avoid displaying the Resource Graph error message, in case of KQL error, you will need to add the parameter -ErrorAction SilentlyContinue in your statement.
But we need to capture the error message if an error occurs at the Resource Graph level. This is the purpose of the -ErrorVariable parameter. It will store any error message as an ArrayList.
We can then test if the arrayList is empty or not. If not, you can throw an exception. To get the error detail, we will have to extract the JSON from the API and convert it from JSON to use it in PowerShell and access the Error Code and the Message.

$resourceGraphQuery = "Resource" 

Search-AzGraph -Query $resourceGraphQuery -ErrorVariable grapherror -ErrorAction SilentlyContinue 

if ($null -ne $grapherror.Length) {

    $errorJSON = $grapherror.ErrorDetails.Message | ConvertFrom-Json

    throw [AzResourceGraphException]::new($errorJSON.error.details.code, $errorJSON.error.details.message)

}

Now that you can throw an exception in case of error with KQL you can use a try/catch block.

The complete solution

class AzResourceGraphException : Exception {
    [string] $additionalData

    AzResourceGraphException($Message, $additionalData) : base($Message) {
        $this.additionalData = $additionalData
    }
}

try {
    $resourceGraphQuery = "Resource" 

    Search-AzGraph -Query $resourceGraphQuery -ErrorVariable grapherror -ErrorAction SilentlyContinue 

    if ($null -ne $grapherror.Length) {

        $errorJSON = $grapherror.ErrorDetails.Message | ConvertFrom-Json

        throw [AzResourceGraphException]::new($errorJSON.error.details.code, $errorJSON.error.details.message)

    }
}
catch [AzResourceGraphException] {
    Write-Host "An error on KQL query"
    Write-Host $_.Exception.message
    Write-Host $_.Exception.additionalData
}
catch {
    Write-Host "An error occurred in the script"
    Write-Host $_.Exception.message
}

The possibility to query Azure Resource Graph with PowerShell open you new possibilities to create a report and query your resources across multiple subscriptions in an efficient way.

23