Public/Test-AzureUtilsTagCompliance.ps1
|
function Test-AzureUtilsTagCompliance { <# .SYNOPSIS Finds resources that are missing one or more required tags. .DESCRIPTION Uses Azure Resource Graph to check every resource in scope against a set of required tag keys and returns one object per violating resource, with a 'MissingTags' list and a human-readable 'Reason'. This is the linter that closes the loop on Export-AzureUtilsTagInventory / Set-AzureUtilsTagInventory: define the tags your governance requires, then find who is out of compliance. A tag counts as present only when it exists AND is non-empty. .PARAMETER RequiredTag One or more tag keys every resource must carry (case-insensitive keys, matched exactly by Azure). Required. .PARAMETER SubscriptionId One or more subscription IDs. Default: every enabled subscription. .PARAMETER ManagementGroupId One or more management groups to scan (requires Az.ResourceGraph). .EXAMPLE Test-AzureUtilsTagCompliance -RequiredTag costCenter, environment .EXAMPLE Test-AzureUtilsTagCompliance -RequiredTag owner -ManagementGroupId 'PLAT' | Export-Csv .\tag-violations.csv -NoTypeInformation .OUTPUTS AzureUtils.TagComplianceViolation #> [CmdletBinding(DefaultParameterSetName = 'Subscriptions')] [OutputType('AzureUtils.TagComplianceViolation')] param( [Parameter(Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [string[]] $RequiredTag, [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-AzureUtilsTagComplianceQuery -RequiredTag $RequiredTag $nameMap = $resolved.NameMap $scope = $resolved.Scope Write-Verbose "Tag-compliance query:`n$query" Invoke-AzureUtilsGraphQuery -Query $query @scope | ForEach-Object { $subId = [string]$_.subscriptionId [pscustomobject]@{ PSTypeName = 'AzureUtils.TagComplianceViolation' Name = $_.name ResourceType = $_.type ResourceGroup = $_.resourceGroup Location = $_.location SubscriptionName = if ($nameMap.ContainsKey($subId)) { $nameMap[$subId] } else { $subId } SubscriptionId = $subId MissingTags = @($_.missingTags) Reason = $_.reason Tags = ConvertTo-AzureUtilsHashtable -InputObject $_.tags ResourceId = $_.id } } } |