SharePoint Online: User Permissions Audit Report for a Site Collection using PnP PowerShell

Requirement: Need a report to audit SharePoint Online site collection permissions.

SharePoint Online: User Permissions report in a Site Collection using PnP PowerShell

Auditing SharePoint Online Site permissions is critical for security as misconfigured permissions may enable users to access to data that they should not have access to. So, regular auditing of SharePoint permissions is crucial to minimizing the risk of data leaks and compliance violations. As there are no out-of-the-box ways to generate permissions report, we can utilize PowerShell to get a list of effective permissions for your SharePoint site collections. This PowerShell script generates permission report on all objects such as SharePoint Online Site collection and its subsites, lists and libraries, folders and list items 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.

SharePoint Online PowerShell Permissions Report

Here is the PnP PowerShell script to generate user permissions report for a SharePoint Online site collection.

#Function to Get Permissions Applied on a particular Object, such as: Web, List, Folder or List Item
Function Get-PnPPermissions([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 
                $Folder = Get-PnPProperty -ClientObject $Object -Property Folder
                $ObjectTitle = $Object.Folder.Name
                $ObjectURL = $("{0}{1}" -f $Web.Url.Replace($Web.ServerRelativeUrl,''),$Object.Folder.ServerRelativeUrl)
            }
            Else #File or List Item
            {
                #Get the URL of the Object
                Get-PnPProperty -ClientObject $Object -Property File, ParentList
                If($Object.File.Name -ne $Null)
                {
                    $ObjectType = "File"
                    $ObjectTitle = $Object.File.Name
                    $ObjectURL = $("{0}{1}" -f $Web.Url.Replace($Web.ServerRelativeUrl,''),$Object.File.ServerRelativeUrl)
                }
                else
                {
                    $ObjectType = "List Item"
                    $ObjectTitle = $Object["Title"]
                    #Get the URL of the List Item
                    $DefaultDisplayFormUrl = Get-PnPProperty -ClientObject $Object.ParentList -Property DefaultDisplayFormUrl                     
                    $ObjectURL = $("{0}{1}?ID={2}" -f $Web.Url.Replace($Web.ServerRelativeUrl,''), $DefaultDisplayFormUrl,$Object.ID)
                }
            }
        }
        Default 
        { 
            $ObjectType = "List or Library"
            $ObjectTitle = $Object.Title
            #Get the URL of the List or Library
            $RootFolder = Get-PnPProperty -ClientObject $Object -Property RootFolder     
            $ObjectURL = $("{0}{1}" -f $Web.Url.Replace($Web.ServerRelativeUrl,''), $RootFolder.ServerRelativeUrl)
        }
    }
  
    #Get permissions assigned to the object
    Get-PnPProperty -ClientObject $Object -Property HasUniqueRoleAssignments, RoleAssignments

    #Check if Object has unique permissions
    $HasUniquePermissions = $Object.HasUniqueRoleAssignments
    
    #Loop through each permission assigned and extract details
    $PermissionCollection = @()
    Foreach($RoleAssignment in $Object.RoleAssignments)
    { 
        #Get the Permission Levels assigned and Member
        Get-PnPProperty -ClientObject $RoleAssignment -Property RoleDefinitionBindings, Member

        #Get the Principal Type: User, SP Group, AD Group
        $PermissionType = $RoleAssignment.Member.PrincipalType
   
        #Get the Permission Levels assigned
        $PermissionLevels = $RoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name

        #Remove Limited Access
        $PermissionLevels = ($PermissionLevels | Where { $_ -ne "Limited Access"}) -join ","

        #Leave Principals with no Permissions
        If($PermissionLevels.Length -eq 0) {Continue}

        #Get SharePoint group members
        If($PermissionType -eq "SharePointGroup")
        {
            #Get Group Members
            $GroupMembers = Get-PnPGroupMembers -Identity $RoleAssignment.Member.LoginName
                
            #Leave Empty Groups
            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-PnPSitePermissionRpt()
{
[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 {
        #Connect to the Site
        Connect-PnPOnline -URL $SiteURL -UseWebLogin
        #Get the Web
        $Web = Get-PnPWeb

        Write-host -f Yellow "Getting Site Collection Administrators..."
        #Get Site Collection Administrators
        $SiteAdmins = Get-PnPSiteCollectionAdmin
        
        $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($Web.Title)
        $Permissions | Add-Member NoteProperty URL($Web.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-PnPListItemsPermission([Microsoft.SharePoint.Client.List]$List)
        {
            Write-host -f Yellow "`t `t Getting Permissions of List Items in the List:"$List.Title
 
            #Get All Items from List in batches
            $ListItems = Get-PnPListItem -List $List -PageSize 500
 
            $ItemCounter = 0
            #Loop through each List item
            ForEach($ListItem in $ListItems)
            {
                #Get Objects with Unique Permissions or Inherited Permissions based on 'IncludeInheritedPermissions' switch
                If($IncludeInheritedPermissions)
                {
                    Get-PnPPermissions -Object $ListItem
                }
                Else
                {
                    #Check if List Item has unique permissions
                    $HasUniquePermissions = Get-PnPProperty -ClientObject $ListItem -Property HasUniqueRoleAssignments
                    If($HasUniquePermissions -eq $True)
                    {
                        #Call the function to generate Permission report
                        Get-PnPPermissions -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)'"
            }
        }

        #Function to Get Permissions of all lists from the given web
        Function Get-PnPListPermission([Microsoft.SharePoint.Client.Web]$Web)
        {
            #Get All Lists from the web
            $Lists = Get-PnPProperty -ClientObject $Web -Property Lists
  
            #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 "Exporting Permissions from List '$($List.Title)' in $($Web.URL)" -Status "Processing Lists $Counter of $($Lists.Count)"

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

                    #Get Lists with Unique Permissions or Inherited Permissions based on 'IncludeInheritedPermissions' switch
                    If($IncludeInheritedPermissions)
                    {
                        Get-PnPPermissions -Object $List
                    }
                    Else
                    {
                        #Check if List has unique permissions
                        $HasUniquePermissions = Get-PnPProperty -ClientObject $List -Property HasUniqueRoleAssignments
                        If($HasUniquePermissions -eq $True)
                        {
                            #Call the function to check permissions
                            Get-PnPPermissions -Object $List
                        }
                    }
                }
            }
        }
  
        #Function to Get Webs's Permissions from given URL
        Function Get-PnPWebPermission([Microsoft.SharePoint.Client.Web]$Web) 
        {
            #Call the function to Get permissions of the web
            Write-host -f Yellow "Getting Permissions of the Web: $($Web.URL)..."  
            Get-PnPPermissions -Object $Web
  
            #Get List Permissions
            Write-host -f Yellow "`t Getting Permissions of Lists and Libraries..."
            Get-PnPListPermission($Web)

            #Recursively get permissions from all sub-webs based on the "Recursive" Switch
            If($Recursive)
            {
                #Get Subwebs of the Web
                $Subwebs = Get-PnPProperty -ClientObject $Web -Property Webs

                #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-PnPWebPermission($Subweb)
                    }
                    Else
                    {
                        #Check if the Web has unique permissions
                        $HasUniquePermissions = Get-PnPProperty -ClientObject $SubWeb -Property HasUniqueRoleAssignments
  
                        #Get the Web's Permissions
                        If($HasUniquePermissions -eq $true) 
                        { 
                            #Call the function recursively                            
                            Get-PnPWebPermission($Subweb)
                        }
                    }
                }
            }
        }

        #Call the function with RootWeb to get site collection permissions
        Get-PnPWebPermission $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"
#endregion

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

Here is the permission report generated by this script:

sharepoint online permission report using PnP PowerShell

While the above script serves the purpose, What if you want to include only up to folders but not list items or files in the permission report? In other words, the above script takes a lot of time when dealing with larger lists and libraries when you use -scanitemlevel switch.

PowerShell Script to Generate Permission Report for SharePoint Online Site

#Function to Get Permissions Applied on a particular Object, such as: Web, List or Folder
Function Get-PnPPermissions([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"
        {
            $ObjectType = "Folder"
            #Get the URL of the Folder 
            $Folder = Get-PnPProperty -ClientObject $Object -Property Folder
            $ObjectTitle = $Object.Folder.Name
            $ObjectURL = $("{0}{1}" -f $Web.Url.Replace($Web.ServerRelativeUrl,''),$Object.Folder.ServerRelativeUrl)
        }
        Default 
        { 
            $ObjectType = $Object.BaseType #List, DocumentLibrary, etc
            $ObjectTitle = $Object.Title
            #Get the URL of the List or Library
            $RootFolder = Get-PnPProperty -ClientObject $Object -Property RootFolder     
            $ObjectURL = $("{0}{1}" -f $Web.Url.Replace($Web.ServerRelativeUrl,''), $RootFolder.ServerRelativeUrl)
        }
    }
   
    #Get permissions assigned to the object
    Get-PnPProperty -ClientObject $Object -Property HasUniqueRoleAssignments, RoleAssignments
 
    #Check if Object has unique permissions
    $HasUniquePermissions = $Object.HasUniqueRoleAssignments
     
    #Loop through each permission assigned and extract details
    $PermissionCollection = @()
    Foreach($RoleAssignment in $Object.RoleAssignments)
    { 
        #Get the Permission Levels assigned and Member
        Get-PnPProperty -ClientObject $RoleAssignment -Property RoleDefinitionBindings, Member
 
        #Get the Principal Type: User, SP Group, AD Group
        $PermissionType = $RoleAssignment.Member.PrincipalType
    
        #Get the Permission Levels assigned
        $PermissionLevels = $RoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name
 
        #Remove Limited Access
        $PermissionLevels = ($PermissionLevels | Where { $_ -ne "Limited Access"}) -join "; "
 
        #Leave Principals with no Permissions assigned
        If($PermissionLevels.Length -eq 0) {Continue}
 
        #Check if the Principal is SharePoint group
        If($PermissionType -eq "SharePointGroup")
        {
            #Get Group Members
            $GroupMembers = Get-PnPGroupMembers -Identity $RoleAssignment.Member.LoginName
                 
            #Leave Empty Groups
            If($GroupMembers.count -eq 0){Continue}
            $GroupUsers = ($GroupMembers | Select -ExpandProperty Title | Where { $_ -ne "System Account"}) -join "; "
            If($GroupUsers.Length -eq 0) {Continue}

            #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 #User
        {
            #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-PnPSitePermissionRpt()
{
[cmdletbinding()]

    Param  
    (    
        [Parameter(Mandatory=$false)] [String] $SiteURL, 
        [Parameter(Mandatory=$false)] [String] $ReportFile,         
        [Parameter(Mandatory=$false)] [switch] $Recursive,
        [Parameter(Mandatory=$false)] [switch] $ScanFolders,
        [Parameter(Mandatory=$false)] [switch] $IncludeInheritedPermissions
    )  
    Try {
        #Connect to the Site
        Connect-PnPOnline -URL $SiteURL -UseWebLogin
        #Get the Web
        $Web = Get-PnPWeb
 
        Write-host -f Yellow "Getting Site Collection Administrators..."
        #Get Site Collection Administrators
        $SiteAdmins = Get-PnPSiteCollectionAdmin
         
        $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($Web.Title)
        $Permissions | Add-Member NoteProperty URL($Web.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 Folders in a given List
        Function Get-PnPFolderPermission([Microsoft.SharePoint.Client.List]$List)
        {
            Write-host -f Yellow "`t `t Getting Permissions of Folders in the List:"$List.Title
            
            #Get All Folders from List
            $ListItems = Get-PnPListItem -List $List -PageSize 2000
            $Folders = $ListItems | Where { ($_.FileSystemObjectType -eq "Folder") -and ($_.FieldValues.FileLeafRef -ne "Forms") -and (-Not($_.FieldValues.FileLeafRef.StartsWith("_")))}

            $ItemCounter = 0
            #Loop through each Folder
            ForEach($Folder in $Folders)
            {
                #Get Objects with Unique Permissions or Inherited Permissions based on 'IncludeInheritedPermissions' switch
                If($IncludeInheritedPermissions)
                {
                    Get-PnPPermissions -Object $Folder
                }
                Else
                {
                    #Check if Folder has unique permissions
                    $HasUniquePermissions = Get-PnPProperty -ClientObject $Folder -Property HasUniqueRoleAssignments
                    If($HasUniquePermissions -eq $True)
                    {
                        #Call the function to generate Permission report
                        Get-PnPPermissions -Object $Folder
                    }
                }
                $ItemCounter++
                Write-Progress -PercentComplete ($ItemCounter / ($Folders.Count) * 100) -Activity "Getting Permissions of Folders in List '$($List.Title)'" -Status "Processing Folder '$($Folder.FieldValues.FileLeafRef)' at '$($Folder.FieldValues.FileRef)' ($ItemCounter of $($Folders.Count))" -Id 2 -ParentId 1
            }
        }
 
        #Function to Get Permissions of all lists from the given web
        Function Get-PnPListPermission([Microsoft.SharePoint.Client.Web]$Web)
        {
            #Get All Lists from the web
            $Lists = Get-PnPProperty -ClientObject $Web -Property Lists
   
            #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 "Exporting Permissions from List '$($List.Title)' in $($Web.URL)" -Status "Processing Lists $Counter of $($Lists.Count)" -Id 1
 
                    #Get Item Level Permissions if 'ScanFolders' switch present
                    If($ScanFolders)
                    {
                        #Get Folder Permissions
                        Get-PnPFolderPermission -List $List
                    }
 
                    #Get Lists with Unique Permissions or Inherited Permissions based on 'IncludeInheritedPermissions' switch
                    If($IncludeInheritedPermissions)
                    {
                        Get-PnPPermissions -Object $List
                    }
                    Else
                    {
                        #Check if List has unique permissions
                        $HasUniquePermissions = Get-PnPProperty -ClientObject $List -Property HasUniqueRoleAssignments
                        If($HasUniquePermissions -eq $True)
                        {
                            #Call the function to check permissions
                            Get-PnPPermissions -Object $List
                        }
                    }
                }
            }
        }
   
        #Function to Get Webs's Permissions from given URL
        Function Get-PnPWebPermission([Microsoft.SharePoint.Client.Web]$Web) 
        {
            #Call the function to Get permissions of the web
            Write-host -f Yellow "Getting Permissions of the Web: $($Web.URL)..." 
            Get-PnPPermissions -Object $Web
   
            #Get List Permissions
            Write-host -f Yellow "`t Getting Permissions of Lists and Libraries..."
            Get-PnPListPermission($Web)
 
            #Recursively get permissions from all sub-webs based on the "Recursive" Switch
            If($Recursive)
            {
                #Get Subwebs of the Web
                $Subwebs = Get-PnPProperty -ClientObject $Web -Property Webs
 
                #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-PnPWebPermission($Subweb)
                    }
                    Else
                    {
                        #Check if the Web has unique permissions
                        $HasUniquePermissions = Get-PnPProperty -ClientObject $SubWeb -Property HasUniqueRoleAssignments
   
                        #Get the Web's Permissions
                        If($HasUniquePermissions -eq $true) 
                        { 
                            #Call the function recursively                            
                            Get-PnPWebPermission($Subweb)
                        }
                    }
                }
            }
        }
 
        #Call the function with RootWeb to get site collection permissions
        Get-PnPWebPermission $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/legal/"
$ReportFile="C:\Temp\Legal-SitePermissionRpt.csv"
#endregion
 
#Call the function to generate permission report
Generate-PnPSitePermissionRpt -SiteURL $SiteURL -ReportFile $ReportFile -ScanFolders
#Generate-PnPSitePermissionRpt -SiteURL $SiteURL -ReportFile $ReportFile -Recursive -ScanFolders -IncludeInheritedPermissions
    

How to Generate Report for All Site Collections in the Tenant?

We can just call the function for all sites in the tenant as:

#Connect to Admin Center
$Cred = Get-Credential
Connect-PnPOnline -Url $TenantAdminURL -Credentials $Cred
  
#Get All Site collections - Exclude: Seach Center, Mysite Host, App Catalog, Content Type Hub, eDiscovery and Bot Sites
$SitesCollections = Get-PnPTenantSite | Where -Property Template -NotIn ("SRCHCEN#0", "SPSMSITEHOST#0", "APPCATALOG#0", "POINTPUBLISHINGHUB#0", "EDISC#0", "STS#-1")
  
#Loop through each site collection
ForEach($Site in $SitesCollections)
{
    #Connect to site collection
    $SiteConn = Connect-PnPOnline -Url $Site.Url -Credentials $Cred
    Write-host "Generating Report for Site:"$Site.Url

    #Call the Function for site collection
    $ReportFile = "C:\Temp\$($Site.URL.Replace('https://','').Replace('/','_')).CSV"
    Generate-PnPSitePermissionRpt -SiteURL $Site.URL -ReportFile $ReportFile -Recursive   

    Disconnect-PnPOnline -Connection $SiteConn
}

Salaudeen Rajack

Information Technology Professional with Two decades of SharePoint Experience.

62 thoughts on “SharePoint Online: User Permissions Audit Report for a Site Collection using PnP PowerShell

  • July 7, 2021 at 12:21 PM

    How can i declare Multiple sites in there? i would like to generate Sites and Sub sites at one instance and not to run the script with separate URL?

    Reply
  • July 5, 2021 at 7:07 AM

    Hi, this script doesn’t work for me. Is there an update to it? I receive 401 unauthorized errors. thanks!

    Reply
  • March 31, 2021 at 8:19 PM

    I am pulling “Error Generating Site Permission Report! Could not find a part of the path ‘C:TempLegal-SitePermissionRpt.csv’.”

    Reply
  • March 24, 2021 at 8:18 PM

    This was answered previously. I follow these steps and it resolved the error:
    https://www.sharepointdiary.com/2019/02/import-module-could-not-load-type-microsoft-sharepoint-administration-designpackagetype-from-assembly.html

    Reply
  • March 19, 2021 at 4:16 PM

    Does it report on files/folders shared? Thanks!

    Reply
  • March 11, 2021 at 4:36 AM

    Is it possible to get report for “everyone” and and “everyone except external users” only? Can we add a switch or something?

    Reply
  • March 3, 2021 at 12:36 AM

    I am getting this error.
    Error Generating Site Permission Report! Cannot process argument transformation on parameter ‘Web’. Cannot convert the “Microsoft.SharePoint.Client.Web” value of type “Microsoft.SharePoint.Client.Web” to type “Microsoft.SharePoint.Client.Web”
    still getting the same error after update the PnP Module: Update-Module SharePointPnPPowerShellOnline
    Please advise
    Thanks,

    Reply
  • March 3, 2021 at 12:32 AM

    I am getting the same error
    Error Generating Site Permission Report! Cannot process argument transformation on parameter ‘Web’. Cannot convert the “Microsoft.SharePoint.Client.Web” value of type “Microsoft.Sh
    arePoint.Client.Web” to type “Microsoft.SharePoint.Client.Web”.
    still get the same error after update PnP Module: Update-Module SharePointPnPPowerShellOnline
    Please advise

    Thanks,

    Reply
  • February 22, 2021 at 10:24 PM

    Doesn’t work in VSCode I guess?

    Error Generating Site Permission Report! Cannot process argument transformation on parameter ‘Web’. Cannot convert the “Microsoft.SharePoint.Client.Web” value of type “Microsoft.SharePoint.Client.Web” to type “Microsoft.SharePoint.Client.Web”.

    Reply
  • February 22, 2021 at 11:06 AM

    Hello there,

    Thanks for this site, really helpful.
    I don’t know much about PS scripting or PS at all really.

    I need to know how to include a listing of all the actual files in the report but also importantly need to include unique sharing permissions, (the permissions that come from sharing links).

    Can anyone help me with this?

    Thanks
    Kyle

    Reply
  • February 4, 2021 at 4:05 PM

    Hello,

    I get issue after issue with the pnp commandlets… not found etc.. Anyone know why?

    Error Generating Site Permission Report! The term ‘Connect-PnPOnline’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

    Error Generating Site Permission Report! The term ‘Get-PnPSiteCollectionAdmin’ is not recognized as the name of a cmdlet, function, script file, or oper
    able program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

    Reply
  • December 7, 2020 at 7:59 PM

    Is there a way to perform this script on a subsite, for instance: xxxxxx.sharepoint.com/Shared%20Documents/ ? Or even to specify further from there?

    Reply
  • November 23, 2020 at 4:06 PM

    Thank you greatly for this script! Is there a way to make use of the “All sites in tenant” part of the script, but add it all to one CSV instead of one-per site collection?

    Reply
    • November 23, 2020 at 6:20 PM

      You can use the last script under section “How to Generate Report for All Site Collections in the Tenant?”, Replace the $ReportFile variable with a path. E.g. “C:ReportsPermissionRpt.csv”

      Reply
    • November 23, 2020 at 7:33 PM

      That seems to just overwrite the report with the most recent collections info. It doesn’t append.

      Reply
    • November 30, 2020 at 2:35 PM

      That did it. Thanks!

      Reply
  • October 11, 2020 at 7:42 AM

    Great script, thank you. I’ve been getting the following error when I run the first script with -Recursive -ScanItemLevel -IncludeInheritedPermissions enabled.

    The collection has not been initialized. It has not been requested or the request has not been executed. It may need to be explicitly requested.

    It doesn’t happen every time. I found restarting the PowerShell ISE or breaking it up to process a subset of results helped. There are approximately 9,000 rows in the full report for the site collection. It would appear to be an “out of memory” problem, but it’s failed with 4.7 GB of memory available. Any ideas? It I’m using version 3.25.2009.1 of the SharePointPnPPowerShellOnline module.

    Reply
    • October 12, 2020 at 8:19 AM

      Follow up: I had more success by putting a delay into the end of the loop in Get-PnPListItemsPermission. This may indicate that the problem is with throttling. However, it does mean the code is slower.

      # Pause for 10 seconds after every 20 items are processed
      if($ItemCounter % 20 -eq 0)
      {
      Start-Sleep -Seconds 10
      }

      Reply
  • October 8, 2020 at 2:41 PM

    Is there anyway to have this iterate through all site collections at once? Maybe by using Get-PnPTenantSite? I’m newer to using/modifying these large scripts.

    Reply
    • November 16, 2020 at 11:10 PM

      When using the last script that iterates through the entire tenant, I am getting the error that “Generate-PnPSitePermissionRpt : The term ‘Generate-PnPSitePermissionRpt’ is not recognized as the name of a cmdlet,”. I have the latest module 3.25.2009.1 (that works at least, supposedly) of SharePointPnPPowerShellOnline. I have done some research looking for this cmdlet or function. Any help is welcomed

      Reply
    • November 17, 2020 at 1:17 PM

      The “Generate-PnPSitePermissionRpt” function is defined just above the script! You got to copy that as well (Till Line#260)!

      Reply
    • November 17, 2020 at 5:30 PM

      Thank you so much for your help. I probably need to copy the top script given the -ScanItemLevel parameter to get granular permissions for the tenant, correct? Line #260 doesn’t make since to copy through in the top script, though. I will work my way through this but if you get a chance, I would not mind a second hint. 🙂

      Reply
  • September 16, 2020 at 8:03 AM

    Great work Salaudeen but I keep getting

    Get-PnPWeb : The remote server returned an error: (403) Forbidden.

    Despite using an account with Sharepoint Admin / Site Owner roles ?

    If I run

    Connect-PnPOnline -URL $SiteURL -UseWebLogin

    I get no errors.

    But this line

    $Web = Get-PnPWeb

    Produces he 403 forbidden

    Reply
  • September 1, 2020 at 2:10 PM

    Nice script. thanks for sharing!

    Reply
  • August 23, 2020 at 11:14 AM

    Hello

    Is there any way to check who provided the permission using powershell? For example I have one admin group with full permission. This admin group having 4 users, who can add/remove/change users to different group like reader and writer group. I want to track the activities which owner provide add/remove/change the user and how many time user add/removed by particular owner.

    I want to do it using PowerShell for SP16 and SPO.

    Regards
    Avian

    Reply
  • August 6, 2020 at 9:41 PM

    Hi, great script. I was able to use the second script and not the first script. The first script would only evaluate sites, no subsites, no lists, or items. It returns 3 rows (1 Site Collection, and two sites of the site collection). The second script seems to work so much better and I was able to make a loop to iterate through the other sites of a Sharepoint.

    Is there a way to write a function Get-PnPItemsPermissions? The item (docx, excel, etc.) could be in a folder or a list. That would help so much and avoid using the -scanitemlevel switch. Thank you so much for your hard work, I’m here to learn!

    Reply
    • August 23, 2020 at 6:55 PM

      Use -ScanFolders switch in the 2nd script to limit searching permissions up to the Folders scope! Otherwise, It runs by scanning Item level permissions.

      Reply
  • July 29, 2020 at 4:01 PM

    The script worked fine for me.

    However, it’s still not displaying the permissions for a lot of documents having unique permissions.
    The library itself has unique permissions so it’s showing for library level but not for file level.
    Please let me know what am I missing.

    Reply
    • July 29, 2020 at 5:00 PM

      Use the First script in this article with -ScanItemLevel switch:
      Generate-PnPSitePermissionRpt -SiteURL $SiteURL -ReportFile $ReportFile -Recursive -ScanItemLevel

      Reply
  • May 18, 2020 at 9:35 PM

    Getting “The remote server returned an error: (400) Bad Request.” with both Get-PnPWeb and Get-PnpSiteCollectionAdmin commands in either script. I’ve got all the prerequisites installed and working and I’ve tried it on two different computers. 2FA is enabled. Both successfully login. One is also Azure joined. No dice.

    Reply
  • May 11, 2020 at 1:18 PM

    How do I capture the email address of the user with permissions too?

    Reply
  • April 30, 2020 at 8:55 PM

    Disregard my previous comment. I followed your instructions for fixing the CSOM assemblies in the GAC and it worked perfectly. This is a superb script and a great site overall. Many thanks!!

    Reply
  • April 30, 2020 at 5:10 PM

    Getting the following error: Error Generating Site Permission Report! Cannot process argument transformation on parameter ‘Object’. Cannot convert the “Microsoft.SharePoint.Client.Web” value of type “Microsoft.SharePoint.Client.Web” to type “Microsoft.SharePoint.Client.SecurableObject”.

    Have tried updating the module but the error remains

    Reply
    • May 3, 2020 at 8:01 PM

      Try restarting the PowerShell ISE or PowerShell console. Otherwise, Remove the [Microsoft.SharePoint.Client.Web] type from the parameter!

      Reply
  • April 16, 2020 at 6:31 AM

    Can I use this script to generate permission for sub-site?

    Reply
  • April 15, 2020 at 9:32 PM

    Excellent script, thanks very much.
    I had to make one small change in the second script: $folders = @( $listItems | where… )
    I think where there is one item, $folders does not have a “count” property and it fails.
    Using @() forces it to be an array

    Reply
  • March 19, 2020 at 4:36 PM

    This is an excellent work. Thank you very much

    Reply
  • March 6, 2020 at 4:18 PM

    Hi,

    I only seem to be able to report on site permissions. I can not report on unique file and folder permissions which are further down the document library.

    Do you know why this is?

    Thanks,

    Sean

    Reply
  • March 5, 2020 at 3:55 AM

    Do you have a similar script that work for SharePoint 2016 on-premise?

    Reply
  • February 28, 2020 at 1:27 PM

    Can you help Error Generating Site Permission Report! Cannot process argument transformation on parameter ‘Object’. Cannot convert the “Microsoft.SharePoint.Client.Web” value of type “Microsoft.SharePoint
    .Client.Web” to type “Microsoft.SharePoint.Client.SecurableObject”.

    Reply
  • February 7, 2020 at 6:51 PM

    Hi,I am getting this error.
    Error Generating Site Permission Report! Cannot process argument transformation on parameter ‘Web’. Cannot convert the
    “Microsoft.SharePoint.Client.Web” value of type “Microsoft.SharePoint.Client.Web” to type “Microsoft.SharePoint.Clien
    t.Web”

    Reply
  • February 4, 2020 at 7:24 PM

    This works perfectly for me needs, thank you so much for sharing your hard work!

    Reply
  • November 29, 2019 at 8:09 PM

    Thank you for great work and sharing the script! I’ve used your script and adjusted to my needs. The only thing is the speed. If I need to generate a report on a Site Collection with few libraries, which have over 35k files/folders. To run it takes forever. As it have to go through each item/file and check if HasUniqueRoleAssignments=TRUE. Is it any way to speed up this? Any way to get List Items with HasUniqueRoleAssignments True filtered from beginning? Was looking arround on Internet and didn’t find any solutions or suggestions. I’m ok to mix it with CSOM if needed, but didn’t find how yet.
    Thanks again for your help!

    Reply
    • December 10, 2019 at 9:38 AM

      I understand! But unfortunately, we don’t have any mechanism to handle this issue as of today!

      Reply
    • December 16, 2019 at 7:34 AM

      and SharePoint Server Object model offers: SPList.GetItemsWithUniquePermission() method which gets you all items with unique permissions from a List instantly! However, this method is not available in CSOM yet.

      Reply
  • October 23, 2019 at 4:46 AM

    Thanks for your excellent sharing! But I encounter a problem when I add my site-url value at the end of this method and run this code with the error of ‘Error generating site permission report! Cannot process argument transformation on parameter ‘Web’. Cannot convert the ‘Microsoft.Sharepoint.Client.Web’ value of type ‘Microsoft.Sharepoint.Client.Web’ to type ‘Microsoft.Sharepoint.Client.Web’. What I have done: connect-SPOService, connect-PnPOnline, and load SharePoint CSOM Assemblies. Can you please to tell me how to figure out this error and export the whole permission report successfully? Thanks a lot.

    Reply
    • October 30, 2019 at 12:51 PM

      Try removing explicit type “[Microsoft.SharePoint.Client.Web]”. E.g. Instead of [Microsoft.SharePoint.Client.Web]$Web , Use just: $Web

      Reply
    • November 21, 2019 at 4:05 PM

      I’ve tried removing [Microsoft.SharePoint.Client.Web] and it failed. Error Generating Site Permission Report! Cannot process argument transformation on parameter ‘Object’. Cannot convert the “Microsoft.SharePoint.Client.Web” value of type “Microsoft.SharePoint.Client.Web” to type “Microsoft.SharePoint.Client.SecurableObject”. Please help!.

      Reply
    • December 10, 2019 at 10:16 AM

      Looks to me this is due version conflict from CSOM assemblies deployed in GAC! Can you try this: https://www.sharepointdiary.com/2019/02/import-module-could-not-load-type-microsoft-sharepoint-administration-designpackagetype-from-assembly.html

      Reply
    • January 9, 2020 at 5:13 AM

      You’re a legend!

      Reply
  • October 16, 2019 at 3:10 AM

    Sorry that after I could not see any items/folder level permission on csv file, do you know what’s wrong with me?

    Reply
  • October 12, 2019 at 1:53 AM

    it’s really good,thank you!
    is it possible to dump all permissions report just for the lists/folders? The code is now exporting every file permissions,and making the csv too big to open

    Reply
  • October 8, 2019 at 2:31 PM

    I was not able to run script and was getting error saying Get-PnPPermissions not recognized. i added Get-PnPGroupPermissions and it worked. I would like to have email address of users too. is it possible for you to modify in such way ?

    Reply
    • October 8, 2019 at 3:31 PM

      Get-PnPPermissions is a custom function name defined in the top of this script! You can get user Email address with: $RoleAssignment.Member.LoginName .

      Reply

Leave a Reply