public/get-AllPBIPermissions.ps1
Function get-AllPBIPermissions{ <# Author = "Jos Lieben (jos@lieben.nu)" CompanyName = "Lieben Consultancy" Copyright = "https://www.lieben.nu/liebensraum/commercial-use/" Parameters: -excludeGroupsAndUsers: exclude group and user memberships from the report, only show role assignments #> Param( [Switch]$skipReportGeneration ) $activity = "Scanning PowerBI" #check if user has a powerbi license or this function will fail if($global:octo.userConfig.authMode -eq "Delegated"){ $powerBIServicePlans = @("PBI_PREMIUM_EM1_ADDON","PBI_PREMIUM_EM2_ADDON","BI_AZURE_P_2_GOV","PBI_PREMIUM_P1_ADDON_GCC","PBI_PREMIUM_P1_ADDON","BI_AZURE_P3","BI_AZURE_P2","BI_AZURE_P1") $hasPowerBI = $False $licenses = New-GraphQuery -Uri "https://graph.microsoft.com/v1.0/users/$($global:octo.currentUser.userPrincipalName)/licenseDetails" -Method GET if($licenses){ foreach($servicePlan in $licenses.servicePlans.servicePlanName){ if($powerBIServicePlans -contains $servicePlan){ $hasPowerBI = $True break } } } if(!$hasPowerBI){ Write-Error "You do not have a PowerBI license, this function requires a PowerBI license assigned to the user you're logged in with unless using a Service Principal" -ErrorAction Continue return $Null } } Write-LogMessage -message "Starting PowerBI scan..." -level 4 New-StatisticsObject -category "PowerBI" -subject "Securables" Write-Progress -Id 1 -PercentComplete 0 -Activity $activity -Status "Retrieving workspaces..." $global:PBIPermissions = @{} try{ $workspaces = New-GraphQuery -Uri "https://api.powerbi.com/v1.0/myorg/admin/groups?`$top=5000" -resource "https://api.fabric.microsoft.com" -method "GET" -maxAttempts 2 }catch{ if($_.Exception.Message -like "*401*"){ Write-Error "You have not (yet) configured the correct permissions in PowerBI, aborting scan of PowerBI. See https://www.lieben.nu/liebensraum/2025/03/allowing-a-service-principal-to-scan-powerbi/ for instructions!" -ErrorAction Continue return $Null }else{ Throw $_ } } $workspaceParts = [math]::ceiling($workspaces.Count / 100) if($workspaceParts -gt 500){ Throw "More than 50000 workspaces detected, this module does not support environments with > 50000 workspaces yet. Submit a feature request." } Write-Progress -Id 1 -PercentComplete 5 -Activity $activity -Status "Submitting $workspaceParts scanjobs for $($workspaces.count) workspaces..." $scanJobs = @() for($i=0;$i -lt $workspaceParts;$i++){ $body = @{"workspaces" = $workspaces.id[($i*100)..($i*100+99)]} | ConvertTo-Json if($i/16 -eq 1){ Write-LogMessage -message "Sleeping for 60 seconds to prevent throttling..." -level 4 Start-Sleep -Seconds 60 } $scanJobs += New-GraphQuery -Uri "https://api.powerbi.com/v1.0/myorg/admin/workspaces/getInfo?datasourceDetails=True&getArtifactUsers=True" -Method POST -Body $body -resource "https://api.fabric.microsoft.com" } if($global:octo.userConfig.authMode -eq "Delegated"){ Write-Progress -Id 1 -PercentComplete 10 -Activity $activity -Status "Retrieving gateways..." $gateways = New-GraphQuery -Uri "https://api.powerbi.com/v2.0/myorg/gatewayclusters?`$expand=permissions&`$skip=0&`$top=5000" -resource "https://api.fabric.microsoft.com" -method "GET" for($g = 0; $g -lt $gateways.count; $g++){ Update-StatisticsObject -category "PowerBI" -subject "Securables" Write-Progress -Id 2 -PercentComplete $(Try{ ($g/$gateways.count)*100 } catch {0}) -Activity "Analyzing gateways..." -Status "$($g+1)/$($gateways.count) $($gateways[$g].id)" foreach($user in $gateways[$g].permissions){ $permissionSplat = @{ targetPath = "/gateways/$($gateways[$g].type)/$($gateways[$g].id)" targetType = "Gateway" targetId = $gateways[$g].id principalRole = $user.role } if($user.principalType -eq "Group"){ $permissionSplat["principalEntraId"] = $user.graphId $permissionSplat["principalEntraUpn"] = "" $permissionSplat["principalSysId"] = $user.graphId $permissionSplat["principalSysName"] = $user.displayName $permissionSplat["principalType"] = "EntraSecurityGroup" $permissionSplat["through"] = "EntraSecurityGroup" New-PBIPermissionEntry @permissionSplat }else{ $userId = $Null; $userId = $user.id.Replace("app-","") if($user.id.startsWith("app-")){ $userMetaData = New-GraphQuery -Uri "https://graph.microsoft.com/v1.0/serviceprincipals(appId='$userId')" -Method GET }else{ try{ $userMetaData = New-GraphQuery -Uri "https://graph.microsoft.com/v1.0/users/$userId" -Method GET -maxAttempts 2 }catch{ Write-LogMessage -level 2 -message "Failed to retrieve user metadata for $($user.id), user was likely deleted, skipping..." continue } } $permissionSplat["principalEntraId"] = $userId $permissionSplat["principalEntraUpn"] = $userMetaData.userPrincipalName $permissionSplat["principalSysId"] = $userMetaData.id $permissionSplat["principalSysName"] = $userMetaData.displayName $permissionSplat["principalType"] = $($user.principalType) New-PBIPermissionEntry @permissionSplat } } } Write-Progress -Id 2 -Completed -Activity "Analyzing gateways..." }else{ Write-LogMessage -level 2 -message "Skipping gateway analysis, this function requires delegated authentication mode" } Write-Progress -Id 1 -PercentComplete 15 -Activity $activity -Status "Waiting for scan jobs to complete..." foreach($scanJob in $scanJobs){ do{ $res = New-GraphQuery -Uri "https://api.powerbi.com/v1.0/myorg/admin/workspaces/scanStatus/$($scanJob.id)" -Method GET -resource "https://api.fabric.microsoft.com" if($res.status -ne "Succeeded"){ Write-LogMessage -message "Scan job $($scanJob.id) status $($res.status), sleeping for 30 seconds..." -level 4 Start-Sleep -Seconds 30 } }until($res.status -eq "Succeeded") Write-LogMessage -message "Scan job $($scanJob.id) completed" -level 4 } Write-Progress -Id 1 -PercentComplete 25 -Activity $activity -Status "Receiving scan job results..." $scanResults = @() foreach($scanJob in $scanJobs){ $scanResults += (New-GraphQuery -Uri "https://api.powerbi.com/v1.0/myorg/admin/workspaces/scanResult/$($scanJob.id)" -Method GET -resource "https://api.fabric.microsoft.com").workspaces } Write-Progress -Id 1 -PercentComplete 45 -Activity $activity -Status "Processing PowerBI securables..." $secureableTypes = @{ "reports" = @{ "Type" = "Report" "UserAccessRightProperty" = "reportUserAccessRight" "CreatedProperty" = "createdDateTime" "ModifiedProperty" = "modifiedDateTime" } "datasets" = @{ "Type" = "Dataset" "UserAccessRightProperty" = "datasetUserAccessRight" "CreatedProperty" = "createdDate" "ModifiedProperty" = "N/A" } "Lakehouse" = @{ "Type" = "Lakehouse" "UserAccessRightProperty" = "artifactUserAccessRight" "CreatedProperty" = "createdDate" "ModifiedProperty" = "lastUpdatedDate" } "warehouses" = @{ "Type" = "Warehouse" "UserAccessRightProperty" = "datamartUserAccessRight" "CreatedProperty" = "N/A" "ModifiedProperty" = "modifiedDateTime" } } for($s=0;$s -lt $scanResults.count; $s++){ Write-Progress -Id 2 -PercentComplete $(Try{ ($s/$scanResults.count)*100 } catch {0}) -Activity "Analyzing securables..." -Status "$($s+1)/$($scanResults.count) $($scanResults[$s].name)" foreach($secureableType in $secureableTypes.Keys){ #$secureableType = "reports" foreach($secureable in $scanResults[$s].$secureableType){ #$secureable = $scanResults[$s].$secureableType[0] Update-StatisticsObject -category "PowerBI" -subject "Securables" $created = $secureableTypes.$secureableType.CreatedProperty -eq "N/A" ? $Null : $secureable.$($secureableTypes.$secureableType.CreatedProperty) $modified = $secureableTypes.$secureableType.ModifiedProperty -eq "N/A" ? $Null : $secureable.$($secureableTypes.$secureableType.ModifiedProperty) foreach($user in $secureable.users){ #$user = $secureable.users[0] $metaData = $Null;$metaData = get-PBIUserMetaData -user $user $permissionSplat = @{ targetPath = "/workspaces/$($scanResults[$s].name)/$secureableType/$($secureable.name)" targetType = $secureableTypes.$secureableType.Type targetId = $secureable.id createdDateTime = $created modifiedDateTime = $modified principalEntraId = $metaData.principalEntraId principalEntraUpn = "" principalSysId = $user.graphId principalSysName = $user.displayName principalType = $metaData.principalType principalRole = $user.$($secureableTypes.$secureableType.UserAccessRightProperty) through = if($metaData.principalType -eq "EntraSecurityGroup"){$metaData.principalType}else{"Direct"} } New-PBIPermissionEntry @permissionSplat } } } } Write-Progress -Id 2 -Completed -Activity "Analyzing securables..." Stop-StatisticsObject -category "PowerBI" -subject "Securables" Write-Progress -Id 1 -PercentComplete 90 -Activity $activity -Status "Writing report..." $permissionRows = foreach($row in $global:PBIPermissions.Keys){ foreach($permission in $global:PBIPermissions.$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" = "Allow" "tenure" = "Permanent" "startDateTime" = $permission.startDateTime "endDateTime" = $permission.endDateTime "createdDateTime" = $permission.createdDateTime "modifiedDateTime" = $permission.modifiedDateTime } } } Add-ToReportQueue -permissions $permissionRows -category "PowerBI" Remove-Variable -Name PBIPermissions -Scope Global -Force -Confirm:$False if(!$skipReportGeneration){ Write-LogMessage -message "Generating report..." -level 4 Write-Report }else{ Reset-ReportQueue } Write-Progress -Id 1 -Completed -Activity $activity } |