Migrate SharePoint Site Designs and Scripts to a new Tenant

I was asked recently to migrate SharePoint site designs and site scripts to a new tenant. In this post all the steps including exporting the site designs and site scripts before importing them. All using PnP PowerShell.

The General Design of the PowerShell scripts

I’m going to use PnP PowerShell to do the migration of my Site Designs and Site Scripts. Did you know that when you use Visual Studio Code to write your PowerShell scripts it suggests large blocks of code. You almost get the feeling that VS Code can ready your mind and predict your next 15 lines of code.

I’m going to talk you through two scripts. First the Export script and then the import script.

At the end of this post I will make downloads to the scripts available. But please do NOT just download the scripts and run them! You might as well ask ChatGPT to wipe your disks clean.

PowerShell Settings

Both my scripts have a settings block where we need to provide a few details.

Clear-Host
# Settings - UPDATE THESE TO YOUR SITE AND TENANT
# Connect to your SharePoint tenant
$siteUrl = "https://pieterveenstraMVP-admin.sharepoint.com"
#Tenant ID
$tenant = "pieterveenstraMVP.onmicrosoft.com"
# Export each site design
$exportPath = "C:\projects\SiteDesignMigration\Exports"
# End of Settings

Install PnP PowerShell

Then the script will install Version 3.1.0. If you have the habit to install PowerShell modules without the CurrentUser scope then you may need to uninstall older version first. There isn’t really a need to ever install PowerShell modules globally on your system. The -Scope CurrentUser switch will also take away the need to be an admin on your system.

# Install PnP.PowerShell if not already installed. Script has been tested with version 3.1.0,
# so we will check for that specific version and install or upgrade as needed.
If( (Get-Module PnP.PowerShell).Version -eq "3.1.0")
{
Write-Host "PnP.PowerShell version 3.1.0 is already installed." -ForegroundColor Green
}
else {
if (!(Get-Module PnP.PowerShell))
{
Write-Host "Installing PnP.PowerShell..." -ForegroundColor Yellow
Install-Module PnP.PowerShell -Scope CurrentUser -Force
} elsif (Get-Module PnP.PowerShell).Version -ne "3.1.0"
{
Upgrade-Module PnP.PowerShell -Scope CurrentUser -Force
}
}

Connect to SharePoint online

The next steps are to connection the SharePoint Online. In many of my past posts you will still find that I’m using different switches to connect SharePoint online using Connect-PnPOnline. But the below method is probably the easiest way to get connected since the default App registration for Interactive Logins was taken away. Using the Register-PnPEntraIDAppForInteractiveLogin Cmdlet we can very quickly create our own app registration. You will however need a Global Admin to approve the app registration or run the script.

Write-Host "Registering PnP.PowerShell app for interactive login..." -ForegroundColor Yellow
# Register the PnP.PowerShell app for interactive login and connect to the tenant
$AppRegistration = Register-PnPEntraIDAppForInteractiveLogin -ApplicationName "PnP.PowerShell" -Tenant $tenant -GraphDelegatePermissions "Sites.Read.All","User.Read" -SharePointDelegatePermissions "AllSites.FullControl" -ErrorAction SilentlyContinue | Out-Null
# Get the Client ID from the app registration
$ClientID = $AppRegistration.'AzureAppId/ClientId'
# Connect to the SharePoint tenant using the registered app
Connect-PnPOnline -Url $siteUrl -ClientId $ClientID

Get the Existing Site Designs

To get the existing site designs we can now use Get-PnP SiteDesign

# Get all site designs
$ExistingSiteDesigns = Get-PnPSiteDesign

Create the export folder

To make sure that our export folder that we specified in the settings exists, I’m running a quick check on my local file system

# Check if export directory exists, if not create it
if (-not (Test-Path $exportPath)) {
Write-Host "Export Directory not found" -ForegroundColor Red
Exit
}

Now we get to the differences between the two scripts. The Export and the Import version of the scripts.

Export the templates

Now to export all of the Site Designs we can step through all the siteDesigns that we found earlier in this post. Then for each Site Design we will collect the site scripts that have been assigned to each of the site designs. Get-PnPSiteDesign and Get-PnPSiteScript will pretty sort it all out.

# Export Site Designs and Site Scripts
foreach ($design in $siteDesigns) {
try {
# Get the full site design information, including the associated site script IDs, and export it to a JSON file
$designJson = Get-PnPSiteDesign -Identity $design.Id
$fileName = "$($design.Title -replace '[\\/:*?"<>|]', '_')_SiteDesign.json"
$filePath = Join-Path $exportPath $fileName
# The Get-PnPSiteDesign cmdlet returns a complex object that includes properties that are not serializable to JSON, such as the Id property which is a GUID object. To export the site design to JSON, we need to create a custom object that only includes the properties we want to export and convert the GUID to a string.
$designJson | ConvertTo-Json | Out-File -FilePath $filePath -Encoding UTF8
Write-Host "Exported: $($design.Title)" -ForegroundColor Green
# After exporting the site design, we will export each associated site script to a separate JSON file. We will use the site design ID in the file name to link the site scripts to the site design.
$DesignId = $design.Id.Guid
# For each site script ID associated with the site design, we will get the site script information and export it to a JSON file. We will use the same naming convention as the site design file, but with "_SiteScript" in the name to differentiate it from the site design file.
foreach ($siteScriptId in $design.SiteScriptIds) {
try {
$siteScriptJson = Get-PnPSiteScript -Identity $siteScriptId
$scriptFileName = "$($design.Title -replace '[\\/:*?"<>|]', '_')_SiteScript_$DesignId.json"
$scriptFilePath = Join-Path $exportPath $scriptFileName
$siteScriptJson| ConvertTo-Json | Out-File -FilePath $scriptFilePath -Encoding UTF8
Write-Host "Exported Site Script: $($siteScriptId) for design: $($design.Title)" -ForegroundColor Green
}
catch {
Write-Host "Error exporting site script $siteScriptId for design $($design.Title): $_" -ForegroundColor Red
}
}
}
catch {
Write-Host "Error exporting $($design.Title): $_" -ForegroundColor Red
}
}

The file names of each of the files created is going to be important. All Site Designs end with SiteDesign.json, while the site Scripts have SiteScript in the name and the unique ID of the site script. This helps to make sure that we know which script is connected to which design. When we import the designs in the new tenant the designs will get given a new Unique ID.

Migrate SharePoint Site Designs and Scripts to a new Tenant
Migrate SharePoint Site Designs and Scripts to a new Tenant

I’ve made sure that the PowerShell script only looked at Site Scripts that have been connected to a Site Design to ensure that any orphaned site scripts are forgotten about.

Import the templates

Now we have a folder with scripts ready to be processed by the Import script. The below script swill collect the Site Design export files before we take the site scripts related to each site design.

Please note that the script below will take care of assigning the new GUID of the imported site script to the newly created site designs.

# Import Site Designs and Site Scripts
Get-Childitem $exportPath -Filter *_SiteDesign.json | ForEach-Object {
# For each site design JSON file, read the content and convert it from JSON to a PowerShell object
$filePath = $_.FullName
$fileContent = Get-Content -Path $filePath -Raw
$siteDesignData = $fileContent | ConvertFrom-Json
$DesignId = $siteDesignData.Id
# Check if the site design already exists
$existingDesign = $ExistingSiteDesigns | Where-Object { $_.Title -eq $siteDesignData.Title }
# If the site design already exists, skip the import. Otherwise, import the site design and its associated site scripts.
if ($existingDesign) {
Write-Host "Site Design '$($siteDesignData.Title)' already exists. Skipping import." -ForegroundColor Yellow
} else {
# Import the site design
try {
# The Add-PnPSiteDesign cmdlet does not support specifying the ID, so we will create the site design without the ID and then update it with the correct site script IDs after importing the site scripts.
$NewDesignData = Add-PnPSiteDesign -Title $siteDesignData.Title -Description $siteDesignData.Description -SiteScriptIds $siteDesignData.SiteScriptIds -WebTemplate $siteDesignData.WebTemplate -PreviewImageUrl $siteDesignData.PreviewImageUrl
Write-Host "Imported Site Design: '$($siteDesignData.Title)'" -ForegroundColor Green
# After importing the site design, we need to import the associated site scripts and update the site design with the correct site script IDs.
Get-Childitem $exportPath -Filter *_SiteScript_$DesignId.json | ForEach-Object {
$scriptFilePath = $_.FullName
$scriptFileContent = Get-Content -Path $scriptFilePath -Raw
$siteScriptData = $scriptFileContent | ConvertFrom-Json
# Check if the site script already exists
$existingScript = Get-PnPSiteScript | Where-Object { $_.Title -eq $siteScriptData.Title }
if ($existingScript) {
# If the site script already exists, we will skip importing it and just update the site design to reference the existing site script ID.
Write-Host "Site Script '$($siteScriptData.Title)' already exists. Skipping import." -ForegroundColor Yellow
Set-PnPSiteDesign -Identity $NewDesignData.Id -SiteScriptIds $existingScript.Id
} else {
# If the site script does not exist, we will import it and then update the site design to reference the new site script ID.
$NewScriptData = Add-PnPSiteScript -Title $siteScriptData.Title -Description $siteScriptData.Description -Content $siteScriptData.Content
Write-Host "Imported Site Script: '$($siteScriptData.Title)'" -ForegroundColor Green
Set-PnPSiteDesign -Identity $NewDesignData.Id -SiteScriptIds $NewScriptData.Id
}
}
} catch {
Write-Host "Error importing Site Design '$($siteDesignData.Title)': $_" -ForegroundColor Red
}
}
}

Get an overview of you Site Designs and Scripts

Not really part of the two scripts however you may want to validate the migration results. Using the following two CmdLets will give you an overview of the Designs and scripts within each tenant

Get-PnPSiteDesign
Get-PnPSiteScript

Once you have migrated the designs you want to make sure that all Designs and Scripts exist within your new tenant and also they should be connected as before. However the GUIDs of all Designs and Scripts will have changed. Any automated processes that use these templates may therefore need to be updated.

Complete Site Design and Script Migration Scripts

The full scripts are available using the below download.


Discover more from SharePains

Subscribe to get the latest posts sent to your email.

Related Posts

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.