Most Power Automate deployments reach a critical mass where nobody has a complete picture of what's running, who owns it, or whether it meets compliance requirements. This lesson teaches you how to implement a comprehensive governance framework using the CoE Toolkit, the Power Platform admin API, and the M365 audit log — including automated compliance reporting that runs without human intervention.

Picture this: your organization has been on a Power Automate journey for about two years. What started as a handful of flows built by IT to automate approval emails has quietly exploded into 847 flows across 23 environments, built by everyone from the CFO's executive assistant to a developer in the Bangalore office who left the company six months ago. Three of those flows are processing customer PII into an unmanaged personal SharePoint site. Two others are running on licenses that expired. And nobody — not your IT admin, not your Power Platform CoE lead, not the original business sponsor — has a complete picture of what's running, who owns it, or whether any of it meets your data handling obligations under GDPR.
This is the governance gap, and it is almost universal in organizations that adopted Power Automate without a structured rollout strategy. The platform's accessibility is its greatest strength and its most significant governance liability. You don't need to be a developer to build a flow that touches production data — which means the traditional "IT controls what goes into production" model breaks down entirely.
By the end of this lesson, you will know how to implement a comprehensive governance framework for Power Automate at enterprise scale. We'll cover the Center of Excellence (CoE) Toolkit architecture, how to implement and enforce flow ownership policies using DLP and environment strategies, how to extract and analyze usage telemetry from the Power Platform admin center and audit logs, and how to build automated compliance reporting pipelines that run without human intervention. You'll leave with working patterns you can adapt to your own tenant immediately.
What you'll learn:
Before working through this lesson, you should be comfortable with:
You'll need Global Admin or Power Platform Admin role to implement most of what we cover here, plus the ability to create app registrations in Azure AD.
The most common mistake organizations make with the CoE Toolkit is treating it as a product you install once and forget. It isn't. The CoE Toolkit is a reference implementation — a curated collection of Power Apps, Power Automate flows, Dataverse tables, and dashboards that Microsoft's Power CAT team built to show you how governance can work, not to be a turnkey solution you hand off to a junior admin.
When you install the CoE Toolkit (via the Power Platform CoE Starter Kit package available through the Power Platform Admin Center or GitHub), it deploys the following key components into a dedicated CoE environment:
Dataverse Tables (the core data store):
admin_Environment — a snapshot of all environments in your tenantadmin_Flow — a record for every flow detected across all environmentsadmin_FlowRun — run history aggregated across environmentsadmin_Maker — a record for every user who has created a Power Platform artifactadmin_MakerActivity — aggregated activity telemetry per makerInventory Sync Flows:
These are scheduled cloud flows that call the Power Platform admin API to enumerate environments, then drill into each environment to collect flows, apps, and connections. The critical one is called "Admin | Sync Template v3 (Flows)" — it runs daily and populates the admin_Flow table. Understanding how this flow works is essential because it defines the timeliness and completeness of everything else.
The sync flow uses the Power Automate Management connector (not to be confused with the HTTP connector calling the admin API directly) along with direct calls to https://api.flow.microsoft.com with a service principal bearer token. The reason it uses both is historical — the management connector covers some operations cleanly, but the raw REST API gives you access to flow run history, connection references, and owner details that aren't exposed through the connector's action surface.
Warning: The CoE Toolkit requires a Dataverse environment with a database. Do not install it into your default environment. Create a dedicated "CoE" environment with a Dataverse database, then assign your CoE admin service account as environment admin. Mixing your governance tooling with general-purpose environments creates a dependency nightmare.
Governance Flows: Once inventory is populated, a second tier of flows handles governance actions — sending welcome emails to new makers, flagging orphaned flows (those owned by departed users), requesting business justifications for premium connectors, and escalating unresponded items. These flows read from Dataverse and write back status, owner responses, and remediation records.
Power BI Dashboard:
The CoE Toolkit ships with a .pbit template that connects to your Dataverse environment. This is where your leadership will actually look at governance data — maker activity trends, environment growth, connector usage, compliance status.
The architecture matters because it tells you where the single points of failure are. If your sync flows break (and they will break — token refresh issues, throttling, API changes), your inventory goes stale and every downstream compliance report becomes unreliable. Build monitoring for the sync flows first, before you build anything else.
Before you can enforce ownership policies, you need to define what "ownership" means in your organization. This sounds obvious but most CoE implementations skip it and end up with policies that don't hold up under real-world conditions.
Model 1: Individual Ownership Every flow has exactly one owner who is a named, active employee. Flows are deleted or reassigned when the owner leaves. This model is simple to audit but fragile — if your CFO's executive assistant builds a critical invoice approval flow and then leaves, someone has to scramble to reassign it before it breaks.
Model 2: Service Account Ownership
Flows that support business processes are owned by a shared service account (e.g., powerautomate-svc@contoso.com) rather than an individual. The service account is managed by IT, and business ownership is tracked in a separate register. This is more resilient but introduces license complexity — a service account needs its own per-user Power Automate license, and you need a process for rotating credentials and maintaining the account's connections.
Model 3: Hybrid with Designated Backup Owners
Individual ownership for the primary contact, but every flow above a certain criticality threshold must have a designated backup owner recorded in Dataverse. The CoE Toolkit's admin_Flow table has a custom field pattern for exactly this — you add a cr_BackupOwner lookup field pointing to admin_Maker and enforce population through the governance flows.
For most mid-to-large enterprises, Model 3 with service accounts for Tier 1 critical flows is the right answer. Here's how to implement it.
Add a custom admin_FlowCriticality choice column to the admin_Flow table in Dataverse with values: Tier1_Critical, Tier2_Important, Tier3_Standard, Tier4_Personal. The classification criteria should be documented and enforced through a governance flow that prompts flow owners to self-classify during onboarding.
Your classification criteria might look like this:
The governance flow that collects this classification is triggered when a new flow record appears in admin_Flow without a criticality value. It sends a Teams Adaptive Card to the flow owner asking them to classify the flow and provide a business description. That response updates Dataverse, which triggers downstream compliance checks.
Ownership policy only works if you pair it with an environment strategy. If every maker can create flows in the default environment with no guardrails, ownership tracking becomes a documentation exercise rather than an operational control.
The recommended environment strategy that makes ownership enforceable:
Default Environment: Locked down. DLP policy blocks all premium connectors and all non-Microsoft connectors. Suitable for personal productivity flows only. No service accounts permitted.
Departmental Sandbox Environments: One per business unit, provisioned by CoE on request. Makers experiment here. Standard DLP applies. Flows cannot process Tier 1 data.
Production Environments: Strictly controlled. Only service accounts or users with explicit CoE approval can create flows. All flows must have a business justification ticket number in the flow's description field (this is checkable via the admin API). Premium connectors permitted but logged.
Shared Services Environment: For IT-owned automation that serves multiple departments. Tightest controls. Service account ownership mandatory.
Here's the DLP policy architecture that supports this. You manage DLP through the Power Platform admin center, but you can automate policy creation via the Power Platform admin connector:
DLP Policy: "Default Environment - Restricted"
Business Data Group (allowed to flow together):
- Office 365 Outlook
- Microsoft Teams
- SharePoint
- OneDrive for Business
- Microsoft 365 Users
Non-Business Data Group (blocked from mixing with Business):
- All other connectors
Blocked:
- HTTP with Azure AD (prevents arbitrary API calls)
- SQL Server (prevents direct database connections)
- All custom connectors
DLP Policy: "Production - Controlled"
Business Data Group:
- All Microsoft first-party connectors
- Approved custom connectors (allowlisted by name)
Non-Business Data Group:
- Third-party SaaS connectors (Salesforce, ServiceNow, etc.)
[Must be explicitly moved to Business group with approval]
Blocked:
- Any connector not reviewed by CoE
Tip: DLP policies are evaluated at flow-save time AND at flow-run time (for new connections added after save). However, existing flows that predate a DLP policy are not immediately broken — they show a warning and are given a grace period. Use the CoE Toolkit's "DLP Impact Analysis" app to see which existing flows a new DLP policy would break before you publish it.
The CoE Toolkit's sync flows give you a Dataverse-based inventory, but for deep usage analytics you need to go to two additional sources: the Power Platform admin center usage reports and the Microsoft 365 Unified Audit Log.
The admin API for flow run history lives at:
GET https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple/scopes/admin/environments/{environmentName}/flows/{flowId}/runs?api-version=2016-11-01
This endpoint returns paginated run history with status (Succeeded, Failed, Cancelled), start time, end time, and duration. To call it programmatically from within Power Automate, you need an app registration with the Flowmanagement application permission and the https://service.flow.microsoft.com/.default scope.
Here's the app registration setup in Azure AD:
PowerPlatform-GovernanceService, single-tenantMicrosoft Flow Service > Application permissions > FlowmanagementPowerApps Service > Application permissions > AppManagement.AllIn your governance flow, you retrieve a token and call the run history endpoint like this:
// Action: Get OAuth Token (HTTP action)
{
"method": "POST",
"uri": "https://login.microsoftonline.com/@{variables('TenantId')}/oauth2/v2.0/token",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"body": "grant_type=client_credentials&client_id=@{variables('ClientId')}&client_secret=@{variables('ClientSecret')}&scope=https%3A%2F%2Fservice.flow.microsoft.com%2F.default"
}
// Action: List Flow Runs (HTTP action)
{
"method": "GET",
"uri": "https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple/scopes/admin/environments/@{items('Apply_to_each_environment')?['name']}/flows/@{items('Apply_to_each_flow')?['name']}/runs?api-version=2016-11-01&$top=50",
"headers": {
"Authorization": "Bearer @{body('Get_OAuth_Token')?['access_token']}"
}
}
The response gives you a value array of run objects. Each run object contains:
{
"name": "08585...",
"id": "/providers/Microsoft.ProcessSimple/.../runs/08585...",
"properties": {
"startTime": "2024-01-15T09:23:41.1234567Z",
"endTime": "2024-01-15T09:23:43.8765432Z",
"status": "Succeeded",
"correlation": {
"clientTrackingId": "08585..."
},
"trigger": {
"name": "Recurrence",
"type": "Recurrence"
}
}
}
The challenge with run history extraction at scale is pagination and throttling. The API caps results at 250 per page and will throttle you if you hit it too aggressively across many environments and flows. The pattern that works:
admin_Flow.LastRun before calling the runs endpoint)The audit log is a source that most CoE implementations completely ignore, which is a mistake. The M365 audit log captures Power Automate events at a different granularity than the admin API — specifically, it logs maker actions (who created, modified, or deleted a flow) rather than flow execution telemetry.
For governance, the audit events you care about are:
| Operation | What it tells you |
|---|---|
CreateFlow |
New flow created — trigger your onboarding governance flow |
DeleteFlow |
Flow deleted — check if it was Tier 1 and whether deletion was approved |
EditFlow |
Flow modified — flag for Tier 1 flows (was this change reviewed?) |
RunFlow |
Manual run initiated — who triggered it and when |
ShareFlow |
Owner or run-only permission granted — to whom? |
CreateConnection |
New connector connection created — flag premium connectors |
To extract these events programmatically, you have two options. Option 1 is the Office 365 Management Activity API. Option 2, which is easier to maintain in a Power Automate context, is using the Office 365 Management API connector or calling the Search API via an HTTP action.
Here's the HTTP call pattern to retrieve audit records for Power Automate events from the last 24 hours:
// Action: Search Audit Log (HTTP)
{
"method": "GET",
"uri": "https://manage.office.com/api/v1.0/@{variables('TenantId')}/activity/feed/subscriptions/content?contentType=Audit.General&startTime=@{formatDateTime(addHours(utcNow(), -24), 'yyyy-MM-ddTHH:mm:ss')}&endTime=@{formatDateTime(utcNow(), 'yyyy-MM-ddTHH:mm:ss')}",
"headers": {
"Authorization": "Bearer @{body('Get_Management_API_Token')?['access_token']}"
}
}
This returns content blob URIs rather than the events directly (the Management API uses an async blob pattern). You then retrieve each blob URI to get the actual events and filter for Workload eq 'MicrosoftFlow'.
Warning: The Management API requires a separate subscription activation — it doesn't just work because you have admin rights. You must POST to
https://manage.office.com/api/v1.0/{tenantId}/activity/feed/subscriptions/start?contentType=Audit.Generalonce to activate the subscription. Events before activation are not retroactively available.
Rather than querying the admin API ad-hoc every time someone wants a report, build a normalized analytics layer in Dataverse. The schema that works well for governance reporting:
admin_FlowMetric table:
admin_FlowId (lookup to admin_Flow)admin_PeriodStart (datetime)admin_PeriodEnd (datetime)admin_TotalRuns (whole number)admin_SuccessfulRuns (whole number)admin_FailedRuns (whole number)admin_AvgDurationSeconds (decimal)admin_P95DurationSeconds (decimal)admin_IsActive (yes/no — any runs in period?)admin_ComplianceStatus table:
admin_FlowId (lookup to admin_Flow)admin_CheckDate (datetime)admin_OwnerValid (yes/no — owner is active in AAD)admin_HasBackupOwner (yes/no)admin_CriticalityClassified (yes/no)admin_DLPCompliant (yes/no)admin_HasBusinessJustification (yes/no)admin_OverallStatus (choice: Compliant, AtRisk, NonCompliant, Exempt)A scheduled flow runs nightly, iterates over all flows in admin_Flow, evaluates each compliance dimension, and writes a new admin_ComplianceStatus record. This gives you a daily compliance history — you can trend compliance rates over time, which is exactly what a CISO or external auditor wants to see.
An orphaned flow is one whose owner is no longer an active user in Azure AD. These are your highest-risk governance items because:
The CoE Toolkit includes orphaned flow detection, but the default implementation has a significant gap: it only checks whether the owner's UPN exists in AAD. It doesn't check whether the user is disabled, whether they're a guest whose invitation has expired, or whether the account is a shared mailbox without a valid license.
Here's a more complete orphan detection pattern:
// In your "Detect Orphaned Flows" scheduled flow:
// Step 1: Get all flows with owners
// [List rows from admin_Flow where admin_Maker is not null]
// Step 2: For each flow, check owner status
// HTTP: GET https://graph.microsoft.com/v1.0/users/{ownerUPN}?$select=id,displayName,accountEnabled,userType,assignedLicenses
// Step 3: Evaluate conditions
// Is accountEnabled = false? -> Orphaned (disabled account)
// Is userType = "Guest"? -> Flag for review (guest access may be revoked)
// Is assignedLicenses empty AND flow uses premium connectors? -> License risk
// Does the UPN 404? -> Orphaned (deleted account)
// Step 4: Update admin_ComplianceStatus.OwnerValid
// Step 5: If orphaned, trigger remediation notification
The remediation flow sends a Teams message to the flow's backup owner (if defined) or to the CoE team's shared channel. It includes:
The response from that Adaptive Card updates the flow's owner in both Dataverse and (critically) via the admin API, which actually reassigns the flow in the Power Platform:
// Action: Reassign Flow Owner (HTTP)
{
"method": "POST",
"uri": "https://api.flow.microsoft.com/providers/Microsoft.ProcessSimple/scopes/admin/environments/@{variables('EnvironmentId')}/flows/@{variables('FlowId')}/modifyowners/root",
"headers": {
"Authorization": "Bearer @{body('Get_Token')?['access_token']}",
"Content-Type": "application/json"
},
"body": {
"put": [
{
"properties": {
"principal": {
"email": "@{variables('NewOwnerEmail')}",
"id": "@{variables('NewOwnerObjectId')}",
"type": "User"
},
"roleName": "Owner"
}
}
]
}
}
Tip: The
modifyownersendpoint replaces the entire owner list, it doesn't append. If you want to add a co-owner without removing the original, you need to first GET the current owners and include them in theputarray along with the new owner. Failing to do this will silently remove the existing owner, which is exactly the kind of undocumented behavior that causes governance incidents.
This is where governance becomes visible to leadership and auditors. A compliance report that requires manual effort to produce will be produced inconsistently. The goal is to schedule a report that runs automatically, collects data from your governance data model, formats it appropriately for different audiences, and distributes it without human intervention.
Your compliance data serves at least three audiences with different needs:
CoE Operations Report (daily, Teams channel): Technical details. Flow counts by environment, new flows in last 24h, orphaned flows detected, DLP violations, sync flow health status. Formatted as a concise Teams message with a link to the Power BI dashboard.
Departmental Governance Report (weekly, email to department heads): Business-oriented. Which of your department's flows are compliant vs. at-risk, flows approaching review deadlines, action items requiring department attention. Personalized per department head.
Executive Compliance Summary (monthly, PDF to CISO/CTO): High-level metrics. Overall compliance rate trend, risk items remediated, open risks, environment growth. Delivered as a PDF attachment.
Let's build the weekly departmental report, as it's the most complex and most valuable.
This flow runs every Monday at 6 AM, collects compliance data from Dataverse, and sends personalized emails to department heads.
Trigger: Recurrence — Weekly, Monday, 6:00 AM UTC
Action 1: List distinct departments
Source: admin_Flow table, distinct values of admin_Department column
Action 2: Apply to each department
Action 2a: Get flows for this department
Filter: admin_Department = current department
AND admin_IsActive = true
Action 2b: Get compliance status for each flow
Join: admin_ComplianceStatus where admin_CheckDate = today
Action 2c: Calculate metrics
TotalFlows: length(body('Get_Flows'))
CompliantFlows: length(filter(body('Get_Flows'), item()?['OverallStatus'] = 'Compliant'))
AtRiskFlows: length(filter(body('Get_Flows'), item()?['OverallStatus'] = 'AtRisk'))
NonCompliantFlows: length(filter(body('Get_Flows'), item()?['OverallStatus'] = 'NonCompliant'))
ComplianceRate: div(mul(variables('CompliantFlows'), 100), variables('TotalFlows'))
Action 2d: Get department head email
Source: admin_Maker where admin_Department = current department
AND admin_IsCoEAdmin = false
AND admin_IsDeptHead = true
Action 2e: Build HTML report body
[Compose action with HTML template]
Action 2f: Send email via Outlook
To: department head email
Subject: "Power Automate Governance Report - @{variables('Department')} - @{formatDateTime(utcNow(), 'MMMM d, yyyy')}"
Body: HTML report
Importance: Normal (High if NonCompliantFlows > 0)
The HTML report body is where most implementations get lazy and produce something unreadable. Here's a report template that actually communicates:
<!DOCTYPE html>
<html>
<body style="font-family: Segoe UI, sans-serif; max-width: 700px; margin: 0 auto;">
<div style="background: #0078d4; color: white; padding: 20px; border-radius: 4px 4px 0 0;">
<h1 style="margin:0; font-size: 20px;">Power Automate Governance Report</h1>
<p style="margin: 5px 0 0 0; opacity: 0.8;">@{variables('Department')} · Week of @{formatDateTime(utcNow(), 'MMMM d, yyyy')}</p>
</div>
<div style="background: #f8f8f8; padding: 20px; border: 1px solid #e0e0e0;">
<h2 style="color: #333; font-size: 16px;">Compliance Summary</h2>
<table style="width: 100%; border-collapse: collapse;">
<tr>
<td style="padding: 10px; background: #e8f4e8; border-radius: 4px; text-align: center; width: 25%;">
<div style="font-size: 28px; font-weight: bold; color: #107c10;">@{variables('CompliantFlows')}</div>
<div style="font-size: 12px; color: #666;">Compliant</div>
</td>
<td style="width: 5%;"></td>
<td style="padding: 10px; background: #fff4e0; border-radius: 4px; text-align: center; width: 25%;">
<div style="font-size: 28px; font-weight: bold; color: #d83b01;">@{variables('AtRiskFlows')}</div>
<div style="font-size: 12px; color: #666;">At Risk</div>
</td>
<td style="width: 5%;"></td>
<td style="padding: 10px; background: #fde7e9; border-radius: 4px; text-align: center; width: 25%;">
<div style="font-size: 28px; font-weight: bold; color: #a4262c;">@{variables('NonCompliantFlows')}</div>
<div style="font-size: 12px; color: #666;">Non-Compliant</div>
</td>
</tr>
</table>
<p style="font-size: 14px; color: #555; margin-top: 15px;">
Overall compliance rate: <strong>@{variables('ComplianceRate')}%</strong>
</p>
</div>
<!-- Non-compliant flows table would be dynamically built here -->
</body>
</html>
Power Automate doesn't have a native PDF generation action, but you have two practical options:
Option A: Word template to PDF via OneDrive
Use the Word Online (Business) connector to populate a .docx template with your metrics, then convert to PDF using the "Convert file" action in OneDrive. This works well but requires maintaining a Word template file.
Option B: Power Automate + Power BI Export API If your Power BI dashboard has a dedicated "Executive Summary" page, you can call the Power BI REST API to export that page as PDF:
// Action: Trigger Power BI Export (HTTP)
{
"method": "POST",
"uri": "https://api.powerbi.com/v1.0/myorg/groups/@{variables('WorkspaceId')}/reports/@{variables('ReportId')}/ExportTo",
"headers": {
"Authorization": "Bearer @{body('Get_PowerBI_Token')?['access_token']}",
"Content-Type": "application/json"
},
"body": {
"format": "PDF",
"paginatedReportConfiguration": null,
"powerBIReportConfiguration": {
"pages": [
{
"pageName": "ExecutiveSummary"
}
],
"reportLevelFilters": [
{
"filter": "Period/Month eq @{formatDateTime(utcNow(), 'yyyy-MM')}"
}
]
}
}
}
The export is asynchronous — you get back an export ID, then poll for completion, then download the blob. Build a Do Until loop that checks the export status every 10 seconds with a maximum of 30 iterations.
Warning: The Power BI Export API requires a Power BI Premium or Embedded capacity, or the specific report must be in a Premium workspace. If you're on Power BI Pro only, this approach won't work. Fall back to Option A or use a third-party PDF service via HTTP action.
If your organization operates in Microsoft 365 GCC, GCC High, or has regional tenants (e.g., an EU tenant for GDPR isolation), the CoE Toolkit's default configuration won't work because the API endpoints differ:
| Cloud | Flow API Base |
|---|---|
| Commercial | https://api.flow.microsoft.com |
| GCC | https://gov.api.flow.microsoft.us |
| GCC High | https://high.api.flow.microsoft.us |
| DoD | https://api.flow.appsplatform.us |
You need to parameterize all API calls with an environment variable that holds the correct base URL. Store this in a Dataverse configuration table (admin_GovernanceConfig) with a row per cloud region, and have your sync flows retrieve the appropriate base URL at the start of each run.
A tempting shortcut is to have all your governance flows run as a single service principal that has Power Platform Admin rights. The problem is that when this service principal's credentials need rotation (and they will — your security team will mandate it), you have to update the credential reference in every flow simultaneously, or your entire governance infrastructure goes dark during the rotation window.
The better pattern is to use Azure Key Vault for all secrets and reference them through the Azure Key Vault connector's "Get Secret" action at the start of each flow. When you rotate the secret, you update it once in Key Vault, and all flows pick up the new value on their next run. This is not just a convenience — it's a security control that your SOC will thank you for.
Flow Start: Initialize Variables
Action: Get Secret - ClientId
Key Vault: kv-powerplatform-governance
Secret Name: gov-service-principal-client-id
Action: Get Secret - ClientSecret
Key Vault: kv-powerplatform-governance
Secret Name: gov-service-principal-client-secret
[All subsequent HTTP calls use these variables]
Guest users who are members of your tenant can create flows, and those flows present unique governance challenges:
Your compliance check flow should flag any flow owned by a userType = "Guest" user as requiring immediate CoE review. The admin_ComplianceStatus table should have a specific admin_OwnerIsGuest boolean field, and any true value should automatically set OverallStatus to AtRisk regardless of other compliance dimensions.
In this exercise, you'll build the core orphaned flow detection flow and connect it to a Teams notification. You'll need access to a Power Platform environment with the CoE Toolkit installed, an Azure app registration with appropriate permissions, and a Teams channel for CoE notifications.
Exercise: Build the Orphaned Flow Detection and Notification Flow
Part 1: Create the flow structure
CoE | Detect Orphaned Flows | Daily.OrphanCount (Integer, 0).admin_Flow table. Filter for: admin_IsActive eq true and admin_OwnerObjectId ne null.Part 2: Check each owner against Azure AD
Add an Apply to Each action iterating over the flow results.
Inside the loop, add an HTTP action to call the Microsoft Graph:
GET https://graph.microsoft.com/v1.0/users/@{items('Apply_to_each')?['admin_ownerobjectid']}?$select=accountEnabled,userType,displayName
Use the "Managed identity" authentication type if your flow environment supports it, otherwise use the service principal token pattern.
Add a Condition: Status code equals 404 OR body accountEnabled equals false
In the Yes branch:
OrphanCount by 1admin_Flow row: set admin_IsOrphaned = true, admin_OrphanDetectedDate = utcNow()🚨 Orphaned Flow Detected
Flow: @{items('Apply_to_each')?['admin_name']}
Environment: @{items('Apply_to_each')?['admin_environment']}
Last Owner: @{items('Apply_to_each')?['admin_ownerdisplayname']}
Last Run: @{items('Apply_to_each')?['admin_lastrun']}
Action needed: Assign new owner or decommission
Part 3: Send the daily summary
OrphanCount is greater than 0Expected outcome: Your CoE Teams channel receives a daily digest of orphaned flows, and the admin_Flow table shows accurate admin_IsOrphaned status for all flows in scope.
Mistake 1: Sync flows run but Dataverse data is stale
This usually means the sync flow is completing without errors but not writing records. Check the connection reference for the Dataverse connector — if the connection was created by a user who has since left, it may be silently failing on write operations even though reads succeed. Recreate the connection using a service account.
Mistake 2: Compliance rate looks artificially low because unclassified flows are marked non-compliant
When you first deploy governance, most flows will be unclassified (no criticality, no business justification). If your compliance logic treats missing data as non-compliant, you'll report 5% compliance on day one, which causes leadership to dismiss the whole program. Add an "Exempt - Pending Classification" status for flows in their first 30 days and build a separate "Classification backlog" metric.
Mistake 3: The ownership reassignment API call removes co-owners
Covered earlier, but worth repeating: the modifyowners/root endpoint is a PUT, not a PATCH. Always GET current owners first and include them in your POST body when adding a new owner.
Mistake 4: Audit log events arrive with variable delay
The Microsoft 365 audit log is not real-time. Events can be delayed by up to 60 minutes for standard audit and up to 24 hours for some operation types. If you're triggering governance flows based on CreateFlow audit events, don't assume the CoE inventory sync has caught the new flow yet when the audit event arrives. Check admin_Flow for the flow ID and handle the "not yet synced" case gracefully.
Mistake 5: DLP impact analysis run before testing in a sandbox environment
Before applying a new DLP policy to a production environment, use the CoE Toolkit's DLP impact tooling to identify which flows will be affected. Then test the policy in a sandbox for at least one week. DLP policy changes that break production flows generate enormous IT support tickets and erode trust in the governance program.
Mistake 6: Scheduling all governance flows at the same time
If your sync flow, compliance check flow, and reporting flow all run at midnight, they'll compete for Dataverse capacity and API rate limits. Stagger them: sync runs at midnight, compliance evaluation runs at 2 AM (after sync completes), reporting flows run at 5 AM. Add an explicit dependency check at the start of the compliance flow that verifies the sync completed successfully in the last 4 hours.
You've built a mental model for enterprise Power Automate governance that goes well beyond clicking through the CoE Toolkit installer. The key architectural insights to carry forward:
The CoE Toolkit is a starting point, not a solution. It gives you the data model and the patterns. You extend it to fit your organization's ownership model, environment strategy, and reporting cadence.
Governance is a data pipeline problem. Raw telemetry comes from the admin API and audit log. It flows through normalization and compliance evaluation into Dataverse. It gets presented through Power BI dashboards and automated reports. Each stage needs monitoring, error handling, and a recovery path when something breaks.
Enforcement without education creates resistance. The most technically complete DLP policy will be circumvented if makers don't understand why it exists. Pair your technical controls with a maker enablement program — training resources, clear escalation paths for exceptions, and recognition for compliant makers who build high-quality automation.
Compliance state needs to be a trend, not a point-in-time snapshot. A single compliance report tells you where you are. A time series of compliance data tells you whether your governance program is working. Store admin_ComplianceStatus records with dates and never overwrite — always insert new records. In six months, you'll be able to show a CISO that compliance rates improved from 42% to 89%, and that is a governance program success story.
For your next steps:
admin_GovernanceHealth Dataverse record, and a separate lightweight flow checks that timestamp every hour and alerts if the sync is more than 26 hours stale.The organizations that do Power Automate governance well aren't the ones with the most sophisticated technical controls. They're the ones that made governance a continuous operational process rather than a quarterly audit event.
Flow Automation Basics