How I Deployed Flows to Every Developer Environment in My Tenant
If you manage a Power Platform tenant with multiple developer environments, you know the pain. Environments get created for testing, POCs, and hackathons—then forgotten. Nobody knows what's alive, what's orphaned, or what's silently eating up capacity.
I wanted a way to prove that every developer environment is reachable and functional. Not with a fancy monitoring solution. Not with a third-party tool. Just a dead-simple scheduled Power Automate flow that runs daily at 8 AM, does absolutely nothing meaningful, and terminates with success.
If it runs, the environment is alive. If it doesn't, something's wrong. That's it.
One script. One login. Deploy a heartbeat flow to every developer environment in the tenant, always create a fresh copy with a UTC timestamp, and export a clean report with environment names, URLs, owners, and flow links.
The Approach
Here's what the script needed to do:
- Authenticate once — no multiple login prompts, no module conflicts
- Discover every environment in the tenant via the BAP admin API
- Filter to Developer environments only
- Deploy a 2-action flow (Recurrence → Terminate Success) to each
- Always deploy fresh — even if a flow already exists, create a new one with a UTC timestamp
- Export a report with environment name, URL, owner, flow URL, and deployment status
Challenge #1: The Authentication Nightmare
This was the hardest part. The Power Platform PowerShell modules have a messy auth story:
Microsoft.PowerApps.Administration.PowerShellhas its own internal auth that doesn't expose tokens- The Flow REST API needs a Bearer token for
https://service.flow.microsoft.com/ - Az.Accounts, MSAL.PS, and the admin module all fight over Microsoft.Identity.Client DLL versions
The solution? Skip all PowerShell modules for auth entirely. Use a raw OAuth2 device code flow with Azure PowerShell's well-known client ID. One login prompt, then silently exchange the refresh token for additional resource tokens.
Authenticate once for the Flow API, then use the refresh token to silently get a BAP admin API token. Zero module dependencies for auth. Zero DLL conflicts.
The Device Code Token Function
Silent Token Exchange (No Second Login)
Challenge #2: The Environment Discovery Gotcha
I initially used Get-AdminPowerAppEnvironment and filtered on EnvironmentType. It returned 4 environments but the filter matched zero. Why?
Because the BAP admin API returns the type under environmentSku, not environmentType (which comes back empty).
A debug dump revealed the truth:
Always dump the raw API response structure before writing filters. The Microsoft docs and the actual API response don't always agree.
Challenge #3: The 400 Bad Request Mystery
Even after getting auth right and discovering environments, the flow creation API returned 400 Bad Request on every attempt. Three things were broken:
- PowerShell hashtable → ConvertTo-Json was mangling
$schemaand nested empty objects - Missing required empty fields — the API silently requires
connectionReferences,parameters, andoutputseven when empty - Encoding issues —
Invoke-RestMethodwasn't sending clean UTF-8
The Fix: Raw JSON + UTF-8 Bytes
The combination of raw JSON string + UTF-8 bytes + Invoke-WebRequest (not Invoke-RestMethod) is what finally got the 200 OK across all environments.
The Flow: Beautifully Simple
The deployed flow has exactly two nodes. That's it. That's the whole thing:
Daily @ 8 AM EST
Status: Succeeded
Each flow gets a unique name with a UTC timestamp:
Every run of the script creates a fresh flow. You can see exactly when each one was deployed just by reading the name.
The Results
Three developer environments. Three flows deployed. One login. Zero manual portal clicks. The HTML report opened automatically with clickable links to every environment and every flow.
Lessons Learned
- Don't trust PowerShell module auth for cross-API scenarios. Device code flow with refresh token exchange is more reliable and avoids DLL conflicts entirely.
- Dump the raw API response before filtering. The BAP API uses
environmentSku, notenvironmentType. Documentation and reality diverge. - Use raw JSON strings for complex API bodies.
ConvertTo-Jsonwith nested hashtables and$schemais a recipe for 400 errors. - Always send UTF-8 bytes explicitly.
Invoke-WebRequestwith[System.Text.Encoding]::UTF8.GetBytes()avoids subtle encoding issues. - The Flow API requires empty objects.
connectionReferences,parameters, andoutputsmust be present even when empty, or you get a cryptic 400.
What's Next?
This script is a foundation. Here's where I'm taking it:
- Run history monitoring — A companion script that checks if each heartbeat flow actually ran today. If it didn't, flag the environment as unhealthy.
- Scheduled via Azure Automation — Run as a weekly runbook to keep deploying fresh flows with timestamps, creating a breadcrumb trail of environment health.
- Teams/email alerts — Push the HTML report to a Teams channel or email distribution list automatically.
- Cleanup automation — Auto-delete heartbeat flows older than 30 days to prevent clutter.
The full script is a single .ps1 file with no module dependencies for auth, handles all edge cases I discovered the hard way, and produces both CSV and HTML reports. If you manage a multi-environment Power Platform tenant, give it a spin.
Download https://limewire.com/d/K5VPO#O1vSFIsg8H
Comments
Post a Comment