SharePoint Online: Site Collection Permissions Report using PowerShell

Requirement: Generate permissions report for a SharePoint Online site collection.

SharePoint Online: Site Collection Permissions Report using PowerShell

Have you ever wanted to get SharePoint Online Site and subsites permissions using PowerShell? it can be difficult to keep track of user permissions for individual sites in a large and complex environment. A site permissions report can be a valuable tool for administrators, providing a clear and concise overview of the role definitions assigned to each site in a SharePoint Online tenant. This PowerShell script generates a permission report on all objects which has unique permissions on the given site collection. It scans through the following securables:

  • Site collection administrators group
  • Given site collection and sub-sites with unique permissions
  • All lists and libraries with unique permissions
  • All list items (and folders) with unique permissions.
You must add the account that runs this PowerShell Scripts as “Site Collection Administrator” to all sites!
How to Add Site Collection Administrator to All SharePoint Online Sites?

Here is the SharePoint Online PowerShell to get the site permissions report:

#sharepoint online powershell permissions report
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"  
 
#Function to Get Permissions Applied on a particular Object, such as: Web, List or Item
Function Get-Permissions([Microsoft.SharePoint.Client.SecurableObject]$Object)
{
    #Determine the type of the object
    Switch($Object.TypedObject.ToString())
    {
        "Microsoft.SharePoint.Client.Web"  { $ObjectType = "Site" ; $ObjectURL = $Object.URL }
        "Microsoft.SharePoint.Client.ListItem"
        { 
            $ObjectType = "List Item"
            #Get the URL of the List Item
            $Object.ParentList.Retrieve("DefaultDisplayFormUrl")
            $Ctx.ExecuteQuery()
            $DefaultDisplayFormUrl = $Object.ParentList.DefaultDisplayFormUrl
            $ObjectURL = $("{0}{1}?ID={2}" -f $Ctx.Web.Url.Replace($Ctx.Web.ServerRelativeUrl,''), $DefaultDisplayFormUrl,$Object.ID)
        }
        Default 
        { 
            $ObjectType = "List/Library"
            #Get the URL of the List or Library
            $Ctx.Load($Object.RootFolder)
            $Ctx.ExecuteQuery()            
            $ObjectURL = $("{0}{1}" -f $Ctx.Web.Url.Replace($Ctx.Web.ServerRelativeUrl,''), $Object.RootFolder.ServerRelativeUrl)
        }
    }
 
    #Get permissions assigned to the object
    $Ctx.Load($Object.RoleAssignments)
    $Ctx.ExecuteQuery()
 
    Foreach($RoleAssignment in $Object.RoleAssignments)
    { 
                $Ctx.Load($RoleAssignment.Member)
                $Ctx.executeQuery()
                 
                #Get the Permissions on the given object
                $Permissions=@()
                $Ctx.Load($RoleAssignment.RoleDefinitionBindings)
                $Ctx.ExecuteQuery()
                Foreach ($RoleDefinition in $RoleAssignment.RoleDefinitionBindings)
                {
                    $Permissions += $RoleDefinition.Name +";"
                }
 
                #Check direct permissions
                if($RoleAssignment.Member.PrincipalType -eq "User")
                {
                        #Send the Data to Report file
                        "$($ObjectURL) `t $($ObjectType) `t $($Object.Title)`t $($RoleAssignment.Member.LoginName) `t User `t $($Permissions)" | Out-File $ReportFile -Append
                }
                 
                ElseIf($RoleAssignment.Member.PrincipalType -eq "SharePointGroup")
                {        
                        #Send the Data to Report file
                        "$($ObjectURL) `t $($ObjectType) `t $($Object.Title)`t $($RoleAssignment.Member.LoginName) `t SharePoint Group `t $($Permissions)" | Out-File $ReportFile -Append
                }
                ElseIf($RoleAssignment.Member.PrincipalType -eq "SecurityGroup")
                {
                    #Send the Data to Report file
                    "$($ObjectURL) `t $($ObjectType) `t $($Object.Title)`t $($RoleAssignment.Member.Title)`t $($Permissions) `t Security Group" | Out-File $ReportFile -Append
                }
    }
}
 
#powershell to get sharepoint online site permissions
Function Generate-SPOSitePermissionRpt($SiteURL,$ReportFile)
{
    Try {
        #Get Credentials to connect
        $Cred= Get-Credential
        $Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.Username, $Cred.Password)
  
        #Setup the context
        $Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
        $Ctx.Credentials = $Credentials
 
        #Get the Web
        $Web = $Ctx.Web
        $Ctx.Load($Web)
        $Ctx.ExecuteQuery()
 
        #Write CSV- TAB Separated File) Header
        "URL `t Object `t Title `t Account `t PermissionType `t Permissions" | out-file $ReportFile
 
        Write-host -f Yellow "Getting Site Collection Administrators..."
        #Get Site Collection Administrators
        $SiteUsers= $Ctx.Web.SiteUsers 
        $Ctx.Load($SiteUsers)
        $Ctx.ExecuteQuery()
        $SiteAdmins = $SiteUsers | Where { $_.IsSiteAdmin -eq $true}
 
        ForEach($Admin in $SiteAdmins)
        {
            #Send the Data to report file
            "$($Web.URL) `t Site Collection `t $($Web.Title)`t $($Admin.Title) `t Site Collection Administrator `t  Site Collection Administrator" | Out-File $ReportFile -Append
        }
 
        #Function to Get Permissions of All List Items of a given List
        Function Get-SPOListItemsPermission([Microsoft.SharePoint.Client.List]$List)
        {
            Write-host -f Yellow "`t `t Getting Permissions of List Items in the List:"$List.Title

            $Query = New-Object Microsoft.SharePoint.Client.CamlQuery
            $Query.ViewXml = "<View Scope='RecursiveAll'><Query><OrderBy><FieldRef Name='ID' Ascending='TRUE'/></OrderBy></Query><RowLimit Paged='TRUE'>$BatchSize</RowLimit></View>"

            $Counter = 0
            #Batch process list items - to mitigate list threshold issue on larger lists
            Do {  
                #Get items from the list
                $ListItems = $List.GetItems($Query)
                $Ctx.Load($ListItems)
                $Ctx.ExecuteQuery()
          
                $Query.ListItemCollectionPosition = $ListItems.ListItemCollectionPosition
 
                #Loop through each List item
                ForEach($ListItem in $ListItems)
                {
                    $ListItem.Retrieve("HasUniqueRoleAssignments")
                    $Ctx.ExecuteQuery()
                    If($ListItem.HasUniqueRoleAssignments -eq $True)
                    {
                        #Call the function to generate Permission report
                        Get-Permissions -Object $ListItem
                    }
                    $Counter++
                    Write-Progress -PercentComplete ($Counter / ($List.ItemCount) * 100) -Activity "Processing Items $Counter of $($List.ItemCount)" -Status "Searching Unique Permissions in List Items of '$($List.Title)'" 
                }
            } While ($Query.ListItemCollectionPosition -ne $null)
        }
 
        #Function to Get Permissions of all lists from the web
        Function Get-SPOListPermission([Microsoft.SharePoint.Client.Web]$Web)
        {
            #Get All Lists from the web
            $Lists = $Web.Lists
            $Ctx.Load($Lists)
            $Ctx.ExecuteQuery()
 
            #Get all lists from the web   
            ForEach($List in $Lists)
            {
                #Exclude System Lists
                If($List.Hidden -eq $False)
                {
                    #Get List Items Permissions
                    Get-SPOListItemsPermission $List
 
                    #Get the Lists with Unique permission
                    $List.Retrieve("HasUniqueRoleAssignments")
                    $Ctx.ExecuteQuery()
 
                    If( $List.HasUniqueRoleAssignments -eq $True)
                    {
                        #Call the function to check permissions
                        Get-Permissions -Object $List
                    }
                }
            }
        }
 
        #Function to Get Webs's Permissions from given URL
        Function Get-SPOWebPermission([Microsoft.SharePoint.Client.Web]$Web) 
        {
            #Get all immediate subsites of the site
            $Ctx.Load($web.Webs)  
            $Ctx.executeQuery()
  
            #Call the function to Get Lists of the web
            Write-host -f Yellow "Getting the Permissions of Web "$Web.URL"..."
 
            #Check if the Web has unique permissions
            $Web.Retrieve("HasUniqueRoleAssignments")
            $Ctx.ExecuteQuery()
 
            #Get the Web's Permissions
            If($web.HasUniqueRoleAssignments -eq $true) 
            { 
                Get-Permissions -Object $Web
            }
 
            #Scan Lists with Unique Permissions
            Write-host -f Yellow "`t Getting the Permissions of Lists and Libraries in "$Web.URL"..."
            Get-SPOListPermission($Web)
  
            #Iterate through each subsite in the current web
            Foreach ($Subweb in $web.Webs)
            {
                 #Call the function recursively                            
                 Get-SPOWebPermission($SubWeb)
            }
        }
 
        #Call the function with RootWeb to get site collection permissions
        Get-SPOWebPermission $Web
 
        Write-host -f Green "Site Permission Report Generated Successfully!"
     }
    Catch {
        write-host -f Red "Error Generating Site Permission Report!" $_.Exception.Message
   }
}
 
#Set parameter values
$SiteURL="https://crescent.sharepoint.com"
$ReportFile="C:\Temp\SitePermissionRpt.csv"
$BatchSize = 500
 
#Call the function
Generate-SPOSitePermissionRpt -SiteURL $SiteURL -ReportFile $ReportFile

Output Report of the script:
The above script generates a CSV file in the provided ReportFile parameter. Here is a sample report generated.

sharepoint online site collection permission report using powershell

This script will generate a report of the permissions for the given SharePoint Online site, including each object such as site, list, list item, etc. site URL and the names of the role definitions that are assigned to each site. The output will be saved to a CSV file in your local file system.

If you are looking for a permission report for a specific user, use my other script: SharePoint Online: User Permissions Report using PowerShell

Update: SharePoint Online Site Permission Report V2

How about extending the script to expand SharePoint Groups (instead of just group name, have all members of the group) and introduce switches for Recursively processing all sub-sites, scan up to Item level permissions, and export all permissions including the one with inheriting permissions from its parent? Here is the SharePoint Online PowerShell to get all user permissions:

Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
   
#Function to Get Permissions Applied on a particular Object, such as: Web or List
Function Get-Permissions([Microsoft.SharePoint.Client.SecurableObject]$Object)
{
    #Determine the type of the object
    Switch($Object.TypedObject.ToString())
    {
        "Microsoft.SharePoint.Client.Web"  { $ObjectType = "Site" ; $ObjectURL = $Object.URL; $ObjectTitle = $Object.Title }
        "Microsoft.SharePoint.Client.ListItem"
        { 
            If($Object.FileSystemObjectType -eq "Folder")
            {
                $ObjectType = "Folder"
                #Get the URL of the Folder
                $Object.Retrieve("Folder")
                $Ctx.ExecuteQuery()
                $ObjectTitle = $Object.Folder.Name
                $ObjectURL = $("{0}{1}" -f $Ctx.Web.Url.Replace($Ctx.Web.ServerRelativeUrl,''),$Object.Folder.ServerRelativeUrl)
            }
            Else #File or List Item
            {
                #Get the URL of the Object
                $Object.Retrieve("File")
                $Ctx.ExecuteQuery()
                If($Object.File.Name -ne $Null)
                {
                    $ObjectType = "File"
                    $ObjectTitle = $Object.File.Name
                    $ObjectURL = $("{0}{1}" -f $Ctx.Web.Url.Replace($Ctx.Web.ServerRelativeUrl,''),$Object.File.ServerRelativeUrl)
                }
                else
                {
                    $ObjectType = "List Item"
                    $ObjectTitle = $Object["Title"]
                    #Get the URL of the List Item
                    $Object.ParentList.Retrieve("DefaultDisplayFormUrl")
                    $Ctx.ExecuteQuery()
                    $DefaultDisplayFormUrl = $Object.ParentList.DefaultDisplayFormUrl
                    $ObjectURL = $("{0}{1}?ID={2}" -f $Ctx.Web.Url.Replace($Ctx.Web.ServerRelativeUrl,''), $DefaultDisplayFormUrl,$Object.ID)
                }
            }
        }
        Default 
        { 
            $ObjectType = "List or Library"
            $ObjectTitle = $Object.Title
            #Get the URL of the List or Library
            $Ctx.Load($Object.RootFolder)
            $Ctx.ExecuteQuery()            
            $ObjectURL = $("{0}{1}" -f $Ctx.Web.Url.Replace($Ctx.Web.ServerRelativeUrl,''), $Object.RootFolder.ServerRelativeUrl)
        }
    }
  
    #Check if Object has unique permissions
    $Object.Retrieve("HasUniqueRoleAssignments")
    $Ctx.ExecuteQuery()
    $HasUniquePermissions = $Object.HasUniqueRoleAssignments
  
    #Get permissions assigned to the object
    $RoleAssignments = $Object.RoleAssignments
    $Ctx.Load($RoleAssignments)
    $Ctx.ExecuteQuery()
   
    #Loop through each permission assigned and extract details
    $PermissionCollection = @()
    Foreach($RoleAssignment in $RoleAssignments)
    { 
        $Ctx.Load($RoleAssignment.Member)
        $Ctx.executeQuery()
   
        #Get the Principal Type: User, SP Group, AD Group
        $PermissionType = $RoleAssignment.Member.PrincipalType
   
        #Get the Permission Levels assigned
        $Ctx.Load($RoleAssignment.RoleDefinitionBindings)
        $Ctx.ExecuteQuery()
        $PermissionLevels = $RoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name

        #Remove Limited Access
        $PermissionLevels = ($PermissionLevels | Where { $_ -ne "Limited Access"}) -join ","
        If($PermissionLevels.Length -eq 0) {Continue}

        #Get SharePoint group members
        If($PermissionType -eq "SharePointGroup")
        {
            #Get Group Members
            $Group = $Ctx.web.SiteGroups.GetByName($RoleAssignment.Member.LoginName)
            $Ctx.Load($Group)
            $GroupMembers= $Group.Users
            $Ctx.Load($GroupMembers)
            $Ctx.ExecuteQuery()
            If($GroupMembers.count -eq 0){Continue}
            $GroupUsers = ($GroupMembers | Select -ExpandProperty Title) -join ","

            #Add the Data to Object
            $Permissions = New-Object PSObject
            $Permissions | Add-Member NoteProperty Object($ObjectType)
            $Permissions | Add-Member NoteProperty Title($ObjectTitle)
            $Permissions | Add-Member NoteProperty URL($ObjectURL)
            $Permissions | Add-Member NoteProperty HasUniquePermissions($HasUniquePermissions)
            $Permissions | Add-Member NoteProperty Users($GroupUsers)
            $Permissions | Add-Member NoteProperty Type($PermissionType)
            $Permissions | Add-Member NoteProperty Permissions($PermissionLevels)
            $Permissions | Add-Member NoteProperty GrantedThrough("SharePoint Group: $($RoleAssignment.Member.LoginName)")
            $PermissionCollection += $Permissions
        }
        Else
        {
            #Add the Data to Object
            $Permissions = New-Object PSObject
            $Permissions | Add-Member NoteProperty Object($ObjectType)
            $Permissions | Add-Member NoteProperty Title($ObjectTitle)
            $Permissions | Add-Member NoteProperty URL($ObjectURL)
            $Permissions | Add-Member NoteProperty HasUniquePermissions($HasUniquePermissions)
            $Permissions | Add-Member NoteProperty Users($RoleAssignment.Member.Title)
            $Permissions | Add-Member NoteProperty Type($PermissionType)
            $Permissions | Add-Member NoteProperty Permissions($PermissionLevels)
            $Permissions | Add-Member NoteProperty GrantedThrough("Direct Permissions")
            $PermissionCollection += $Permissions
        }
    }
    #Export Permissions to CSV File
    $PermissionCollection | Export-CSV $ReportFile -NoTypeInformation -Append
}
  
#Function to get sharepoint online site permissions report
Function Generate-SPOSitePermissionRpt()
{    
[cmdletbinding()]

    Param  
    (    
        [Parameter(Mandatory=$false)] [String] $SiteURL, 
        [Parameter(Mandatory=$false)] [String] $ReportFile,         
        [Parameter(Mandatory=$false)] [switch] $Recursive,
        [Parameter(Mandatory=$false)] [switch] $ScanItemLevel,
        [Parameter(Mandatory=$false)] [switch] $IncludeInheritedPermissions        
    )  
    Try {
        #Get Credentials to connect
        $Cred= Get-Credential
   
        #Setup the context
        $Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
        $Ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.Username, $Cred.Password)
  
        #Get the Web & Root Web
        $Web = $Ctx.Web
        $RootWeb = $Ctx.Site.RootWeb
        $Ctx.Load($Web)
        $Ctx.Load($RootWeb)
        $Ctx.ExecuteQuery()
  
        Write-host -f Yellow "Getting Site Collection Administrators..."
        #Get Site Collection Administrators
        $SiteUsers= $RootWeb.SiteUsers 
        $Ctx.Load($SiteUsers)
        $Ctx.ExecuteQuery()
        $SiteAdmins = $SiteUsers | Where { $_.IsSiteAdmin -eq $true}
        
        $SiteCollectionAdmins = ($SiteAdmins | Select -ExpandProperty Title) -join ","
        #Add the Data to Object
        $Permissions = New-Object PSObject
        $Permissions | Add-Member NoteProperty Object("Site Collection")
        $Permissions | Add-Member NoteProperty Title($RootWeb.Title)
        $Permissions | Add-Member NoteProperty URL($RootWeb.URL)
        $Permissions | Add-Member NoteProperty HasUniquePermissions("TRUE")
        $Permissions | Add-Member NoteProperty Users($SiteCollectionAdmins)
        $Permissions | Add-Member NoteProperty Type("Site Collection Administrators")
        $Permissions | Add-Member NoteProperty Permissions("Site Owner")
        $Permissions | Add-Member NoteProperty GrantedThrough("Direct Permissions")
              
        #Export Permissions to CSV File
        $Permissions | Export-CSV $ReportFile -NoTypeInformation
  
        #Function to Get Permissions of All List Items of a given List
        Function Get-SPOListItemsPermission([Microsoft.SharePoint.Client.List]$List)
        {
            Write-host -f Yellow "`t `t Getting Permissions of List Items in the List:"$List.Title
 
            $Query = New-Object Microsoft.SharePoint.Client.CamlQuery
            $Query.ViewXml = "<View Scope='RecursiveAll'><Query><OrderBy><FieldRef Name='ID' Ascending='TRUE'/></OrderBy></Query><RowLimit Paged='TRUE'>$BatchSize</RowLimit></View>"
 
            $ItemCounter = 0
            #Batch process list items - to mitigate list threshold issue on larger lists
            Do {  
                #Get items from the list
                $ListItems = $List.GetItems($Query)
                $Ctx.Load($ListItems)
                $Ctx.ExecuteQuery()
           
                $Query.ListItemCollectionPosition = $ListItems.ListItemCollectionPosition
  
                #Loop through each List item
                ForEach($ListItem in $ListItems)
                {
                    #Get Objects with Unique Permissions or Inherited Permissions based on 'IncludeInheritedPermissions' switch
                    If($IncludeInheritedPermissions)
                    {
                        Get-Permissions -Object $ListItem
                    }
                    Else
                    {
                        $ListItem.Retrieve("HasUniqueRoleAssignments")
                        $Ctx.ExecuteQuery()
                        If($ListItem.HasUniqueRoleAssignments -eq $True)
                        {
                            #Call the function to generate Permission report
                            Get-Permissions -Object $ListItem
                        }
                    }
                    $ItemCounter++
                    Write-Progress -PercentComplete ($ItemCounter / ($List.ItemCount) * 100) -Activity "Processing Items $ItemCounter of $($List.ItemCount)" -Status "Searching Unique Permissions in List Items of '$($List.Title)'"
                }
            } While ($Query.ListItemCollectionPosition -ne $null)
        }

        #Function to Get Permissions of all lists from the web
        Function Get-SPOListPermission([Microsoft.SharePoint.Client.Web]$Web)
        {
            #Get All Lists from the web
            $Lists = $Web.Lists
            $Ctx.Load($Lists)
            $Ctx.ExecuteQuery()
  
            #Exclude system lists
            $ExcludedLists = @("Access Requests","App Packages","appdata","appfiles","Apps in Testing","Cache Profiles","Composed Looks","Content and Structure Reports","Content type publishing error log","Converted Forms",
            "Device Channels","Form Templates","fpdatasources","Get started with Apps for Office and SharePoint","List Template Gallery", "Long Running Operation Status","Maintenance Log Library", "Images", "site collection images"
            ,"Master Docs","Master Page Gallery","MicroFeed","NintexFormXml","Quick Deploy Items","Relationships List","Reusable Content","Reporting Metadata", "Reporting Templates", "Search Config List","Site Assets","Preservation Hold Library"
            "Site Pages", "Solution Gallery","Style Library","Suggested Content Browser Locations","Theme Gallery", "TaxonomyHiddenList","User Information List","Web Part Gallery","wfpub","wfsvc","Workflow History","Workflow Tasks", "Pages")
            
            $Counter = 0
            #Get all lists from the web   
            ForEach($List in $Lists)
            {
                #Exclude System Lists
                If($List.Hidden -eq $False -and $ExcludedLists -notcontains $List.Title)
                {
                    $Counter++
                    Write-Progress -PercentComplete ($Counter / ($Lists.Count) * 100) -Activity "Processing Lists $Counter of $($Lists.Count) in $($Web.URL)" -Status "Exporting Permissions from List '$($List.Title)'"

                    #Get Item Level Permissions if 'ScanItemLevel' switch present
                    If($ScanItemLevel)
                    {
                        #Get List Items Permissions
                        Get-SPOListItemsPermission -List $List
                    }

                    #Get Lists with Unique Permissions or Inherited Permissions based on 'IncludeInheritedPermissions' switch
                    If($IncludeInheritedPermissions)
                    {
                        Get-Permissions -Object $List
                    }
                    Else
                    {
                        #Check if List has unique permissions
                        $List.Retrieve("HasUniqueRoleAssignments")
                        $Ctx.ExecuteQuery()
                        If($List.HasUniqueRoleAssignments -eq $True)
                        {
                            #Call the function to check permissions
                            Get-Permissions -Object $List
                        }
                    }
                }
            }
        }
  
        #Function to Get Web's Permissions from given URL
        Function Get-SPOWebPermission([Microsoft.SharePoint.Client.Web]$Web) 
        {
            #Get all immediate subsites of the site
            $Ctx.Load($web.Webs)  
            $Ctx.executeQuery()
   
            #Call the function to Get permissions of the web
            Write-host -f Yellow "Getting Permissions of the Web: $($Web.URL)..."  
            Get-Permissions -Object $Web
  
            #Get List Permissions
            Write-host -f Yellow "`t Getting Permissions of Lists and Libraries..."
            Get-SPOListPermission($Web)

            #Recursively get permissions from all sub-webs based on the "Recursive" Switch
            If($Recursive)
            {
                #Iterate through each subsite in the current web
                Foreach ($Subweb in $web.Webs)
                {
                    #Get Webs with Unique Permissions or Inherited Permissions based on 'IncludeInheritedPermissions' switch
                    If($IncludeInheritedPermissions)
                    {
                        Get-SPOWebPermission($Subweb)
                    }
                    Else
                    {
                        #Check if the Web has unique permissions
                        $Subweb.Retrieve("HasUniqueRoleAssignments")
                        $Ctx.ExecuteQuery()
  
                        #Get the Web's Permissions
                        If($Subweb.HasUniqueRoleAssignments -eq $true) 
                        { 
                            #Call the function recursively                            
                            Get-SPOWebPermission($Subweb)
                        }
                    }
                }
            }
        }
  
        #Call the function with RootWeb to get site collection permissions
        Get-SPOWebPermission $Web
  
        Write-host -f Green "`n*** Site Permission Report Generated Successfully!***"
     }
    Catch {
        write-host -f Red "Error Generating Site Permission Report!" $_.Exception.Message
   }
}
  
#region ***Parameters***
$SiteURL="https://crescent.sharepoint.com/sites/marketing"
$ReportFile="C:\Temp\SitePermissionRpt.csv"
$BatchSize = 500
#endregion

#Call the function to generate permission report
Generate-SPOSitePermissionRpt -SiteURL $SiteURL -ReportFile $ReportFile
#Generate-SPOSitePermissionRpt -SiteURL $SiteURL -ReportFile $ReportFile -Recursive -ScanItemLevel -IncludeInheritedPermissions

This script produces the output below with each object in the site and their permission assignments! It gets SharePoint Online site and subsites along with its child object permission using PowerShell:

SharePoint Online Site Permission Report

In summary, Generating a site permissions report in SharePoint Online is a straightforward process that can be accomplished using the PowerShell scripts shared in this article. This script retrieves the permissions for each object in your site and generates a CSV permission report. Whether you are looking to ensure compliance with security policies or simply need to get a better understanding of your SharePoint Online environment permissions, a site permissions report can be a valuable asset.

To generate a permission report for a site collection using PnP PowerShell, use: SharePoint Online site Permissions Report using PnP PowerShell

Salaudeen Rajack

Salaudeen Rajack - Information Technology Expert with Two-decades of hands-on experience, specializing in SharePoint, PowerShell, Microsoft 365, and related products. He has held various positions including SharePoint Architect, Administrator, Developer and consultant, has helped many organizations to implement and optimize SharePoint solutions. Known for his deep technical expertise, He's passionate about sharing the knowledge and insights to help others, through the real-world articles!

103 thoughts on “SharePoint Online: Site Collection Permissions Report using PowerShell

  • Hi,
    What is the license of those scripts?

    Reply
  • Hi,
    Would love to be able to run your script only on a specific list/document library rather than the full site. Would you be able to help?

    Reply
  • Does anyone know how to run this script against all sites in the tenant instead one site?

    Reply
  • I installed the SharePoint Online SDK and get the Connect-SPOService : The ‘Connect-SPOService’ command was found in the module
    ‘Microsoft.Online.SharePoint.PowerShell’, but the module could not be loaded. For more information, run ‘Import-Module
    Microsoft.Online.SharePoint.PowerShell’.
    But there is a known issue with SharePoint Online Management Shell module and SharePoint Client Components SDK when both are installed.

    Reply
    • Try Uninstalling the SharePoint Online SDK and installing the SharePoint Online PowerShell module: Install-module Microsoft.Online.SharePoint.PowerShell

      Reply
  • Does anyone know how to run this script against all sites in the tenant instead one site?

    Reply
  • Can this be modified for MFA?

    Reply
  • Does this work for Teams sites?

    Reply
  • I’ve tried your v1 script but I am getting an error – Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The underlying connection was closed: An unexpected error occurred on a send.”.

    It seems to occur on line 102.

    Reply
  • Hello Salaudeen,

    Thank you for the script that you have made. I am able to run the script, it runs successfully and generates the output in CSV file. But the results seems to incomplete. It doesn’t go through and fetch details for all the lists and libraries..

    Any suggestion why it doesn’t fetch details of other list and libraries.

    Reply
    • I, too, am having this issue the -Recursive -ScanItemLevel -IncludeInheritedPermission switches. Has anyone been able to remedy this?

      Reply
  • To anyone: I was running the script with no issues, until a couple of weeks ago. It is randomly stopping with the following error:

    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The remote server returned an error: (400) Bad Request.”

    Seems to be a throttling issue with Microsoft, but can’t confirm. I went through all the comments and even tried the Start-Sleep -Seconds 1.5

    Any ideas?

    Reply
  • Hi Salaudeen, Thanks for the great script. Even after using ScanItemLevel switch and -Recursive -IncludeInheritedPermissions. Still I am not able to get the permissions for each files in the site. I am getting output generated only for subsites. Please assist me with this.

    Reply
  • The second script does not work to recursively pull subitemslistssites… etc.. The first script does work however, it is very slow as it iterates through all items. Wondering if it would be better to somehow get the broken inheritance list from the permissions page first, and so all you have to iterate throgh is the main site, any itenmssubitems that are known to have differedbroken inheritance. Should cut processing time in half. Furthermore it may be better to run script 1 first, then process the result as a psobject to replace group names with its membersusers.

    Reply
  • I’m getting the same.

    Reply
  • The script was working before, but I getting the below error

    Getting Site Collection Administrators…
    Getting the Permissions of Web https://xxxx.sharepoint.com/sites/xxxx …
    Getting the Permissions of Lists and Libraries in https://xxxx.sharepoint.com/xxxx …
    Getting Permissions of List Items in the List: Calendar
    Getting Permissions of List Items in the List: Documents
    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “Exception from HRESULT: 0x80131904”

    Reply
  • hi,

    instead of showing the user display name, can we show the user email address?

    Reply
  • hi, how do i run this to include folders and subfolders?

    Reply
  • Can you add more than one site to the script. I have a location with 60 sites that I need to run permissions for?

    Reply
  • I keep getting this error unfortunately.

    Error Getting List Permissions! Exception calling “ExecuteQuery” with “0” argument(s): “‘center’ is an unexpected
    token. The expected token is ‘”‘ or ”’. Line 7, position 12.”

    Reply
  • I get the following error:
    Get-PnPPermissions : Cannot process argument transformation on parameter ‘Object’. Cannot convert the “Microsoft.SharePoint.Client.ListItem” value of type “Microsoft.SharePoint.Client.ListItem” to
    type “Microsoft.SharePoint.Client.SecurableObject”.

    Reply
    • Hi! Any solution on this?

      Reply
    • Try:
      1. Remove the SharePoint CSOM assemblies from GAC (Go to: C:\Windows\Microsoft.NET\assembly\GAC_MSIL, Remove all Folders starting with name: Microsoft.SharePoint*)

      2. Restart your machine, and Re-install SharePoint Online PowerShell module using “Install-Module -Name Microsoft.Online.SharePoint.PowerShell”

      Reply
  • Thanks heaps for this however how do you remove documents being scanned as part of this? I just need to check permissions on folders only so it won’t reach the 5000 limit if I am just checking it.

    Reply
  • We’re getting the below:

    Getting Site Collection Administrators…
    Getting Permissions of the Web: https://XXXXXXXXXXXXXXX.sharepoint.com…
    Getting Permissions of Lists and Libraries…
    Getting Permissions of List Items in the List: AAAA
    Getting Permissions of List Items in the List: BBBB
    Getting Permissions of List Items in the List: CCCC
    Getting Permissions of List Items in the List: DDDD
    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The remote server returned an error: (429).”

    Tried implementing the fix mentioned above: “i ended up implementing this https://fangdahai.blogspot.com/2018/02/how-to-handle-429-error-in-powershell.html to get it to run reliably for me.” – It got further than the previous attempt but the Excel doc is only getting to around 6000 odd lines (just under 5000 odd before the fix was applied).

    So I now have the following in PowerShell (where >> indicated added lines)

    #Function to Get Permissions Applied on a particular Object, such as: Web or List
    Function Get-Permissions([Microsoft.SharePoint.Client.SecurableObject]$Object)
    {
        #Determine the type of the object
    >> for($retryAttempts=0; $retryAttempts -lt $Global:_retryCount; $retryAttempts++){
    >> Try{
    >> $ctx.ExecuteQuery()
    >> break
    >> }
    >> Catch [system.exception]{
    >> Start-Sleep -s $Global:_retryInterval
    >> }
    >>}
    Switch($Object.TypedObject.ToString())

    Any ideas? I’ve now trying by using the following values at the top of the script:
    $Global:_retryCount = 10000 (was 1000)
    $Global:_retryInterval = 15 (was 10)

    Reply
    • As above I tried with these values:
      $Global:_retryCount = 10000 (was 1000)
      $Global:_retryInterval = 15 (was 10)

      It got slightly further, but not that much, started the next list but did not complete and produced the same error message, any ideas?

      Reply
    • I had the 429 error. The script would crash out with this error due to what seems to be the number of request for the SharePoint server to handle. I tried above fix, the script went a little bit further but crashed out again. I then added a sleep snippet in the code which seemed to fix the issue. The script took longer to run though:
      Code; (> added new code):

      Function Get-Permissions([Microsoft.SharePoint.Client.SecurableObject]$Object)
      {
      > Start-Sleep -Seconds 1.5
      #Determine the type of the object
      for($retryAttempts=0; $retryAttempts -lt $Global:_retryCount; $retryAttempts++)

      ———-
      I’m no expert but I believe it helped the SharePoint server deal with the number of request better.

      Reply
  • Hi Salaudeen, i can’t seem to be able to run this for sites, subsites libraries and folders all at once. i’m having to enter each url separately. any ideas?

    Reply
    • Use -Recursive and -IncludeInheritedPermissions switches. E.g.
      Generate-SPOSitePermissionRpt -SiteURL $SiteURL -ReportFile $ReportFile -Recursive -IncludeInheritedPermissions

      Reply
  • i ended up implementing this https://fangdahai.blogspot.com/2018/02/how-to-handle-429-error-in-powershell.html to get it to run reliably for me.

    Reply
  • I’m getting a lot of these errors. I’m running using Recursive and scanitemlevel: Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The operation has timed out” and Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The remote server returned an error: (503) Server Unavailable.”

    Reply
  • I am getting this error, could you please help:

    Add-Type : Cannot bind parameter ‘Path’ to the target. Exception setting “Path”: “Cannot find path ‘C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll’ because it does
    not exist.”
    At C:\pradeep\SPOsitepermission.ps1:1 char:16
    + … -Type -Path “C:Program FilesCommon FilesMicrosoft SharedWeb Serve …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : WriteError: (:) [Add-Type], ParameterBindingException
    + FullyQualifiedErrorId : ParameterBindingFailed,Microsoft.PowerShell.Commands.AddTypeCommand

    Add-Type : Cannot bind parameter ‘Path’ to the target. Exception setting “Path”: “Cannot find path ‘C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll’ because it
    does not exist.”
    At C:pradeepSPOsitepermission.ps1:2 char:16
    + … -Type -Path “C:\Program Files\Common Files\Microsoft Shared\Web Serve …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : WriteError: (:) [Add-Type], ParameterBindingException
    + FullyQualifiedErrorId : ParameterBindingFailed,Microsoft.PowerShell.Commands.AddTypeCommand

    cmdlet Get-Credential at command pipeline position 1
    Supply values for the following parameters:
    Credential
    Error Generating Site Permission Report! Cannot find type [Microsoft.SharePoint.Client.ClientContext]: verify that the assembly containing this type is loaded.

    Reply
  • I’ve run the script 2 times. Once with Recursive and once without. Each time it appears to hang on the same List in a subsite. It was hanging for 3 hours this morning before I cancelled it. I was Not doing ScanItemLevel permissions. Could it be because there are a lot of items in the document library? It says Exporting Permissions from List ‘Site Collection Documents’ and hangs there.

    Reply
  • Is this script supposed to report on subsites when given a site collection? It only works at whatever site is requested and not its sub sites.

    Reply
    • This script gets permission only for given web – When you don’t use -Recursive switch or when the subsites are inheriting permissions!

      Reply
  • Hello,
    This script worked great! I was wondering if you had a script that adds the users email address as well?

    Reply
    • Use: $GroupUsersEmails = ($GroupMembers | Select -ExpandProperty Email) -join “,”

      Reply
  • Hi,

    Thanks for writing the script!

    I am running v2 script but seem to not be able to report on unique folder and file permissions that sit within a document library. I am only getting reports for the top level of the site. I have tried using the recursive switch amongst others but does not appear to be working.

    Do you know how i can get the script to include all files and folders that have unique permissions like it shows in the print screen.

    Many Thanks,

    Sean

    Reply
    • You have to use the -ScanItemLevel switch. You can see the switches and parameters that are available at the very bottom of the script.

      Reply
  • Hi. Script gives me this error:

    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The remote server returned an error: (401) Unauthorized.”

    Any idea why? The user is a member of Sharepoint Administrators

    Reply
  • Hi Salaudeen, Thanks for the great script. Unfortunately, I am unable to fetch the permissions Recursively get permissions from all sub-webs. Can you please look in to this once and let me know.

    Reply
    • The first Script doesn’t get objects with Inherited permissions. In V2, include switches -Recursive -IncludeInheritedPermissions.

      Reply
    • Thank you! I am able to connect to SPO, however not sure how to use it with your above script to pull a detailed permission report. I replaced these two lines in the script:

      $Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
      $Ctx.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.UserName, $Cred.Password)

      with:

      $authManager = New-Object OfficeDevPnP.Core.AuthenticationManager
      $Ctx = $authManager.GetWebLoginClientContext($SiteURL)

      Also added the relevant packages from Nuget. Was very happy when the script ran, but clearly I am not as smart as I thought. ?‍♂️? the script only outputs the SCAs and the root web permissions – doesn’t recurse through subsites/lists/items etc.

      Reply
    • Hi Salaudeen,

      Is there a way to make this script work with MFA? We are not allowed to use app passwords either. TIA!

      Reply
    • Even After using switches in V2 of the script? -Recursive -ScanItemLevel -IncludeInheritedPermissions

      Reply
  • Thanks for writing this script. I am hitting an issue when the script hits an item with unique permissions that appears to be locked down ( even for the SPO Admin)
    Getting Permissions of List Items in the List: DataFeed
    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(
    s): “The remote server returned an error: (401) Unauthorized.”

    Now 1)I could add the list to the ExludedList but 2)I would prefer to generate a “Unable to read permissions on Item id : nn” and able to gracefully get passed this item.
    I have put a breakpoint on the line of code I think is the issue but any advice is welcome.

    Reply
    • You must have “Site collection Admin” rights! Add yourself to Site collection admin prior running the script!

      Reply
    • That is a good question. I believe I am as a member of admins group. it is only a certain listitem ID that seems to trigger the exception. I have Added this to your script to see what is causing the issue If ($ListItem.Id -eq nnnn)
      {
      Set-PSBreakpoint -Script Generate-SPOSitePermissionsRpt.Ps1 -Line 250
      }

      Reply
  • Hi Salaudeen, are we able to iterate through subsites and subsites of subsites?

    Reply
    • Hi Tony, Yes! This PowerShell script iterates through all subsites of all levels recursively.

      Reply
  • will the same script work for 2010?I need to compare source and target

    Reply
  • Awesome script, thanks a bunch! Saved me sooo much time.

    Reply
  • I’ve tried running this script (the v2 version) on an office 365 site, but it doesn’t seem to be working recursively. It lists the permissions of the objects on the site, but not for any folders or files in a document library. Any ideas on what I’m doing wrong?

    Reply
    • I’ve tried to do some debugging on this and when i put a break at “foreach ($Subweb in $Web.Webs)” and then look for at $web.webs.count it comes up as 0. The site I’m looking at has a few lists and a document library with some folders with unique permissions, but these are not being interrogated. Also the “If($recursive)” line is only being reached after the permissions of the immediate sites contents are noted. Is this normal?

      Reply
  • This is excellent stuff. I am concerned however about the execution time. I am analyzing a fairly large site collection and the script has been running for over 2 hours now. Is this typical?

    Also, can PowerShell handle 2 Progress Meters, where the first can show progress over the entire collection and the second as you currently have it?

    Reply
  • Version 1 Script is taking an exceptionally long time to run. Is this typical? I am analyzing a fairly large site collection. It has been running for over 2 hours now and not sure how close it is to completion. Can PowerShell handle two Progress Meters, The first to show progress over the entire collection and the second show progress as it currently does?

    Reply
  • Hi, thanks for the script. I got below, can you give me some suggestions please?
    ———————
    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The remote server returned an error: (500) Internal Server Error.”

    Reply
  • Hi, Is it possible to have it show the AzureAD displayName instead of the ObjectID on the Users?

    Reply
  • Thanks for a great script!
    Question – is it possible to run this on a specific folder/subfolder in the document library?

    Reply
  • Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The remote server retur
    ned an error: (401) Unauthorized.”

    Reply
    • Add the account that you are using as a owner to the site.

      Reply
  • Hi,

    I have followed the script where we can find SharePoint Groups and their members along with the permissions, but it doesn’t give unique users list and their permissions and second , it gives GUIDs instead of names . Can you look into this ?

    Thanks

    Reply
    • And this doesn’t include AD groups too. We are looking for a script where we can find permissions of all groups ( AD and SharePoint both ) and users ( permissions given directly ) for a SharePoint Online site collection . How can this be achieved ?

      Reply
  • Hi, I’m having this error while running the script. Any clues of what might be wrong or where I could look for the problem? Thanks in advance!

    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “Entity (External Content Type) cannot be found with Namespace = ‘namespace’, Name = ‘Role’.”

    Reply
  • Thank you for this great script. I’m getting this error if you could help :

    Error Generating User Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The remote server returned an error: (500) Internal Server Error.”

    I ran a search and found many “ExecuteQuery()”. Do i need to put a value in everyone? and what value should they be?

    Thank you

    Reply
  • This has helped and working well as is. Thank you!

    Reply
  • thanks for the script! I am running it over one document library where sub-folders have different permissions. is there a way to change column A so that it includes the true path for example it’s currently showing
    https://hereismy.sharepoint.com/sites/hereismysitename/Documents/Forms/DispForm.aspx?ID=8280
    but I would like to show the folder structure instead of the ID:
    https://hereismy.sharepoint.com/sites/hereismysitename/Documents/name_of_folder/name_of_subfolder

    Also can you please confirm (I think I saw it in your screen shot) that if a user is added direct to the Document Library (not to a sharepoint permission group) that there name will appear on the output. Thank you 🙂

    Reply
  • Same error as just above for me too.

    Reply
    • i think it has something to do with the list view setting in SP Online, I’ve ran successfully on a doc library with 4000 files but it failed on one with 9000

      Reply
  • thanks works really well,
    i seem to get the below error on my larger document libraries, any idea why?
    “ExecuteQuery” with “0” argument(s): “The remote server returned an error: (429)

    thanks!

    Reply
  • Hey Salaudeen, it seems like this is the only script for that purpose, so thanks for writing it!

    I tried to run it and was able to successfully output some info like SC admins and permissions of the web, but when it gets to the function for the lists items, it fails with the following error:

    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “Cannot complete this action.

    Please try again.”

    Any ideas where to look?

    Reply
  • Thanks but I noticed that this isn’t iterating through the sharepoint groups and exporting out the members… can you add that?

    Reply
  • Seeing this error on running. Is there a problem with the function?

    PS C:\users\Victor\Desktop> .SPO-SiteCollectionPermissionsReport.ps1

    At C:\Users\Victor\Desktop\SPO-SiteCollectionPermissionsReport.ps1:127 char:48
    + … ewXml = “2000”
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Unexpected token ‘RecursiveAll”>2000″‘ in expression or statement.
    + CategoryInfo : ParserError: (:) [], ParseException
    + FullyQualifiedErrorId : UnexpectedToken

    Reply
    • Thanks for the update. Any idea how to update this further? We have some pretty beefy lists/document library upwards of 5000 items. It seems to stall on bigger ones still.

      Reply
  • Salaudeen, this is great! But I’m still getting issues with lists over 5000 items — actually, lists over 2000 items. The script seems to stall. Are you sure the list threshold issue is resolved? Do you need to paginate the query?

    Reply
  • CAn i use this to get permissions for a specifi library?

    Reply
  • Thank you! This is a great script.

    The output for folders and subfolders are written as ../Forms/DispForm.aspx?ID=nnn. Can you include the actual name of the folders and subfolders?

    Thank you!

    Reply
  • Hi Salaudeen,

    Did you modify this script?

    I’m getting error like,

    “Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The sign-in name or password does not match one in the Microsoft account system.”

    I have entered the correct credentials and I’m a tenant admin for SharePoint Online.

    Thanks.

    Reply
    • I am having the same error and I am using MFA. Where do you “Leave the -Credentials parameter” at?

      Reply
    • You will need to use your app password for MFA instead of the regular password you use. When you enable the MFA it displays a special app password string you will use for static apps or scripts to use.

      Reply
  • Hi I am getting below list view threshold error, any ideas please because we have more list in our sites.
    Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The attempted operation is prohibited because it exceeds the list view threshold enforced by the administrator.”

    Reply
    • This is because: You have larger lists with > 5000 List items. The script has been updated to handle larger lists. Try now!

      Reply
  • The script does not show a title for the list item

    Reply
  • This comment has been removed by the author.

    Reply
  • hi i have the same error
    Getting below error: Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The specified user could not be found.”

    #Read more: https://www.sharepointdiary.com/2018/09/sharepoint-online-site-collection-permission-report-using-powershell.html#ixzz5SlTVyKNr

    Reply
  • Getting below error: Error Generating Site Permission Report! Exception calling “ExecuteQuery” with “0” argument(s): “The specified user could not be found.”

    Any thought?

    Reply
    • Guess it happens when the user doesn’t exist (Orphan Users!).. Let me check if this can be avoided.

      Reply
  • I tried your script and it worked like a charm!! Thanks a Million Salaudeen!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *