azure-powershell icon indicating copy to clipboard operation
azure-powershell copied to clipboard

Query works but `Search-AzGraph` in `Az.ResourceGraph` v1.0.0 does not manage to parse the output

Open o-l-a-v opened this issue 1 year ago • 7 comments

Description

Following query works in Azure Resource Graph Explorer in the Azure portal ( https://portal.azure.com/#view/HubsExtension/ArgQueryBlade ), and if running Search-AzGraph with -Debug I can see that the API returns data.

resources
| where type in~ ("Microsoft.Automation/automationAccounts","Microsoft.DataFactory/factories","Microsoft.DocumentDB/databaseAccounts","Microsoft.Logic/workflows","Microsoft.KeyVault/vaults","Microsoft.Network/bastionHosts","Microsoft.Network/networkSecurityGroups","Microsoft.Network/publicIPAddresses","Microsoft.Network/virtualNetworkGateways","Microsoft.ServiceBus/namespaces")
// Add tags from resource group
| join kind = leftouter (
  resourcecontainers
  | where type == "microsoft.resources/subscriptions/resourcegroups"
  | project rgName = name, rgSubscriptionId = subscriptionId, rgTags = tags
) on $left.resourceGroup == $right.rgName and $left.subscriptionId == $right.rgSubscriptionId
| project-away rgName, rgSubscriptionId
// Add tags from subscription
| join kind = leftouter (
  resourcecontainers
  | where type == "microsoft.resources/subscriptions"
  | project subName = name, subId = subscriptionId, subTags = tags
) on $left.subscriptionId == $right.subId
| project-away subId

But Search-AzGraph fails with error:

Search-AzGraph: Cannot process argument because the value of argument "name" is not valid. Change the value of the "name" argument and run the operation again.

Issue script & Debug output

PS > Search-AzGraph -UseTenantScope -Query 'resources
| where type in~ ("Microsoft.Automation/automationAccounts","Microsoft.DataFactory/factories","Microsoft.DocumentDB/databaseAccounts","Microsoft.Logic/workflows","Microsoft.KeyVault/vaults","Microsoft.Network/bastionHosts","Microsoft.Network/networkSecurityGroups","Microsoft.Network/publicIPAddresses","Microsoft.Network/virtualNetworkGateways","Microsoft.ServiceBus/namespaces")
// Add tags from resource group
| join kind = leftouter (
  resourcecontainers
  | where type == "microsoft.resources/subscriptions/resourcegroups"
  | project rgName = name, rgSubscriptionId = subscriptionId, rgTags = tags
) on $left.resourceGroup == $right.rgName and $left.subscriptionId == $right.rgSubscriptionId
| project-away rgName, rgSubscriptionId
// Add tags from subscription
| join kind = leftouter (
  resourcecontainers
  | where type == "microsoft.resources/subscriptions"
  | project subName = name, subId = subscriptionId, subTags = tags
) on $left.subscriptionId == $right.subId
| project-away subId'

Search-AzGraph: Cannot process argument because the value of argument "name" is not valid. Change the value of the "name" argument and run the operation again.

PS >

Environment data

  • Windows 11 24H2 x64
  • PowerShell v7.4.6

Module versions

  • Az.Accounts v3.0.4
  • Az.ResourceGraph v1.0.0

Error output

PS > Resolve-AzError -Last

Message        : Cannot process argument because the value of argument "name" is not valid. Change the value of the "name" argument and run the operation again.
StackTrace     :    at Microsoft.Azure.Commands.ResourceGraph.Utilities.ResultExtensions.ToPsObjects(Object data)
                    at Microsoft.Azure.Commands.ResourceGraph.Cmdlets.SearchAzureRmGraph.ExecuteCmdlet()
Exception      : System.ArgumentOutOfRangeException
InvocationInfo : {Search-AzGraph}
Line           : Search-AzGraph @Splat -Debug
Position       : At line:1 char:1
                 + Search-AzGraph @Splat -Debug
                 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
HistoryId      : 53

Message        : Cannot process argument because the value of argument "name" is not valid. Change the value of the "name" argument and run the operation again.
StackTrace     :    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ConvertPropertyValueForPsObject(JToken propertyValue)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.JTokenExtensions.ToPsObject(JToken jtoken)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.ResultExtensions.ToPsObjects(IList`1 rows)
                    at Microsoft.Azure.Commands.ResourceGraph.Utilities.ResultExtensions.ToPsObjects(Object data)
Exception      : System.Management.Automation.PSArgumentException
InvocationInfo : {Search-AzGraph}
Line           : Search-AzGraph @Splat -Debug
Position       : At line:1 char:1
                 + Search-AzGraph @Splat -Debug
                 + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
HistoryId      : 53

PS >

o-l-a-v avatar Oct 24 '24 13:10 o-l-a-v

Might be relevant: I started researching changing out Search-AzGraph with Invoke-AzRestMethod and found that PowerShell could not convert $Response.content from JSON to PSCustomObject with error:

PS > ConvertFrom-Json -InputObject $Response.'content'

ConvertFrom-Json: The provided JSON includes a property whose name is an empty string, this is only supported using the -AsHashTable switch.

PS >

So the JSON response from the Azure RM API clearly contains a "property whose name is an empty string".


Final workaround (it's not pretty, but it works)
# Assets
$Resources = [System.Collections.Generic.List[System.Management.Automation.OrderedHashtable]]::new()
Remove-Variable -Name 'Response' -Force -ErrorAction 'Ignore'
$Query = [string] 'resources
| where type in~ ("Microsoft.Automation/automationAccounts","Microsoft.DataFactory/factories","Microsoft.DocumentDB/databaseAccounts","Microsoft.Logic/workflows","Microsoft.KeyVault/vaults","Microsoft.Network/bastionHosts","Microsoft.Network/networkSecurityGroups","Microsoft.Network/publicIPAddresses","Microsoft.Network/virtualNetworkGateways","Microsoft.ServiceBus/namespaces")
// Add tags from resource group
| join kind = leftouter (
resourcecontainers
| where type == "microsoft.resources/subscriptions/resourcegroups"
| project rgName = name, rgSubscriptionId = subscriptionId, rgTags = tags
) on $left.resourceGroup == $right.rgName and $left.subscriptionId == $right.rgSubscriptionId
| project-away rgName, rgSubscriptionId
// Add tags from subscription
| join kind = leftouter (
resourcecontainers
| where type == "microsoft.resources/subscriptions"
| project subName = name, subId = subscriptionId, subTags = tags
) on $left.subscriptionId == $right.subId
| project-away subId'

# Run resource graph query and handle paging
do {
    $Body = [ordered]@{
        'query'   = [string] $ResourceGraphQuery
        'options' = [ordered]@{
            '$top'               = [byte] 100
            'resultFormat'       = [string] 'objectArray'
            'allowPartialScopes' = [bool] $false
        }
    }
    if (-not [string]::IsNullOrEmpty($Content.'$skipToken')) {
        $Body.'options'.Add('$skipToken', $Content.'$skipToken')
    }
    $Response = Invoke-AzRestMethod -Method 'Post' -Path '/providers/Microsoft.ResourceGraph/resources?api-version=2022-10-01' -Payload (
        ConvertTo-Json -Depth 2 -Compress -InputObject $Body
    )
    if ($Response.'StatusCode' -ne 200) {
        Throw ('That failed with HTTP status code {0}.' -f $Response.'StatusCode')
    }
    $Content = ConvertFrom-Json -InputObject $Response.'content' -AsHashtable
    $Content.'data'.ForEach{$Resources.Add($_)}
    Write-Output -InputObject ('Currently at {0}' -f $Resources.'Count'.ToString())
} until ([string]::IsNullOrEmpty($Content.'$skipToken'))

# Output total of resources
Write-Output -InputObject ('Found a total of {0} resources.' -f $Resources.'Count'.ToString())

Thanks to EnterprisePolicyAsCode for inspiration. 😊

o-l-a-v avatar Oct 24 '24 14:10 o-l-a-v

Another workaround is to exclude properties you're not interested in, hopefully you'll only end up with parsable output.

Example line:

| project id, name, type, location, resourceGroup, subscriptionId, tags

Full example query:

resources
| where type in~ ("Microsoft.Automation/automationAccounts","Microsoft.DataFactory/factories","Microsoft.DocumentDB/databaseAccounts","Microsoft.Logic/workflows","Microsoft.KeyVault/vaults","Microsoft.Network/bastionHosts","Microsoft.Network/networkSecurityGroups","Microsoft.Network/publicIPAddresses","Microsoft.Network/virtualNetworkGateways","Microsoft.ServiceBus/namespaces")
| project id, name, type, location, resourceGroup, subscriptionId, tags
// Add tags from resource group
| join kind = leftouter (
  resourcecontainers
  | where type == "microsoft.resources/subscriptions/resourcegroups"
  | project rgName = name, rgSubscriptionId = subscriptionId, rgTags = tags
) on $left.resourceGroup == $right.rgName and $left.subscriptionId == $right.rgSubscriptionId
| project-away rgName, rgSubscriptionId
// Add tags from subscription
| join kind = leftouter (
  resourcecontainers
  | where type == "microsoft.resources/subscriptions"
  | project subName = name, subId = subscriptionId, subTags = tags
) on $left.subscriptionId == $right.subId
| project-away subId

o-l-a-v avatar Oct 25 '24 11:10 o-l-a-v

Let me loop in ResourceGraph team to understand if the JSON response from the Azure RM API clearly contains a "property whose name is an empty string" is expected behavior.

isra-fel avatar Oct 31 '24 07:10 isra-fel

Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @venu-l.

Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @venu-l.

Hi @o-l-a-v , I tried the same query in latest version and I am able to retrieve the results. Can you please try with latest version and check if that is working for you. If not can you please share more details.

Image

Name Version


Az.ResourceGraph 1.2.1

srinivaskmsft avatar Nov 11 '25 06:11 srinivaskmsft

@srinivaskmsft

I'm at a new customer were I haven't yet gotten any Azure RM access, so I can't test.

o-l-a-v avatar Nov 11 '25 06:11 o-l-a-v