How to export all premium Power Apps and Power Automate Flows using PowerShell

Hand turning a knob over grey background and selecting the automatic mode. Manufacturing process automation or automatic testing concept. Composite image between a hand photography and a 3D background.

Managing Power Apps and Power Automate in an enterprise tenant with thousands or even tens of thousands of users can be a bit of a challenge. Especially with the current licensing model with premium connectors requiring additional licenses that require us to analyse how premium connectors are used across our tenant to stay compliant.

The Microsoft Power Platform Center of Excellence Starter Kit will get you parts of the way but also often fall short, simply not gathering enough information.

Recently I wanted a list of all the apps and flows in a specific environment that use any of the premium connectors. I also want to include information about all owners and users that are able to edit these apps and flows and not only the maker.

Below you will find a PowerShell script that will export this information to a number of different CSV files that then can be parsed.

The output will be two files for Power Apps apps and two files for Power Automate flows. One file with all the apps/flows and their premium connectors and another one with all the owners and users that can manage these apps and flows together with all the apps and flows using premium connectors.

Import-Module -Name Microsoft.PowerApps.Administration.PowerShell
Import-Module -Name Microsoft.PowerApps.PowerShell
Import-Module AzureADPreview

$folderPath = 'C:\Temp\Exports'
$environment = '[ID]'

Add-PowerAppsAccount

Connect-AzureAD

###################################################
# Get Premium Apps

$apps = @(Get-AdminPowerApp -EnvironmentName $environment)

Write-host ('Number of apps: ' + $apps.Count)

$premiumApps = @()
$premiumAppsOwners = @()
$totalApps = $apps.Count

foreach ($app in $apps) {
    Write-host ('App: ' + $app.AppName )

    $premiumConnectors = ''
    $appOwners = ''

    # Go through the connections and look for premium connectors.
    foreach($conRef in $app.Internal.properties.connectionReferences)
    {
        foreach($connection in $conRef)
        {
            foreach ($connId in ($connection | Get-Member -MemberType NoteProperty).Name) 
            {
                $connDetails = $($connection.$connId)

                if ($connDetails.apiTier -eq 'Premium') {
                    $premiumConnectors += (&{if ($premiumConnectors) { ', ' }}) + $connDetails.displayName 
                }
            }
        }
    }

    # Get all the users who can edit the power app.
    if ($premiumConnectors.length -gt 0) {
        $roleAssignment = Get-AdminPowerAppRoleAssignment -EnvironmentName $app.EnvironmentName -AppName $app.AppName

        $owners = $roleAssignment | Where-Object { $_.RoleType -eq 'CanEdit' -or $_.RoleType -eq 'Owner' }

        foreach ($asgmt in $owners) {
    
            if ($asgmt.PrincipalEmail.length -gt 0) {

                $appOwners += (&{if($appOwners) { ', ' }}) + $asgmt.PrincipalEmail 

                $appOwner = $premiumAppsOwners | Where-Object { $_.Mail -eq $asgmt.PrincipalEmail }
                
                if ($appOwner) {
                    $appOwner.Apps += ', ' + $app.DisplayName + '(' + $premiumConnectors + ')'
                } else {
                    $appOwnerRow = @{
                        Mail = $asgmt.PrincipalEmail
                        Apps = $app.DisplayName + '(' + $premiumConnectors + ')'
                    }
                    $premiumAppsOwners += $(new-object psobject -Property $appOwnerRow)
                }
            }


        }

        $row = @{
            ResourceType = 'PowerApp'
            DisplayName = $app.displayName
            Name = $app.appName
            EnvironmentName = $app.environmentName
            CreatedByObjectId = $app.owner.id
            CreatedByEmail = $app.owner.email
            Owners = $appOwners
            PremiumConnectors = $premiumConnectors
        }

        $premiumApps += $(new-object psobject -Property $row)

        Write-host 'Premium connectors found.'
    }
}

$premiumApps | Export-Csv -Path ($folderPath + '\PremiumApps2.csv') -Delimiter ';' -NoTypeInformation
$premiumAppsOwners | Export-Csv -Path ($folderPath + '\PremiumAppOwners2.csv') -Delimiter ';' -NoTypeInformation


###################################################
# Get Premium Flows

$flows = @(Get-AdminFlow -EnvironmentName $environment)

Write-host ('Number of flows: ' + $flows.Count)

$premiumFlows = @()
$premiumFlowOwners = @()

$totalFlows = $flows.Count

foreach ($flow in $flows) {

    Write-host ('Flow: ' + $flow.DisplayName )

    # Load the Flow details. Without this we don't get the proper and detailed connection details. 
    $flowDetails = $flow | Get-AdminFlow

    $premiumConnectors = ''
    $flowOwners = ''

    # Look for premium connectors.
    foreach($conRef in $flowDetails.Internal.properties.connectionReferences)
    {
        foreach($connection in $conRef)
        {
            foreach ($connId in ($connection | Get-Member -MemberType NoteProperty).Name) 
            {
                $connDetails = $($connection.$connId)

                $connector = $connectors | Where-Object { $_.DisplayName -eq $connDetails.displayName }

                if ($connDetails.tier -eq 'Premium') {
                    $premiumConnectors += (&{if ($premiumConnectors) { ', ' }}) + $connDetails.displayName
                }
            }
        }
    }

    # Get all the users who can edit the flow.
    if ($premiumConnectors.Length -gt 0) {
        $flowOwnerRole = @(Get-AdminFlowOwnerRole -FlowName $flow.FlowName -EnvironmentName $flow.EnvironmentName)

        $owners = $flowOwnerRole | Where-Object { $_.RoleType -eq 'CanEdit' -or $_.RoleType -eq 'Owner' }

        foreach ($role in $owners) {
 
            $user = Get-AzureADUser -ObjectId $role.PrincipalObjectId

            if ($user.Mail.length -gt 0) {

                $flowOwners += (&{if($flowOwners) { ', ' }}) + $user.Mail 
        

                $flowOwner = $premiumFlowOwners | Where-Object { $_.Mail -eq $user.Mail }
                
                if ($flowOwner) {
                    $flowOwner.Flows += ', ' + $flow.DisplayName + '(' + $premiumConnectors + ')'
                } else {
                    $flowOwnerRow = @{
                        Mail = $user.Mail
                        Flows = $flow.DisplayName + '(' + $premiumConnectors + ')'
                    }
                    $premiumFlowOwners += $(new-object psobject -Property $flowOwnerRow)
                }
            }

        }

        $row = @{
            ResourceType = 'Flow'
            DisplayName = $flow.DisplayName
            Id = $flow.FlowName
            EnvironmentName = $flow.environmentName
            CreatedByObjectId = $flow.CreatedBy.objectId
            Owners = $flowOwners
            PremiumConnectors = $premiumConnectors
        }

        $premiumFlows += $(new-object psobject -Property $row)

        Write-host 'Premium connectors found.'
    }
}

$premiumFlows | Export-Csv -Path ($folderPath + '\PremiumFlows2.csv') -Delimiter ';' -NoTypeInformation
$premiumFlowOwners | Export-Csv -Path ($folderPath + '\PremiumFlowOwners2.csv') -Delimiter ';' -NoTypeInformation

Please note that the script looks for the tier information (premium or standard) stored in the connection references in the save app or flow. Analysing this tier information in the saved apps or flows will tell you if these connections were premium or standard at the time the app or flow was saved. It’s not that often that Microsoft change the tier of an existing connector, but it happens. If you have a lot of old apps and flows you may want to consider looking for updated tier information in a current list with all the connectors instead of the information stored in each app or flow.

Got a lot of inspiration from Denise Moran and her excellent article PowerShell scripts to discover and manage specific features in the Power Platform.