How to Copy User Permissions in SharePoint Online using PowerShell?

Permission management in SharePoint Online is always a complex task, especially in large environments. Granting permissions in SharePoint becomes cumbersome when you are trying to clone an existing user’s access rights. Consider this scenario: You have an existing user in a department granted access to various SharePoint Sites, lists, folders, files, etc. and when a new user joins this department, You – SharePoint Administrator get the requirement of adding a new user to all the places with the same access rights as the existing team member!

So, How to clone user permissions in SharePoint Online? There are no ways to copy permissions from one user to another user out of the box! You have to iterate through the below permission hierarchy, check if the source user has permissions, and then add the new user manually. Sounds simple? Hold-on! This will be nearly impossible when you have a larger site with lots of data! You can’t scan through 1000s of files in a document library, isn’t it?

SharePoint Online: Copy Permissions from One user to Another using PowerShell

Creating an AD group, SharePoint group, or Microsoft 365 group to manage permissions would be one option! So, you can simply add the new user to the respective group. Using 3rd party tools such as ShareGate, ControlPoint, Boost Solutions Permission manager would be another option that involves cost. Well, there is another free option. PowerShell!

SharePoint Online Permission Hierarchy

Any user in SharePoint Online gets access through these ways:

  • Added as a site collection administrator (or primary owner)
  • Permissions granted at site level, either as part of SharePoint group or with direct permissions
  • Permissions granted to list or libraries by breaking inheritance 
  • Access rights may be via List item (File) or folder level permissions.

So, if you want to copy an existing user’s permissions, You have to look through all the above objects and then grant permission to the new user, if the existing user you want to copy has permissions on it.

You must run this script with Site collection Administrator permissions!

PowerShell Script to Clone User Permissions in SharePoint Online

Let’s use PowerShell to clone users’ permissions. This script iterates through each level of the permission hierarchy such as Site Collection, Subsite, List and libraries, Folder, List Item. It copies the given source user’s permissions to the target user if the source user has permissions on it. Set the parameters for variables $SourceUser, $TargetUser, and $SiteURL accordingly and run the script. You’ll find the script outputs log text on the screen, wherever it copies permissions.

#Function to copy user permissions 
Function Copy-PnPUserPermission($SourceUser, $TargetUser, [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" ; $ObjectURL = $Object.FieldValues.FileRef; $ObjectTitle = $Object.FieldValues.FileLeafRef
            }
            ElseIf($Object.FileSystemObjectType -eq "File")
            {
                $ObjectType = "File" ; $ObjectURL = $Object.FieldValues.FileRef; $ObjectTitle = $Object.FieldValues.FileLeafRef
            }
            Else
            {
                $ObjectType = "List Item"; $ObjectURL = $Object.FieldValues.FileRef ;$ObjectTitle = $Object.FieldValues.Title
            }
        }
        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 = $Object.RootFolder.ServerRelativeUrl
        }
    }
    
    #Get all users and group permissions assigned to the object
    $RoleAssignments = Get-PnPProperty -ClientObject $Object -Property RoleAssignments

    #Get Direct Permissions applied to the Target User on the Object
    $TargetUserPermissions=@()
    ForEach($RoleAssignment in $RoleAssignments)
    {
        Get-PnPProperty -ClientObject $RoleAssignment -Property RoleDefinitionBindings, Member
        $TargetUserRA = $RoleAssignment | Where {$_.Member.PrincipalType -eq "User" -and $_.Member.LoginName -eq $TargetUser.LoginName}
        If($TargetUserRA)
        {    
            $TargetUserPermissions = $RoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name
        }
    }

    #Loop through each user or group associated with the object
    $TotalRoleAssignments = $Object.RoleAssignments.Count    
    For($i=0; $i -lt $TotalRoleAssignments; $i++)
    {
        $SourceRoleAssignment = $Object.RoleAssignments[$i]
        #Get all permission levels assigned to User account directly or via SharePoint Group
        $SourceUserPermissions=@()
        Get-PnPProperty -ClientObject $SourceRoleAssignment -Property RoleDefinitionBindings, Member

        #Leave the Hidden Permissions
        If($SourceRoleAssignment.Member.IsHiddenInUI -eq $False)
        {        
            #Get the Permission Levels assigned
            $SourceUserPermissions = $SourceRoleAssignment.RoleDefinitionBindings | Select -ExpandProperty Name
            #Leave Principals with no Permissions assigned
            If($SourceUserPermissions.Length -eq 0) {Continue}
  
            #Check Source Permissions granted directly or through SharePoint Group
            If($SourceUserPermissions)
            {
                #Check if the Principal is SharePoint group
                If($SourceRoleAssignment.Member.PrincipalType -eq "SharePointGroup")
                { 
                    #Get the Group Name
                    $GroupName = $SourceRoleAssignment.Member.LoginName

                    #Leave Limited Access Groups
                    If($GroupName -notlike "*limited access*" -and $GroupName -notlike "*SharingLinks*" -and $GroupName -notlike "*tenant*")
                    {
                        #Check if the Source user is member of the Group
                        $SourceUserIsGroupMember = Get-PnPGroupMember -Identity $GroupName | Where {$_.LoginName -eq $SourceUser.LoginName}
                        If($SourceUserIsGroupMember -ne $null)
                        {
                            #Check if user is already member of the group - If not, Add to group
                            $TargetUserIsGroupMember = Get-PnPGroupMember -Identity $GroupName | Where {$_.LoginName -eq $TargetUser.LoginName}

                            If($TargetUserIsGroupMember -eq $null)
                            {
                                ##Add Target User to the Source User's Group
                                Add-PnPGroupMember -LoginName $TargetUser.LoginName -Identity $GroupName
                                Write-Host -f Green "`tAdded user to the Group '$GroupName' on $ObjectType '$ObjectTitle' at '$ObjectURL'"
                            }
                        }
                    }
                }
                ElseIf ($SourceRoleAssignment.Member.PrincipalType -eq "User" -and $SourceRoleAssignment.Member.LoginName -eq $SourceUser.LoginName)
                {     
                    #Add Each Direct permission (such as "Full Control", "Contribute") to the Target User
                    ForEach($SourceRoleDefinition in $SourceUserPermissions)
                    {
                        #Get the Permission Level  
                        $RoleDefinition = Get-PnPRoleDefinition -Identity $SourceRoleDefinition
                        If($RoleDefinition.Hidden -eq $false)
                        {
                            #Check if the Target User does not have the Permission Level already
                            If($TargetUserPermissions -notcontains $RoleDefinition.Name)
                            {
                                #Grant Source User's Permission Level to the Target User
                                $RoleDefBinding = New-Object Microsoft.SharePoint.Client.RoleDefinitionBindingCollection($Object.Context)
                                $RoleDefBinding.Add($RoleDefinition)
                                $Permissions = $Object.RoleAssignments.Add($TargetUser,$RoleDefBinding)
                                $Object.Update()
                                Invoke-PnPQuery
                                Write-Host  -f Green "`tGranted $($RoleDefinition.Name) Permission to the User on $ObjectType '$ObjectTitle' at '$ObjectURL'"
                            }
                        }                   
                    }
                } 
            }
        }
    }
}

#Function to Clone Permissions on the Web and its underlying objects such as Lists and Libraries, Folders and List Items
Function Clone-PnPPermission
{
    [cmdletbinding()]
 
    Param 
    (    
        [Parameter(Mandatory=$True)] [Microsoft.SharePoint.Client.Web] $Web,
        [Parameter(Mandatory=$True)] [Microsoft.SharePoint.Client.User] $SourceUser,
        [Parameter(Mandatory=$True)] [Microsoft.SharePoint.Client.User] $TargetUser,
        [Parameter(Mandatory=$false)] [bool] $ScanSubsites,
        [Parameter(Mandatory=$false)] [bool] $ScanFolders,
        [Parameter(Mandatory=$false)] [bool] $ScanFiles
    )

    #Call the function to clone permissions on the web
    Write-host -f Yellow "Scanning Permissions of the Web: $($Web.URL)"
    Copy-PnPUserPermission -SourceUser $SourceUser -TargetUser $TargetUser -Object $Web
    
    #Clone Permissions on Lists
    Write-host "Scanning Permissions on Lists at $($web.url)" -f Yellow
    #Exclude system lists
    $ExcludedLists = @("Site Assets","Preservation Hold Library","Style Library", "Site Collection Images","Site Pages", "Content and Structure Reports",
                        "Form Templates", "Home Page Links", "Forms", "Workflow Tasks", "MicroFeed")
    $Lists = Get-PnPProperty -ClientObject $Web -Property Lists
    $Lists = $Lists | Where {($_.Hidden -eq $false) -and $ExcludedLists -notcontains $_.Title}
    
    Foreach($List in $Lists)
    {
        $ListHasUniquePermissions = Get-PnPProperty -ClientObject $List -Property HasUniqueRoleAssignments
        If($List.HasUniqueRoleAssignments)
        {
            #Call the function to Copy Permissions to TargetUser on lists
            Copy-PnPUserPermission -SourceUser $SourceUser -TargetUser $TargetUser -Object $List
        }

        #Get List Items (and folders)
        If($ScanFolders -or $ScanFiles)
        {
             #Get Items from List
             If($List.ItemCount -gt 0)
             {
                $global:counter = 0;
                $AllListItems = Get-PnPListItem -List $List -PageSize 500 -Fields ID, FileSystemObjectType, FileLeafRef -ScriptBlock `
                 { Param($items) $global:counter += $items.Count; Write-Progress -PercentComplete ($global:Counter / ($List.ItemCount) * 100) `
                          -Activity "Getting Items from List '$($List.Title)'" -Status "Processing Items $global:Counter to $($List.ItemCount)";}
                    Write-Progress -Activity "Completed Retrieving Items from List $($List.Title)" -Completed
             }
        }

        If($ScanFolders)
        {
            #Clone Permissions on List folders
            Write-host "Scanning Permissions on Folders on List '$($List.Title)'" -f Yellow

            #Get Folders from List Items
            $Folders = $AllListItems | Where { ($_.FileSystemObjectType -eq "Folder") -and ($_.FieldValues.FileLeafRef -ne "Forms") -and (-Not($_.FieldValues.FileLeafRef.StartsWith("_")))}

            If($Folders.count -gt 0)
            {
                $ItemCounter = 1 
                #Get Folder permissions
                Foreach($Folder in $Folders)
                {
                    Write-Progress -PercentComplete ($ItemCounter / ($Folders.Count) * 100) -Activity "Processing Folders $ItemCounter of $($Folders.Count)" -Status "Searching Unique Permissions in Folders of List '$($List.Title)'"
                
                    $FolderHasUniquePermissions = Get-PnPProperty -ClientObject $Folder -Property HasUniqueRoleAssignments
                    If($FolderHasUniquePermissions -eq $True)
                    {
                        #Call the function to Copy Permissions to TargetUser
                        Copy-PnPUserPermission -SourceUser $SourceUser -TargetUser $TargetUser -Object $folder
                    }
                    $ItemCounter++ 
                }
                Write-Progress -Activity "Completed Cloning Folder Permissions on List $($List.Title)" -Completed
            }
        }         
       
        If($ScanFiles)
        {
            Write-host "Scanning Permissions on Files/Items on List '$($List.Title)'" -f Yellow

            #Get Files / List Items with Unique Permissions
            $ListItems =  $AllListItems | Where { ($_.FileSystemObjectType -ne "Folder") }
            
            If($ListItems.count -gt 0)
            {                
                $ItemCounter = 1              
                #Get Item level permissions
                Foreach($Item in $ListItems)
                {
                    Write-Progress -PercentComplete ($ItemCounter / ($ListItems.Count) * 100) -Activity "Processing Items $ItemCounter of $($ListItems.Count)" -Status "Searching Unique Permissions in Files/List Items of '$($List.Title)'"
                
                    $ItemHasUniquePermissions = Get-PnPProperty -ClientObject $Item -Property HasUniqueRoleAssignments
                    If($ItemHasUniquePermissions -eq $True)
                    {                    
                        #Call the function to Copy Permissions to TargetUser
                        Copy-PnPUserPermission -SourceUser $SourceUser -TargetUser $TargetUser -Object $Item
                    }
                    $ItemCounter++
                }
                Write-Progress -Activity "Completed Cloning Item Permissions on List $($List.Title)" -Completed
            }
        }
    }
}

Function Clone-PnPUser
{
    [cmdletbinding()]
 
    Param 
    (    
        [Parameter(Mandatory=$True)] [String] $SiteURL, 
        [Parameter(Mandatory=$True)] [String] $SourceUserEmail,         
        [Parameter(Mandatory=$True)] [String] $TargetUserEmail,
        [Parameter(Mandatory=$false)] [bool] $ScanSubsites,
        [Parameter(Mandatory=$false)] [bool] $ScanFolders,
        [Parameter(Mandatory=$false)] [bool] $ScanFiles
    )   
        #Connect to PnP Online
        Connect-PnPOnline -Url $SiteURL -Interactive
        #Get the Context of the Root web
        $Ctx = Get-PnPContext

        #Prepare the source and target users
        $SourceUser = Get-PnPUser | Where-Object Email -eq $SourceUserEmail
        If($SourceUser -eq $null) {        
            $SourceUser = New-PnPUser -LoginName $SourceUserEmail
        }
        $TargetUser = Get-PnPUser | Where-Object Email -eq $TargetUserEmail
        If($TargetUser -eq $null) {        
            $TargetUser = New-PnPUser -LoginName $TargetUserEmail
        }

        #Check Whether the Source User is a Site Collection Administrator
        Write-host "Scanning Site Collection Administrators..." -f Yellow        
        $SourceUserIsSiteAdmin = Get-PnPSiteCollectionAdmin | Where {$_.LoginName -eq $SourceUser.LoginName}
        If($SourceUserIsSiteAdmin -ne $Null)
        {
            #Check if the target user is site collection admin already!
            $TargetUserIsSiteAdmin = Get-PnPSiteCollectionAdmin | Where {$_.LoginName -eq $TargetUser.LoginName}
            If($TargetUserIsSiteAdmin -eq $Null)
            {
                #Add the Target user as Site Collection Admin
                Add-PnPSiteCollectionAdmin -Owners $TargetUser
                Write-host "Added Site Collection Administrator!" -f Green
            }
        }

        #Clone Permissions on the site and its underlying objects
        $Web = Get-PnPWeb
        Clone-PnPPermission -Web $Web -SourceUser $SourceUser -TargetUser $TargetUser -ScanSubsites $ScanSubSites -ScanFolders $ScanFolders -ScanFiles $ScanFiles
        
        #Call the function for subsites
        If($ScanSubsites)
        {
            #Get all subsites
            $WebsCollection = Get-PnPSubWeb -Includes HasUniqueRoleAssignments
            #Loop throuh each SubSite (web)
            Foreach($SubWeb in $WebsCollection)
            {
                If($SubWeb.HasUniqueRoleAssignments -eq $True)
                {  
                    #Call the function to Copy Permissions to TargetUser
                    Clone-PnPPermission -Web $SubWeb -SourceUser $SourceUser -TargetUser $TargetUser -ScanSubsites $ScanSubSites -ScanFolders $ScanFolders -ScanFiles $ScanFiles
                }           
            }
        }
    Write-Host -f Green "*** Permission Cloned Successfully! *** "
}
 
#Define Parameters
$SiteURL = "https://Crescent.sharepoint.com/sites/Marketing"
$SourceUserEmail ="Salaudeen@Crescent.com"
$TargetUserEmail ="Steve@Crescent.com"
 
#Call the function to clone user permissions
Clone-PnPUser -SiteURL $SiteURL -SourceUserEmail $SourceUserEmail -TargetUserEmail $TargetUserEmail
#Clone-PnPUser -SiteURL $SiteURL -SourceUserEmail $SourceUserEmail -TargetUserEmail $TargetUserEmail -ScanSubsites $true -ScanFolders $true -ScanFiles $True

Please note, this script doesn’t copy Active Directory Group or Microsoft 365 group permissions. And it’s scoped at the site collection level. So, If you want to clone permissions for multiple site collections, You have to call the function “Clone-PnPUser” with the relevant site URLs.

This would be extremely helpful when a new team member replaces an old team member. With this script, all the permissions of the old team member can simply be transferred to the new team member. Not just direct permissions, but this script also adds the target user to all the SharePoint groups the source user is a part of.

Salaudeen Rajack

Salaudeen Rajack - SharePoint Expert with Two decades of SharePoint Experience. Love to Share my knowledge and experience with the SharePoint community, through real-time articles!

11 thoughts on “How to Copy User Permissions in SharePoint Online using PowerShell?

  • Found out I can debug with VS Code 🙂 It returned two users for the same user one with onmicrosoft domain. I changed 276 line to $SourceUser[0] and it picked up the first one. Don’t know why returned two users.

    Clone-PnPPermission : Cannot process argument transformation on parameter ‘SourceUser’. Cannot convert the “System.Object[]” value of type “System.Object[]” to type “Microsoft.SharePoint.Client.User”.

    Reply
    • so it seemed like, for me, this line was returning two results for certain.
      Get-PnPUser | Where-Object Email -eq $SourceUserEmail
      I changed it to
      New-PnPUser -LoginName $SourceUserEmail

      Reply
  • For some reason I get an error but only with one sharepoint online site. Gives me an error below. I checked azure and sharepoint and the source user exists. Any idea whats going on?

    Clone-PnPPermission : Cannot process argument transformation on parameter ‘SourceUser’. Cannot convert the “System.Object[]” value of type “System.Object[]” to type “Microsoft.SharePoint.Client.User”.
    At E:\sharepoint_permissions_clone_user.ps1:276 char:51
    + Clone-PnPPermission -Web $Web -SourceUser $SourceUser -Target …
    + ~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Clone-PnPPermission], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Clone-PnPPermission

    Reply
  • Is there a problem with folders that have “_” symbol in the name?
    I ran the script and the folders with “_” symbol in front did not get permissions copied.

    Otherwise this is a life saver! I don’t understand how this is not built into sharepoint. Do they only create groups and assign them to each user? A group for every folder?

    Reply
    • Yes I also confirmed this. If “_” symbol is in front it does not add permissions on that folder.

      Reply
    • Yes! If needed, In line#180, You can disable the condition that checks if the folder name starts with “_”.

      Reply
      • Is there a reason for this check? Do some kind of system folders use it? Shouldn’t “_” be used in front?

        Reply
  • I get this when running the script:

    Scanning Site Collection Administrators…
    Clone-PnPPermission : 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”.
    At C:\temp\protomore.ps1:276 char:34
    + Clone-PnPPermission -Web $Web -SourceUser $SourceUser -Target …
    + ~~~~
    + CategoryInfo : InvalidData: (:) [Clone-PnPPermission], ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Clone-PnPPermission

    *** Permission Cloned Successfully! ***

    Reply
  • Hi Salaudeen,

    Thank you for sharing this script.
    I have an additional question regarding permissions. Is it possible to fetch all sites wherein an user have Full Control?

    Reply

Leave a Reply

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