public/get-PowerPlatformPermissions.ps1

Function get-PowerPlatformPermissions{
    <#
        Author = "Jos Lieben (jos@lieben.nu)"
        CompanyName = "Lieben Consultancy"
        Copyright = "https://www.lieben.nu/liebensraum/commercial-use/"
    #>
        
    Param(
        [Boolean]$isParallel=$False
    )

    if($global:octo.userConfig.authMode -eq "Delegated"){
        Write-Error "You can only scan Power Platform permissions when using ServicePrincipal or ManagedIdentity authentication mode" -ErrorAction Continue
        return $Null
    }

    $global:PowerPlatformPermissions = @{}
    New-StatisticsObject -category "PowerPlatform" -subject "Securables"

    $activity = "Scanning Power Platform"
    Write-Progress -Id 1 -Activity $activity -Status "Starting scan" -PercentComplete 0
    Write-LogMessage -message "Starting Power Platform scan with Environments..." -level 4
    try{
        $environments = New-GraphQuery -Uri "$($global:octo.babUrl)/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?api-version=2020-10-01&`$expand=properties" -Method GET -resource $global:octo.babUrl
    }catch{
        Write-Error "You have not (yet) configured the correct permissions for the Power Platform, aborting scan. See https://www.lieben.nu/liebensraum/2025/05/scanning-the-power-platform/ for instructions!" -ErrorAction Continue
        return $Null
    }

    foreach($environment in $environments){
        Update-StatisticsObject -category "PowerPlatform" -subject "Securables"
        Write-LogMessage -message "Scanning environment $($environment.name)...." -level 4
        Write-Progress -Id 1 -Activity $activity -Status "Scanning environment $($environment.name)...." -PercentComplete 0
        $environmentId = $environment.name            
        $flows = New-GraphQuery -Uri "$($global:octo.flowUrl)/providers/Microsoft.ProcessSimple/scopes/admin/environments/$environmentId/v2/flows?api-version=2016-11-01&`$select=permissions" -Method GET -resource $global:octo.ppResource
        Write-LogMessage -message "Got $($flows.Count) Flows..." -level 4
        for($f = 0; $f -lt $flows.Count; $f++){
            Update-StatisticsObject -category "PowerPlatform" -subject "Securables"
            $flow = $flows[$f]
            $percentComplete = try{$f / $flows.Count * 100} catch{0}
            Write-Progress -Id 2 -Activity "Scanning flows" -Status "Checking $($flow.properties.displayName) ($f of $($flows.Count))..." -PercentComplete $percentComplete
            
            try{
                $flowUsers = $Null; $flowUsers = New-GraphQuery -ignoreableErrors @("409") -Uri "$($global:octo.flowUrl)/providers/Microsoft.ProcessSimple/scopes/admin/environments/$environmentId/flows/$($flow.name)/permissions?api-version=2016-11-01" -Method GET -resource $global:octo.ppResource
            }catch{
                Write-LogMessage $_ -level 3
                continue
            }
            $deletedUserIds = @();
            for($u = 0; $u -lt $flowUsers.Count; $u++){
                if(!$flowUsers[$u].properties.principal){continue}
                if($flowUsers[$u].properties.principal.type -eq "Tenant"){
                    Write-LogMessage -message "Flow user $($flowUsers[$u].properties.permissionType) $($flowUsers[$u].properties.principal.id) is a tenant"
                    continue
                }
                $aadObj = get-aadObject -id $flowUsers[$u].properties.principal.id
                if(!$aadObj -and $deletedUserIds -notcontains $flowUsers[$u].properties.principal.id){
                    $deletedUserIds += $flowUsers[$u].properties.principal.id
                }
            }

            for($u = 0; $u -lt $flowUsers.Count; $u++){
                if(!$flowUsers[$u].properties.principal){continue}
                if($deletedUserIds -contains $flowUsers[$u].properties.principal.id){
                    Write-LogMessage -message "Flow user $($flowUsers[$u].properties.permissionType) $($flowUsers[$u].properties.principal.id) does not exist in Entra, skipping..." -level 5
                    continue
                }
                $flowUser = $flowUsers[$u]
                if($flowUser.properties.permissionType -ne "Principal"){
                    Write-LogMessage -message "Flow user $($flowUser.properties.permissionType) is not a principal, skipping..." -level 3
                    continue
                }

                $permissionSplat = @{
                    targetPath = "/$environmentId/flows/$($flow.name)/$($flow.properties.displayName)"
                    targetType = "PowerPlatformFlow"
                    targetId = $flow.name
                    principalEntraId = if($flowUser.properties.principal.type -eq "Tenant"){$flowUser.properties.principal.tenantId}else{$flowUser.properties.principal.id}
                    principalSysId = $flowUser.name
                    principalSysName = $flowUser.name
                    principalType = $flowUser.properties.principal.type
                    principalRole = $flowUser.properties.roleName
                    tenure = "Permanent"
                } 
                New-PowerPermissionEntry @permissionSplat
            }
        }
        Write-Progress -Id 2 -Activity "Scanning flows" -Completed

        $powerApps = New-GraphQuery -Uri "$($global:octo.pappsUrl)/providers/Microsoft.PowerApps/scopes/admin/environments/$environmentId/apps?api-version=2016-11-01&`$expand=permissions" -Method GET -resource $global:octo.ppResource
        Write-LogMessage -message "Got $($powerApps.Count) PowerApps..." -level 4
        for($p = 0; $p -lt $powerApps.Count; $p++){
            Update-StatisticsObject -category "PowerPlatform" -subject "Securables"
            $powerApp = $powerApps[$p]
            $percentComplete = try{$p / $powerApps.Count * 100} catch{0}
            Write-Progress -Id 2 -Activity "Scanning powerApps" -Status "Checking $($powerApp.properties.displayName) ($p of $($powerApps.Count))..." -PercentComplete $percentComplete
            try{
                $powerAppUsers = $Null; $powerAppUsers = New-GraphQuery -ignoreableErrors @("409") -Uri "$($global:octo.pappsUrl)/providers/Microsoft.PowerApps/scopes/admin/environments/$environmentId/apps/$($powerApp.name)/permissions?api-version=2016-11-01" -Method GET -resource $global:octo.ppResource
            }catch{
                Write-LogMessage $_ -level 3
                continue
            }
            $deletedUserIds = @();
            for($u = 0; $u -lt $powerAppUsers.Count; $u++){
                if(!$powerAppUsers[$u].properties.principal){continue}
                if($powerAppUsers[$u].properties.principal.type -eq "Tenant"){
                    Write-LogMessage -message "PowerApps user $($powerAppUsers[$u].properties.permissionType) $($powerAppUsers[$u].properties.principal.id) is a tenant"
                    continue
                }                    
                $aadObj = get-aadObject -id $powerAppUsers[$u].properties.principal.id
                if(!$aadObj -and $deletedUserIds -notcontains $powerAppUsers[$u].properties.principal.id){
                    $deletedUserIds += $powerAppUsers[$u].properties.principal.id
                }
            }

            for($u = 0; $u -lt $powerAppUsers.Count; $u++){
                if(!$powerAppUsers[$u].properties.principal){continue}
                if($deletedUserIds -contains $powerAppUsers[$u].properties.principal.id){
                    Write-LogMessage -message "PowerApps user $($powerAppUsers[$u].properties.permissionType) $($powerAppUsers[$u].properties.principal.id) does not exist in Entra, skipping..." -level 5
                    continue
                }
                $powerAppUser = $powerAppUsers[$u]
                $permissionSplat = @{
                    targetPath = "/$environmentId/powerapps/$($powerApp.name)/$($powerApp.properties.displayName)"
                    targetType = "PowerPlatformApp"
                    targetId = $powerApp.name
                    principalEntraId = if($powerAppUser.properties.principal.type -eq "Tenant"){$powerAppUser.properties.principal.tenantId}else{$powerAppUser.properties.principal.id}
                    principalSysId = $powerAppUser.properties.principal.email
                    principalSysName = $powerAppUser.properties.principal.displayName
                    principalType = $powerAppUser.properties.principal.type
                    principalRole = $powerAppUser.properties.roleName
                    tenure = "Permanent"
                    createdDateTime = $powerAppUser.properties.createdOn
                } 
                New-PowerPermissionEntry @permissionSplat
            }
        }
        Write-Progress -Id 2 -Activity "Scanning powerApps" -Completed
    }

    Write-Progress -Id 1 -Completed -Activity "Scanning Azure"  
    
    Stop-statisticsObject -category "PowerPlatform" -subject "Securables"

    $permissionRows = foreach($row in $global:PowerPlatformPermissions.Keys){
        foreach($permission in $global:PowerPlatformPermissions.$row){
            [PSCustomObject]@{
                "targetPath" = $row
                "targetType" = $permission.targetType
                "targetId" = $permission.targetId
                "principalEntraId" = $permission.principalEntraId
                "principalSysId" = $permission.principalSysId
                "principalSysName" = $permission.principalSysName
                "principalType" = $permission.principalType
                "principalRole" = $permission.principalRole
                "through" = $permission.through
                "parentId" = $permission.parentId
                "accessType" = $permission.accessType
                "tenure" = $permission.tenure
                "startDateTime" = $permission.startDateTime
                "endDateTime" = $permission.endDateTime
                "createdDateTime" = $permission.createdDateTime
                "modifiedDateTime" = $permission.modifiedDateTime
            }
        }
    }
    Write-Progress -Id 2 -Activity $activity -Completed
    Add-ToReportQueue -permissions $permissionRows -category "PowerPlatform"
    Remove-Variable -Name PowerPlatformPermissions -Scope Global -Force -Confirm:$False
    if(!$isParallel){
        Write-Report         
    }else{
        [System.GC]::GetTotalMemory($true) | out-null         
    }        
}