SharePoint Online: Copy Files Between Site Collections using PowerShell

Requirement: Copy Files between document libraries in SharePoint Online Site Collections.

How to Copy a File Between SharePoint Online Site Collections?

Here is how to copy files between document libraries in SharePoint Online:

  • Navigate to your SharePoint Online document library>>  Select the file to copy >> Click on “Copy To” in the toolbar. (or you can right-click on a document and choose “Copy To” from the context menu)
    sharepoint online powershell copy documents
  • This opens the information panel on the right. Select the target library to which your file needs to be copied. You can select any folder in the current library, any other library in the current site, or even a library in any different site collections.
    powershell sharepoint online copy files
  • Pick the target location to copy the document and click on the “Copy Here” button to start copying the file.

You’ll see the “Copying” message in the toolbar and your file will be copied momentarily. You can use this method to copy files between libraries in the same site or between different site collections.

PowerShell to Copy Files between Site Collections in SharePoint Online:

Let’s copy a single file from source to destination.

#Load SharePoint CSOM Assemblies
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"

#Set parameter values
$SourceSiteURL="https://crescent.sharePoint.com/sites/sales"
$TargetSiteURL="https://crescent.sharepoint.com/sites/Ops/"

#Set Source and Destination File URLs - Relative path
$SourceFileURL="/Sites/Sales/Project Documents/Active Users.xlsx"
$TargetFileURL="/Sites/Ops/Shared Documents/Active Users.xlsx"

#Setup Credentials to connect
$Cred= Get-Credential
$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.Username, $Cred.Password)

#Setup the contexts
$SourceCtx = New-Object Microsoft.SharePoint.Client.ClientContext($SourceSiteURL)
$SourceCtx.Credentials = $Credentials
$TargetCtx = New-Object Microsoft.SharePoint.Client.ClientContext($TargetSiteURL)
$TargetCtx.Credentials = $Credentials

#Get the Source File
$FileInfo = [Microsoft.SharePoint.Client.File]::OpenBinaryDirect($SourceCtx, $SourceFileURL)

#Copy File to the Target location
[Microsoft.SharePoint.Client.File]::SaveBinaryDirect($TargetCtx, $TargetFileURL, $FileInfo.Stream,$True)

This script simply copies a particular file from source site/site collection to the target site/site collection.

Copy All Files and Folder from One Library to Another Library between different Site Collections:

#Load SharePoint CSOM Assemblies
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 Copy-Files
{
  param
    (
        [Parameter(Mandatory=$true)] [Microsoft.SharePoint.Client.Folder] $SourceFolder,
        [Parameter(Mandatory=$true)] [Microsoft.SharePoint.Client.Folder] $TargetFolder
    )
    Try {
        #Get all Files from the source folder
        $SourceFilesColl = $SourceFolder.Files
        $SourceFolder.Context.Load($SourceFilesColl)
        $SourceFolder.Context.ExecuteQuery()

        #Iterate through each file and copy
        Foreach($SourceFile in $SourceFilesColl)
        {
            #Get the source file
            $FileInfo = [Microsoft.SharePoint.Client.File]::OpenBinaryDirect($SourceFolder.Context, $SourceFile.ServerRelativeUrl)
            
            #Copy File to the Target location
            $TargetFileURL = $TargetFolder.ServerRelativeUrl+"/"+$SourceFile.Name
            [Microsoft.SharePoint.Client.File]::SaveBinaryDirect($TargetFolder.Context, $TargetFileURL, $FileInfo.Stream,$True)

            Write-host -f Green "Copied File '$($SourceFile.ServerRelativeUrl)' to '$TargetFileURL'"
        }

        #Process Sub Folders
        $SubFolders = $SourceFolder.Folders
        $SourceFolder.Context.Load($SubFolders)
        $SourceFolder.Context.ExecuteQuery()
        Foreach($SubFolder in $SubFolders)
        {
            If($SubFolder.Name -ne "Forms")
            {
                #Prepare Target Folder
                $TargetFolderURL = $SubFolder.ServerRelativeUrl -replace $SourceLibrary.RootFolder.ServerRelativeUrl, $TargetLibrary.RootFolder.ServerRelativeUrl
                Try {
                        $Folder=$TargetFolder.Context.web.GetFolderByServerRelativeUrl($TargetFolderURL)
                        $TargetFolder.Context.load($Folder)
                        $TargetFolder.Context.ExecuteQuery()
                    }
                catch {
                        #Create Folder
                        if(!$Folder.Exists)
                        {
                            $TargetFolderURL
                            $Folder=$TargetFolder.Context.web.Folders.Add($TargetFolderURL)
                            $TargetFolder.Context.Load($Folder)
                            $TargetFolder.Context.ExecuteQuery()
                            Write-host "Folder Added:"$SubFolder.Name -f Yellow
                        }
                    }
                #Call the function recursively
                Copy-Files -SourceFolder $SubFolder -TargetFolder $Folder
            }
        } 
    }
    Catch {
        write-host -f Red "Error Copying File!" $_.Exception.Message
    } 
}

#Set Parameter values
$SourceSiteURL="https://crescent.sharePoint.com/sites/sales"
$TargetSiteURL="https://crescent.sharepoint.com/sites/ops"

$SourceLibraryName="Project Documents"
$TargetLibraryName="Documents"

#Setup Credentials to connect
$Cred= Get-Credential
$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.Username, $Cred.Password)

#Setup the contexts
$SourceCtx = New-Object Microsoft.SharePoint.Client.ClientContext($SourceSiteURL)
$SourceCtx.Credentials = $Credentials
$TargetCtx = New-Object Microsoft.SharePoint.Client.ClientContext($TargetSiteURL)
$TargetCtx.Credentials = $Credentials
     
#Get the source library and Target Libraries
$SourceLibrary = $SourceCtx.Web.Lists.GetByTitle($SourceLibraryName)
$SourceCtx.Load($SourceLibrary)
$SourceCtx.Load($SourceLibrary.RootFolder)

$TargetLibrary = $TargetCtx.Web.Lists.GetByTitle($TargetLibraryName)
$TargetCtx.Load($TargetLibrary)
$TargetCtx.Load($TargetLibrary.RootFolder)
$TargetCtx.ExecuteQuery()

#Call the function 
Copy-Files -SourceFolder $SourceLibrary.RootFolder -TargetFolder $TargetLibrary.RootFolder 

Please note, copying files will not preserve version history! You need to use “Move” instead of copy if you want to preserve versioning.

Copy All Files from One Library to Another Library along with Metadata values:

This script copies all files and folders between document libraries in different site collections, preserving metadata field values such as: Created by, Modified By, Created At, Modified At, etc.

#Load SharePoint CSOM Assemblies
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 Copy-AllFilesWithMetadata
{
  param
    (
        [Parameter(Mandatory=$true)] [Microsoft.SharePoint.Client.Folder] $SourceFolder,
        [Parameter(Mandatory=$true)] [Microsoft.SharePoint.Client.Folder] $TargetFolder
    )
    Try {
        #Get all Files from the source folder
        $SourceFilesColl = $SourceFolder.Files
        $SourceFolder.Context.Load($SourceFilesColl)
        $SourceFolder.Context.ExecuteQuery()

        #Iterate through each file and copy
        Foreach($SourceFile in $SourceFilesColl)
        {
            #Get the source file
            $FileInfo = [Microsoft.SharePoint.Client.File]::OpenBinaryDirect($SourceFolder.Context, $SourceFile.ServerRelativeUrl)
            
            #Copy File to the Target location
            $TargetFileURL = $TargetFolder.ServerRelativeUrl+"/"+$SourceFile.Name
            [Microsoft.SharePoint.Client.File]::SaveBinaryDirect($TargetFolder.Context, $TargetFileURL, $FileInfo.Stream,$True)

            #Copy Metadata field values
            $SourceListItem = $SourceFile.ListItemAllFields
            $SourceFolder.Context.Load($SourceListItem)
            $SourceFolder.Context.ExecuteQuery()
            
            #Get the new file created
            $TargetFile = $TargetFolder.Context.Web.GetFileByServerRelativeUrl($TargetFileURL)
            $TargetListItem = $TargetFile.ListItemAllFields
            
            #Set Metadata values from the source
            $Author =$TargetFolder.Context.web.EnsureUser($SourceListItem["Author"].Email)
            $TargetListItem["Author"] = $Author
            $Editor =$TargetFolder.Context.web.EnsureUser($SourceListItem["Editor"].Email)
            $TargetListItem["Editor"] = $Editor
            $TargetListItem["Created"] = $SourceListItem["Created"]
            $TargetListItem["Modified"] = $SourceListItem["Modified"]
            $TargetListItem.Update()
            $TargetFolder.Context.ExecuteQuery()

            Write-host -f Green "Copied File '$($SourceFile.ServerRelativeUrl)' to '$TargetFileURL'"
        }

        #Process Sub Folders
        $SubFolders = $SourceFolder.Folders
        $SourceFolder.Context.Load($SubFolders)
        $SourceFolder.Context.ExecuteQuery()
        Foreach($SubFolder in $SubFolders)
        {
            If($SubFolder.Name -ne "Forms")
            {
                #Prepare Target Folder
                $TargetFolderURL = $SubFolder.ServerRelativeUrl -replace $SourceLibrary.RootFolder.ServerRelativeUrl, $TargetLibrary.RootFolder.ServerRelativeUrl
                Try {
                        $Folder=$TargetFolder.Context.web.GetFolderByServerRelativeUrl($TargetFolderURL)
                        $TargetFolder.Context.load($Folder)
                        $TargetFolder.Context.ExecuteQuery()
                    }
                catch {
                        #Create Folder
                        if(!$Folder.Exists)
                        {
                            $TargetFolderURL
                            $Folder=$TargetFolder.Context.web.Folders.Add($TargetFolderURL)
                            $TargetFolder.Context.Load($Folder)
                            $TargetFolder.Context.ExecuteQuery()
                            Write-host "Folder Added:"$SubFolder.Name -f Yellow
                        }
                    }
                #Call the function recursively
                Copy-AllFilesWithMetadata -SourceFolder $SubFolder -TargetFolder $Folder
            }
        } 
    }
    Catch {
        write-host -f Red "Error Copying File!" $_.Exception.Message
    } 
}

#Set Parameter values
$SourceSiteURL="https://crescent.sharepoint.com/sites/sales"
$TargetSiteURL="https://crescent.sharepoint.com/sites/Ops"

$SourceLibraryName="Project Documents"
$TargetLibraryName="Documents"

#Setup Credentials to connect
$Cred= Get-Credential
$Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.Username, $Cred.Password)

#Setup the contexts
$SourceCtx = New-Object Microsoft.SharePoint.Client.ClientContext($SourceSiteURL)
$SourceCtx.Credentials = $Credentials
$TargetCtx = New-Object Microsoft.SharePoint.Client.ClientContext($TargetSiteURL)
$TargetCtx.Credentials = $Credentials
     
#Get the source library and Target Libraries
$SourceLibrary = $SourceCtx.Web.Lists.GetByTitle($SourceLibraryName)
$SourceCtx.Load($SourceLibrary)
$SourceCtx.Load($SourceLibrary.RootFolder)

$TargetLibrary = $TargetCtx.Web.Lists.GetByTitle($TargetLibraryName)
$TargetCtx.Load($TargetLibrary)
$TargetCtx.Load($TargetLibrary.RootFolder)
$TargetCtx.ExecuteQuery()

#Call the function 
Copy-AllFilesWithMetadata -SourceFolder $SourceLibrary.RootFolder -TargetFolder $TargetLibrary.RootFolder 

PnP PowerShell to Copy Files and Folders between Site Collections

This script copies files and folders between document libraries (or folders) from one site collection to another.

#Parameters
$SiteURL = "https://crescent.sharepoint.com/sites/Marketing/"
$SourceFolderURL = "/sites/marketing/Branding"
$TargetFolderURL = "/sites/Purchase/Shared Documents"
 
#Connect to PnP Online
Connect-PnPOnline -Url $SiteURL -UseWebLogin
 
#Copy All Files and Folders between source and target folders
Copy-PnPFile -SourceUrl $SourceFolderURL -TargetUrl $TargetFolderURL -SkipSourceFolderName -Force -OverwriteIfAlreadyExists

Salaudeen Rajack

Salaudeen Rajack is a SharePoint Architect with Two decades of SharePoint Experience. He loves sharing his knowledge and experiences with the SharePoint community, through his real-world articles!

19 thoughts on “SharePoint Online: Copy Files Between Site Collections using PowerShell

  • May 3, 2021 at 10:13 AM

    Hi,

    I wanted to move a file from One List to another list (This list is inside a subsite of this site collection). tried with File.Move it didn’t work. Can you please suggest?

    Reply
  • January 19, 2021 at 5:36 PM

    Thank you Salaudeen this is very helpful. I am trying to copy just the files from a site library sub folder to another site library and not finding it possible. Any guidance with this I would be grateful. Thanks Dave.

    Reply
  • January 15, 2021 at 12:11 AM

    Hi,
    Thanks for your work because the script works like a charm, but, can works with personalized metadata or document set?
    Thanks in advance anyone have a similar necesity?
    Regards

    Reply
  • January 14, 2021 at 4:55 PM

    I have several documents that I need to copy from one library in a site collection to another library in a different site collection based on Modified date. Is there a way to use a CSV for source and target?

    Reply
  • September 8, 2020 at 8:39 AM

    Hi,
    Thanks for your work because the script works.
    However, I would like to ask if you have any suggestion on how to keep the Author and Created values for the folders.
    It’s working great for the files but not to the folders.
    I would really appreciate if you could provide some ideas on that.

    Thanks in advance

    Reply
  • August 10, 2020 at 12:22 PM

    So, I was able to make it works and it’s working but there is just one detail that is not working: when the script copies a folder or sub-folder it’s not keeping the author and the Created By and Modified by original dates.
    Any suggestion to keep the original metadata for folders?
    Thanks

    Reply
  • July 28, 2020 at 4:56 PM

    Thanks for the post and the script. It’s really good but I am having some issues to making it work.
    The only problem is that is not keeping the Author and the Editor.
    When I use the script as is I am getting an error message:

    Error Copying File! Exception calling “ExecuteQuery” with “0” argument(s): “The specified user could not be found.”

    If I change these lines of your code:
    $Author =$TargetFolder.Context.web.EnsureUser($SourceListItem[“Author”].Email)
    $TargetListItem[“Author”] = $Author
    $Editor =$TargetFolder.Context.web.EnsureUser($SourceListItem[“Editor”].Email)
    $TargetListItem[“Editor”] = $Editor
    By this code:
    $TargetListItem[“Author”] = $SourceListItem[“Author”].Email
    $TargetListItem[“Editor”] = $SourceListItem[“Editor”].Email

    The error message does not show and the items are copied but the metadata is not being kept for the Author and the Editor.
    Do you have any suggestions?
    Thanks in advance

    Reply
    • July 28, 2020 at 5:21 PM

      Just to provide you with some more information:
      I am connecting to 2 SharePoint online sites, not on premises.
      Also, I found that the web member is not part of the Context.
      When I type in this: $TargetFolder.Context. (I cannot get the “web” option but only “WebRequestExecutorFactory”)
      Perhaps that’s the issue and it’s preventing the below instruction to work as expected:
      $Author =$TargetFolder.Context.web.EnsureUser($SourceListItem[“Author”].Email)

      It’s just a thought, I am not sure.
      Any help is very appreciated.

      Reply
  • July 20, 2020 at 4:35 PM

    For some reason, I keep getting “The sign-in name or password does not match one in the Microsoft account system.”, even though I am an admin on the site and I know I’m typing the right credentials. Anything I’m doing wrong?

    Reply
  • May 1, 2020 at 10:09 AM

    Getting an error “Error copying file! Exception calling “Execute Query” with “0” arguments: The specified user [email protected] could not be found”, where the folder and file creator no longer exists.

    Can you pls help how to handle this?

    Reply
  • April 30, 2020 at 3:03 PM

    Script works fine for other library, but when i copy “Site Pages” am getting this error” Error Copying File! Exception calling “SaveBinaryDirect” with “4” argument(s): “The remote server returned an error: (401) Unauthorized.” Can you help?

    Reply
  • April 16, 2020 at 12:17 PM

    This really works great. Is there any way to exclude files in copying from the source library which were not accessed in last 2 yrs? please suggest

    Reply
    • April 17, 2020 at 4:10 PM

      You can filter list items based on created/modified date. However, it appears there isn’t any solution to get last accessed date of items in SharePoint Online.

      Reply
  • June 24, 2019 at 12:03 AM

    Hi there, how does this work with file/folder modification? For example: We have the library on siteA and we copy to siteB. Once the copy has finished and we run it again, what happens? Ideally, I’d we’d like to run this on a regular basis like a ‘sync’. Any help would be amazing!

    Reply

Leave a Reply