MigrationFox Docs

Power Platform Inventory

A per-tenant pre-migration audit of every Power Platform environment, Dataverse solution, Power Automate flow, Power App, connector, and DLP policy. Surfaces the risks that are invisible from the Power Apps portal: orphan flows, connectors blocked by DLP but still referenced, environments with zero governance, and premium connector sprawl.

What it produces

Five data slices per scan: environments, solutions, flows, apps, and DLP policies. Plus a tenant-wide connector usage matrix that aggregates every connector referenced by any flow or app and cross-references it against every active DLP policy to flag “blocked connectors still in use” — the silently-broken artifacts in every tenant.

When to run it

Access requirements

Power Platform admin APIs use a different authentication surface than Microsoft Graph. The same Azure AD service principal you use for a governance assessment authenticates fine — only the audience and the authorization model are different. Grants are layered in three tiers. Each tier unlocks a specific data slice in the scan result:

Tier 1 — Required

Tenant-level BAP access

What you get: environments list, DLP policies list. Without this tier the scan cannot proceed at all — the inventory result will show a prereq checklist instead of data.

What to grant:

  1. Assign the Entra directory role Power Platform Administrator to the service principal. (Global Administrator also works but is more access than needed.)
  2. Register the service principal with BAP as a tenant management app — one PowerShell command, run once per tenant.
Install-Module -Name Microsoft.PowerApps.Administration.PowerShell -Scope CurrentUser -Force
Add-PowerAppsAccount                         # sign in as a Global Administrator
New-PowerAppManagementApp -ApplicationId <YOUR_CLIENT_ID>

Why both: the directory role opens the door; the management-app registration lets app-only tokens through. Human users don’t need the management-app step because their delegated tokens already carry identity. Service principals do. This is Microsoft’s documented pattern — the same one the CoE Starter Kit uses. See Fix: Power Platform scan returns 0 flows + 0 apps for the full troubleshooting walk-through.

Token audience: https://service.powerapps.com/.default

Symptom when missing: the scan completes but the result shows “BAP access prerequisites missing” with the one-time setup checklist. Zero environments, zero DLP policies, zero flows, zero apps. Nothing to act on except the checklist.

Tier 2 — For flows + apps

Environment Admin on each environment

The PowerShell path is deprecated for modern environments

As of 2026, the Set-AdminPowerAppEnvironmentRoleAssignment cmdlet returns LinkedEnvironmentForbiddenOperation (HTTP 403) for every environment that has a Dataverse database — which in practice is all of them. The old BAP /modifyRoleAssignments endpoint no longer grants Environment Admin on CDS 2.0 linked envs. Microsoft’s own cmdlet reference confirms this: the cmdlet only works on Dataverse-less environments. Use the Dataverse Application User flow below instead.

What you get: the flow list and app list for every environment where you grant this role. Without it, Tier 1 gives you environment names but the per-environment /flows and /powerApps endpoints return empty arrays.

What to grant (current path):

  1. Open the Power Platform Admin Center.
  2. Go to Environments, click into each environment you want inventoried.
  3. Navigate to Settings → Users + permissions → Application users.
  4. Click + New app user.
  5. In App, search for the service principal by its Application (client) ID and select it.
  6. In Business unit, leave the environment’s root business unit selected.
  7. In Security roles, assign System Administrator. (Narrower custom roles work in production; see Tier 3 below.)
  8. Click Create.

Why per-environment: Microsoft deliberately keeps environments autonomous. Having tenant-level Power Platform Admin lets you see that environments exist, but each environment owner decides who reads its contents. The per-env grant is what unlocks the flow and app enumeration endpoints.

Token audience: same as Tier 1 (https://service.powerapps.com/.default) — the authorization check is where this differs, not the audience.

Symptom when missing: the inventory shows environment and DLP counts correctly, but the Apps stat turns amber at 0 and a “Scan Diagnostics” panel appears with a message like “listApps returned 0 apps across N environments”. Flow details (connector references) also come back empty. Before the CDS 2.0 deprecation this symptom meant “run the old Environment Admin PowerShell grant”; today it means “create the Application User above.”

Tier 3 — For Dataverse solutions

Application user per Dataverse environment

What you get: the Dataverse solution list for each environment (unique name, friendly name, version, managed or unmanaged, publisher). Environments with no Dataverse database are skipped cleanly.

What to grant:

  1. Power Platform Admin Center → Environments → your env → Settings → Users + permissions → Application users.
  2. Click + New app user.
  3. Select the service principal’s app registration.
  4. Assign a role that can read the solutions table. System Administrator works; a narrower custom role with Solution: Read is preferable in production.

Why this is a separate grant: Dataverse is its own identity plane. The BAP admin role doesn’t imply Dataverse access. Each Dataverse organization authorizes via its own role-based access and needs an explicit Application User record per service principal.

Token audience: {dataverseUrl}/.default — different per environment because the audience is the org’s own URL (e.g. https://contoso.crm.dynamics.com/.default). Our BAP client caches Dataverse tokens per URL so the overhead is one token per scan per env.

Symptom when missing: Solutions count is 0 for that specific environment. Other environments (where the grant is correct) still show solutions. No scan-wide failure.

Token audience reference

Data sliceEndpointToken audienceRequired tier
Environmentsapi.bap.microsoft.com/.../admin/environmentsservice.powerapps.com/.defaultTier 1
DLP policiesapi.bap.microsoft.com/.../scopes/admin/apiPolicies?api-version=2018-01-01service.powerapps.com/.defaultTier 1
Flowsapi.flow.microsoft.com/.../admin/environments/{env}/v2/flowsservice.powerapps.com/.defaultTier 2
Appsapi.bap.microsoft.com/.../admin/environments/{env}/powerAppsservice.powerapps.com/.defaultTier 2
Solutions{dataverseUrl}/api/data/v9.2/solutions{dataverseUrl}/.defaultTier 3 (per env)

DLP policies are read from the legacy apiPolicies endpoint, not the newer Governance/v2/policies endpoint. The v2 endpoint returns policy metadata wrapped in a policyDefinition envelope but does not include connector classifications. The legacy endpoint returns the full definition.apiGroups.{hbi, lbi, blocked}.apis[] data needed to classify each connector against each policy.

Graceful degradation

The scan never hard-fails because a tier is missing. Every missing tier produces a diagnostic message on the result page:

This lets you run the scan immediately after granting Tier 1 to confirm the plumbing works, then iterate on Tier 2 and Tier 3 grants without re-running the whole audit from scratch each time.

What the scan result looks like

A completed inventory page opens with a stats row (environments, solutions, flows, apps, DLP policies), followed by risk highlights. The tenant-wide connector usage matrix lists every connector used anywhere in the tenant, sorted by total flow + app references, with pills for Premium, Business-classified, Non-Business-classified, and Blocked. Rows where a connector is Blocked by DLP but still has non-zero references are highlighted — those flows and apps are silently broken and the owner doesn’t know.

Each environment renders as a collapsible card with per-env flow and app tables (owner email, state, connectors). The DLP policies section shows each policy’s Business / Non-Business / Blocked buckets with the first few connectors previewed. A one-click CSV export dumps all five data slices for handover to the client.

Scan scope and caps

Troubleshooting

SymptomLikely causeFix
Inventory says “BAP access prerequisites missing” Tier 1 not granted: either the directory role is missing or New-PowerAppManagementApp was never run. Follow Tier 1 in Access requirements, then re-run.
Environments show but 0 apps across all Tier 2 missing. Service principal has no per-environment grant. Follow Tier 2 via the Application User flow in PPAC.
Set-AdminPowerAppEnvironmentRoleAssignment returns 403 LinkedEnvironmentForbiddenOperation Environment is CDS 2.0 linked; the BAP /modifyRoleAssignments endpoint is deprecated for modern envs. Use the PPAC Application User flow instead (Tier 2 above). The PowerShell cmdlet only works on Dataverse-less environments.
Solutions count is 0 for specific environments only Tier 3 Application User is missing for those envs. Others are fine because they have it. Follow Tier 3 for the envs with 0 solutions.
Flow rows show but with empty connector lists Scan hit the 150-flow detail-fetch cap, or some flows have no connections configured. Check the Scan Diagnostics panel — it reports “N of M flows still had no connectors after enrichment”. Beyond the cap is expected; genuinely empty connections are legit for triggered-without-action flows.
DLP policies listed but all three buckets show 0 connectors The v2 /policies list endpoint returns policy summaries with empty connectorGroups. We fetch the detail endpoint per policy to hydrate buckets. Ensure the scan reached completion — the Scan Diagnostics panel will show how many policies needed detail-fetching and how many still had empty buckets.
Solution-scoped flows show an empty Connectors column Connection references for solution-aware flows (Dynamics 365 OOB flows, AI Builder flows, managed-solution flows) live in the Dataverse workflow.clientdata field, not in the admin Flow API envelope. The admin endpoint returns a workflowUniqueId that maps to Dataverse’s workflowidunique column, not the workflowid primary key — querying workflows({id}) with the unique id returns 404 with 0x80040217. Ensure the service principal has the Tier 3 Application User role on that environment’s Dataverse instance. The scanner falls back to a Dataverse workflowidunique lookup automatically when the admin Flow API returns no connection references.
App types all show “Canvas” including obvious Model-driven apps The Microsoft.PowerApps admin /powerApps endpoint does not reliably populate the appType field, so every app defaults to Canvas in the raw response. Known Dataverse system apps (Plugin Monitor, API Playground, Overview, Solution Health Hub, and similar) are auto-classified as Model-driven via display-name override in the inventory result. No action needed; user-created Model-driven apps without a recognized display name may still need manual classification.

One service principal, different audiences

You do not need a separate credential for Power Platform vs Graph. The same Azure AD app registration (clientId + secret) authenticates against Microsoft Graph, BAP, Flow admin, and Dataverse — each uses a different audience in the token exchange. MigrationFox handles the token caching and audience switching automatically; you just grant the roles above to the same service principal you already use for governance assessments.

Related