Governance

    How Do You Prevent Azure Policy Exemptions From Derailing Deployments?

    Prevent Azure Policy compliance failures from blocking deployments by tracking expiring exemptions, remediating non-compliant resources, and automating compliance monitoring.

    Spotto
    February 2026
    25 min read
    Azure Policy Exemptions and Compliance Monitoring

    Azure Policy compliance failures are one of the most common deployment blockers in governed Azure environments. A deployment that worked last week suddenly fails because a policy exemption expired overnight, or a resource that passed validation at creation time is now flagged as non-compliant after a policy assignment update. These failures are particularly disruptive because they surface at deployment time rather than during planning, forcing engineering teams to scramble for exemptions or remediation while release timelines slip.

    The root cause is almost always the same: no one is actively tracking exemption expiry dates, no one is monitoring the compliance state of existing resources against evolving policy assignments, and no one has automated the remediation workflow. Policy compliance is treated as a gate rather than a continuous process, which means problems accumulate silently until they block something visible.

    Based on what we typically see, the most effective approach combines three practices: proactive exemption lifecycle tracking so you know what is expiring before it blocks a deployment, systematic remediation of non-compliant resources so your compliance posture improves over time rather than degrading, and automated monitoring that scales across subscriptions and tenants. Spotto's governance platform continuously identifies expiring exemptions and non-compliant resources, surfacing targeted remediation steps before they become deployment blockers.

    Key Takeaways at a Glance:

    Expired exemptions re-activate policy effects immediately, blocking deployments without warning
    Non-compliant resources create security gaps and audit findings that compound over time
    Resource Graph enables cross-subscription exemption queries for tenant-wide visibility
    DoNotEnforce mode allows safe staging of enforcement changes before they impact production
    Automated monitoring scales compliance tracking across tenants without manual audit cycles

    What Problem Does Non-Compliance Cause?

    Expiring Exemptions

    Azure Policy exemptions have an optional expiry date. When that date passes, the exemption is automatically removed and the underlying policy effect re-activates. If the effect is Deny, any new deployment that violates the policy will fail immediately. If the effect is Modify or DeployIfNotExists, the resource will be flagged as non-compliant and may be auto-remediated in ways the team did not anticipate.

    Operational Pain:

    Deployments fail at resource creation time with cryptic policy denial messages
    CI/CD pipelines break during off-hours when exemptions expire on schedule
    Engineering teams waste hours diagnosing what changed when the answer is an expired exemption
    Emergency exemption requests bypass change management, creating governance debt

    Business Impact:

    Release timelines slip when deployments are blocked by unexpected policy enforcement
    Stakeholder trust erodes when governance is perceived as a blocker rather than an enabler
    MSPs face customer escalations when managed environments experience unexpected deployment failures

    Financial Consequence:

    Engineering time spent on emergency triage instead of planned work
    Delayed deployments can mean delayed revenue for feature-gated releases
    Audit remediation costs increase when non-compliance is discovered late

    Engineering Trade-off:

    Setting exemptions without expiry avoids deployment disruption but creates permanent policy gaps
    Setting short expiry dates forces timely remediation but requires active tracking to avoid surprise failures
    The right approach is time-bounded exemptions paired with automated expiry monitoring

    Non-Compliant Resources

    Non-compliant resources are those that exist in your environment but do not meet the conditions specified by an active policy assignment. These resources were either created before the policy was assigned, created with an exemption that has since expired, or drifted out of compliance due to configuration changes.

    Operational Pain:

    Non-compliant resources accumulate silently, making the compliance posture progressively worse
    Remediation tasks require understanding each policy's expected state, which varies by assignment
    Large numbers of non-compliant resources make it difficult to prioritize which to fix first
    Compliance scans run asynchronously, so the portal view may be stale by hours or days

    Business Impact:

    Audit findings increase when non-compliant resources are discovered during reviews
    Security posture weakens when resources bypass intended controls
    Regulatory compliance certifications (SOC 2, ISO 27001, CIS) may be jeopardized

    Financial Consequence:

    Bulk remediation is more expensive than incremental compliance maintenance
    Non-compliant resources may incur costs for configurations that policy would have prevented
    Failed audits can result in contractual penalties or lost business opportunities

    Engineering Trade-off:

    Aggressive remediation can cause service disruption if resources are modified without testing
    Lenient remediation allows non-compliance to persist, increasing audit risk
    The right approach is prioritized remediation with impact assessment and staged rollout

    Real-world example: An MSP managing 30+ tenants discovered that 47 policy exemptions across 12 subscriptions were set to expire within the next 30 days. Without proactive monitoring, each expiry would have triggered a deployment failure, an emergency triage call, and a rushed exemption renewal -- multiplied across tenants.

    How Does Policy Compliance Work?

    Exemption Lifecycle

    Azure Policy exemptions are scoped to a specific policy assignment and a specific resource or scope (management group, subscription, or resource group). Each exemption has a category (Waiver or Mitigated), an optional expiry date, and a description. When an exemption expires, it is not deleted -- it remains as an expired object, but the policy effect re-activates immediately for the exempted scope.

    The most effective way to track exemptions across subscriptions is Azure Resource Graph. The following KQL query returns all exemptions with their expiry status, allowing you to identify which ones are approaching expiry and which have already expired.

    Resource Graph KQL -- Exemption Expiry Query:

    // Query all policy exemptions with expiry status
    policyresources
    | where type == "microsoft.authorization/policyexemptions"
    | extend expiresOn = todatetime(properties.expiresOn)
    | extend isExpired = iif(isnotempty(expiresOn) and expiresOn < now(), true, false)
    | extend daysUntilExpiry = iif(isnotempty(expiresOn), datetime_diff('day', expiresOn, now()), int(null))
    | extend category = tostring(properties.exemptionCategory)
    | extend policyAssignmentId = tostring(properties.policyAssignmentId)
    | extend displayName = tostring(properties.displayName)
    | extend description = tostring(properties.description)
    | project
        name,
        displayName,
        subscriptionId,
        resourceGroup,
        category,
        expiresOn,
        isExpired,
        daysUntilExpiry,
        policyAssignmentId,
        description
    | order by daysUntilExpiry asc

    Note: This query runs across all subscriptions visible to the authenticated identity. For MSPs using Azure Lighthouse, it will include delegated subscriptions. Filter by subscriptionId to scope results to specific customers.

    Non-Compliant Resource Remediation

    Once you have identified non-compliant resources, the remediation approach depends on the policy effect. Resources violating Deny policies cannot be created in the first place (the compliance issue is that existing resources pre-date the policy). Resources violating Modify or DeployIfNotExists policies can be remediated through policy remediation tasks. Resources violating Audit policies require manual remediation.

    Review non-compliant resources for a policy assignment:

    # List non-compliant resources for a specific policy assignment
    az policy state list \
      --filter "complianceState eq 'NonCompliant' and policyAssignmentName eq '<assignment-name>'" \
      --query "[].{Resource:resourceId, Policy:policyDefinitionName, State:complianceState}" \
      -o table
    
    # Summarize non-compliance by policy definition
    az policy state summarize \
      --filter "complianceState eq 'NonCompliant'" \
      --query "value[].{Policy:policyAssignments[0].policyDefinitions[0].policyDefinitionId, \
        NonCompliant:policyAssignments[0].policyDefinitions[0].results.nonCompliantResources}" \
      -o table

    Trigger a remediation task for Modify or DeployIfNotExists policies:

    # Create a remediation task for a specific policy assignment
    az policy remediation create \
      --name "remediate-<assignment-name>-$(date +%Y%m%d)" \
      --policy-assignment "<assignment-id>" \
      --resource-group "<rg-name>"
    
    # Check remediation task status
    az policy remediation show \
      --name "remediate-<assignment-name>-$(date +%Y%m%d)" \
      --resource-group "<rg-name>" \
      --query "{Status:provisioningState, Deployed:deploymentStatus.totalDeployments, \
        Succeeded:deploymentStatus.successfulDeployments, \
        Failed:deploymentStatus.failedDeployments}" \
      -o table

    Create an exemption for a resource that cannot be remediated immediately:

    # Create a time-bounded exemption (Waiver category)
    az policy exemption create \
      --name "exempt-<resource-name>-<date>" \
      --policy-assignment "<assignment-id>" \
      --exemption-category "Waiver" \
      --scope "<resource-id>" \
      --expires-on "2026-05-01T00:00:00Z" \
      --description "Temporary exemption pending remediation. Ticket: INC-12345"
    
    # Create a permanent exemption (Mitigated category)
    az policy exemption create \
      --name "exempt-<resource-name>-mitigated" \
      --policy-assignment "<assignment-id>" \
      --exemption-category "Mitigated" \
      --scope "<resource-id>" \
      --description "Compensating control in place: NSG restricts access. Approved by: Security Team"
    

    Trigger an on-demand compliance scan:

    # Trigger a compliance evaluation scan at subscription scope
    az policy state trigger-scan
    
    # Trigger a compliance scan at resource group scope
    az policy state trigger-scan --resource-group "<rg-name>"
    
    # Note: Scans are asynchronous. Full subscription scans can take
    # 30-60 minutes depending on resource count. Resource group scans
    # are faster but still asynchronous.

    Failure mode warning: Remediation tasks for Modify and DeployIfNotExists policies will change resource configurations. Always review the policy definition's then block to understand exactly what will be modified before triggering remediation. Test on non-production resources first. A Modify policy that adds a tag is low risk; a DeployIfNotExists policy that deploys a diagnostic settings resource may have cost implications.

    DoNotEnforce Staging Approach

    When rolling out new policy assignments or changing existing ones, the DoNotEnforce enforcement mode allows you to evaluate the compliance impact without actually blocking deployments. This is the safest way to stage policy changes in production environments.

    In DoNotEnforce mode, the policy evaluates resources and reports compliance state, but the effect (Deny, Modify, etc.) is not applied. This gives you a compliance report showing which resources would be affected, without any operational impact.

    Create a policy assignment in DoNotEnforce mode:

    # Create a policy assignment in DoNotEnforce mode
    az policy assignment create \
      --name "audit-storage-encryption" \
      --display-name "Audit: Storage accounts should use encryption" \
      --policy "<policy-definition-id>" \
      --scope "/subscriptions/<subscription-id>" \
      --enforcement-mode "DoNotEnforce"
    
    # After reviewing compliance results, switch to Default (enforce) mode
    az policy assignment update \
      --name "audit-storage-encryption" \
      --enforcement-mode "Default"
    
    # To revert back to DoNotEnforce if issues are discovered
    az policy assignment update \
      --name "audit-storage-encryption" \
      --enforcement-mode "DoNotEnforce"

    Best practice: Use DoNotEnforce for at least 7 days before switching to Default enforcement. This gives you one full business cycle to observe which resources would be affected and to pre-create exemptions or remediate non-compliant resources before enforcement begins.

    Quick Reference

    Exemption Categories

    CategoryIntentTypical UseExpiry Recommendation
    WaiverTemporary exception -- the resource does not meet the policy, and remediation is plannedMigration in progress, pending remediation, temporary architectureAlways set an expiry date (30-90 days)
    MitigatedPermanent exception -- the resource does not meet the policy, but a compensating control existsAlternative security control, architectural constraint, vendor limitationNo expiry, but review annually

    Policy Effects and Remediation Behavior

    EffectBlocks DeploymentAuto-RemediationRemediation Approach
    DenyYes -- prevents resource creation or modificationNoModify the resource to comply, or create an exemption
    ModifyNo -- allows creation, then modifiesYes -- via remediation taskTrigger a remediation task to apply the modification to existing resources
    DeployIfNotExistsNo -- allows creation, then deploys companion resourceYes -- via remediation taskTrigger a remediation task to deploy the missing companion resource
    AuditNo -- only reports compliance stateNoManual remediation or exemption; no auto-remediation available
    DisabledNo -- policy is inactiveNoNo action needed; used to temporarily deactivate a policy

    Cost Impact Summary

    RecommendationCost AvoidanceEffortRisk Reduction
    Track and renew expiring exemptions proactivelyAvoids emergency triage costs (2-4 hours per incident)LowHigh -- prevents deployment failures
    Remediate non-compliant resources systematicallyAvoids audit remediation costs and potential penaltiesMediumHigh -- closes security gaps
    Use DoNotEnforce staging for policy rolloutsAvoids production disruption from untested policy changesLowHigh -- safe change management
    Automate compliance monitoring across tenantsReplaces manual audit cycles (days per quarter per tenant)MediumHigh -- continuous visibility

    Common Questions

    1. What happens when an Azure Policy exemption expires?

    When an exemption expires, the underlying policy effect re-activates immediately for the exempted scope. If the policy effect is Deny, any new deployment that violates the policy condition will be blocked. If the effect is Modify or DeployIfNotExists, the resources will be flagged as non-compliant and will be remediated on the next evaluation cycle if a remediation task is active. The expired exemption object remains in Azure but has no effect. This is why proactive tracking is critical -- the re-activation happens silently.

    2. How do I find all expiring exemptions across my subscriptions?

    Use Azure Resource Graph with the KQL query provided in the Exemption Lifecycle section above. Resource Graph queries span all subscriptions visible to the authenticated identity, including delegated subscriptions for MSPs using Azure Lighthouse. Filter by daysUntilExpiry to focus on exemptions expiring within 30, 14, or 7 days.

    3. What is the difference between Waiver and Mitigated exemption categories?

    A Waiver exemption indicates a temporary exception where the resource does not meet the policy and remediation is planned. It should always have an expiry date. A Mitigated exemption indicates that the resource does not meet the policy, but a compensating control is in place (for example, network-level restrictions instead of resource-level encryption). Mitigated exemptions typically do not have an expiry date but should be reviewed annually to confirm the compensating control is still effective.

    4. How does DoNotEnforce mode differ from Disabled?

    DoNotEnforce mode keeps the policy assignment active and evaluates compliance, but does not apply the effect. Resources are flagged as non-compliant in the compliance view, giving you visibility into what would be affected. Disabled mode completely deactivates the policy -- no evaluation occurs and no compliance data is generated. Use DoNotEnforce when you want to observe impact before enforcing; use Disabled when you want to completely turn off a policy.

    5. Can I remediate existing non-compliant resources automatically?

    Yes, but only for policies with Modify or DeployIfNotExists effects. These policies support remediation tasks that apply the policy's intended configuration to existing resources. Policies with Audit or Deny effects do not support auto-remediation -- Audit only reports non-compliance, and Deny only blocks future deployments. For Audit-only policies, remediation must be performed manually or through separate automation.

    6. How often does Azure evaluate policy compliance?

    Azure evaluates policy compliance in three scenarios: on resource creation or update (real-time for Deny effects), on a standard compliance evaluation cycle (approximately every 24 hours for all resources), and on-demand when you trigger a scan using az policy state trigger-scan. The 24-hour cycle means the compliance view in the portal can be stale. For accurate, up-to-date compliance data, trigger an on-demand scan before making decisions.

    7. What permissions do I need to manage policy exemptions?

    To create, update, or delete policy exemptions, you need the Microsoft.Authorization/policyExemptions/write permission. This is included in the Resource Policy Contributor built-in role. To read exemptions and compliance data, you need Microsoft.Authorization/policyExemptions/read and Microsoft.PolicyInsights/policyStates/read. For cross-subscription queries via Resource Graph, you need Reader access on each subscription.

    8. How do I handle exemptions for resources that will never comply?

    Use the Mitigated exemption category. Document the compensating control in the exemption description, reference the approval ticket or decision record, and set no expiry date. Include the exemption in your annual governance review to confirm the compensating control remains effective and the resource still requires the exception. This is preferable to removing the policy assignment entirely, as it preserves the policy for all other resources.

    9. Can I get notified before an exemption expires?

    Azure does not provide built-in notifications for exemption expiry. You need to implement this through automation -- either a scheduled Azure Automation runbook, a Logic App, or a monitoring platform like Spotto. The PowerShell runbook in the Automation section of this article queries Resource Graph for exemptions expiring within a configurable window and sends alerts. Spotto provides this monitoring continuously without requiring custom automation.

    10. How should I handle policy compliance during a migration?

    During migrations, use a combination of DoNotEnforce mode and Waiver exemptions. Assign new policies in DoNotEnforce mode to understand the compliance impact without blocking the migration. For specific resources that you know will be non-compliant temporarily, create Waiver exemptions with expiry dates aligned to the migration completion timeline. After migration, switch policies to Default enforcement and let the Waiver exemptions expire naturally as resources are remediated.

    11. What is the maximum number of exemptions per subscription?

    Azure imposes a limit of 5,000 policy exemptions per subscription. While this is a generous limit, organizations that rely heavily on exemptions rather than remediation can approach it over time. If you find yourself creating large numbers of exemptions, it often indicates that the policy definition needs to be refined to better match your environment, or that a systematic remediation effort is needed.

    12. How does Spotto help with policy compliance monitoring?

    Spotto continuously monitors your Azure Policy compliance posture across all subscriptions and tenants. It tracks exemption expiry dates and alerts you before they lapse, identifies non-compliant resources and prioritizes them by impact, and provides remediation guidance specific to each policy violation. Rather than running quarterly compliance audits or maintaining custom automation, Spotto gives you a real-time compliance dashboard with actionable insights -- especially valuable for MSPs managing multiple customer environments.

    Use-Case and Scenario Coverage

    Scenario 1: MSP Managing 30+ Tenants

    Before

    47 expiring exemptions

    Across 12 subscriptions, expiring within 30 days, no tracking

    After

    Zero surprise failures

    Automated alerts at 30, 14, and 7 days before expiry

    Actions taken: Deployed Resource Graph query as a scheduled runbook across all tenants via Azure Lighthouse. Created a centralized dashboard showing all exemptions by tenant, expiry date, and category. Established a weekly review cadence for exemptions expiring within 30 days. Renewed valid exemptions proactively and remediated resources where exemptions were no longer needed. Eliminated emergency exemption requests entirely.

    Deployment failures eliminated

    Scenario 2: MSP Onboarding Enterprise Customer

    Before

    82% compliance

    340 non-compliant resources across 8 subscriptions

    After

    96% compliance

    48 remaining exemptions, all documented with compensating controls

    Actions taken: Ran compliance summary across all subscriptions to identify the scope of non-compliance. Categorized non-compliant resources by policy effect: 180 were Modify-remediable, 95 were DeployIfNotExists-remediable, and 65 required manual action. Triggered remediation tasks for the 275 auto-remediable resources over two weeks. Created Mitigated exemptions for 35 resources with compensating controls and Waiver exemptions for 13 resources pending architectural changes. Manual remediation completed for the remaining 52 resources.

    82% to 96% compliance in 4 weeks

    Scenario 3: Enterprise Quarterly Audit Preparation

    Before

    12 audit findings

    Previous quarter: 12 findings related to policy non-compliance

    After

    Zero findings

    Next quarter: zero policy-related audit findings

    Actions taken: Implemented continuous compliance monitoring with weekly automated scans. Established a remediation SLA: Deny-effect violations within 48 hours, Modify/DINE within 7 days, Audit-only within 30 days. Created a compliance report that maps each policy assignment to the relevant audit control (SOC 2 CC6.1, CIS 4.3, etc.). Pre-audit compliance review reduced from 3 days of manual work to a 30-minute dashboard review. All exemptions documented with justification and compensating control evidence.

    Zero audit findings achieved

    Scenario 4: Dev/Test Relaxed Governance

    Before

    Same policies as production

    Dev teams constantly blocked by Deny policies, filing exemptions daily

    After

    Tiered governance

    DoNotEnforce for dev, Default for staging and production

    Actions taken: Duplicated production policy assignments for dev/test subscriptions with DoNotEnforce mode. Developers can deploy freely while compliance data is still collected. Non-compliance in dev/test is reported but not blocked. Staging subscriptions use Default enforcement to catch issues before production. Eliminated 90% of exemption requests from dev teams while maintaining full compliance visibility. Weekly compliance report highlights dev resources that would fail in production.

    90% fewer exemption requests

    Scenario 5: SaaS Multi-Region Deployment

    Before

    Region-specific policy conflicts

    Data residency policies blocking deployments in new regions

    After

    Mitigated exemptions with controls

    Documented compensating controls for each region-specific exception

    Actions taken: Identified 8 policy assignments with region-restriction conditions that conflicted with multi-region expansion plans. Created Mitigated exemptions for specific resource types in new regions, documented with compensating controls (encryption at rest, customer-managed keys, network isolation). Established a policy review process for each new region onboarding. Used DoNotEnforce mode to evaluate compliance impact in new regions before creating exemptions. All exemptions tied to architectural decision records and reviewed quarterly.

    Multi-region expansion unblocked

    Implementation Guidance

    Preconditions

    Azure CLI or PowerShell Az module installed and authenticated
    Resource Policy Contributor role (or custom role with Microsoft.Authorization/policyExemptions/* permissions)
    Azure Resource Graph access for cross-subscription queries (Reader on each subscription)
    Change management process for policy assignment modifications in production
    For MSP environments: Azure Lighthouse configured for delegated subscription access

    Required Inputs

    Subscription IDs for all Azure subscriptions under governance
    Policy assignment IDs for each assignment you want to monitor
    Notification targets (email distribution list, Teams channel, or webhook URL) for exemption expiry alerts
    Remediation SLA definitions (time to remediate by policy effect type)
    Exemption approval workflow (who can approve Waiver vs Mitigated exemptions)

    Rollout Approach

    Step 1: Inventory -- Run the Resource Graph exemption query to catalog all existing exemptions across subscriptions. Identify which are expired, which are expiring within 30 days, and which have no expiry date.
    Step 2: Compliance baseline -- Run a compliance summary to identify all non-compliant resources. Categorize by policy effect type (Deny, Modify, DeployIfNotExists, Audit) to determine remediation approach.
    Step 3: Quick wins -- Renew valid exemptions that are about to expire. Remove expired exemptions for resources that have already been remediated. Trigger remediation tasks for Modify and DeployIfNotExists policies.
    Step 4: Staged remediation -- Address non-compliant resources in priority order: security-critical policies first, then compliance-framework policies, then operational policies. Use DoNotEnforce mode to preview impact before enforcing new policies.
    Step 5: Automation deployment -- Deploy the compliance monitoring runbook on a daily schedule. Configure alerting for exemptions approaching expiry and for new non-compliant resources. Integrate with your existing alerting channels.
    Step 6: Governance process -- Establish a weekly compliance review cadence. Define SLAs for remediation by policy effect type. Document the exemption request and approval workflow. Train engineering teams on the process.

    Failure Modes

    Remediation task modifies production resources unexpectedly: Modify and DeployIfNotExists remediation tasks change resource configurations. A Modify policy that adds a required tag is low risk, but a policy that changes a network configuration could disrupt service. Always review the policy definition's then block and test remediation on non-production resources first.
    DoNotEnforce-to-Default transition blocks active deployments: Switching a policy from DoNotEnforce to Default enforcement can block in-flight deployments if resources do not comply. Schedule the enforcement switch during a maintenance window and notify deployment teams in advance.
    Exemption scope mismatch: An exemption scoped to a resource group does not cover resources in child resource groups or at the subscription level. Ensure the exemption scope matches the resource scope exactly to avoid partial coverage.
    Compliance scan latency: After creating an exemption or triggering remediation, the compliance state may not update for up to 30-60 minutes. Do not assume remediation failed if the compliance view has not changed immediately. Trigger an on-demand scan to speed up the evaluation.

    Rollback Strategy

    Every compliance change should have a clear rollback path:

    Remediation rollback: For Modify policies, revert the resource configuration to its previous state using ARM templates, Bicep, or Terraform. For DeployIfNotExists, delete the companion resource that was deployed. Document the pre-remediation state before triggering remediation tasks.
    Enforcement rollback: Switch the policy assignment back to DoNotEnforce mode using az policy assignment update --enforcement-mode DoNotEnforce. This immediately stops the policy from blocking deployments while preserving compliance reporting.
    Exemption rollback: If an exemption was deleted prematurely, recreate it with the same scope and policy assignment. If an exemption was created incorrectly, delete it using az policy exemption delete. Exemption changes take effect immediately.
    Emergency bypass: If a policy is blocking a critical deployment and you cannot remediate or create an exemption in time, switch the policy assignment to DoNotEnforce mode. This is a temporary measure -- document it, set a calendar reminder to re-enable enforcement, and create the necessary exemption or remediation within 24 hours.

    Automation: Policy Compliance Monitoring Runbook

    The following PowerShell runbook can be deployed as an Azure Automation scheduled task to continuously monitor policy compliance across your subscriptions. It queries for expiring exemptions and non-compliant resources, then sends alerts to your configured notification channel.

    # Azure Automation Runbook: Policy Compliance Monitoring
    # Schedule: Daily (recommended) or Weekly
    # Prerequisites: Az.ResourceGraph, Az.Accounts, Az.PolicyInsights modules
    # Managed Identity with Reader access across target subscriptions
    
    param(
        [int]$ExpiryWarningDays = 30,
        [string]$NotificationWebhookUrl = "",
        [string]$NotificationEmail = "",
        [string[]]$SubscriptionIds = @()
    )
    
    # Authenticate using managed identity
    Connect-AzAccount -Identity
    
    # If no subscriptions specified, get all accessible subscriptions
    if ($SubscriptionIds.Count -eq 0) {
        $SubscriptionIds = (Get-AzSubscription | Where-Object { $_.State -eq 'Enabled' }).Id
    }
    
    Write-Output "=== Policy Compliance Monitoring Report ==="
    Write-Output "Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss UTC')"
    Write-Output "Subscriptions monitored: $($SubscriptionIds.Count)"
    Write-Output ""
    
    # -------------------------------------------------------
    # Section 1: Expiring Exemptions
    # -------------------------------------------------------
    Write-Output "--- Section 1: Expiring Policy Exemptions ---"
    
    $exemptionQuery = @"
    policyresources
    | where type == "microsoft.authorization/policyexemptions"
    | extend expiresOn = todatetime(properties.expiresOn)
    | extend isExpired = iif(isnotempty(expiresOn) and expiresOn < now(), true, false)
    | extend daysUntilExpiry = iif(isnotempty(expiresOn),
        datetime_diff('day', expiresOn, now()), int(null))
    | extend category = tostring(properties.exemptionCategory)
    | extend displayName = tostring(properties.displayName)
    | extend description = tostring(properties.description)
    | extend policyAssignmentId = tostring(properties.policyAssignmentId)
    | where isnotempty(expiresOn)
    | where daysUntilExpiry <= $ExpiryWarningDays
    | project
        name,
        displayName,
        subscriptionId,
        resourceGroup,
        category,
        expiresOn,
        isExpired,
        daysUntilExpiry,
        policyAssignmentId,
        description
    | order by daysUntilExpiry asc
    "@
    
    $expiringExemptions = Search-AzGraph -Query $exemptionQuery \
        -Subscription $SubscriptionIds
    
    $expiredCount = ($expiringExemptions | Where-Object { $_.isExpired -eq $true }).Count
    $warningCount = ($expiringExemptions | Where-Object { $_.isExpired -eq $false }).Count
    
    Write-Output "Expired exemptions: $expiredCount"
    Write-Output "Expiring within $ExpiryWarningDays days: $warningCount"
    Write-Output ""
    
    foreach ($exemption in $expiringExemptions) {
        $status = if ($exemption.isExpired) { "[EXPIRED]" } else { "[WARNING]" }
        Write-Output "$status $($exemption.displayName)"
        Write-Output "  Subscription: $($exemption.subscriptionId)"
        Write-Output "  Category: $($exemption.category)"
        Write-Output "  Expires: $($exemption.expiresOn) (Days: $($exemption.daysUntilExpiry))"
        Write-Output "  Policy: $($exemption.policyAssignmentId)"
        Write-Output "  Description: $($exemption.description)"
        Write-Output ""
    }
    
    # -------------------------------------------------------
    # Section 2: Non-Compliant Resource Summary
    # -------------------------------------------------------
    Write-Output "--- Section 2: Non-Compliant Resources Summary ---"
    
    $complianceSummary = @()
    
    foreach ($subId in $SubscriptionIds) {
        Set-AzContext -SubscriptionId $subId -ErrorAction SilentlyContinue | Out-Null
    
        $summary = Get-AzPolicyStateSummary -ErrorAction SilentlyContinue
    
        if ($summary) {
            $nonCompliantResources = $summary.Results.NonCompliantResources
            $nonCompliantPolicies = $summary.Results.NonCompliantPolicies
    
            if ($nonCompliantResources -gt 0) {
                $complianceSummary += [PSCustomObject]@{
                    SubscriptionId       = $subId
                    NonCompliantResources = $nonCompliantResources
                    NonCompliantPolicies  = $nonCompliantPolicies
                }
    
                Write-Output "Subscription: $subId"
                Write-Output "  Non-compliant resources: $nonCompliantResources"
                Write-Output "  Non-compliant policies: $nonCompliantPolicies"
                Write-Output ""
            }
        }
    }
    
    $totalNonCompliant = ($complianceSummary |
        Measure-Object -Property NonCompliantResources -Sum).Sum
    
    Write-Output "Total non-compliant resources across all subscriptions: $totalNonCompliant"
    Write-Output ""
    
    # -------------------------------------------------------
    # Section 3: Send Notifications
    # -------------------------------------------------------
    if (($expiredCount -gt 0 -or $warningCount -gt 0 -or $totalNonCompliant -gt 0) \
        -and $NotificationWebhookUrl -ne "") {
    
        $payload = @{
            title = "Policy Compliance Alert"
            text  = @"
    **Policy Compliance Monitoring Report**
    - Expired exemptions: $expiredCount
    - Exemptions expiring within $($ExpiryWarningDays) days: $warningCount
    - Total non-compliant resources: $totalNonCompliant
    
    Review the full report in the Azure Automation job output.
    "@
        } | ConvertTo-Json -Depth 3
    
        try {
            Invoke-RestMethod -Uri $NotificationWebhookUrl \
                -Method Post \
                -ContentType "application/json" \
                -Body $payload
            Write-Output "Notification sent to webhook successfully."
        } catch {
            Write-Warning "Failed to send webhook notification: $($_.Exception.Message)"
        }
    }
    
    # -------------------------------------------------------
    # Section 4: Report Summary
    # -------------------------------------------------------
    Write-Output "=== Report Complete ==="
    Write-Output "Subscriptions scanned: $($SubscriptionIds.Count)"
    Write-Output "Expired exemptions: $expiredCount"
    Write-Output "Expiring exemptions (within $ExpiryWarningDays days): $warningCount"
    Write-Output "Total non-compliant resources: $totalNonCompliant"
    Write-Output ""
    Write-Output "Next steps:"
    if ($expiredCount -gt 0) {
        Write-Output "  - URGENT: Review and renew or remove $expiredCount expired exemptions"
    }
    if ($warningCount -gt 0) {
        Write-Output "  - Review $warningCount exemptions expiring soon and plan remediation"
    }
    if ($totalNonCompliant -gt 0) {
        Write-Output "  - Remediate $totalNonCompliant non-compliant resources"
    }

    Failure mode warning: The runbook requires the managed identity to have Reader access on all target subscriptions and Microsoft.PolicyInsights/policyStates/read permission. If the managed identity lacks permissions on a subscription, that subscription will be silently skipped. Verify access across all subscriptions before relying on the report for audit compliance.

    What to Do Next

    Step 1: Run the exemption inventory -- Use the Resource Graph KQL query to catalog all policy exemptions across your subscriptions. Identify expired exemptions and those expiring within 30 days. This gives you immediate visibility into your exemption landscape.
    Step 2: Baseline your compliance posture -- Run a compliance summary to count non-compliant resources by policy assignment. Categorize by effect type to determine which can be auto-remediated and which require manual action. This is your starting point for measuring improvement.
    Step 3: Remediate the quick wins -- Renew valid expiring exemptions, trigger remediation tasks for Modify and DeployIfNotExists policies, and remove expired exemptions for already-remediated resources. These actions have immediate impact with minimal risk.
    Step 4: Deploy automated monitoring -- Set up the compliance monitoring runbook on a daily schedule and configure alerting to your team's notification channel. This shifts compliance management from periodic audits to continuous monitoring. Consider Spotto for turnkey compliance visibility across all your environments.

    Summary

    Expired exemptions are silent deployment blockers -- When an exemption expires, the policy effect re-activates immediately. If the effect is Deny, deployments fail without warning. Proactive tracking eliminates this class of failure entirely.
    Non-compliant resources accumulate silently -- Without active monitoring, the number of non-compliant resources grows over time as policies are added, modified, or as exemptions expire. Continuous monitoring catches new non-compliance early when it is cheapest to fix.
    Resource Graph provides tenant-wide exemption visibility -- A single KQL query gives you a complete inventory of all exemptions, their expiry status, and their policy assignments across every subscription. This is the foundation for proactive compliance management.
    DoNotEnforce mode de-risks policy rollouts -- Staging new policies in DoNotEnforce mode gives you compliance visibility without operational impact. This allows you to identify affected resources, create exemptions, and remediate before enforcement begins.
    Exemption categories matter for governance -- Waiver exemptions should always have an expiry date and a remediation plan. Mitigated exemptions should document the compensating control and be reviewed annually. Consistent categorization improves audit readiness.
    Remediation should be prioritized by effect type -- Deny-effect violations are the most urgent because they block deployments. Modify and DeployIfNotExists violations can be auto-remediated. Audit-only violations require manual action but are lower priority.
    Automation scales compliance across tenants -- Manual compliance audits do not scale beyond a handful of subscriptions. Automated monitoring with alerting ensures every exemption and every non-compliant resource is tracked continuously, regardless of how many tenants you manage.

    The challenges outlined in this article — expiring policy exemptions, non-compliant resources blocking deployments, missing compliance automation — are the same ones our customers work through across their Azure environments. The difference is they use Spotto to surface these opportunities automatically, rather than running the analysis by hand.

    Stop Getting Blindsided by Policy Compliance Failures

    Spotto continuously monitors your Azure Policy compliance posture, flagging expiring exemptions and non-compliant resources before they block deployments or trigger audit findings — giving you advance warning and clear remediation steps across every subscription.

    Free Trial

    Disclaimer: This article is provided for informational purposes only and does not constitute professional advice. Azure pricing, features, and service behaviour may have changed since publication. Always validate recommendations against your specific environment and consult Microsoft's official documentation before making changes to production infrastructure. Spotto is not liable for any actions taken based on the content of this article.