Public/Get-AzureUtilsResourceInventory.ps1

function Get-AzureUtilsResourceInventory {
    <#
        .SYNOPSIS
            Returns a normalized inventory of Azure resources as pipeline objects.

        .DESCRIPTION
            The object-emitting sibling of Export-AzureUtilsTagInventory: same Azure
            Resource Graph backend and scope filters, but instead of writing an .xlsx
            it streams one object per resource to the pipeline (ready for Where-Object,
            Group-Object, Export-Csv, ConvertTo-Json, etc.). Scope can be narrowed by
            subscription(s) or management group(s), resource group(s), a name substring
            and one or more resource types.

        .PARAMETER SubscriptionId
            One or more subscription IDs. Default: every enabled subscription.

        .PARAMETER ManagementGroupId
            One or more management groups to scan (requires Az.ResourceGraph).

        .PARAMETER ResourceGroupName
            Restrict to these resource groups.

        .PARAMETER NameContains
            Keep only resources whose name contains this text (case-insensitive).

        .PARAMETER ResourceType
            Restrict to these ARM resource types
            (e.g. 'microsoft.compute/virtualmachines').

        .PARAMETER First
            Stop after this many resources (0 = no limit, the default).

        .EXAMPLE
            Get-AzureUtilsResourceInventory | Group-Object ResourceType |
                Sort-Object Count -Descending

        .EXAMPLE
            Get-AzureUtilsResourceInventory -ResourceType 'microsoft.compute/virtualmachines' `
                -NameContains 'prod' | Export-Csv .\vms.csv -NoTypeInformation

        .OUTPUTS
            AzureUtils.ResourceInventory
    #>

    [CmdletBinding(DefaultParameterSetName = 'Subscriptions')]
    [OutputType('AzureUtils.ResourceInventory')]
    param(
        [Parameter(ParameterSetName = 'Subscriptions')]
        [string[]] $SubscriptionId,

        [Parameter(ParameterSetName = 'ManagementGroup', Mandatory)]
        [string[]] $ManagementGroupId,

        [string[]] $ResourceGroupName,

        [string] $NameContains,

        [string[]] $ResourceType,

        [ValidateRange(0, [int]::MaxValue)]
        [int] $First = 0
    )

    $null = Assert-AzureUtilsContext

    $resolved = Resolve-AzureUtilsScope -SubscriptionId $SubscriptionId -ManagementGroupId $ManagementGroupId
    if ($resolved.Type -eq 'Subscription' -and -not $resolved.HasSubscriptions) {
        Write-Warning 'No accessible subscriptions in scope.'
        return
    }

    $query   = New-AzureUtilsResourceInventoryQuery -ResourceGroupName $ResourceGroupName -NameContains $NameContains -ResourceType $ResourceType
    $nameMap = $resolved.NameMap
    $scope   = $resolved.Scope
    Write-Verbose "Resource-inventory query:`n$query"

    Invoke-AzureUtilsGraphQuery -Query $query @scope -First $First | ForEach-Object {
        $subId = [string]$_.subscriptionId
        [pscustomobject]@{
            PSTypeName       = 'AzureUtils.ResourceInventory'
            Name             = $_.name
            ResourceType     = $_.type
            ResourceGroup    = $_.resourceGroup
            Location         = $_.location
            Kind             = $_.kind
            Sku              = $_.skuName
            SubscriptionName = if ($nameMap.ContainsKey($subId)) { $nameMap[$subId] } else { $subId }
            SubscriptionId   = $subId
            Tags             = ConvertTo-AzureUtilsHashtable -InputObject $_.tags
            ResourceId       = $_.id
        }
    }
}