Who Has Access? PowerShell to List Every User with Access to a SharePoint Site, Library or File

To list who can access a SharePoint site, read the site's role assignments, expand each SharePoint group to its members, expand Microsoft 365 and security groups, and add the site collection admins. For a file, do the same on the file if it has unique permissions, otherwise on the nearest parent that does, and include sharing links. Get-PnPUser alone lists everyone who ever visited, not who has access. The free SharePoint Essentials Toolkit does all of this across sites without code.

Published:

Last updated: 2026-10-29

Why the obvious commands give the wrong answer

  • Get-PnPUser / Get-SPOUser list the site's User Information List: everyone who has ever been added or visited, including people whose access was removed. It's not who has access.
  • Group membership isn't permission. A SharePoint group only grants access through a role assignment. Members of a group with no role on the site have nothing.
  • Groups nest. A SharePoint group can contain a Microsoft 365 group or a security group, which contains people. "Everyone except external users" is one entry but means the whole organization.
  • Unique permissions. Libraries, folders and files can break inheritance. The site's permissions say nothing about them.
  • Sharing links grant access through hidden "SharingLinks" groups, and leave "Limited Access" entries up the hierarchy.
  • Site collection admins have full control without any role assignment.

Option 1: Check in the browser

  • A site: Settings, Site permissions, then Advanced permissions settings for the role assignments. Click a group to see its members. Site collection admins are under the ribbon's Site Collection Administrators.
  • A file: select it, Manage access. It shows the people, groups and sharing links, but not which group members are behind "Members" or an Entra ID group.
  • Check Permissions on the Advanced permissions page answers "what can this one person do here", including through groups.

Fine for one site or file. For anything bigger, use a script or a report.

Option 2, script 1: Everyone with access to a site

This read-only PnP PowerShell script lists the site's role assignments, expands SharePoint groups and Entra ID groups to people, and adds the site collection admins. It needs PnP PowerShell 3.x and your own Entra ID app registration with Group.Read.All to expand Entra groups.

$ClientId = "<your-app-id>"
$SiteUrl  = "https://contoso.sharepoint.com/sites/Projects"
Connect-PnPOnline -Url $SiteUrl -Interactive -ClientId $ClientId

function Expand-Principal($p, $via) {
    # Entra ID / Microsoft 365 group: "c:0t.c|tenant|<id>" (security) or "c:0o.c|federateddirectoryclaimprovider|<id>" (M365)
    if ($p.PrincipalType -eq "SecurityGroup" -and $p.LoginName -match '\|([0-9a-f-]{36})(_o)?
    
    
    
    
    
  

) {
        $id = $Matches[1]
        $members = if ($Matches[2]) { Get-PnPMicrosoft365GroupOwner -Identity $id } else {
            try { Get-PnPMicrosoft365GroupMember -Identity $id } catch { Get-PnPEntraIDGroupMember -Identity $id } }
        foreach ($m in $members) { [pscustomobject]@{ User = $m.UserPrincipalName; Via = "$via > $($p.Title)" } }
    } elseif ($p.PrincipalType -eq "SecurityGroup") {
        [pscustomobject]@{ User = $p.Title; Via = "$via (claim: $($p.LoginName))" }   # Everyone, Everyone except external users
    } else {
        [pscustomobject]@{ User = $p.Email ? $p.Email : $p.LoginName; Via = $via }
    }
}

$web = Get-PnPWeb -Includes RoleAssignments
$rows = foreach ($ra in $web.RoleAssignments) {
    $member = Get-PnPProperty -ClientObject $ra -Property Member
    $roles  = (Get-PnPProperty -ClientObject $ra -Property RoleDefinitionBindings | Where-Object Name -ne "Limited Access").Name -join ", "
    if (-not $roles) { continue }                                   # Limited Access only
    if ($member.PrincipalType -eq "SharePointGroup") {
        foreach ($u in Get-PnPGroupMember -Group $member.Title) {
            Expand-Principal $u "Group: $($member.Title)" | Add-Member -PassThru Permission $roles
        }
    } else {
        Expand-Principal $member "Direct" | Add-Member -PassThru Permission $roles
    }
}
$admins = Get-PnPSiteCollectionAdmin | ForEach-Object { [pscustomobject]@{ User = $_.Email ? $_.Email : $_.Title; Via = "Site collection admin"; Permission = "Full Control" } }
$all = @($rows) + @($admins)
$all | Sort-Object User | Format-Table User, Permission, Via -AutoSize
$all | Export-Csv .\site-access.csv -NoTypeInformation

A person appears once per path; someone in Members and also given Edit directly shows twice. Group by User in Excel for one row per person.

Option 2, script 2: Everyone with access to one file

A file either has its own permissions or inherits them from the nearest folder, library or site that does. This walks up until it finds the scope that applies, then lists it, including sharing link groups:

$FileUrl = "/sites/Projects/Shared Documents/Contracts/Contoso MSA.docx"   # server-relative
$item = Get-PnPFile -Url $FileUrl -AsListItem
$scope = $item; $path = $FileUrl
while (-not (Get-PnPProperty -ClientObject $scope -Property HasUniqueRoleAssignments)) {
    $path = $path.Substring(0, $path.LastIndexOf('/'))
    $folder = Get-PnPFolder -Url $path -Includes ListItemAllFields -ErrorAction SilentlyContinue
    $scope = if ($folder.ListItemAllFields.Id) { $folder.ListItemAllFields }
             else { $l = Get-PnPList -Identity $path -ErrorAction SilentlyContinue; if ($l) { $l } else { Get-PnPWeb } }
}
"Permissions come from: $path"
foreach ($ra in (Get-PnPProperty -ClientObject $scope -Property RoleAssignments)) {
    $m = Get-PnPProperty -ClientObject $ra -Property Member
    $r = (Get-PnPProperty -ClientObject $ra -Property RoleDefinitionBindings | Where-Object Name -ne "Limited Access").Name -join ", "
    if (-not $r) { continue }
    $kind = if ($m.Title -like "SharingLinks.*") { "Sharing link" } else { $m.PrincipalType }
    "{0,-14} {1,-45} {2}" -f $kind, $m.Title, $r
    if ($m.PrincipalType -eq "SharePointGroup") { Get-PnPGroupMember -Group $m.Title | ForEach-Object { "               - {0}" -f ($_.Email ? $_.Email : $_.Title) } }
}

"Anyone" links don't have members: anyone with the URL can open the file. "People in your organization" links grant access to everyone who has the link. Only "Specific people" links list the people. Add the site collection admins from script 1.

Doing this across many sites

Both scripts make several calls per role assignment and per group. Across hundreds of sites, with unique permissions on thousands of items, that's tens of thousands of calls: expect hours and throttling. Find the items with unique permissions first and expand only those. HasUniqueRoleAssignments isn't a column, so checking it costs a Get-PnPProperty call per item; on large libraries, check folders first, since one folder with unique permissions often explains hundreds of files.

Option 3: All users and their access with SharePoint Essentials Toolkit

The toolkit's Permissions report does the walk above for every site you pick: site, lists, folders and items with unique permissions, SharePoint groups expanded, sharing links and admins included.

SharePoint permissions report results grouped by scope with totals for external users and Full Control
Grouped by scope: each site, list, folder or item with its own permissions.
All Permissions view of a SharePoint permissions report showing principal, scope, title and account type
All Permissions view: one row per person or group per scope, ready to filter.

For the site-level answer, the matrix shows owners, members, visitors and admins in one place:

Permissions matrix report grouped by scope: one site collection admin, site Members with Edit, Owners with Full Control, Visitors with Read, and a user with Full Control through the Owners group
Admins, Owners, Members and Visitors, with the permission level each gets.

The reverse question, everything one person can access across many sites, is Check user permissions:

SharePoint user access report listing every site, list and item one external user can access
Every site, list and item one account can reach.

Export either report to Excel or CSV, and remove or change access from the report's actions panel; changes can be undone from History.

Community Edition is free on up to 20 sites per Microsoft 365 tenant.

Frequently asked questions

How do I list all users with access to a SharePoint site in PowerShell?

Read the site's RoleAssignments, expand each SharePoint group with Get-PnPGroupMember, expand Entra ID and Microsoft 365 groups, skip Limited Access, and add Get-PnPSiteCollectionAdmin. Get-PnPUser alone is not who has access.

Why does Get-PnPUser show people who no longer have access?

It reads the User Information List, which keeps everyone who was ever added to or visited the site. Removing access doesn't remove them from it.

How do I see who has access to a specific SharePoint file?

Check HasUniqueRoleAssignments on the file; if false, walk up to the nearest folder, library or site that has unique permissions and read its role assignments, including SharingLinks groups.

What does Limited Access mean in SharePoint permissions?

It's added automatically when someone has access to something inside a site or list, such as one file, so they can reach it. It grants nothing on its own; skip it when listing access.

Do sharing links show up in SharePoint permissions?

Yes, as SharePoint groups named SharingLinks.<id>.<type> on the file or folder. Specific people links list members; organization and Anyone links don't, because anyone with the link can use it.

See everyone with access to SharePoint, free

SharePoint Essentials Toolkit Community Edition is free. Report every user, group, sharing link and admin with access to your sites, lists and files, on up to 20 sites per Microsoft 365 tenant. Get your free Community Edition license key.

About the author. The Cognillo SharePoint Team builds and supports SharePoint Essentials Toolkit and has worked with SharePoint administrators on permissions audits, migrations and cleanup projects since 2012.