SharePoint Top Link Bar Synchronization

Applies To: SharePoint

The Top Link Bar (sometimes called the Tab bar) provides global navigation for a site collection. It’s a great spot to link to subsites, other site collections and more. It’s also relatively easy to use and customize. On a standard team site you can go to Site Actions > Site Settings and then choose the Navigation link under Look and Feel to customize. This all works really well. However, it can be difficult to maintain consistent navigation between multiple site collections. Add in the complication of relative URLs, audiences and attempting to keep those sites in sync with any more than 2 or 3 site collections – suddenly any navigational changes become a full project!

IMG_0347

Fortunately, using the below PowerShell script you can easily setup navigation on a single site collection using the out of the box tools and then propagate those changes to as many additional site collections as needed:

The Script

You can copy the script below and paste it into notepad and save it as NavigationPropagation.ps1

$sourceweb = "http://somesite.com"
$destwebs = "http://somesite.com/sites/humanresources", "http://somesite.com/panda", "http://someothersite.com/pickles"

$keepAudiences = $true #set to false to have it totally ignore audiences (good for testing)

Function CreateNode($node,$dWeb,$destCollection){
    if ($node.Properties.NodeType -ne [Microsoft.SharePoint.Publishing.NodeTypes]::Area -and $node.Properties.NodeType -ne [Microsoft.SharePoint.Publishing.NodeTypes]::Page) {

        Write-Host "    Node: " + $node.Title

        #make URLs relative to destination
    	$hnUrl = $node.Url
    	#Write-Host "O URL: $hnUrl"
        $IsHeading = $false
        if($node.Properties.NodeType -eq [Microsoft.SharePoint.Publishing.NodeTypes]::Heading){
            $IsHeading = $true
        }
    	$hnUrl = SwapUrl $hnUrl $sourceWeb $dWeb $IsHeading
    	#Write-Host "N URL: $hnUrl"

        $hnType = $node.Properties.NodeType
        if($hnType -eq $null){
            $hnType = "None"
        }

        $hNode = [Microsoft.SharePoint.Publishing.Navigation.SPNavigationSiteMapNode]::CreateSPNavigationNode($node.Title,$hnUrl,[Microsoft.SharePoint.Publishing.NodeTypes]$hnType, $destCollection)
    	$hNode.Properties.Description = $node.Properties.Description
        if($keepAudiences){
    	   $hNode.Properties.Audience = $node.Properties.Audience
        }
    	$hNode.Properties.Target = $node.Properties.Target
        $hNode.Update()

        if($node.Children.Count -gt 0) {
    	   foreach($child in $node.Children) {
                CreateNode $child $dWeb $hNode.Children
    	   }
        }
    }
}

Function SwapUrl([string]$currentUrl,[string]$sourceRoot,[string]$destRoot,[boolean]$isHeading) {
	if ($currentUrl -ne "/") {
		if ($currentUrl.StartsWith("/")) {
			$currentUrl = $sourceRoot + $currentUrl
		} elseif ($currentUrl.StartsWith($destRoot)) {
			$currentUrl = $currentUrl.Replace($destRoot, "")
		}
	} else {
		$currentUrl = [System.String]::Empty
        if(!$isHeading){
            Write-Host "        This is a blank non-heading! Using $sourceRoot" -ForegroundColor Yellow
            $currentUrl = $sourceRoot
        }
	}
	$currentUrl
}

$sw = Get-SPWeb $sourceweb
$psw = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($sw)

foreach ($destweb in $destwebs) {

	Write-Host "***************************************" -ForegroundColor Cyan
	Write-Host "Propogatin' That There $destweb" -ForegroundColor Cyan
	Write-Host "***************************************" -ForegroundColor Cyan

	$dw = Get-SPWeb $destweb
	$pdw = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($dw)

	$nodeCount = $pdw.Navigation.GlobalNavigationNodes.Count
	Write-Host "  Removing Current Nodes ($nodeCount)..." -ForegroundColor DarkYellow
	for ($i=$nodeCount-1; $i -ge 0; $i--) {
		$pdw.Navigation.GlobalNavigationNodes[$i].Delete()
	}

	Write-Host "  Propagating to $destweb..." -ForegroundColor DarkYellow
	foreach($n in $psw.Navigation.GlobalNavigationNodes){
		[void](CreateNode $n $destweb $pdw.Navigation.GlobalNavigationNodes)
	}

	$dw.dispose()
}

$sw.dispose()

What it Does and How to Use it

Since this is a script that you will typically run with the same parameters over and over I just made them variables at the top of the script. There are just 3 of them:

  • $sourceweb: This is the url of the site collection to copy the navigation from
  • $destwebs: This is a comma separated (array) list of urls to copy the navigation to
  • $keepAudiences: When $true audiences are propagated along with the links, when $false then these are totally ignored. Setting this to $false can be really helpful for testing just to make sure everything is being copied correctly before you add in the complication of audience filtering

Once you set those and save, open the SharePoint Management Shell (You’ll want to run-as a farm administrator) and navigate to the location of the script you can simply run it by typing: .\NavigationPropagation.ps1

IMG_0349

The script will cycle through each of the $destwebs and delete all the Global Navigation Nodes from the site. It will then copy all the nodes (excluding Area and Page types since these are automatic depending on your settings) from the $sourceweb. Each URL is made relative or adjusted to be fully qualified as needed. This is done recursively allowing any number of levels of child nodes (more on this in an upcoming post).

Typical use case for us is to save the script with all the parameters set and whenever we make a change on the $sourceweb we simply run the script and everything gets synchronized. If you’re making frequent updates then you could always use Task Scheduler to run the script automatically.

How it Works

There are 2 functions in the script: CreateNode (lines 4-40) and SwapUrl (lines 42-57). CreateNode copies a passed node into the specified collection mapping the necessary properties and calling SwapUrl which ensures relative Urls are fully qualified and Urls that should be relative are made relative. The main thing to note is that CreateNode is recursive and will copy all child nodes as well.

The main execution of the script starts at line 59 and is pretty straightforward. It retrieves the source web and then loops through the destination webs. For each destination web it deletes all the existing navigation nodes and then calls CreateNode passing each of the source web’s nodes and the destinations node collection.

 

Keeping your navigation consistent across the farm is essential to a good user experience but can be difficult to manage using out of the box tools. Fortunately with the above script this can be relatively simple.

Advertisements

2 thoughts on “SharePoint Top Link Bar Synchronization

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s