Skip to content

Commit

Permalink
Merge pull request #791 from timtatam/copy-files-and-folders-across-t…
Browse files Browse the repository at this point in the history
…enants

add script 'spo-copy-library-across-tenants'
  • Loading branch information
pkbullock authored Dec 11, 2024
2 parents a7a47fc + c295039 commit 8e4c0b9
Show file tree
Hide file tree
Showing 3 changed files with 285 additions and 0 deletions.
223 changes: 223 additions & 0 deletions scripts/spo-copy-library-across-tenants/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
---
plugin: add-to-gallery
---

# Copying a subset of a document library to another SharePoint tenants with resume functionality

## Summary

You might have the case to copy a subset of document library from one tenant to another one.
This examples
- containes resume functionality, especially when dealing with huge libraries
- select subset of files based on a meta data property value
- specify additional meta data columns to copy to target library
- creates necessary folder structure in target library
- creates a logfile with file copy status


### Prerequisites

- An EntraId app for each source and target tenant, either with app-only or delegated permissions to access the specific site
- Installed PowerShell modules PnP.PowerShell and ImportExcel


# [PnP PowerShell](#tab/pnpps)

```PowerShell
# Install the necessary module
#Install-Module -Name ImportExcel -Scope CurrentUser
#Install-Module PnP.PowerShell -Scope CurrentUser
###################
### User Config ###
###################
$sourceSite = "https://contoso.sharepoint.com/sites/source-site"
$sourceLibrary = "SourceDocLib"
$sourceDataQueryProperty = "<filter property>"
$sourceDataQueryPropertyValue = "<filter property value>"
$sourceAppId = "<source app id>"
$sourceTenantId = "<source tenant id>"
$sourceCertThumb ="<source cert thumb>"
$targetSite = "https://fabrikam.sharepoint.com/sites/target-site"
$targetLibrary = "TargetDocLib"
$targetAppId = "<target app id>"
$targetTenantId = "<target tenant id>"
$targetCertThumb = "<target cert thumb>"
$propertiesToMigrate = @('<ColumnName1>', '<ColumnName2>') # List of internal properties to migrate
$sourceCtx = Connect-PnPOnline -Url $sourceSite -ClientId $sourceAppId -Tenant $sourceTenantId -Thumbprint $sourceCertThumb -ReturnConnection
$targetCtx = Connect-PnPOnline -Url $targetSite -ClientId $targetAppId -Tenant $targetTenantId -Thumbprint $targetCertThumb -ReturnConnection
# Alternative for interactive login
#$sourceCtx = Connect-PnPOnline -Url $sourceSite -ClientId $sourceAppId -Tenant $sourceTenantId -Interactive -ReturnConnection
#$targetCtx = Connect-PnPOnline -Url $targetSite -ClientId $targetAppId -Tenant $targetTenantId -Interactive -ReturnConnection
###############
### Helpers ###
###############
# # Create fields in target library
# foreach ($property in $propertiesToMigrate) {
# Add-PnPField -Type Text -InternalName $property -DisplayName $property -Group "EHS" -Connection $targetCtx
# }
###############################################
###############################################
#### DO NOT CHANGE ANYTHING BELOW THIS LINE ###
###############################################
###############################################
function GetFileCopyLog
{
param(
[Parameter(Mandatory=$true)]
[string]$PathToFileCopyLog
)
if (-not (Test-Path $PathToFileCopyLog -PathType Leaf)) {
$ListItemQuery = "<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name='$($sourceDataQueryProperty)'/><Value Type='Text'>$($sourceDataQueryPropertyValue)</Value></Eq></Where></Query></View>"
$allItemsToCopy = (Get-PnPListItem -List $sourceLibrary -Query $ListItemQuery -PageSize 1000 -Connection $sourceCtx).FieldValues
$sourceList = Get-PnPList -Identity $sourceLibrary -Connection $sourceCtx
foreach ($currentItem in $allItemsToCopy) {
# Write log
[PSCustomObject]@{
Filename = $currentItem.FileLeafRef
UniqueId = $currentItem.UniqueId
SourceItemId = $currentItem.ID
SourceFileRef = $currentItem.FileRef
SourceFileDirRef = $currentItem.FileDirRef
SourceFileDirRefRelative = $currentItem.FileDirRef.Replace($sourceList.RootFolder.ServerRelativeUrl, $sourceLibrary)
TargetFileDirRefRelative = $currentItem.FileDirRef.Replace($sourceList.RootFolder.ServerRelativeUrl, $targetLibrary)
CopiedProperties = ""
Status = "Not copied"
} | Export-Excel $PathToFileCopyLog -Append
}
}
}
function CreateFoldersInTarget
{
param(
[Parameter(Mandatory=$true)]
[string]$PathToFileCopyLog
)
# Read file with folders
$logFile = Import-Excel -Path $PathToFileCopyLog
# Get unique folders
$uniqueFolders = $logFile.TargetFileDirRefRelative | Sort-Object -Unique
# Create folder structure in target
Write-Host -ForegroundColor White "Creating folders in target: $($uniqueFolders.Count)"
foreach ($folder in $uniqueFolders) {
Write-Host -ForegroundColor White "Creating folder: $($folder)"
$folderResult = Resolve-PnPFolder -SiteRelativePath $folder -Connection $targetCtx
}
Write-Host -ForegroundColor Green "DONE"
}
function Copy-Files
{
param(
[Parameter(Mandatory=$true)]
[string]$PathToFileCopyLog
)
# Read file with folders
$logFile = Import-Excel -Path $PathToFileCopyLog
$itemsToCopy = $logFile | Where-Object { $_.Status -ne "Copied" }
Write-Host -ForegroundColor White "Starting copy of files: $($itemsToCopy.Count)"
$counter = 0
foreach ($itemToCopy in $itemsToCopy) {
$counter++
Write-Host -ForegroundColor White "Copy job '$($counter)/$($itemsToCopy.Count)': $($itemToCopy.Filename)"
# Read properties
$fileAsListItem = $null
$fileAsListItem = Get-PnPListItem -List $sourceLibrary -UniqueId $itemToCopy.UniqueId -Connection $sourceCtx
# Read file
$fileAsMemStream = $null
$fileAsMemStream = Get-PnPFile -Url $itemToCopy.SourceFileRef -AsMemoryStream -Connection $sourceCtx
# Get item properties from source
$propertiesHashtable = @{}
$propertiesAsString = $null
foreach($prop in $propertiesToMigrate){
$propertiesHashtable[$prop] = $fileAsListItem.FieldValues.$prop
$propertiesAsString = $propertiesAsString,"$($prop)=$($fileAsListItem.FieldValues.$prop)" -join ','
}
# Create file in target with properties
$copyStatus = $null
try {
$result = Add-PnPFile -Folder $itemToCopy.TargetFileDirRefRelative -FileName $itemToCopy.Filename -Stream $fileAsMemStream -Values $propertiesHashtable -Connection $targetCtx
$copyStatus = "Copied"
}
catch {
$copyStatus = "Error"
}
# Udate the status in the file copy log
$itemToCopy.Status = $copyStatus
$itemToCopy.CopiedProperties = $propertiesAsString
$logFile | Export-Excel -Path $PathToFileCopyLog
}
return $null
}
#######################
#######################
Write-Host -ForegroundColor Green "Starting file copy job"
$jobstart = Get-Date
# Define logging file
$FileCopyLog = "$PSScriptRoot\CopyLibraryBetweenTenants-FileCopyLog.xlsx"
GetFileCopyLog -PathToFileCopyLog $FileCopyLog
CreateFoldersInTarget -PathToFileCopyLog $FileCopyLog
Copy-Files -PathToFileCopyLog $FileCopyLog
$jobend = Get-Date
$jobduration = New-TimeSpan -Start $jobstart -End $jobend
Write-Host -ForegroundColor White ""
Write-Host -ForegroundColor White "Job duration: '$($jobduration.Hours)'h '$($jobduration.Minutes)'min '$($jobduration.Seconds)'sec (system time: '$($jobduration)')"
Write-Host -ForegroundColor Green "DONE"
```
[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)]
***

## Contributors

| Author(s) |
|-----------|
| Timo Vomstein |


[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)]
<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-copy-library-across-tenants" aria-hidden="true" />
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions scripts/spo-copy-library-across-tenants/assets/sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[
{
"name": "spo-copy-library-across-tenants",
"source": "pnp",
"title": "Copying a document library between different tenants with resume functionality",
"shortDescription": "This sample shows how to copy a document library including files and folder structure from one tenant to another one. It includes a resume functionality which comes handy for large libraries. Additionally you can specify custom properties which shall also copied to the target.",
"url": "https://pnp.github.io/script-samples/spo-copy-library-across-tenants/README.html",
"longDescription": [
"This sample shows how to copy a document library including files and folder structure from one tenant to another one."
],
"creationDateTime": "2024-12-10",
"updateDateTime": "2024-12-10",
"products": [
"SharePoint"
],
"metadata": [
{
"key": "PNP-POWERSHELL",
"value": "2.12.0"
},
{
"key": "POWERSHELL",
"value": "7.4.6"
}
],
"categories": [
"Configure",
"Security"
],
"tags": [
"Connect-PnPOnline",
"Get-PnPList",
"Get-PnPListItem",
"Resolve-PnPFolder",
"Get-PnPFile",
"Add-PnPFile"
],
"thumbnails": [
{
"type": "image",
"order": 100,
"url": "https://raw.githubusercontent.com/pnp/script-samples/main/scripts/spo-copy-library-across-tenants/assets/preview.png",
"alt": "preview image for the sample"
}
],
"authors": [
{
"name": "Timo Vomstein",
"gitHubAccount": "timtatam",
"company": "",
"pictureUrl": "https://avatars.githubusercontent.com/u/8087836?v=4"
}
],
"references": [
{
"name": "Want to learn more about PnP PowerShell and the cmdlets",
"description": "Check out the PnP PowerShell site to get started and for the reference to the cmdlets.",
"url": "https://aka.ms/pnp/powershell"
}
]
}
]

0 comments on commit 8e4c0b9

Please sign in to comment.