Ever needed to sync or copy SharePoint’s OOTB Mega Menu across different Site Collections ? Well, you might come across a requirement as such some day!
This post covers the operations involved in copying the Mega Menu from a given Site Collection to another Site Collection. At a high level, the script does the following,
- Retrieves the Site Navigation from source Site Collection – (Copy from)
- Retrieves the Site Navigation from destination Site Collection – (Copy to)
- Compares the Source and Destination to check if there are any differences – this step is optional. I’d recommend this to avoid unnecessary syncing when the source and destination are identical.
- Deletes the existing Site Collection Navigation from the destination – Verify that you are not deleting the source site collection’s navigation 😉
- Uses the Source Site Navigation collection retrieved in step 1 to populate the Site Navigation in the Destination.
param([Parameter(Mandatory=$false)]
[System.Management.Automation.PSCredential]$credentials = (Get-Credential)
)
$srcNavObjects = $null
function Get-SPSiteNavigation{
param(
[Parameter(Mandatory=$true)]
[string] $Identity,
[Parameter(Mandatory=$false)]
[string]$prefixUrl
)
begin{
$exportNavCol = @()
# This counter is used in order to maintain the order of the navigation.
# The navigation is returned in the order it appears.
$counter = 1
Write-Debug "$((Get-Date).ToString("dd/MM/yyyy HH:mm:ss")) Get-SPSiteNavigation function started."
}
process{
# Connect to the site with the navigation to base all the other sites on
$connection = Connect-PnPOnline -Url $identity -Credentials $credentials
$site = Get-PnPSite
Write-Debug "$((Get-Date).ToString("dd/MM/yyyy HH:mm:ss")) Exporting navigation from $($site.Url)..."
# Get the master navigation
$navigationNodes = Get-PnPNavigationNode -Location QuickLaunch -Connection $connection
# Iterate through the navigation and capture all the nodes on all 3 levels
foreach($navigationNode in $navigationNodes){
$parentNode = Get-PnPNavigationNode -id $navigationNode.Id
if($parentNode.Title -eq 'Recent'){
Continue
}
$navInfo = New-Object PSObject -property @{
Level = "Level 1"
Id = $navigationNode.Id
Title = $navigationNode.Title
Url = $navigationNode.Url
ParentId = "0"
ParentTitle = ""
Visible = $navigationNode.IsVisible
Order = $counter
}
# Add the navInfo collection to the collection we're going to export.
$exportNavCol += $navInfo
$counter++
# Get the second level navigation
$navigation = Get-PnPNavigationNode -Id $navigationNode.Id
$children = $navigation.Children
# If children exist proceed
if($children){
foreach($child in $children){
# Get the node and further information about the link
$childNode = Get-PnPNavigationNode -Id $child.Id
$navInfo = New-Object PSObject -property @{
Level = "Level 2"
Id = $childNode.Id
Title = $childNode.Title
Url = $childNode.Url
ParentId = $parentNode.Id
ParentTitle = $parentNode.Title
Visible = $childNode.IsVisible
Order = $counter
}
# Add the navInfo collection to the collection we're going to export.
$exportNavCol += $navInfo
$counter++
# Get the third level navigation
$subChildren = $childNode.Children
# if children exist proceed
if($subChildren) {
foreach($subChild in $subChildren) {
# Get the node and further information about the link
$subChildNode = Get-PnPNavigationNode -Id $subChild.Id
$navInfo = New-Object PSObject -property @{
Level = "Level 3"
Id = $subChildNode.Id
Title = $subChildNode.Title
Url = $subChildNode.Url
ParentId = $childNode.Id
ParentTitle = $childNode.Title
Visible = $childNode.IsVisible
Order = $counter
}
$exportNavCol += $navInfo
$counter++
}
}
}
}
}
Disconnect-PnPOnline -Connection $connection
}
end{
# Rebuild collection with sort
$navObjects = @()
$navObjects = $exportNavCol | Sort-Object Order
Write-Debug "$((Get-Date).ToString("dd/MM/yyyy HH:mm:ss")) Get-SPSiteNavigation finished."
return $navObjects
}
}
function Copy-SPSiteNavigation {
param (
[Parameter(Mandatory=$true)]
[string] $Identity
)
begin{
$newNavigationArray = @()
Write-Debug "$((Get-Date).ToString("dd/MM/yyyy HH:mm:ss")) Copy-SPSiteNavigation function started."
}
process{
# Connect to the target site with where the navigation will be added
$connection = Connect-PnPOnline -Url $identity -Credentials $credentials
$site = Get-PnPSite
$currentLevel = 1
$parentId = 0
# Import navigation CSV
$navigationCol = $srcNavObjects #Import-Csv $importCsv
Write-Debug "$((Get-Date).ToString("dd/MM/yyyy HH:mm:ss")) Removing existing navigation."
# Remove existing navigation
Remove-PnPNavigationNode -All -Force
# Remove any remaining nodes. Experienced instances where the RemovePnpNavigationNode -All hasn't worked.
$navNodes = Get-PnPNavigationNode -Location QuickLaunch -Connection $connection
$navNodes | Remove-PnPNavigationNode -Force
Write-Debug "$((Get-Date).ToString("dd/MM/yyyy HH:mm:ss")) Importing navigation from $($importCsv) to $($site.Url)..."
foreach($navigationObject in $navigationCol){
Try{
Write-Debug "$((Get-Date).ToString("dd/MM/yyyy HH:mm:ss")) Adding navigation node: $($navigationObject.Title)"
#Write-Debug "$($navigationObject.ParentId) $($navigationObject.Title)"
# If the parent is 0 then it is a top-level navigation node
if($navigationObject.ParentId -eq 0) {
$parentId = 0
$level = 1
if($navigationObject.Url.Length -gt 0) {
# Navigation node has a Url so create it as a top level menu item
$addedNav = Add-PnPNavigationNode -Url $navigationObject.Url -Title $navigationObject.Title -Location QuickLaunch
}else{
# Navigation node hasn't a Url so create it as a top level header item
$addedNav = Add-PnPNavigationNode -Title $navigationObject.Title -Location QuickLaunch
}
}else{
# Get the parent for the child node from the master navigation array
$parentNav = $navigationCol | Where-Object{$_.Id -eq $navigationObject.ParentId}
if($parentNav.Url.Length -gt 0) {
# Navigation node has a Url so get the newly created navigation node from the new array
# via the Title and Url to get the new Id for the parent item and add it to the navigation
$newNav = $newNavigationArray | Where-Object {$_.Title -eq $parentNav.Title -and $_.Url -eq $parentNav.Url}
if($currentLevel -ne $navigationObject.Level){
$parentId = $newNav.Id
$level = $navigationObject.Level
}
$addedNav = Add-PnPNavigationNode -Url $navigationObject.Url -Title $navigationObject.Title -Location QuickLaunch -Parent $parentId
}else{
# Navigation node does not have a Url so get the newly created navigation node from the new array
# via the Title only to get the new Id for the parent item and add it to the navigation
$newNav = $newNavigationArray | Where-Object{$_.Title -eq $parentNav.Title}
if($currentLevel -ne $navigationObject.Level){
$parentId = $newNav.Id
$level = $navigationObject.Level
}
$addedNav = Add-PnPNavigationNode -Title $navigationObject.Title -Location QuickLaunch -Parent $parentId
}
$parentNav = $null
}
$currentLevel = $navigationObject.Level
# Capture the navigation node created to store their IDs
$navigationObject = New-Object PSObject -property @{
Id = $addedNav.Id
Url = $addedNav.Url
Title = $addedNav.Title
Level = $level
ParentId = $parentId
}
# Add the recently added navigation to the new navigation array
$newNavigationArray += $navigationObject
}
Catch{
$_ | Out-File $($PSScriptRoot + "/NavigationSyncLog.txt")
}
}
}
end{
Disconnect-PnPOnline -Connection $connection
}
}
$DebugPreference = "Continue" #Continue; SilentlyContinue
$srcNavUrl = "https://[tenant].sharepoint.com/sites/source"
$destNavUrl = "https://[tenant].sharepoint.com/sites/dest"
$srcNavObjects = Get-SPSiteNavigation -identity $srcNavUrl -prefixUrl "https://[tenant].sharepoint.com"
$destNavObjects = Get-SPSiteNavigation -identity $destNavUrl -prefixUrl ""
$propertiesToCompare = "Visible","Title","Url","ParentTitle","Order"
$compareRes = Compare-Object -ReferenceObject $srcNavObjects -DifferenceObject $destNavObjects -Property $propertiesToCompare -CaseSensitive #-ExcludeDifferent
$diff = $compareRes | Where {$_.SideIndicator -eq "<="}
#Run the script only if the nav in source site collection has updates.
if($diff)
{
Write-Host "Changes detected.. The Navigation items will be re-synchronised" -ForegroundColor Yellow
$sitesToReplicateMegaMenu = @("https://[tenant].sharepoint.com/sites/dest")
foreach($site in $sitesToReplicateMegaMenu){
Write-Host "Creating Mega Menu in $site..." -ForegroundColor Yellow
Copy-SPSiteNavigation -identity $site
Write-Host "Mega Menu in $site has been created successfully" -ForegroundColor Green
}
}else{
Write-Host "The Navigation items are identical. No sync is required" -ForegroundColor Green
}
Save the script as syncMegaMenu.ps1
PS C:\dev\scripts> .\syncMegaMenu.ps1 $credendtials = (Get-Credential)
Pass the credentials parameter as argument
PS C:\dev\scripts> .\syncMegaMenu.ps1 -credentials $credentials