The governance meeting starts the way it always does. Someone shares the compliance dashboard, the big number says 74%, and for a moment everyone nods. Then someone asks the obvious question: is that better than last quarter?
Silence. Nobody knows. The dashboard shows today's state, and today's state only. Maybe you were at 81% before a team deployed a hundred non-compliant storage accounts. Maybe you were at 60% and the remediation work is paying off. The number cannot tell you, so the meeting moves on and the score stays a decoration.
That is the trap with Azure Policy compliance at scale. The percentage is easy to get. The two questions that actually matter, which assignments are drifting and whether you are getting better or worse, take real work to answer.
The Problem: Compliance Is a State Machine, but Azure Shows You One State
Azure Policy evaluates resources continuously and stores the verdict per resource, per assignment. What it does not store, at least not anywhere you can easily query, is the transition. A resource that fell out of compliance on Tuesday looks identical to one that has been non-compliant since the assignment was created.
That structural gap produces a familiar set of blind spots:
- No trend. Every look at the dashboard is a fresh snapshot. Improvement or decay between looks is invisible.
- Silent assignment changes. A policy assignment can be modified or deleted, and compliance for its scope simply stops being reported. The score can go up because governance got weaker.
- Exemptions without context. An exemption expires, a batch of resources flips to non-compliant overnight, and nobody remembers why the exemption existed.
- Nothing to show an auditor. Auditors rarely ask "are you compliant today?" They ask "when did you detect this, and how fast did you respond?" A snapshot cannot answer a question about time.
Querying Policy State with Azure Resource Graph
The raw state is queryable, and you should know how. Azure Resource Graph exposes policy states through the policyresources table, which works across every subscription you can see.
Non-compliant resource counts by assignment, worst first:
policyresources
| where type =~ 'microsoft.policyinsights/policystates'
| where properties.complianceState =~ 'NonCompliant'
| summarize nonCompliantCount = count() by assignment = tostring(properties.policyAssignmentName)
| order by nonCompliantCount desc
The individual non-compliant resources, with enough identity to go fix them:
policyresources
| where type =~ 'microsoft.policyinsights/policystates'
| where properties.complianceState =~ 'NonCompliant'
| project resourceId = tostring(properties.resourceId),
resourceType = tostring(properties.resourceType),
policy = tostring(properties.policyDefinitionName),
assignment = tostring(properties.policyAssignmentName)
One honest caveat: for built-in policies, policyDefinitionName is the definition's GUID, not its display name. To get human-readable names you have to join against the policy definitions themselves or look them up separately, which makes the raw output less shareable than it first appears. On the CLI, az policy state summarize gives you a similar rollup per subscription.
These queries are worth keeping. But look at what they hand you:
- A snapshot with no trend. Run it today and next month, and unless you are exporting results and diffing them yourself, the two runs cannot be compared.
- GUIDs where names should be. Useful output needs an extra join or lookup step, every time.
- No exemption story. Exempted resources are just absent from the problem list, with no expiration dates and no record of why.
- No assignment history. If someone modified or deleted an assignment last week, nothing in the current state will tell you, and the Activity Log entry ages out on its own schedule.
What StratoLens Adds: the Timeline
StratoLens scans your Azure hierarchy on a schedule and keeps the history, which turns each of those gaps into a queryable answer:
- Compliance transitions between scans. See exactly which resources became non-compliant (or compliant) since the last scan, so "is 74% better than last quarter?" has an actual answer.
- Policy change detection. Added, modified, and deleted assignments surface in scan-over-scan comparison, so a weakened assignment shows up as a change, not as a mysteriously improved score.
- Exemption tracking. Exemptions carry their expiration dates, so you can remediate before one lapses instead of discovering it in the next audit.
- Full hierarchy view. Assignments, initiatives, and compliance state from management groups down to individual resources, in one place instead of one subscription at a time.
And because StratoLens deploys into your own Azure tenant, the compliance history it builds stays in your subscription, which tends to matter to the same auditors who asked about response time.
Treat Compliance as a Timeline, Not a Score
If you run the queries above on a schedule, export the results, and diff them month over month, you are most of the way to real compliance monitoring, and that is a legitimate way to start. The alternative is letting StratoLens keep the timeline for you: transitions, assignment changes, and exemption expirations, already correlated. That story lives at Policy Governance in StratoLens.