FUAM gives me a useful view of my Fabric environment. But when I look at a workspace role assigned to a group, I still have the question that started the review: who are the actual people behind that group?
Fabric-Finance-Contributors is a sensible way to manage access. It is not a list of people. And opening Entra to inspect every group is not my idea of a good monitoring workflow.
So I built FabricPermissions: a PowerShell report for a quick check, and a Fabric notebook for repeatable collection and change tracking.
The missing step in an access review
Fabric Unified Admin Monitoring, or FUAM, combines administrative and operational data in Fabric. Its open lakehouse-based architecture also gives you room to enrich that data yourself.
That is where this fits. Not as a replacement for FUAM, and not as an official FUAM module. It is a separate collector that adds the directory-membership context I want alongside workspace monitoring.
If you have not deployed FUAM yet, my earlier post on deploying FUAM with GitHub Copilot covers the setup and the approvals that should stay with a human.
The difference is easiest to see with a fictional example:
| Workspace | Assigned principal | Role | Person behind the assignment |
|---|---|---|---|
| Finance Analytics | Fabric-Finance-Contributors | Contributor | Alex Example |
| Finance Analytics | Fabric-Finance-Contributors | Contributor | Sam Example |
| Finance Analytics | Taylor Example | Viewer | Taylor Example, directly assigned |
These names are synthetic, not output from a tenant scan.
The group still matters. It tells you how somebody gets access. The person tells you who to discuss that access with.
I took a similar approach in my Purview permissions exporter. Different permission system, same practical problem: the assignment is only the start of the explanation.
Two ways to use it
The repository contains three main files:
| File | Purpose |
|---|---|
get-fabric-workspace-access.ps1 | Collect workspace assignments, expand direct group members, and write HTML and CSV reports |
WorkspaceAccessReport.ipynb | Collect assignments, expand transitive group membership, and merge observed access into a lakehouse table |
setup-service-principal.ps1 | Set up the Graph application identity used by the notebook |
There is an important difference here. The current PowerShell script reads direct group members. The notebook reads transitive members, including users in nested groups. They are not interchangeable if your group structure contains nesting.
The workflow is straightforward:
Fabric workspace role assignments
+ Microsoft Graph group membership
-> people, roles, and source groups
-> HTML / CSV, or a lakehouse Delta table
Neither collector changes workspace permissions or group membership. The notebook writes reporting data. The identity setup script does make Entra changes, so treat that as a separate, approved administrative operation.
1. Start with the local report
For a one-off review, use PowerShell 7 and Azure CLI:
Set-Location D:\
git clone https://github.com/Remc0000/FabricPermissions.git
Set-Location D:\FabricPermissions
az login
.\get-fabric-workspace-access.ps1 -OutputDirectory "D:\FabricAccessReports"
The script uses the Azure CLI session for its API calls and writes timestamped files:
workspace-access-report-<timestamp>.html
workspace-access-report-<timestamp>.csv
Open the HTML report for inspection. Use the CSV when you want to filter, compare, or take the results into another reporting tool.
Before running it, check two separate permission boundaries. The Fabric identity needs permission to read workspace assignments, and the Graph identity needs permission to read group membership. Being able to open a workspace does not automatically give you both.
Microsoft’s workspace role-assignment API documentation requires Member or Admin, not merely Viewer. For delegated calls it also lists Workspace.Read.All or Workspace.ReadWrite.All.
That is stricter than the Viewer prerequisite currently stated in the repository README. Follow the API documentation.
2. Use the notebook for repeated collection
For a pilot, create a dedicated capacity-backed workspace such as blog_fabric_permissions, import WorkspaceAccessReport.ipynb, and attach a lakehouse.
The notebook uses a Fabric token for workspace discovery and role assignments. It obtains its Microsoft Graph token separately through a service principal. Configuring that Graph identity does not automatically configure the Fabric execution identity.
The supplied setup script creates or reuses an application named FabricWorkspaceAccessReport, generates a one-year client secret, and grants Graph application permissions GroupMember.Read.All and User.Read.All with admin consent:
.\setup-service-principal.ps1
Read the script and get administrator approval before running it. It prints the generated secret, and rerunning it resets application credentials and replaces the required-permissions manifest. Do not casually point it at an existing shared application.
The notebook expects SP_CLIENT_ID, SP_CLIENT_SECRET, and SP_TENANT_ID. Retrieve the secret through your approved secret store, such as Azure Key Vault, rather than saving it in a notebook committed to GitHub. Keep console output containing credentials out of transcripts and screenshots.
Run manually before considering a schedule. Check authentication for both APIs, including that the token audience is accepted by the Fabric endpoint in your execution context. The current notebook requests the Power BI token audience; do not assume that successful token acquisition proves the Fabric calls will work.
No new Fabric environment was deployed for this article. These are setup instructions based on the published implementation, not a claim of a fresh end-to-end tenant test.
What the lakehouse gives you
The notebook writes workspace_access_report, including workspace, principal, role, source-group, and snapshot fields. Its change-detection key is:
WorkspaceId | PrincipalId | Role
It preserves a granted_on_dt date for an existing key and sets revoked_on_dt when that key disappears from a later collection.
Once a complete collection has succeeded, inspect active user records through the lakehouse SQL endpoint:
SELECT
WorkspaceName,
PrincipalDisplayName,
Role,
GroupName,
granted_on_dt
FROM workspace_access_report
WHERE revoked_on_dt IS NULL
AND PrincipalType = 'User'
ORDER BY WorkspaceName, Role, PrincipalDisplayName;
The user filter matters because the notebook also retains directly assigned non-user principals. Multiple access paths can produce more than one row for the same person, so row counts are not necessarily distinct-user counts.
For recently detected removals:
SELECT
WorkspaceName,
PrincipalDisplayName,
Role,
revoked_on_dt
FROM workspace_access_report
WHERE revoked_on_dt >= DATEADD(day, -30, CAST(GETUTCDATE() AS date))
AND PrincipalType = 'User'
ORDER BY revoked_on_dt DESC;
That table is a useful input for a custom access-review report alongside FUAM. The repository does not automatically extend FUAM’s semantic model or install a new FUAM report page.
Do not confuse a snapshot with an audit log
The dates record when the collector observed a change, not the exact time an administrator changed access. The first collection is a baseline, not the original grant date.
There is another distinction worth making: the current notebook uses a Delta merge. It updates existing records rather than appending an immutable event for every run. If access is removed and later restored, it clears the revocation date for that key. It is therefore not a complete historical ledger of every grant and revoke.
The key also excludes GroupId. A change in the group supplying the same workspace, person, and role is not a separate access event.
Useful change tracking? Yes. A replacement for authoritative audit events? No.
The boundaries I would check before scheduling
This is a focused collector, not a claim that every possible access path has been resolved.
- Workspace scope: it calls the standard list-workspaces API for the executing identity, not a tenant-wide admin inventory.
- Pagination: the notebook follows Graph membership pages, but the current Fabric workspace and role-assignment calls do not follow continuation pages. The PowerShell collector also reads only the first membership page.
- Completeness: failed workspace reads or group expansion can be logged while collection continues. Missing records can then look like revoked access. A run with those warnings is not a trustworthy change baseline.
- Effective permissions: the local report’s user summary is not a full role-precedence calculation. Review the underlying assignments when somebody has several roles or access paths.
- Other access layers: workspace roles do not inventory all item sharing, app audiences, semantic-model permissions, OneLake security rules, or row-level security.
Microsoft Graph also requires additional permission for hidden-membership groups. Its transitive-members documentation describes both that requirement and the possibility of limited member details when permissions are insufficient.
Before enabling a daily notebook activity in a Fabric pipeline, compare a known direct user, a known group member, and a nested-group user with the expected assignments. Repeat collection without changes, and then test a grant and removal in an approved test workspace. Resolve warnings, pagination gaps, and duplicate merge keys before treating the result as a scheduled control.
If Fabric returns 403, check the execution identity’s workspace role and API permissions. If Graph returns 403 or incomplete names, check Graph consent and group visibility. An empty result is not proof that nobody has access.
Keep the report private
The output contains directory identities and access relationships. That is useful governance data, not something to put in a public GitHub issue.
Protect the HTML and CSV files, restrict lakehouse access, and agree on retention. Running the notebook uses Fabric capacity and lakehouse storage; adding Key Vault brings its own resource costs. This extension does not require an Azure resource group unless you introduce Azure prerequisites yourself.
When the pilot is finished, disable its schedule, remove unneeded report files and test items, and revoke dedicated application credentials and consent when they are no longer required. Check that the identity is not shared before removing it.
My take
I am not trying to replace group-based access. Groups are exactly how I want to manage it.
I want the monitoring view to go one step further. A workspace owner should not have to translate an Entra group into a list of people before the access review can begin.
That is the point of FabricPermissions: keep the group as the access path, but put the people in the review.
Sources
Implementation reviewed at FabricPermissions commit 9a6d087. Sources accessed 15 October 2026:
Loading comments…