Azure / azure-powershell

Microsoft Azure PowerShell
Other
4.27k stars 3.87k forks source link

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

Open o-l-a-v opened 1 month ago

o-l-a-v commented 1 month ago

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

Module versions

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 commented 1 month ago

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) ```pwsh # 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 commented 1 month ago

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
isra-fel commented 1 month ago

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.

microsoft-github-policy-service[bot] commented 1 month ago

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

microsoft-github-policy-service[bot] commented 1 month ago

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