Adding nodes to top navigation on SharePoint on premise via PowerShell
Sometimes it can be useful to add a navigation node to the top navigation which links to the current web. In our case we worked on a customer’s intranet, where it was necessary to do so. Default SharePoint behavior is to put the link to the current web to home node. But if you want it to be on another node, for example if you want an sorted navigation, you have to work a little bit for it.
Let us think of a case where this could happen:
For example, we have five site collections (A, B, C, D, E) as publishing webs which we want to navigate between via top navigation. SharePoint will put the home link differently according to which site collection we are on. On site collection A our top navigation will look like this:
On site collection B the top navigation will look like this:
Our aim is to let every site collection appear with its own link in top navigation, but not using the home button, because we want a consistant top navigation which will look the same on all site collections. You can add navigation nodes in the UI under site settings and then under navigation. But we want to do it automated via PowerShell because its cooler and for bigger environments a lot easier.
To get this version of the UI we need we need to set the navigation settings to structural navigation first. Of course we do it with PowerShell. Therefore, we write a function which takes the URL of a site collection, the name and URL of the navigation node and in this case the name of the SharePoint group for the audience setting as parameter. These parameters are all of type string. The function then gets the site collection object with the URL and the Get-SPSite function and then do a for each loop for all webs inside the site collection. Root web and sub webs need a different treatment.
function Set-NavigationNodes { [CmdletBinding()] param( [string] $SiteUrl, [string] $NavNodeName, [string] $NavNodeUrl, [string] $AudienceGroupName ) $site = Get-SPSite -Identity $SiteUrl try { $webs = $site.AllWebs foreach ($web in $webs) { # Execute rootweb # Execute subweb } } finally { if ($null -ne $site) { $site.Dispose() } } }
For the root web it is necessary to get the publishing web object and from this we need the web navigation settings. In the web navigation settings, we set sources of global and current navigation to “PortalProvider” to enable structural navigation (see https://docs.microsoft.com/en-us/dotnet/api/microsoft.sharepoint.publishing.navigation.standardnavigationsource?view=sharepoint-server for more information). Important is that the update function is called for the web navigation settings to apply our changes. After that we set including of subsites and pages to false on global and current navigation, because in our example we do not want them to appear on top navigation. If you want them to display you set these two properties to true obviously and may have to set DynamicChildLimit property to an arbitrary integer value to limit the children that are displayed in top navigation if you want that. For sub webs we want to set inherit navigation property to true for global and current navigation on the publishing web, so that sub webs have the same settings as their parent web has. Then an update of the publishing web is required to apply changes.
try { # Execute rootweb if ($web.Url -eq $site.Url) { $publishingWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web) $navigation = $publishingWeb.Navigation $webNavigationSettings = New-Object Microsoft.SharePoint.Publishing.Navigation.WebNavigationSettings($web); $webNavigationSettings.GlobalNavigation.Source = "PortalProvider" $webNavigationSettings.CurrentNavigation.Source = "PortalProvider" $webNavigationSettings.Update() $navigation.GlobalIncludeSubSites = $false $navigation.GlobalIncludePages = $false $navigation.GlobalDynamicChildLimit = 20 $navigation.CurrentIncludeSubSites = $false $navigation.CurrentIncludePages = $false $navigation.CurrentDynamicChildLimit = 20 } # Execute subweb else { $publishingWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web) $publishingWeb.InheritGlobalNavigation = $true $publishingWeb.InheritCurrentNavigation = $true } $publishingWeb.Update() $navigationNode = $pubWeb.Navigation.GlobalNavigationNodes | Where-Object { $_.Title -eq $NavigationNodeName } if ($null -ne $navigationNode) { Write-Warning "Navigation node '$($NavigationNodeName)' already exists in web '$($WebUrl)'" } else { $navigationNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode($NavigationNodeName, $NavigationNodeUrl, $true) $pubweb.Navigation.GlobalNavigationNodes.AddAsLast($navigationNode) } $navigationNode.Properties["NodeType"] = "Heading" $audienceGroup = $web.SiteGroups | Where-Object {$_.Name -Match "_F"} if ($null -ne $audienceGroup) { $navigationNode.Properties["Audience"] = ";;;;$($audienceGroup.Name)" } $navigationNode.Update() $pubWeb.Update() } finally { if ($null -ne $web) { $web.Dispose() } }
After this we can step forward to the creation of navigation links itself. First our function should check if there exists a navigation node with our name already, because we don’t want nodes with duplicated names in our navigation. If there is no node with the same name, we create an SPNavigationNode object with the New-Object method and we add it to the GlobalNavigationNodes in navigation of the publishing web.
$navigationNode = $pubWeb.Navigation.GlobalNavigationNodes | Where-Object { $_.Title -eq $NavigationNodeName } if ($null -ne $navigationNode) { Write-Warning "Navigation node '$($NavigationNodeName)' already exists in web '$($WebUrl)'" } else { $navigationNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode($NavigationNodeName, $NavigationNodeUrl, $true) $pubweb.Navigation.GlobalNavigationNodes.AddAsLast($navigationNode) }
You can optionally add an audience to the link to prevent user from other SharePoint groups to see it. Therefore, we load the site groups from the web, based on its name in this case, check if the group was found and then set the audience property of our navigation link with the group name. The string in the audience property needs to have a special format:
“{SharePointAudienceID};;{ActiveDirectoryGroupObjectLDAPPath};;{SharePointGroup}”
Our aim is to add a SharePoint group, so our audience string will look like this:
“;;;;{Name of SharePointGroup}”
You can add several SharePointGroups by separating the group names with commas:
“;;;;{Name of SharePoint group 1},{Name of SharePoint group 2}”
In our case we only add one group to keep the example simpler. Therefore we load the SharePoint group with this name from our web object and set the Audience property with it. After setting the audience we update the navigation node and the publishing web and finally after that you should dispose the publishing web and site collection object to clean RAM.
$audienceGroup = $web.SiteGroups | Where-Object {$_.Name -Match "_F"} if ($null -ne $audienceGroup) { $navigationNode.Properties["Audience"] = ";;;;$($audienceGroup.Name)" } $navigationNode.Update() $pubWeb.Update()
Now we finished the first function we need for our aim. This function works perfectly fine, if you do not want to add a navigation node to the current web itself. As you can see below the nodes are in the navigation of the site collection but the one with the self-link is missing.
The Audience is set to our desired SharePoint group.
The self-link is on the home button by default in SharePoint. But we want to add it to the navigation bar with a custom name and we need to add a little trick to our function to do so. We add code which sets the NodeType property of the navigation node explicit to “Heading”.
$navigationNode.Properties["NodeType"] = "Heading"
If we do so the node will be added to the navigation list and displayed correctly in the setting and the UI. If the other node are already there, the new node will be added at the bottom.
Concluding I have the whole function for you:
function Set-NavigationNodes { [CmdletBinding()] param( [string] $SiteUrl, [string] $NavNodeName, [string] $NavNodeUrl, [string] $AudienceGroupName ) $site = Get-SPSite -Identity $SiteUrl try { $webs = $site.AllWebs foreach ($web in $webs) { try { # Execute rootweb if ($web.Url -eq $site.Url) { $publishingWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web) $navigation = $publishingWeb.Navigation $webNavigationSettings = New-Object Microsoft.SharePoint.Publishing.Navigation.WebNavigationSettings($web); $webNavigationSettings.GlobalNavigation.Source = "PortalProvider" $webNavigationSettings.CurrentNavigation.Source = "PortalProvider" $webNavigationSettings.Update() $navigation.GlobalIncludeSubSites = $false $navigation.GlobalIncludePages = $false $navigation.GlobalDynamicChildLimit = 20 $navigation.CurrentIncludeSubSites = $false $navigation.CurrentIncludePages = $false $navigation.CurrentDynamicChildLimit = 20 } # Execute subweb else { $publishingWeb = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPublishingWeb($web) $publishingWeb.InheritGlobalNavigation = $true $publishingWeb.InheritCurrentNavigation = $true } $publishingWeb.Update() $navigationNode = $pubWeb.Navigation.GlobalNavigationNodes | Where-Object { $_.Title -eq $NavigationNodeName } if ($null -ne $navigationNode) { Write-Warning "Navigation node '$($NavigationNodeName)' already exists in web '$($WebUrl)'" } else { $navigationNode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode($NavigationNodeName, $NavigationNodeUrl, $true) $pubweb.Navigation.GlobalNavigationNodes.AddAsLast($navigationNode) } $navigationNode.Properties["NodeType"] = "Heading" $audienceGroup = $web.SiteGroups | Where-Object {$_.Name -Match "_F"} if ($null -ne $audienceGroup) { $navigationNode.Properties["Audience"] = ";;;;$($audienceGroup.Name)" } $navigationNode.Update() $pubWeb.Update() } finally { if ($null -ne $web) { $web.Dispose() } } } } finally { if ($null -ne $site) { $site.Dispose() } } }
Hopefully this helped you for your coding. Thanks for reading.