Get-FlowsByPersonConnection
This PowerShell script is built for a specific offboarding question: "this person is leaving, what will break?" It searches every Power Automate flow, across one or all environments in a tenant, for two ways a departing person puts a flow at risk — they own the flow, or a connector action inside the flow authenticates using their credentials, regardless of who owns the flow itself. The connection-owner case is the more dangerous one, since the flow silently breaks the moment the account is disabled, even if someone else is listed as the flow's owner.
Purpose
- Enumerates every connection in the target environment(s) and every flow's connection references, joining them by connection ID to find where a specific person's credentials are actually being used to authenticate a flow
- Flags flows the target person owns outright, since ownership usually needs reassigning before the account is disabled or the flow becomes orphaned
- Flags flows where the target person owns a connection a flow depends on, even if someone else owns the flow — this is the case that breaks silently on offboarding
- Can scope the search to a single environment or run across every environment in the tenant
- Exports a CSV of every matching flow, with the match reason, connector, and whether the flow is currently enabled, plus a console breakdown split by match reason
Scope
- Matches on Entra Object ID (GUID), not UPN or email —
Get-AdminFlowandGet-AdminPowerAppConnectionboth record ownership by Object ID, so the script does too, even though the parameter is still named-TargetUpnfor backwards compatibility - This is a targeted offboarding check for one person, not a full tenant governance audit — it doesn't flag service-account patterns or general ownership sprawl
- Some flows fail to resolve connection references entirely; those are skipped silently for the connection check, but the flow-ownership check still applies to them
- Searching every environment in a tenant is slower than scoping to one — use
-EnvironmentNamewhen the person's flows are known to live in a specific environment
Prerequisites
- Microsoft.PowerApps.Administration.PowerShell and Microsoft.PowerApps.PowerShell modules
- Power Platform Administrator or Global Administrator role
- Windows PowerShell 5.1 —
Microsoft.PowerApps.Administration.PowerShellis not fully compatible with PowerShell 7/pwsh; run frompowershell.exeif you hit aSystem.Web.UI.WebResourceAttributeload error - The target person's Entra Object ID, found at entra.microsoft.com → Users → search their name → Object ID field on their profile page
PowerShell Script
<#
.SYNOPSIS
Finds every Power Automate flow, across all (or one) environment, where a
specific person either owns the flow or owns a connection the flow uses.
Built for offboarding checks — e.g. "this person is leaving, what will
break?" — rather than a full tenant governance audit.
.DESCRIPTION
Reuses the same flow-to-connection join logic as the full ownership
audit script, but scoped to a single target person instead of flagging
everyone who isn't a service account. Two ways a person shows up as a
risk when they leave:
1. FLOW OWNER - they created the flow. Ownership can usually be
reassigned without breaking the flow, but it needs doing before
their account is disabled or the flow becomes orphaned.
2. CONNECTION OWNER - a connector action inside the flow authenticates
using THEIR credentials, regardless of who owns the flow itself.
This is the more dangerous case: the flow silently breaks the
moment their account is disabled, even if someone else "owns" it.
.PARAMETER TargetUpn
Entra Object ID (GUID) of the person leaving. Get-AdminFlow and
Get-AdminPowerAppConnection record ownership by Object ID, not by
email/UPN, so that's what this script matches against. Look the GUID up
via entra.microsoft.com -> Users -> [person] -> Object ID field. The
parameter is still named TargetUpn for backwards compatibility with
earlier calls to this script - pass the GUID into it, not an email
address.
.PARAMETER EnvironmentName
Optional. GUID of a single environment to search. Omit to search every
environment in the tenant (slower, but complete).
.PARAMETER OutputFolder
Where to write the CSV. Defaults to current directory.
.EXAMPLE
.\Get-FlowsByPersonConnection.ps1 -TargetUpn "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
.EXAMPLE
.\Get-FlowsByPersonConnection.ps1 -TargetUpn "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -EnvironmentName "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
.NOTES
Requires: Microsoft.PowerApps.Administration.PowerShell, Microsoft.PowerApps.PowerShell
Requires: Power Platform Administrator or Global Administrator role
Requires: Windows PowerShell 5.1 (Microsoft.PowerApps.Administration.PowerShell
is not fully compatible with PowerShell 7 / pwsh - if you hit a
"Could not load type 'System.Web.UI.WebResourceAttribute'" error, run
this from powershell.exe rather than pwsh)
To find someone's Object ID: entra.microsoft.com -> Users -> search their
name -> Object ID field on their profile page (next to User principal name)
Author: Cameron Griffiths
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$TargetUpn = "",
[Parameter(Mandatory = $false)]
[string]$EnvironmentName = "",
[Parameter(Mandatory = $false)]
[string]$OutputFolder = "."
)
# --- Modules ---
$requiredModules = @("Microsoft.PowerApps.Administration.PowerShell", "Microsoft.PowerApps.PowerShell")
foreach ($mod in $requiredModules) {
if (-not (Get-Module -ListAvailable -Name $mod)) {
Write-Host "Installing $mod ..." -ForegroundColor Cyan
Install-Module -Name $mod -Scope CurrentUser -Force -AllowClobber
}
}
# --- Auth ---
Write-Host "Signing in to Power Platform admin APIs ..." -ForegroundColor Cyan
Add-PowerAppsAccount
# --- Resolve environments in scope ---
if ($EnvironmentName) {
$envs = Get-AdminPowerAppEnvironment -EnvironmentName $EnvironmentName
if (-not $envs) {
Write-Error "Environment '$EnvironmentName' not found or not accessible."
return
}
}
else {
Write-Host "Enumerating all environments in the tenant ..." -ForegroundColor Cyan
$envs = Get-AdminPowerAppEnvironment
}
Write-Host "Searching $($envs.Count) environment(s) for '$TargetUpn' ..." -ForegroundColor Cyan
$results = New-Object System.Collections.Generic.List[Object]
$envCounter = 0
foreach ($env in $envs) {
$envCounter++
Write-Host "`n[$envCounter/$($envs.Count)] Environment: $($env.DisplayName)" -ForegroundColor Yellow
# --- Connections in this environment, keyed by connection ID ---
$connLookup = @{}
try {
$conns = Get-AdminPowerAppConnection -EnvironmentName $env.EnvironmentName -ErrorAction Stop
foreach ($c in $conns) {
$connLookup[$c.ConnectionName] = $c
}
Write-Host " $($conns.Count) connection(s) in this environment." -ForegroundColor Gray
$personConns = $conns | Where-Object { $_.CreatedBy.userId -eq $TargetUpn }
if ($personConns.Count -gt 0) {
Write-Host " -> $($personConns.Count) connection(s) owned by $TargetUpn" -ForegroundColor Magenta
}
}
catch {
Write-Host " Could not enumerate connections: $($_.Exception.Message)" -ForegroundColor Red
continue
}
# --- Flows in this environment ---
try {
$flows = Get-AdminFlow -EnvironmentName $env.EnvironmentName -ErrorAction Stop
Write-Host " $($flows.Count) flow(s) in this environment." -ForegroundColor Gray
}
catch {
Write-Host " Could not enumerate flows: $($_.Exception.Message)" -ForegroundColor Red
continue
}
$flowCounter = 0
foreach ($flow in $flows) {
$flowCounter++
Write-Progress -Activity "Scanning flows in $($env.DisplayName)" `
-Status $flow.DisplayName `
-PercentComplete (($flowCounter / [Math]::Max($flows.Count,1)) * 100)
$isFlowOwner = $flow.CreatedBy.userId -eq $TargetUpn
# Pull connection references for this flow
$connectionRefs = $null
try {
$connectionRefs = Get-AdminFlow -EnvironmentName $env.EnvironmentName `
-FlowName $flow.FlowName `
-ReturnConnectionReferences -ErrorAction Stop
}
catch {
# Some flows fail to resolve connection references - skip silently, ownership check still applies
}
$refs = $connectionRefs.Internal.properties.connectionReferences
$matchedOnConnection = $false
if ($refs -and $refs.PSObject.Properties.Count -gt 0) {
foreach ($refProp in $refs.PSObject.Properties) {
$ref = $refProp.Value
$connId = $ref.connectionName
$connectorName = $ref.id -replace ".*/providers/Microsoft.PowerApps/apis/", ""
$matchedConn = $connLookup[$connId]
if ($matchedConn -and $matchedConn.CreatedBy.userId -eq $TargetUpn) {
$matchedOnConnection = $true
$results.Add([PSCustomObject]@{
EnvironmentName = $env.DisplayName
FlowName = $flow.DisplayName
FlowId = $flow.FlowName
FlowOwner = $flow.CreatedBy.userId
MatchReason = "Connection owner"
ConnectorName = $connectorName
ConnectionId = $connId
FlowEnabled = $flow.Enabled
})
}
}
}
# If they own the flow but weren't already captured via a connection match, add a row for that too
if ($isFlowOwner -and -not $matchedOnConnection) {
$results.Add([PSCustomObject]@{
EnvironmentName = $env.DisplayName
FlowName = $flow.DisplayName
FlowId = $flow.FlowName
FlowOwner = $flow.CreatedBy.userId
MatchReason = "Flow owner"
ConnectorName = ""
ConnectionId = ""
FlowEnabled = $flow.Enabled
})
}
}
Write-Progress -Activity "Scanning flows in $($env.DisplayName)" -Completed
}
# --- Output ---
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$safeUpn = ($TargetUpn -replace "[^a-zA-Z0-9]", "_")
$outPath = Join-Path $OutputFolder "FlowsFor_${safeUpn}_$timestamp.csv"
$results | Sort-Object EnvironmentName, FlowName, MatchReason | Export-Csv -Path $outPath -NoTypeInformation -Encoding UTF8
Write-Host "`n============================================" -ForegroundColor Green
Write-Host "Search complete for: $TargetUpn" -ForegroundColor Green
Write-Host "Report: $outPath" -ForegroundColor Green
Write-Host "Total matching rows: $($results.Count)" -ForegroundColor Green
Write-Host "============================================`n" -ForegroundColor Green
if ($results.Count -gt 0) {
$byReason = $results | Group-Object MatchReason | Select-Object Name, Count
Write-Host "Breakdown:" -ForegroundColor Yellow
$byReason | Format-Table -AutoSize
Write-Host "`nFlows where this person owns the CONNECTION (highest risk - breaks on offboarding regardless of flow owner):" -ForegroundColor Red
$results | Where-Object { $_.MatchReason -eq "Connection owner" } |
Select-Object EnvironmentName, FlowName, ConnectorName, FlowEnabled |
Format-Table -AutoSize
}
else {
Write-Host "No flows found where '$TargetUpn' is the flow owner or a connection owner." -ForegroundColor Green
}
Usage Notes
-TargetUpnis required and takes the departing person's Entra Object ID (GUID) — despite the parameter name, this is not an email address or UPN-EnvironmentNameis optional and scopes the search to a single environment's GUID; omit it to search every environment in the tenant, which is slower but complete-OutputFolderdefaults to the current directory; the CSV is namedFlowsFor_<sanitised-id>_<timestamp>.csv- Run from
powershell.exe(Windows PowerShell 5.1), notpwsh— the Power Apps admin module isn't fully PowerShell 7 compatible - Rows with
MatchReason = "Connection owner"are the highest-risk group — the flow will break the moment the account is disabled, regardless of who is listed as the flow's owner - Rows with
MatchReason = "Flow owner"need ownership reassigned before the account is disabled, but reassignment alone won't break the flow's connections - Flows whose connection references fail to resolve are skipped for the connection check only — the flow-ownership check still runs against them
Related
- Get-FlowConnectionOwnershipAudit — reuses the same flow-to-connection join logic, but sweeps the whole tenant against a nominated service-account baseline instead of one departing person