SharePoint Online: Copy a Document Library using PowerShell

Requirement: PowerShell to Copy a Document Library in SharePoint Online

How to copy a Document Library in SharePoint Online?

Have you ever wanted to copy a document library in SharePoint Online? Perhaps you want to create a new library with the same structure and content as an existing one, or you need to duplicate a library’s contents for archiving or testing purposes. Whatever the reason, it’s easy to do. This article will show you how to use PowerShell to copy a document library on the SharePoint Online site.

There are no direct ways to copy a document library in SharePoint Online. However, You can use this list template workaround:

  1. Save the source document library as a template – Go to the source library >> Click on Settings gear >> Choose Library Settings >> Click on “Save as Template”. More info: How to Save List as Template in SharePoint Online?
  2. Create a new document library from the list template – Login to your SharePoint Online site >> Click on the Settings gear icon and click “Add an app”. You can use the pagination at the bottom of the page to find your custom list template or use the “Find an App” search box and pick your list template. More info: SharePoint Online: Create List from Custom Template using PowerShell
  3. Copy all items from the source document library to the new document library – Once you have created a new instance of the document library from the existing list template, you can copy all files and folders from the source library to the destination library by selecting all items in the source library and choose “Copy to” button in the toolbar and then specify the document library created in step 2.
    sharepoint online powershell copy document library

Let’s automate the above steps to copy a document library in SharePoint Online using PowerShell:

If you want to copy a document library without any metadata columns, You can simply create a new document library and use the “Copy to” option from the toolbar to copy all files and folders to a new library!

Make sure you have a custom script enabled for the site collection before proceeding with this method! Otherwise, You’ll end up in an “Access denied” error. How to Enable Custom Script in SharePoint Online?

SharePoint Online: PowerShell to Copy a Document Library

The PowerShell can be used to automate tasks and improve efficiency in your SharePoint Online environment. Let me show you how to copy a document library using PowerShell. This script saves the existing SharePoint Online document library as a template, creates a new instance from it, and then copies all files and folders from the source document library to the new library.

#Parameters
$SiteURL = "https://crescent.sharepoint.com/sites/Retail"
$SourceLibraryName = "Invoices"
$DestinationLibraryName = "Invoices V1"
 
Try {
    #Connect to the Site
    Connect-PnPOnline -URL $SiteURL -Interactive
 
    #Get the Source library
    $SourceLibrary =  Get-PnPList -Identity $SourceLibraryName -Includes RootFolder
 
    #Get Timestamp
    $Timestamp = (Get-Date).tostring("yyyyMMdd-HHmmss")
    $TemplateName = $SourceLibrary.Title+"_"+$Timestamp

    #Step 1: Save the  Source Library as a template
    $SourceLibrary.SaveAsTemplate($TemplateName, $TemplateName, [string]::Empty, $False);
    Invoke-PnPQuery

    #Get the Library Template created
    $Ctx = Get-PnPContext
    $Web = Get-PnPWeb
    $RootWeb = $Ctx.Site.RootWeb
    $ListTemplates = $Ctx.Site.GetCustomListTemplates($RootWeb)
    $Ctx.Load($RootWeb)
    $Ctx.Load($ListTemplates)
    Invoke-PnPQuery
    $ListTemplate = $ListTemplates | Where {$_.Name -eq $TemplateName}
  
    #Step 2:  Create the destination library from the source library template
    If(!(Get-PnPList -Identity $DestinationLibraryName))
    {
        #Create the destination library
        $ListCreation = New-Object Microsoft.SharePoint.Client.ListCreationInformation
        $ListCreation.Title = $DestinationLibraryName
        $ListCreation.ListTemplate = $ListTemplate
        $DestinationList = $Web.Lists.Add($ListCreation)
        Invoke-PnPQuery
        Write-host "New Library '$DestinationLibraryName' created successfully!" -f Green
    }
    Else
    {
        Write-host "Library '$DestinationLibraryName' already exists!" -f Yellow
    }
    #Remove the List Template
    $TemplatePath = $Web.ServerRelativeUrl+"/_catalogs/lt/"+$ListTemplate.InternalName
    $TemplateFile = Get-PnPFile -Url $TemplatePath
    $TemplateFile.DeleteObject()
    Invoke-PnPQuery
    #Remove-PnPFile -ServerRelativeUrl $TemplatePath -Recycle -Force
 
    #Step 3: Copy content from Source to the destination library
    $DestinationLibrary = Get-PnPList $DestinationLibraryName -Includes RootFolder

    #Calculate Site Relative URL of the Folder
    If($Web.ServerRelativeURL -eq "/")
    {
	    $FolderSiteRelativeUrl = $SourceLibrary.RootFolder.ServerRelativeUrl
    }
    Else
    {      
	    $FolderSiteRelativeUrl = $SourceLibrary.RootFolder.ServerRelativeUrl.Replace($Web.ServerRelativeURL,[string]::Empty)
    }

    #Get All Content from Source Library's Root Folder
    $RootFolderItems = Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl | Where {($_.Name -ne "Forms") -and (-Not($_.Name.StartsWith("_")))}
        
    #Copy Items to the Destination
    $RootFolderItems | ForEach-Object {
        $DestinationURL = $DestinationLibrary.RootFolder.ServerRelativeUrl
        Copy-PnPFile -SourceUrl $_.ServerRelativeUrl -TargetUrl $DestinationURL -Force -OverwriteIfAlreadyExists
        Write-host "`tCopied '$($_.ServerRelativeUrl)'" -f Green    
    }    
}
Catch {
    write-host -f Red "Error:" $_.Exception.Message
}

Although the above script works just fine for document libraries with <5000 items, on larger document libraries the PnP PowerShell cmdlet Get-PnPFolderItem gives an error “Get-PnPFolderItem : The attempted operation is prohibited because it exceeds the list view threshold.”. So, to mitigate that issue, we can replace the copying part (From Line# 56 to 74) with:

#Copy All Content from Source Library's Root Folder to the Destination Library
If($SourceLibrary.ItemCount -gt 0)
{
	#Get All Items from the Root Folder of the Library
	$global:counter = 0
	$ListItems = Get-PnPListItem -List $SourceLibraryName -PageSize 500 -Fields ID -ScriptBlock {Param($items) $global:counter += $items.Count; Write-Progress -PercentComplete `
		(($global:Counter / $SourceLibrary.ItemCount) * 100) -Activity "Getting Items from List" -Status "Getting Items $global:Counter of $($SourceLibrary.ItemCount)"}
	$RootFolderItems = $ListItems | Where { $_.FieldValues.FileRef.Replace(("/"+$_.FieldValues.FileLeafRef),[string]::Empty) -eq $SourceLibrary.RootFolder.ServerRelativeUrl}

	Write-Progress -Activity "Completed Getting Items from Library $($SourceLibrary.Title)" -Completed
	
	#Copy Items to the Destination
	$RootFolderItems | ForEach-Object {
		$DestinationURL = $DestinationLibrary.RootFolder.ServerRelativeUrl
		Copy-PnPFile -SourceUrl $_.FieldValues.FileRef -TargetUrl $DestinationURL -Force -OverwriteIfAlreadyExists
		Write-host "`tCopied $($_.FileSystemObjectType) '$DestinationURL'" -f Green    
	}
}

Copy Document Library with Files and Folders and Metadata

You can copy larger document libraries along with their files and folders and metadata using the below PowerShell script:

#Parameters
$SiteURL = "https://crescent.sharepoint.com/sites/Retail"
$SourceLibraryName = "Invoices"
$DestinationLibraryName = "Invoices V2"
 
Try {
    #Connect to the Site
    Connect-PnPOnline -URL $SiteURL -Interactive
 
    #Get the Source library
    $SourceLibrary =  Get-PnPList -Identity $SourceLibraryName -Includes RootFolder
 
    #Get Timestamp
    $Timestamp = (Get-Date).tostring("yyyyMMdd-HHmmss")
    $TemplateName = $SourceLibrary.Title+"_"+$Timestamp

    Write-host "Preparing Document Template..." -f Yellow -NoNewline
    #Step 1: Save the  Source Library as a template
    $SourceLibrary.SaveAsTemplate($TemplateName, $TemplateName, [string]::Empty, $False);
    Invoke-PnPQuery

    #Get the Library Template created
    $Ctx = Get-PnPContext
    $Web = Get-PnPWeb
    $RootWeb = $Ctx.Site.RootWeb
    $ListTemplates = $Ctx.Site.GetCustomListTemplates($RootWeb)
    $Ctx.Load($RootWeb)
    $Ctx.Load($ListTemplates)
    Invoke-PnPQuery
    $ListTemplate = $ListTemplates | Where {$_.Name -eq $TemplateName}
    Write-host "`tDone!" -f Green

    Write-host "Ensuring Document Library..." -f Yellow -NoNewline
    #Step 2:  Create the destination library from the source library template
    If(!(Get-PnPList -Identity $DestinationLibraryName))
    {
        #Create the destination library
        $ListCreation = New-Object Microsoft.SharePoint.Client.ListCreationInformation
        $ListCreation.Title = $DestinationLibraryName
        $ListCreation.ListTemplate = $ListTemplate
        $DestinationList = $Web.Lists.Add($ListCreation)
        Invoke-PnPQuery
        Write-host "New Library '$DestinationLibraryName' created successfully!" -f Green
    }
    Else
    {
        Write-host "Library '$DestinationLibraryName' already exists!" -f Yellow
    }
    #Remove the List Template
    $TemplatePath = $Web.ServerRelativeUrl+"/_catalogs/lt/"+$ListTemplate.InternalName
    $TemplateFile = Get-PnPFile -Url $TemplatePath
    $TemplateFile.DeleteObject()
    Invoke-PnPQuery
 
    Write-host "Copying Content Between Document Libraries..." -f Yellow
    #Step 3: Copy content from Source to the destination library
    $DestinationLibrary = Get-PnPList $DestinationLibraryName -Includes RootFolder

    #Copy All Content from Source Library's Root Folder to the Destination Library
    If($SourceLibrary.ItemCount -gt 0)
    {
        #Get All Items from the Root Folder of the Library
        $global:counter = 0
        $ListItems = Get-PnPListItem -List $SourceLibraryName -PageSize 500 -Fields ID -ScriptBlock {Param($items) $global:counter += $items.Count; Write-Progress -PercentComplete `
            (($global:Counter / $SourceLibrary.ItemCount) * 100) -Activity "Getting Items from List" -Status "Getting Items $global:Counter of $($SourceLibrary.ItemCount)"}
        $RootFolderItems = $ListItems | Where { $_.FieldValues.FileRef.Replace(("/"+$_.FieldValues.FileLeafRef),[string]::Empty) -eq $SourceLibrary.RootFolder.ServerRelativeUrl}
        Write-Progress -Activity "Completed Getting Items from Library $($SourceLibrary.Title)" -Completed
        
        #Copy Items to the Destination
        $RootFolderItems | ForEach-Object {
            $DestinationURL = $DestinationLibrary.RootFolder.ServerRelativeUrl
            Copy-PnPFile -SourceUrl $_.FieldValues.FileRef -TargetUrl $DestinationURL -Force -OverwriteIfAlreadyExists
            Write-host "`tCopied $($_.FileSystemObjectType) '$($_.FieldValues.FileRef)'" -f Green
        }

        Write-host "Copying Metadata Between Libraries..." -f Yellow -NoNewline
        #Get All Items from Destination Library
        $DestinationItems = Get-PnPListItem -List $DestinationLibraryName -PageSize 500
        
        #Copy Metadata
        ForEach($SourceItem in $ListItems)
        {
            #Get Metadata from Source Items
	        $Metadata = @{
		        'Title' = $SourceItem.FieldValues.Title
		        'Created'= $SourceItem.FieldValues.Created.DateTime
		        'Modified' = $SourceItem.FieldValues.Modified.DateTime
		        'Author' = $SourceItem.FieldValues.Author.Email
		        'Editor' = $SourceItem.FieldValues.Editor.Email
		        }

	        #Update Metadata in the destination Items
            $SourceItemServerRelativeURL = $SourceItem.FieldValues.FileLeafRef
            $DestinationItemServerRelativeURL = $SourceItem.FieldValues.FileLeafRef.Replace($SourceLibrary.RootFolder.ServerRelativeUrl,$DestinationLibrary.RootFolder.ServerRelativeUrl)
	        $MatchingItem = $DestinationItems | Where {$_.FieldValues.FileLeafRef -eq $DestinationItemServerRelativeURL}
            Set-PnPListItem -List $DestinationLibrary -Identity $MatchingItem.Id -Values $Metadata | Out-Null
        }
        Write-host "`tDone!" -f Green
    }
}
Catch {
    write-host -f Red "Error:" $_.Exception.Message
}

This PowerShell method retains all metadata except created/modified timestamps and created by, modified by field values. To copy a document library to another site, use: How to Copy a Document Library to another Site in SharePoint Online using PowerShell?

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!

3 thoughts on “SharePoint Online: Copy a Document Library using PowerShell

  • Hi,

    Thx for your post, really helpfull !!!

    But, it seems that the Copy-PnPFile doesn’t work with root folders.
    If I specify a folder or a file, it works fine, but, just at the root, i’ve got an error :

    Copy-PnPFile : {“odata.error”:{“code”:”-2147467261,
    System.ArgumentNullException”,”message”:{“lang”:”fr-FR”,”value”:”Value cannot be null.\r\nParameter name: Null value
    for source item at https://darkana365.sharepoint.com/sites/SOGEPROM/Documents partages”}}}
    Au caractère C:\PnP\libraryContentCopyOnly.ps1:23 : 5
    + Copy-PnPFile -SourceUrl “Documents%20partages” -TargetUrl $Destin …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : WriteError: (:) [Copy-PnPFile], HttpRequestException
    + FullyQualifiedErrorId : EXCEPTION,PnP.PowerShell.Commands.Files.CopyFile

    After a little search, i get this bug report on PnP : https://github.com/pnp/powershell/issues/410

    So, How did you get this script Worked ?

    Reply
    • In the Legacy PnP PowerShell Module, it was possible to copy Document Library’s Root folders and its contents. Unfortunately, in the New PnP.PowerShell that’s not supported as of today. So, I’ve updated the script accordingly now.

      Reply

Leave a Reply

Your email address will not be published.