Private/New-AzureUtilsTagComplianceQuery.ps1

function New-AzureUtilsTagComplianceQuery {
    <#
        .SYNOPSIS
            Builds the Resource Graph (KQL) query for required-tag compliance.
        .DESCRIPTION
            Pure, side-effect-free. For every resource, accumulates the required tag
            keys that are missing or empty into a 'missing' array and keeps only the
            resources that violate at least one requirement. Tag keys are escaped
            against query injection.
        .PARAMETER RequiredTag
            One or more tag keys that every resource must carry (non-empty).
        .OUTPUTS
            System.String (the KQL query)
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string[]] $RequiredTag
    )

    # Escape a value for use inside a single-quoted KQL string literal.
    $escape = { param($value) ($value -replace '\\', '\\') -replace "'", "\'" }

    $lines = [System.Collections.Generic.List[string]]::new()
    $lines.Add('resources')
    $lines.Add('| extend __missing = dynamic([])')

    foreach ($key in $RequiredTag) {
        $safe = & $escape $key
        $lines.Add("| extend __missing = iff(isempty(tostring(tags['$safe'])), array_concat(__missing, dynamic(['$safe'])), __missing)")
    }

    $lines.Add('| where array_length(__missing) > 0')
    $lines.Add("| extend reason = strcat('Missing required tag(s): ', strcat_array(__missing, ', '))")
    # 'missing' is a reserved identifier in the Resource Graph KQL dialect, so the
    # projected column is named 'missingTags'.
    $lines.Add('| project id, name, type, resourceGroup, location, subscriptionId, tags, reason, missingTags = __missing')

    return ($lines -join "`n")
}