Public/Find-AzureUtilsPolicyNonCompliant.ps1

function Find-AzureUtilsPolicyNonCompliant {
    <#
        .SYNOPSIS
            Finds resources that are non-compliant with Azure Policy.

        .DESCRIPTION
            Uses Azure Resource Graph (the 'policyresources' table) to list resources
            whose current Azure Policy compliance state is 'NonCompliant', naming the
            offending policy assignment and definition in a 'Reason' column. Returns
            one object per non-compliant (resource, policy) pair - a resource that
            violates several policies appears once per policy.

            Compliance is evaluated on Azure's own schedule; a resource just fixed may
            still show here until the next evaluation.

        .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-AzureUtilsPolicyNonCompliant

        .EXAMPLE
            Find-AzureUtilsPolicyNonCompliant -ManagementGroupId 'PLAT' |
                Group-Object PolicyAssignment | Sort-Object Count -Descending

        .OUTPUTS
            AzureUtils.PolicyNonCompliant
    #>

    [CmdletBinding(DefaultParameterSetName = 'Subscriptions')]
    [OutputType('AzureUtils.PolicyNonCompliant')]
    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-AzureUtilsPolicyNonCompliantQuery
    $nameMap = $resolved.NameMap
    $scope   = $resolved.Scope
    Write-Verbose "Policy-compliance query:`n$query"

    Invoke-AzureUtilsGraphQuery -Query $query @scope | ForEach-Object {
        $subId = [string]$_.subscriptionId
        [pscustomobject]@{
            PSTypeName       = 'AzureUtils.PolicyNonCompliant'
            Name             = $_.resName
            ResourceType     = $_.resourceType
            ResourceGroup    = $_.resourceGroup
            Location         = $_.location
            SubscriptionName = if ($nameMap.ContainsKey($subId)) { $nameMap[$subId] } else { $subId }
            SubscriptionId   = $subId
            PolicyAssignment = $_.policyAssignment
            PolicyDefinition = $_.policyDefinition
            Reason           = $_.reason
            ResourceId       = $_.resourceId
        }
    }
}