[AP 4] Effects: What Each One Commits You To

Eleven effects, a fixed order of evaluation, and a delete-protection effect whose cascade behaviour decides whether it protects anything at all.


Choosing an effect is choosing what the platform does to somebody at four in the afternoon. Most of them report. Two of them change a resource before it exists. One refuses a deletion, and whether it actually refuses anything depends on a property most people never set.

The eleven, and the three groups worth thinking in

Azure Policy supports eleven effects: addToNetworkGroup, append, audit, auditIfNotExists, deny, denyAction, deployIfNotExists, disabled, manual, modify and mutate. Two of those are not general purpose. mutate belongs to the Kubernetes resource provider mode and addToNetworkGroup to Virtual Network Manager, and both are boundary notes here rather than subjects. Policy for Kubernetes is a series of its own that this site has not written and does not promise.

The rest divide cleanly by what they cost you when they are wrong. The reporting effects, audit and auditIfNotExists and manual, cost you a wrong number on a dashboard. The blocking effects, deny and denyAction, cost you a failed deployment or a refused delete. The altering effects, append and modify and deployIfNotExists, cost you a change to a resource that somebody else owns, made by an identity acting in your name, and they are the ones to be careful with.


The order is fixed, and it explains behaviour you will otherwise call a bug

When a create or update request arrives, Azure Policy builds the list of assignments that apply and evaluates the effects in a documented sequence rather than in the order the assignments were made. disabled is checked first, so a disabled rule costs nothing further. Then append and modify, because both can alter the request and it would be incoherent to judge a request before the platform has finished changing it. Then deny, then audit, then manual, then auditIfNotExists. denyAction evaluates last. Once the resource provider returns success, auditIfNotExists and deployIfNotExists run again to decide whether related resources are missing.

That sequence has a consequence people report as a defect. A modify assignment that adds a required tag runs before the deny assignment that requires the tag, so the deny never fires and the compliance report shows no violations at all. The estate is compliant because one policy is quietly fixing the input of another. That is a legitimate design and a genuinely good one, and it is also why removing what looks like a redundant modify assignment can cause a wave of deployment failures a week later, from a team that never knew it was being helped.

An estate can be compliant because one policy is silently repairing the input to another. Remove the repair and the failures arrive from teams who never knew they were being helped.


Audit, and the two-condition effects

audit judges a resource on its own properties. auditIfNotExists judges it on the properties of something else, a child or extension resource that ought to be there. The distinction sounds academic until you write your first one, because the compliance logic is not what people assume. A resource is non-compliant under auditIfNotExists when the if condition is true and the existence condition is false. Both halves have to line up, and a rule that never reports anything is usually a rule whose existence condition is quietly satisfied by something you did not intend to accept.

The practical guidance is that audit and deny are interchangeable in a way that audit and auditIfNotExists are not. You can promote an audit rule to deny by changing a parameter. You cannot promote an audit rule to auditIfNotExists without rewriting it, because the second one needs details the first never had.


Deny, and why it is the easy one

deny stops a non-conforming create or update before it reaches the resource provider and returns a 403 naming the assignment. Existing resources that would violate it are marked non-compliant and left alone, which is the property that makes deny safe to introduce on a populated estate and the property people forget when they assume switching on a deny will clean anything up. It will not. Deny is a rule about the future.

The error message is worth designing. An assignment can carry non-compliance messages, and the difference between a workload team reading a policy GUID and reading a sentence that says which control they hit and who owns it is the difference between a ticket to you and a fix by them.


DenyAction, and the fine print that decides whether it protects anything

denyAction blocks an action rather than a state, and the only action it supports today is DELETE. Resources covered by it report a compliance state of Protected, which is the only state in the product that describes a capability rather than a condition. Used well it is the answer to the recurring question of how to stop somebody deleting the production database at speed.

Used carelessly it protects nothing, and the failure is silent because the assignment keeps reporting Protected the entire time. Four things decide the outcome. Deleting a subscription removes everything inside it and policy does not intervene. Deleting a resource group only stops if the definition sets cascadeBehaviors to deny, and only for resources that support tags and location, so anything in mode: all or without those properties goes anyway. Deleting a parent resource implicitly removes the children, and a denyAction assignment targeting a child does not save it when the parent is the target. And a deliberate set of objects is exempt from denyAction enforcement entirely, including policy assignments, deny assignments, deployment stacks, subscriptions and locks, because the alternative is a tenant nobody can unwind.

My rule is to treat denyAction as protection against a specific mistake rather than as a general safety net, and to write down which mistake when the assignment is created. It stops somebody deleting the resource directly. It does not stop somebody deleting the thing that contains it, and if the containing resource group is what you are worried about, a lock and a conversation are still the honest answer.


Append, modify, and deployIfNotExists

These three change things. append adds fields to a request as it passes. modify adds, replaces or removes properties and tags, and unlike append it can also operate on existing resources through a remediation task. deployIfNotExists runs a template deployment when a related resource is missing. All three are the subject of the next article, because what they have in common matters more than what separates them: each one needs a managed identity holding real permissions, and creating the assignment is the act that grants them.

One constraint to bank now. deployIfNotExists supports nested templates and does not support linked templates. Anyone arriving from an infrastructure-as-code habit of factoring templates into files will hit that on their first attempt.


Manual, for the controls no scanner can see

Some controls are not machine-observable. Whether your organisation reviews access quarterly, whether a data classification policy exists, whether staff completed training. The manual effect exists for these, and it works by inverting the usual model: the compliance state starts at Unknown and a human moves it, through an attestation object that can carry evidence links and a justification.

This is not a niche feature. The large regulatory initiatives all carry manual definitions, including FedRAMP High and Moderate, HIPAA, ISO 27001, NIST SP 800-53 in both revisions, PCI DSS 4.0 and SWIFT. If you have assigned one of those and wondered why a slice of the controls sits at Unknown forever, that is the answer, and the honest reading is that the platform is telling you it cannot do that part for you.

Design manual policies against the scope that matches the boundary of what is being attested, usually a subscription or a management group rather than individual resources. Attesting the same control five hundred times is nobody’s governance programme.


Rules about who is asking

The newest surface in the product lets a policy rule inspect the identity behind a request rather than only the resource in it. Microsoft’s documented examples are blocking interactive users from deleting critical resources while leaving pipelines free, and refusing create, update and delete requests from users who have not satisfied multifactor authentication. That is a meaningful change in what Policy is for, since it moves the product from governing state into governing conduct.

Two things to know before building on it, both checked in August 2026. It carries no preview label in the documentation and no general availability announcement either, which is a status worth stating rather than resolving. And the rules act at request time only: the compliance engine marks them NotApplicable, so a rule of this kind that is working perfectly will never appear as a compliant control on any report you produce. If your programme is measured by a compliance percentage, an identity-aware rule contributes nothing to the number while doing some of the most valuable work in the estate.


From here

The build sheet that follows assembles the guardrail set that most estates actually need, and it deliberately picks up where the Landing Zones build sheet stopped rather than repeating it: allowed resource types, a working delete protection with its cascade behaviour set on purpose, and the resource selectors that let you aim any of it at part of the estate first.


Azure Policy
‹ Previous: [AP 3.1] Build Sheet: Assign the Baseline and Read What It Did
Next: [AP 4.1] Build Sheet: The Guardrail Set