Azure Deployment Drift Detector
Daily, the flow enumerates deployed Azure resources via Azure Resource Manager and compares them to an expected inventory/baseline in Dataverse (or a Git-stored manifest), flagging additions, deletions, and configuration changes. Logs drift, alerts the platform team in Teams, and emails a daily drift report. Catches unmanaged changes to the environment.
Provided as-is, without warranty of any kind. Review and test each pattern in a non-production environment before deploying it to live automations. See our Terms.
Overview
This flow catches unmanaged changes (drift) to the Azure estate. Each day it enumerates every resource currently deployed in the target subscription via Azure Resource Manager (ARM) and compares that *actual* state to an *expected* baseline stored in Dataverse. Resources present in Azure but not in the baseline are flagged Added (ClickOps / unmanaged), and baseline resources no longer deployed are flagged Removed (deletions). Every finding is written to a Dataverse drift-log table, a scorecard is posted to a Teams channel, and a daily drift report is emailed via Outlook.
Why it matters: Resources created or deleted outside IaC cause configuration drift, security gaps, and cost surprises. A daily diff against intended state surfaces those changes so they can be reconciled or reverted.
> Build status: Built via API-first (workflow-table) deployment. Flow ships Off. Going live requires only: (1) authorize the Teams/Dataverse/Outlook connections, (2) set the Azure credential env vars (tenant/client/secret/subscription) for an SP with Reader on the subscription, (3) seed the baseline table, (4) turn the flow on.
Use Case
A platform team running infrastructure-as-code wants to know when the live Azure environment diverges from the intended baseline — new resources, deletions, or configuration changes — without manually auditing the portal.
Flow Architecture
Daily Drift Sweep
Recurrence (Day/1 @ 05:00)Drift-sweep cadence
Initialize Correlation Id
Initialize Variable (@guid())One trace id per sweep, stamped on every finding + notification
Initialize Arm Base Url
Initialize VariableARM base URL from env var (sovereign-cloud portable)
Initialize Subscription Id
Initialize VariableTarget subscription scope
Initialize Arm Api Version
Initialize VariableARM Resources API version (defaults 2021-04-01)
List Deployed Resources
HTTP + ActiveDirectoryOAuthCalls ARM GET /subscriptions/{sub}/resources = actual state
Get Baseline
Dataverse ListRecordsReads expected inventory = baseline state
Select Baseline Ids
SelectLowercased array of baseline resource ids
Select Deployed Ids
SelectLowercased array of live ARM resource ids
Filter Added Resources
Query (Filter array)Deployed ids NOT in baseline → Added drift
Filter Removed Resources
Query (Filter array)Baseline ids NOT in deployed → Removed drift
Environment Variables
| Schema name | Type | Default | Description |
|---|---|---|---|
| flowlibs_ArmBaseUrl | String | https://management.azure.com | ARM base URL (sovereign-cloud portable) |
| flowlibs_AzureSubscriptionId | String | <configure> | Subscription scope of the sweep |
| flowlibs_AzureTenantId | String | <your-tenant-id> | AAD tenant for the SP OAuth |
| flowlibs_AzureClientId | String | <configure> | App registration / SP client id |
| flowlibs_AzureClientSecret | String | <configure> | SP client secret (Reader on scope) |
| flowlibs_ArmApiVersion | String | 2021-04-01 | ARM Resources API version |
| flowlibs_BaselineTable | String | flowlibs_resourcebaselines | Entity-set name of the baseline table |
| flowlibs_DriftLogTable | String | flowlibs_drifts | Entity-set name of the drift-log table |
| flowlibs_TeamsGroupId | String | <your-team-id> | Teams team (group) for the alert |
| flowlibs_TeamsChannelId | String |
Connectors & Connections
| Connector | API name | Actions used |
|---|---|---|
| Microsoft Dataverse | shared_commondataserviceforapps | ListRecords CreateRecord |
| Microsoft Teams | shared_teams | PostMessageToConversation |
| Office 365 Outlook | shared_office365 | SendEmailV2 |
Note — All connections are referenced as solution connection references; the flow is portable between environments as long as a connection is mapped at import time.
Customization Guide
Almost every realistic variant of this flow can be implemented by changing environment variable values. A few cases require small edits inside the flow definition — those are called out explicitly below.
- Git baseline
- Read the expected inventory from a repo (state file / manifest) via the GitHub/GitLab connector instead of (or in addition to) Dataverse.
- Configuration drift
- Extend beyond add/remove to compare per-resource properties (SKU, tags, location) by hashing the ARM resource object and storing the hash in the baseline; flag mismatches as a third Changed drift type.
- Auto-revert
- Gate an Approvals step, then remove unmanaged additions on approval.
- Change correlation
- Join drift to the Azure Activity Log actor who made the change.
- Scope
- Sweep multiple subscriptions / management groups by looping over a subscription list env var.
Key Expressions
The flow is intentionally light on Power Fx / WDL gymnastics — the heaviest expressions are the branch-name concatenation and the approval outcome check. They are listed below in the order they appear in the flow.
EXPR.01Added resources
Filter array over the live ARM list: deployed ids not present in the baseline.
EXPR.02Removed resources
Filter array over the baseline rows: baseline ids no longer deployed.
EXPR.03ARM URI
Builds the ARM resources list endpoint.
EXPR.04ARM OAuth audience
Resolves to https://management.azure.com/ for the OAuth token.
EXPR.05Correlation id
Minted in the first action, stamped on every drift record + notification.
Customize & download
Generate a ready-to-import copy of this solution with your environment-variable values baked in — available on Base, Pro, or Team.
Upgrade to customize
Comments
Sign in to join the conversation.
Sign inNo comments yet. Be the first to share your experience with this flow.