| name | azure-policy-audit |
| description | Audit Azure Policy definitions for a specific service (e.g. Storage, Compute, KeyVault). Finds built-in policy definitions related to the service, checks which are currently assigned in the subscription, and returns the unassigned policies with their names and IDs. Use this skill when asked about Azure policy coverage, missing policies, unassigned policies, or policy gaps for an Azure service. Also use this skill when asked to check if specific policies are assigned, verify policy deployment status, confirm whether policies are deployed in Azure, or look up assignment status for specific policy names or IDs. |
Azure Policy Audit Skill
When the user asks you to audit Azure policies for a specific service, follow these steps exactly.
Step 0 — Detect EPAC repo context (if available)
Before starting the audit, check if you are running inside an EPAC repository by searching for a Definitions/global-settings.jsonc file using glob patterns like **/Definitions/global-settings.jsonc.
If found, read and parse global-settings.jsonc to extract pacEnvironments:
{
"pacEnvironments": [
{
"pacSelector": "epac-dev",
"tenantId": "...",
"deploymentRootScope": "/providers/Microsoft.Management/managementGroups/mg-name"
}
]
}
- If there is exactly one
pacEnvironment, use its deploymentRootScope as the scope for querying assignments in Step 3. Do NOT prompt the user for scope selection.
- If there are multiple
pacEnvironment entries, ask the user which pac selector to use for the audit scope.
- Use the
deploymentRootScope value from the selected pac environment instead of defaulting to the tenant root management group.
If no global-settings.jsonc is found, fall back to the tenant root management group approach in Step 3.
Step 1 — Determine audit mode
The skill supports two modes:
Mode A — Specific policy check
If the user provides specific policy definition IDs, names, or display names to check (e.g. "check if these two policies are assigned"), skip Step 3 entirely. Use the provided policy identifiers directly and proceed to Step 4 to check their assignment status.
If policy definition IDs were provided, use them directly. If only display names or short names were provided, resolve them first:
# Resolve by display name
Get-AzPolicyDefinition | Where-Object { $_.DisplayName -eq '<DisplayName>' } | Select-Object -Property DisplayName, Name, @{N='PolicyDefinitionId';E={$_.Id}}
Mode B — Full service audit (default)
If the user asks about a service category broadly (e.g. "audit Storage policies"), continue to Step 2 to retrieve all built-in policies for that service.
Sub-topic filtering
If the user specifies a sub-topic within a service (e.g. "HTTPS policies for Storage", "encryption policies for SQL"), note the keyword filter and apply it in Step 3 after retrieving policies by category — filter results where DisplayName or Description matches the sub-topic keyword.
Step 2 — Identify the target service
Ask the user which Azure service to audit if not already specified. Common service keywords include:
- Storage →
Microsoft.Storage
- Compute →
Microsoft.Compute
- KeyVault →
Microsoft.KeyVault
- SQL →
Microsoft.Sql
- Network →
Microsoft.Network
- App Service →
Microsoft.Web
- Kubernetes →
Microsoft.ContainerService
- Cosmos DB →
Microsoft.DocumentDB
Use the resource provider namespace to match policy definitions accurately.
Step 3 — Retrieve built-in policy definitions for the service
Skip this step if using Mode A (specific policy check) from Step 1.
Use Az PowerShell to list built-in policy definitions related to the target service. Replace <ServiceKeyword> with the service name the user specified (e.g. "Storage", "Key Vault").
First, try filtering by the policy metadata category which is the most reliable method:
Get-AzPolicyDefinition | Where-Object { $_.PolicyType -eq 'BuiltIn' -and $_.Metadata.category -eq '<ServiceKeyword>' } | Select-Object -Property DisplayName, Name, @{N='PolicyDefinitionId';E={$_.Id}}
If the category filter returns no results, fall back to matching on DisplayName and Description:
Get-AzPolicyDefinition | Where-Object { $_.PolicyType -eq 'BuiltIn' -and ($_.DisplayName -match '<ServiceKeyword>' -or $_.Description -match '<ServiceKeyword>') } | Select-Object -Property DisplayName, Name, @{N='PolicyDefinitionId';E={$_.Id}}
Store the resulting list of policy definition IDs and display names.
If a sub-topic keyword was identified in Step 1 (e.g. "HTTPS", "encryption", "TLS"), further filter the results:
$policies = $policies | Where-Object { $_.DisplayName -match '<SubTopicKeyword>' -or $_.Description -match '<SubTopicKeyword>' }
Step 4 — Retrieve current policy assignments
Use Az PowerShell to retrieve all policy assignments at the target scope. The scope depends on whether EPAC context was detected in Step 0.
If EPAC context was detected (global-settings.jsonc found)
Use the deploymentRootScope from the selected pac environment:
$rootScope = "<deploymentRootScope from global-settings.jsonc>"
$subId = (Get-AzContext).Subscription.Id
If no EPAC context (fallback)
Use the tenant root management group:
$tenantId = (Get-AzContext).Tenant.Id
$subId = (Get-AzContext).Subscription.Id
$rootScope = "/providers/Microsoft.Management/managementGroups/$tenantId"
Query assignments
Then retrieve policy assignments from both the root scope and the subscription (with descendants). The -IncludeDescendent switch is not supported at management group scope, so we query each scope separately and deduplicate:
$mgAssignments = Get-AzPolicyAssignment -Scope $rootScope
$subAssignments = Get-AzPolicyAssignment -Scope "/subscriptions/$subId" -IncludeDescendent
$assignments = @($mgAssignments) + @($subAssignments) | Sort-Object -Property Id -Unique
From the assignment results, extract the PolicyDefinitionId from each assignment. These are the policies that are currently assigned.
$assignedPolicyDefIds = @()
foreach ($assignment in $assignments) {
$defId = $assignment.PolicyDefinitionId
if ($defId -match 'policySetDefinitions') {
# This is an initiative — resolve the individual policy definitions inside it
try {
$setDef = Get-AzPolicySetDefinition -Id $defId -ErrorAction Stop
foreach ($p in $setDef.PolicyDefinition) {
$assignedPolicyDefIds += $p.policyDefinitionId
}
} catch {
Write-Warning "Could not resolve initiative: $defId"
}
} else {
$assignedPolicyDefIds += $defId
}
}
$assignedPolicyDefIds = $assignedPolicyDefIds | Sort-Object -Unique
IMPORTANT: Policy assignments can reference either individual policy definitions or policy set definitions (initiatives). When checking initiatives, you need to look inside each initiative's policyDefinitions array to find the individual policy definition references contained within. A policy definition is considered "assigned" if:
- It is directly assigned as a standalone policy assignment, OR
- It is included within an assigned initiative (policy set definition)
Step 5 — Compare and identify unassigned policies
Compare the list of built-in policy definitions from Step 3 (or the specific policies from Step 1 Mode A) against the assigned policy definition IDs from Step 4.
A policy is unassigned if its definition ID does not appear in any current assignment, either directly or within an initiative.
Step 6 — Return the results
Present the results in a clear, structured format. Always include two sections:
Assigned Policies
List policies that ARE currently assigned:
| Display Name | Policy Definition ID | Assignment Type |
|---|
| name | /providers/Microsoft.Authorization/policyDefinitions/xxx | Direct / Via Initiative |
Unassigned Policies
List policies that are NOT currently assigned:
| Display Name | Policy Definition ID |
|---|
| name | /providers/Microsoft.Authorization/policyDefinitions/xxx |
Summary
- Total built-in policies found for the service: N
- Currently assigned: N (direct: N, via initiative: N)
- Not assigned: N
Important Notes
- Only include BuiltIn policy definitions. Do not include Custom policies unless the user explicitly asks.
- The policy definition ID is the full resource ID (e.g.
/providers/Microsoft.Authorization/policyDefinitions/<guid>).
- If the user asks to assign the unassigned policies, provide the policy definition IDs in a format that can be consumed by other skills or scripts.
- When returning results for consumption by other skills, output a JSON array:
[
{
"displayName": "Storage accounts should restrict network access",
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/xxx"
}
]