Exporting and Migrating SharePoint Libraries with Folders Using PnP PowerShell
Introduction
Recently, I was asked how to export the first-level folders in document libraries using PnP PowerShell site provisioning cmdlets. While Get-PnPSiteTemplate
allows exporting SharePoint site configurations, it doesn’t directly cater to folders. After experimenting with various options, including Add-PnPDataRowsToSiteTemplate
, I discovered the solution: the Add-PnPListFoldersToSiteTemplate
cmdlet.
The Challenge
The initial attempt involved using the Add-PnPDataRowsToSiteTemplate
cmdlet with a CAML query to filter folders and it did not work.
Add-PnPDataRowsToSiteTemplate -Path Site.xml -List 'Shared Documents' -Query '<Query><Where><And><Eq><FieldRef Name="FSObjType"/><Value Type="Integer">1</Value></Eq><IsNotNull><FieldRef Name="Title"/></IsNotNull></And></Where></Query>' -Fields "FileRef"
I explored the Using a PnP Site Template including files and list items by the wonderful Kasper, but it did not address folders.
Solution Overview
After reviewing the PnP PowerShell documentation, I found the Add-PnPListFoldersToSiteTemplate
cmdlet, which perfectly fits the requirement.
The script below demonstrates how to export a SharePoint site template with folder structures from document libraries and apply it to a destination site.
Key Features:
- Exports folder structures from all document libraries in the source site.
- Applies the exported template to the destination site.
- Provides visual cues for script progress using Write-Host.
Script: Export and Apply Site Template
param(
[Parameter(Mandatory=$true)]
[string]$SourceSiteUrl,
[Parameter(Mandatory=$true)]
[string]$DestinationSiteUrl,
[Parameter(Mandatory=$false)]
[string]$TemplateFileName = "SiteTemplate.xml"
)
# Display script parameters
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "SITE TEMPLATE MIGRATION SCRIPT" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Source Site URL: $SourceSiteUrl" -ForegroundColor White
Write-Host "Destination Site URL: $DestinationSiteUrl" -ForegroundColor White
Write-Host "Template File: $TemplateFileName" -ForegroundColor White
Write-Host "========================================`n" -ForegroundColor Cyan
# Connect to source site and export template
Write-Host "Connecting to source site..." -ForegroundColor Green
Connect-PnPOnline -Interactive -Url $SourceSiteUrl
Write-Host "Exporting site template..." -ForegroundColor Green
Get-PnPSiteTemplate -Out $TemplateFileName -Handlers Lists
# Get all document libraries and loop through them to add folders
Write-Host "Getting all document libraries..." -ForegroundColor Cyan
$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")
$libraries = Get-PnPList | Where-Object { $_.BaseTemplate -eq 101 -and $_.Hidden -eq $false -and $_.Title -notin $ExcludedLists}
if ($libraries.Count -gt 0) {
Write-Host "Found $($libraries.Count) document libraries:" -ForegroundColor Green
foreach ($library in $libraries) {
Write-Host " - $($library.Title)" -ForegroundColor Yellow
}
Write-Host "`nAdding folder structures to site template..." -ForegroundColor Cyan
foreach ($library in $libraries) {
try {
Write-Host "Processing library: $($library.Title)..." -ForegroundColor Yellow
Add-PnPListFoldersToSiteTemplate -Path $TemplateFileName -List $library.Title
Write-Host "✓ Successfully added folders for '$($library.Title)'" -ForegroundColor Green
}
catch {
Write-Host "✗ Error processing library '$($library.Title)': $($_.Exception.Message)" -ForegroundColor Red
}
}
} else {
Write-Host "No document libraries found." -ForegroundColor Yellow
}
# Connect to destination site and apply template
Write-Host "`nConnecting to destination site..." -ForegroundColor Green
Connect-PnPOnline -Interactive -Url $DestinationSiteUrl
Write-Host "Applying site template to destination..." -ForegroundColor Green
Invoke-PnPSiteTemplate -Path $TemplateFileName -Handlers Lists
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host "MIGRATION COMPLETED SUCCESSFULLY!" -ForegroundColor Green
Write-Host "Template applied from: $SourceSiteUrl" -ForegroundColor White
Write-Host "Template applied to: $DestinationSiteUrl" -ForegroundColor White
Write-Host "========================================" -ForegroundColor Cyan
Note: I used Github Copilot to improve original script to above to add the visual cues of script progress related to the write-host
Results
Exported Template
The script generates a site template containing folder structures from the source site’s document libraries. For example:
<pnp:Folders>
<pnp:Folder Name="two" />
<pnp:Folder Name="one" />
</pnp:Folders>
Migrated Folders
When applied to the destination site, the folder structures are replicated successfully.