I lied, not just Teams, also Sharepoint and Onedrive!
As I am asked often how to report on specific permissions granted to individual (groups) of (internal/external) users….and Microsoft doesn’t have a good built-in solution, nor does the community seem to yet….this something was just asking to be coded!
My TeamPermissions PowerShell module will do exactly the above, a full report in XLSX, CSV or HTML format that contains ALL unique permissions for a given Team, Sharepoint site or Onedrive location for all files, folders, lists, list items etc. Example:
It uses the safe Entra Delegated Permission Flow for authentication so your credentials/tokens stay with you, but this does mean you have to run it as a Sharepoint Administrator (or Global Admin), there is no support for MI/SPN runs yet but can be added easily if there is much demand.
Since it exports to Excel in append mode, you could run it for multiple (or all) team sites and use e.g. Pivots to view all permissions for a given user.
Do note that although some work has been done on performance, it does not scan multiple locations in parallel yet, this will be added in a future version.
Example:
Install-PSResource -Name TeamPermissions -Repository PSGallery
#then get xlsx/html reports for the INT-Finance Department Team:
Get-TeamPermissions -teamName "INT-Finance Department" -ExpandGroups -OutputFormat XLSX,HTML
#Or get all permission for a Sharepoint site:
Get-TeamPermissions -TeamSiteUrl "https://tenant.sharepoint.com/sites/site" -ExpandGroups -OutputFormat Default
Notes
Required PS modules: PnP.PowerShell, ImportExcel
Running multiple times will append data if you don’t move the (xlsx, csv, html) file, turning the report into a multi-location report.
Microsoft is planning to modify the Office 365 sign in process slightly by September 15th or October 1st and has kindly supplied advance notice and support for OnedriveMapper, but only for V3.29+ and V5.15+
Teams Room accounts are usually excluded from conditional access. To do so, they have to be in a security group, which of course we don’t want to do manually.
Most companies choose to use a naming standard and simply use that as a rule to create an exclusion group. This is easy to circumvent, I can create a guest user / get invited with the right name et voila zero CA policies!
A better way is to identify the accounts based on their assigned licenses, e.g. Teams Rooms Basic (6af4b3d6-14bb-4a2a-960c-6c902aad34f3). This, however, is not supported as an Azure AD group membership rule as this is stored in the AssignedLicenses property which will throw an “Unsupported Property” error.
The assignedPlans property however does contain the GUID we need.
The following Azure AD Group dynamic membership rule only matches users that have a Teams Room Basic, Teams Room Standard or Teams Room Pro license:
When working with the api.interfaces.records.teams.microsoft.com API, I noticed that the MS portal uses an X-MS-Forest header.
At first, ignoring this went fine as doing GET calls to this api didn’t seem to require it. But, of course the moment I wanted more, it suddenly WAS required (PUT/POST).
The question was; how does the portal determine the value for this header and how do we replicate that? Well, that wasn’t difficult: apparently a call to api.interfaces.records.teams.microsoft.com/Teams.Tenant/tenants suffices and returns the value for the X-MS-Forest header for the tenant identified in your token. Example:
$headers = Get-GraphToken -tenantid $tenantId -scope "https://api.interfaces.records.teams.microsoft.com/user_impersonation"
#get the correct forest
$tenantInfo = Invoke-RestMethod -Method GET -uri "https://api.interfaces.records.teams.microsoft.com/Teams.Tenant/tenants" -UseBasicParsing -ContentType "application/json" -Headers $headers
#add the X-MS-Forest header (required) for subsequent calls
$headers["X-MS-Forest"] = $tenantInfo.serviceDiscovery.Headers.'X-MS-Forest'