Public/Find-AzureUtilsDuplicateResource.ps1

function Find-AzureUtilsDuplicateResource {
    <#
        .SYNOPSIS
            Finds resources that share the same name and type (likely duplicates).

        .DESCRIPTION
            Uses Azure Resource Graph to group resources by a case-insensitive
            name + type key and returns every member of any group that occurs more
            than once, with the group size in a 'Count' column. Useful for spotting
            accidental duplication (e.g. a resource recreated in a second resource
            group) before a cleanup.

            This is a heuristic: the same name and type in different environments
            (dev / prod) is often intentional. Review before acting.

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

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

        .EXAMPLE
            Find-AzureUtilsDuplicateResource

        .EXAMPLE
            Find-AzureUtilsDuplicateResource -ManagementGroupId 'PLAT' |
                Sort-Object Name, ResourceGroup

        .OUTPUTS
            AzureUtils.DuplicateResource
    #>

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

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

    $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-AzureUtilsDuplicateResourceQuery
    $nameMap = $resolved.NameMap
    $scope   = $resolved.Scope
    Write-Verbose "Duplicate-resource query:`n$query"

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