Microsoft 365 Governance

Governing the Power Platform: Environment Strategy, DLP Connector Policies, and Tenant Isolation

The default environment is the failure mode. Every tenant ships with one Default environment, every licensed user is a maker in it, and every connector is in scope. Left alone, it becomes a swamp of personal flows wired to SQL, Dataverse, and SharePoint with no owner, no DLP, and no audit story. Power Platform governance replaces that swamp with a landscape: a deliberate set of environments, connector policies that separate business from personal data, tenant isolation at the boundary, and an inventory you can query. This guide builds that control plane end to end with the admin PowerShell modules, the PAC CLI, and the Center of Excellence (CoE) starter kit.

The governance surface: what you are actually controlling

Three planes matter, and they are independent. Get all three or your story has holes.

Plane Control Tooling
Where makers build Environment strategy + creation governance Microsoft.PowerApps.Administration.PowerShell, admin center
What data can mix DLP policies (connector classification) Microsoft.PowerApps.Administration.PowerShell, PPAC
Who can connect in/out of the tenant Tenant isolation Power Platform admin center (PPAC)
Drift / inventory / sharing Managed Environments + CoE starter kit PPAC, Dataverse, Power BI

Licensing reality check: DLP policies and environment strategy are free — they ship with the platform. Managed Environments features (sharing limits, weekly digest, solution checker enforcement, IP firewall) require every user of that environment to have a standalone Power Platform / Power Apps / Power Automate premium license (the per-app or per-user plans); Microsoft 365 seeded licenses do not entitle Managed Environments. Confirm entitlement before you flip the switch, because Managed Environments is enforced on the environment, not the user.

Install the admin module and connect. The Microsoft.PowerApps.Administration.PowerShell module is the durable, scriptable surface for environments and DLP; Microsoft.PowerApps.PowerShell adds maker-level cmdlets.

# Admin cmdlets for environments + DLP. PowerShell 5.1 or 7.x.
Install-Module -Name Microsoft.PowerApps.Administration.PowerShell -Scope CurrentUser
Install-Module -Name Microsoft.PowerApps.PowerShell -Scope CurrentUser -AllowClobber

# Interactive sign-in (Power Platform admin / Global admin)
Add-PowerAppsAccount

# Sanity check: list every environment and its type
Get-AdminPowerAppEnvironment |
    Select-Object DisplayName, EnvironmentName, EnvironmentType, @{
        n = "Region"; e = { $_.Location }
    } | Format-Table -AutoSize

For solution and pipeline work you will also want the Power Platform CLI (pac), which installs as a .NET tool and authenticates per-environment:

dotnet tool install --global Microsoft.PowerPlatform.Cli.Tool
pac auth create --name tenant-admin
pac admin list   # lists environments via the admin API

1. Environment strategy: default, production, developer, and the maker landing zone

An environment is a security and data boundary: it has its own Dataverse instance (optionally), its own DLP assignment, its own security roles, and its own capacity draw. Give each purpose its own environment so a control applies cleanly.

A workable baseline for an enterprise tenant:

Create a production environment with a Dataverse database (the database is what makes it solution-aware and gives you security roles):

New-AdminPowerAppEnvironment `
    -DisplayName "Contoso - Finance - PROD" `
    -Location "unitedstates" `
    -EnvironmentSku Production `
    -ProvisionDatabase `
    -CurrencyName "USD" `
    -LanguageName 1033
# Same thing via the CLI, including a Managed Environment in one shot
pac admin create \
  --name "Contoso - Finance - PROD" \
  --region unitedstates \
  --type Production \
  --currency USD \
  --language 1033

Region is immutable. You cannot move an environment between geographies after creation — only Microsoft-assisted tenant moves change geo. Pick the region for data residency and latency up front, and standardize it so a DLP-or-isolation gap never appears because one team provisioned in the wrong geo.

2. Restricting environment creation, capacity, and licensing

The single highest-leverage governance action: stop everyone from creating production and sandbox environments. In PPAC, under Settings -> Tenant settings, set:

Developer environments are governed separately — you generally want makers to have them as the sanctioned sandbox, but you can restrict that too if Default is your only allowed playground.

Capacity is the other lever. Dataverse storage (Database, File, Log) is tenant-pooled and metered. Allocate capacity add-ons per environment so one runaway environment cannot starve the rest:

# Inspect tenant + per-environment capacity consumption
Get-AdminPowerAppEnvironmentCapacity -EnvironmentName <env-guid>

# Total tenant capacity picture
Get-AdminPowerAppTenantCapacity

Drive licensing decisions from this: production environments that hold premium connectors (SQL, custom connectors, on-prem gateway, HTTP) require premium Power Apps/Automate seats for their users; standard-only environments can run on seeded Microsoft 365 licenses. Use the environment boundary to contain premium so your license true-up is predictable.

3. DLP policies: business / non-business / blocked classification

A DLP policy does not look at data content — it controls which connectors can share data with which other connectors inside the same app or flow. Every connector lands in exactly one of three groups, and the rule is simple: a connector in the Business group can never combine with a connector in the Non-Business group in the same resource. That separation is the whole mechanism.

The trap people fall into: the Default classification. When a new connector appears in the catalog, it lands in whichever group you designate as default. Set Default to Non-Business (or Blocked) so a newly released connector is not silently usable with corporate data until you deliberately move it.

Create a tenant-wide policy that blocks the riskiest connectors, puts the corporate stack in Business, and defaults everything else to Non-Business:

# Pull the catalog of connectors so you reference real IDs
Get-AdminPowerAppConnector | Select-Object DisplayName, ConnectorName |
    Sort-Object DisplayName | Format-Table -AutoSize

# Build the connector group definition
$businessConnectors = @(
    @{ id = "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps" }  # Dataverse
    @{ id = "/providers/Microsoft.PowerApps/apis/shared_sharepointonline" }
    @{ id = "/providers/Microsoft.PowerApps/apis/shared_office365" }                  # Outlook
    @{ id = "/providers/Microsoft.PowerApps/apis/shared_sql" }
)

$blockedConnectors = @(
    @{ id = "/providers/Microsoft.PowerApps/apis/shared_twitter" }
)

$policy = New-DlpPolicy `
    -DisplayName "Contoso Tenant DLP - Baseline" `
    -EnvironmentType "AllEnvironments"

# Classify groups: Business (Confidential), Non-Business (General, default), Blocked
$policy = Set-DlpPolicy -PolicyName $policy.PolicyName -UpdatedPolicy @{
    displayName    = "Contoso Tenant DLP - Baseline"
    defaultConnectorsClassification = "General"   # new connectors -> Non-Business
    connectorGroups = @(
        @{ classification = "Confidential"; connectors = $businessConnectors }
        @{ classification = "Blocked";      connectors = $blockedConnectors }
    )
}

Naming note: in the API the Business group is Confidential, Non-Business is General, and Blocked is Blocked. The portal labels them Business / Non-Business / Blocked. Two connectors that cannot break out of the platform — HTTP and HTTP with Microsoft Entra ID — deserve special attention; many teams Block raw HTTP in production environments because it is the easiest exfiltration path.

Scope policies precisely. A tenant-wide “AllEnvironments” baseline is your floor; layer environment-specific policies on top for production environments that need a tighter or looser stance. When multiple DLP policies apply to one environment, the most restrictive classification wins per connector.

# A tighter policy scoped to specific production environments
New-DlpPolicy -DisplayName "Finance PROD - Strict" `
    -EnvironmentType "OnlyEnvironments" `
    -Environments @(@{ name = "<finance-prod-guid>" })

4. Endpoint filtering and custom-connector controls within DLP

Connector classification is coarse — “SQL is allowed” says nothing about which SQL server. Endpoint filtering narrows a connector to specific hosts/URLs. It is supported on a subset of connectors (HTTP, HTTP with Entra ID, SQL Server, custom connectors, and a few others) and uses ordered allow/deny rules with the last rule as the catch-all.

Author endpoint rules so production data connectors can only reach sanctioned endpoints:

# Restrict the SQL connector to one server; deny everything else.
# Rules are ordered; the final * rule is the catch-all (Deny).
$connectorConfig = @{
    connectorRuleClassifications = @{ name = "Allow" }
    rules = @(
        @{ order = 1; behavior = "Allow"; endpoint = "contoso-sql.database.windows.net" }
        @{ order = 2; behavior = "Deny";  endpoint = "*" }
    )
}

Set-PowerAppDlpConnectorConfigurations `
    -TenantId <tenant-guid> `
    -PolicyName $policy.PolicyName `
    -NewConnectorConfigurations @{
        connectorActionConfigurations = @()
        endpointConfigurations = @(
            @{
                connectorId = "/providers/Microsoft.PowerApps/apis/shared_sql"
                endpointRules = $connectorConfig.rules
            }
        )
    }

Custom connectors are the other gap. By default custom connectors are not automatically placed in any group, and an unclassified custom connector can become a bypass. Two controls:

  1. In each DLP policy, explicitly classify custom connectors (they appear under a Custom connectors tab) into Business or Blocked, or set the custom-connector URL patterns so connectors matching a host pattern are auto-classified.
  2. Block makers from publishing custom connectors at all in production by restricting maker permissions and requiring custom connectors to flow through a solution + pipeline rather than ad-hoc creation.
# Auto-classify custom connectors by URL pattern within a policy
Set-PowerAppDlpConnectorConfigurations -TenantId <tenant-guid> `
    -PolicyName $policy.PolicyName `
    -NewConnectorConfigurations @{
        connectorActionConfigurations = @()
        endpointConfigurations = @()
        customConnectorUrlPatternsConfiguration = @(
            @{
                rules = @(
                    @{ order = 1; customConnectorRuleClassification = "Confidential"; pattern = "https://api.contoso.com/*" }
                    @{ order = 2; customConnectorRuleClassification = "Blocked";      pattern = "*" }
                )
            }
        )
    }

5. Tenant isolation: blocking cross-tenant connections inbound and outbound

DLP governs connector-to-connector mixing inside your tenant. Tenant isolation governs the boundary: it blocks Power Platform connections that use Microsoft Entra ID authentication from reaching out to other tenants, and from other tenants reaching into yours. This is the control that stops a user from authenticating a Dataverse or SharePoint connector against a partner’s (or an attacker’s) tenant.

Tenant isolation is distinct from Entra Cross-Tenant Access Settings. It applies specifically to Power Platform connectors that use Entra ID OAuth. It does not affect guest access or non-Entra connectors. Run both — Entra cross-tenant settings for identity, Power Platform tenant isolation for connectors.

Configure it in PPAC under Settings -> Tenant settings -> Tenant isolation:

Manage the allowlist as code so it is reviewable. Using the BAP (Business Application Platform) REST API with an admin token:

# Read current tenant isolation policy
curl -s -X GET \
  "https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/tenantIsolationPolicy?api-version=2020-10-01" \
  -H "Authorization: Bearer $TOKEN" | jq .

# Set default-deny with an explicit partner allowlist (both directions)
curl -s -X PUT \
  "https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/tenantIsolationPolicy?api-version=2020-10-01" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "properties": {
          "isDisabled": false,
          "allowedTenants": [
            { "tenantId": "<partner-tenant-guid>", "direction": { "inbound": true, "outbound": true } }
          ]
        }
      }'

When isolation is on with default Block, a maker who tries to create a connection authenticating against a non-allowlisted tenant gets a hard failure at connection time — exactly where you want the boundary enforced.

6. Managed Environments, sharing limits, and weekly maker digests

Managed Environments is the premium governance layer you enable per-environment. It does not change what an app does; it changes what governance you get for free around it:

Enable Managed Environments and set sharing limits via PowerShell:

# Turn on Managed Environments for an environment and constrain sharing
Set-AdminPowerAppEnvironmentRuntimeState -EnvironmentName <env-guid> -RuntimeState Enabled

# Governance configuration: protection level + sharing controls
Set-AdminPowerAppEnvironmentGovernanceConfiguration `
    -EnvironmentName <env-guid> `
    -UpdatedGovernanceConfiguration @{
        protectionLevel = "Standard"
        settings = @{
            extendedSettings = @{
                excludeEnvironmentFromAnalysis = "false"
                isGroupSharingDisabled         = "true"   # block sharing to security groups
                maxLimitUserSharing            = "20"      # cap per-app user sharing
                limitSharingMode               = "excludeSharingToSecurityGroups"
                includeOnHomepageInsights      = "true"
                makerOnboardingMarkdown        = "Welcome. Review the Contoso maker guidelines before publishing."
            }
        }
    }

The weekly digest is configured in PPAC per environment (or tenant-wide). Treat it as your lightweight detective control: it surfaces the apps that suddenly got shared with hundreds of people, which is your early warning for a shadow business-critical app that needs to be brought into ALM.

7. Deploying the Center of Excellence starter kit

The CoE Starter Kit is Microsoft’s open-source solution that gives you tenant-wide inventory: every environment, app, flow, connector, connection, and maker, synced into Dataverse and surfaced through a Power BI dashboard and management apps. It is the difference between guessing what is in your tenant and querying it. Install it into a dedicated, Managed, production environment with a Dataverse database — never the Default environment.

# 1. Create a dedicated CoE environment (Production + Dataverse, Managed)
pac admin create --name "Contoso - CoE" --region unitedstates \
  --type Production --currency USD --language 1033

# 2. Authenticate to it
pac auth create --name coe --environment "Contoso - CoE"

Then, in the maker portal for that environment:

  1. Create the connections the kit needs (the inventory uses the Power Platform for Admins, Dataverse, Office 365 Users, and Outlook connectors). Own these flows with a dedicated service account holding the Power Platform Administrator role so inventory does not break when an individual leaves.
  2. Import the CoE Starter Kit core managed solution (from aka.ms/CoEStarterKitDownload), populating its environment variables (admin email, admin environment name, tenant ID) during import.
  3. Turn on the inventory flows (the modern, Dataverse-based “Sync Template” cloud flows) that crawl the tenant and write objects into Dataverse on a schedule.
  4. Connect the Production CoE Power BI template to your environment’s Dataverse URL and publish the dashboard.

Run the inventory under app credentials and least privilege. The recommended pattern is a dedicated identity with the Power Platform Administrator role for the admin connection; do not run tenant inventory under a named human account. Also: the CoE Starter Kit is a Microsoft-supported sample, not a product SLA — pin a version, test upgrades in a sandbox, and read the release notes before each upgrade because the inventory flows change.

Once populated, the kit’s Dataverse tables (admin_App, admin_Flow, admin_Environment, admin_Connector, admin_Maker) become the source of truth you report and alert from.

8. Monitoring with admin activity logs and admin analytics

Two telemetry surfaces close the loop.

Power Platform admin activity logging flows into the Microsoft Purview / Microsoft 365 audit log. Power Apps, Power Automate, connectors, and DLP changes emit audit records you query in the Purview portal or with Security & Compliance PowerShell — this is where you catch who changed a DLP policy or who created an environment.

# Search the unified audit log for Power Platform DLP + environment events
Connect-IPPSSession   # Security & Compliance PowerShell

Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) `
    -RecordType PowerPlatformAdministratorActivity `
    -ResultSize 5000 |
    Select-Object CreationDate, UserIds, Operations |
    Sort-Object CreationDate -Descending

For Dataverse data-plane activity (record reads/writes inside an environment), enable Dataverse auditing per environment. For security analytics, route Power Platform activity into Microsoft Sentinel and write KQL detections:

// Sentinel: DLP policy changes in the last 24h
PowerPlatformAdminActivity
| where TimeGenerated > ago(24h)
| where Operation has_any ("DlpPolicy", "TenantIsolation")
| project TimeGenerated, UserId, Operation, EnvironmentName=tostring(Properties.EnvironmentName)
| sort by TimeGenerated desc

Power Platform admin analytics in PPAC (the Analytics blades for Power Apps, Power Automate, and Dataverse) give you adoption and reliability trends — active makers, app launches, flow run success rates, API and storage consumption. Use these to right-size capacity and to find environments whose usage no longer justifies their premium footprint.

Verify

Confirm each control actually took effect — do not trust the wizard.

# 1. Environment creation is locked to admins (read tenant settings via API/PPAC)
Get-AdminPowerAppEnvironment | Measure-Object   # baseline count; create as a non-admin should fail

# 2. DLP policy exists and classifies the right connectors
Get-DlpPolicy | Select-Object DisplayName, PolicyName, EnvironmentType
Get-DlpPolicy -PolicyName $policy.PolicyName |
    Select-Object -ExpandProperty connectorGroups

# 3. Managed Environment is enabled and sharing is capped
Get-AdminPowerAppEnvironment -EnvironmentName <env-guid> |
    Select-Object DisplayName, @{ n="Managed"; e={ $_.Internal.properties.governanceConfiguration.protectionLevel } }

Then do the empirical tests that prove the boundary holds:

Enterprise scenario

A global manufacturer’s platform team inherited a tenant where the Default environment held 4,000+ personal flows, dozens of which moved finance and HR data through consumer connectors a maker had quietly authenticated against personal accounts. Security flagged it after a leaver’s personal OneDrive was still receiving an automated export of an internal report. The constraint: they could not “big bang” a blocking DLP policy across the whole tenant overnight — thousands of live, undocumented flows would break simultaneously and bury the help desk.

The fix was a phased rollout driven by inventory. They deployed the CoE Starter Kit first to get an authoritative list of every flow, its connectors, and its owner. They then created a tenant-wide DLP baseline that classified the corporate stack as Business and set the default for new connectors to Non-Business, while temporarily leaving the worst offenders only flagged via the inventory rather than blocked. In parallel they turned on tenant isolation with default Block and an allowlist of exactly two partner tenants — which immediately killed the cross-tenant exfiltration path without touching the in-tenant flows. Finally, they Blocked the consumer connectors in the tenant DLP only after the CoE digest showed owners had migrated the ~30 affected flows into a sanctioned, Managed production environment.

The mechanism that made the cutover safe was scoping the strict block to specific environments first, then promoting it tenant-wide:

# Phase 1: strict block scoped to the new sanctioned PROD env only
New-DlpPolicy -DisplayName "Sanctioned PROD - Strict" `
    -EnvironmentType "OnlyEnvironments" `
    -Environments @(@{ name = "<sanctioned-prod-guid>" })

# Phase 2 (after migration confirmed in CoE digest): tighten the tenant baseline
Set-DlpPolicy -PolicyName $tenantBaseline.PolicyName -UpdatedPolicy @{
    displayName = "Contoso Tenant DLP - Baseline"
    defaultConnectorsClassification = "Blocked"   # new connectors blocked by default
    connectorGroups = $hardenedGroups
}

The lesson: inventory before enforcement, boundary controls (tenant isolation) before in-tenant controls (DLP blocking), and scope-then-promote so nothing breaks faster than owners can react.

Governance checklist

Power PlatformGovernanceDLPEnvironmentsCoE

Comments

Keep Reading