Managed Navigation with Powershell
Goal
Our goal is a complete setup for a test site collection with managed metadata navigation and existing pages with different levels. The challenge is the correct configuration for the site with powershell and also create the correct terms. With the script you can set the amount of navigation entries.
Preparation
We use SharePoint PnP Powershell Package to get help with some tasks.
- PnP Powershell 2019 Download
- CSOM dlls “Microsoft.SharePoint.Client.dll” “Microsoft.SharePoint.Client.Runtime.dll” “Microsoft.SharePoint.Client.Taxonomy.dll” “Microsoft.SharePoint.Client.Publishing.dll” from “\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\isapi\” , more details here
- Create a classic team site collection
Key Points
The Invoke-SetupFeatures enables needed features for publishing, what is the requirement.
The Invoke-SetupTermSet creates your termset and Invoke-SetupNavigation enables navigation features. We also use Invoke-EnableTagging to allow tagging, that means we also override the default permission behaviour. (Metadata navigation is not visible to users with read permissions) more information.
Furthermore we collect the page layout item Get-PageLayoutItem and create our terms Invoke-CreateTerms. For each term we create also first a new site page Add-PublishingPageWithContent and connect this page with the new created term.
The Invoke-UpdateTaxonomyCache checks changes for taxonomy and avoids save conflicts.
Content not crawled by search
We also had some issues to crawl a big amount (over 3000) of friendly pages from search. That means you can create the pages, but the search index does not collect it any more - at least the friendly url version.
The official limit for “Number of terms in managed navigation term set” from Microsoft is at 2000.
After some research, on a full crawl for example the complete termset is crawled with all pages. That leads to a timeout. You can override this in search central administration here. We had to set it to a high value, the crawl tooks over 12 minutes - so that is maybe in some cases no optimal solution.
Complete Script
# -------------------------------------------------------------------------------------
# Configuration
# -------------------------------------------------------------------------------------
$siteurl = "https://contoso.com/sites/sitecollection1"
$termSetName = "GlobalNav"
# -------------------------------------------------------------------------------------
# Functions
# -------------------------------------------------------------------------------------
function Invoke-CreateTerms($context, $PublishingWeb, $PageLayoutItem, $termStore, $termObject, $path, $level, $maxlevel, $maxItemsPerLevel, $overallMax) {
$max = $maxItemsPerLevel
$level = $level + 1
for ($i = 1; $i -lt $max + 1; $i++) {
if ($global:countTerms -lt $overallMax) {
if ($path -eq "") {
$newPath = "$i"
}
else {
$newPath = "$($path)_$($i)"
}
$termName = "Term$newPath"
$newpage = Add-PublishingPageWithContent $context $PublishingWeb $PageLayoutItem "$($termName).aspx" $termName $termName
$url = $newpage["FileRef"]
$global:countTerms++
Write-Host "Create term $($termName) - $($global:countTerms)/$overallMax..." -f Yellow -NoNewline
$newTerm = $termObject.CreateTerm($termName, 1033, [System.Guid]::NewGuid().toString())
$newTerm.SetLocalCustomProperty('_Sys_Nav_TargetUrl', $url)
Write-Host "Done" -f Green
if ($level -lt $maxlevel ) {
Invoke-CreateTerms $context $PublishingWeb $PageLayoutItem $termStore $newTerm $newPath $level $maxlevel $maxItemsPerLevel $overallMax
}
}
}
$termStore.CommitAll()
Invoke-PnPQuery
}
function Get-PageLayoutItem($PageLayoutName) {
$Ctx = Get-PnPContext
Write-host -f Yellow "Getting Page Layout..." -NoNewline
#Get the Page Layout
$RootWeb = $Ctx.Site.RootWeb
$MasterPageList = $RootWeb.Lists.GetByTitle('Master Page Gallery')
$CAMLQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
$CAMLQuery.ViewXml = "<View><Query><Where><Eq><FieldRef Name='FileLeafRef' /><Value Type='Text'>$PageLayoutName</Value></Eq></Where></Query></View>"
$PageLayouts = $MasterPageList.GetItems($CAMLQuery)
$Ctx.Load($PageLayouts)
$Ctx.ExecuteQuery()
$PageLayoutItem = $PageLayouts[0]
$Ctx.Load($PageLayoutItem)
$Ctx.ExecuteQuery()
Write-host -f Green "Done"
return $PageLayoutItem
}
function Add-PublishingPageWithContent($Ctx, $PublishingWeb , $PageLayoutItem , $PageName, $PageTitle, $PageContent) {
#Create Publishing page
Write-host -f Yellow "Creating New Page $PageName ..." -NoNewline
$PageInfo = New-Object Microsoft.SharePoint.Client.Publishing.PublishingPageInformation
$PageInfo.Name = $PageName
$PageInfo.PageLayoutListItem = $PageLayoutItem
$Page = $PublishingWeb.AddPublishingPage($PageInfo)
$Ctx.ExecuteQuery()
$ListItem = $Page.ListItem
$Ctx.Load($ListItem)
$Ctx.ExecuteQuery()
#Update Page Contents
$ListItem["Title"] = $PageTitle
$ListItem["PublishingPageContent"] = $PageContent
$ListItem.Update()
$Ctx.ExecuteQuery()
#Publish the page
$ListItem.File.CheckIn([string]::Empty, [Microsoft.SharePoint.Client.CheckinType]::MajorCheckIn)
$ListItem.File.Publish([string]::Empty)
$Ctx.ExecuteQuery()
Write-host -f Green "Done"
return $ListItem
}
function Invoke-SetupFeatures() {
#publihsing Infrastructure site feature check
$FeaturePublishingInfraSiteId = "f6924d36-2fa8-4f0b-b16d-06b7250180fa" #Site Scoped Publishing Feature
$Feature = Get-PnPFeature -Scope Site -Identity $FeaturePublishingInfraSiteId
If ($null -eq $Feature.DefinitionId) {
Write-host -f Yellow "Activating Publishing Infrastructure Site Feature..." -NoNewline
Enable-PnPFeature -Scope Site -Identity $FeaturePublishingInfraSiteId -Force
Write-host -f Green "Done"
}
Else {
Write-host -f Yellow "Publishing Infrastructure Site Feature already activated..." -NoNewline
Write-host -f Green "Done"
}
#publishing Infrastructure web feature check
$FeaturePublishingInfraWebId = "94c94ca6-b32f-4da9-a9e3-1f3d343d7ecb"
$Feature = Get-PnPFeature -Scope Web -Identity $FeaturePublishingInfraWebId
If ($null -eq $Feature.DefinitionId) {
Write-host -f Yellow "Activating Publishing Infrastructure Web Feature..."
Enable-PnPFeature -Scope Web -Identity $FeaturePublishingInfraWebId -Force
Write-host -f Green "Done"
}
Else {
Write-host -f Yellow "Publishing Infrastructure Web Feature already activated..." -NoNewline
Write-host -f Green "Done"
}
#Wait complete all
Start-Sleep -Seconds 10
}
function Invoke-SetupTermSet($termSetName) {
$context = Get-PnPContext
$CurrentSite = Get-PnPSite
$taxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($context)
$TermStore = $taxonomySession.GetDefaultSiteCollectionTermStore();
$SiteCollectionTermGroup = $TermStore.GetSiteCollectionGroup($CurrentSite, $false)
$context.Load($taxonomySession)
$context.Load($SiteCollectionTermGroup)
$context.Load($TermStore)
Invoke-PnPQuery
$termgroupname = $SiteCollectionTermGroup.Name
$termSets = Get-PnPTermSet -TermGroup $termgroupname
$exists = ($termSets | Where-Object { $_.Name -eq $termSetName } | Measure-Object).Count -gt 0
if ($exists -eq $false ) {
Write-Host "Created termset $termSetName ..." -f Yellow -NoNewline
$termSet = New-PnPTermSet -Name $termSetName -TermGroup $SiteCollectionTermGroup -Lcid 1033 -IsOpenForTermCreation
$TermStore.CommitAll()
Write-Host "Done" -f Green
}
else {
Start-Sleep -Seconds 5
Write-Host "Termset already exists $($termSet.Name)" -f Yellow -NoNewline
$termSet = Get-PnPTermSet -Identity $termSetName -TermGroup $termgroupname
Write-Host "Done" -f Green
}
}
function Invoke-SetupNavigation($termSetName) {
Write-Host "Reset Navigation..." -f Yellow -NoNewline
$context = Get-PnPContext
$CurrentSite = Get-PnPSite
$taxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($context)
$TermStore = $taxonomySession.GetDefaultSiteCollectionTermStore();
$SiteCollectionTermGroup = $TermStore.GetSiteCollectionGroup($CurrentSite, $false)
$termsets = $SiteCollectionTermGroup.TermSets
$context.Load($taxonomySession)
$context.Load($SiteCollectionTermGroup)
$context.Load($termsets)
$context.Load($TermStore)
Invoke-PnPQuery
$navigationTermSet = $termsets | Where-Object { $_.Name -eq $termSetName }
$context.Load($navigationTermSet)
Invoke-PnPQuery
Write-Host "Done" -f Green
Write-Host "Set taxonomy navigation..." -f Yellow -NoNewline
$context = Get-PnPContext
$Web = Get-PnPWeb
$navigationSettings = New-Object Microsoft.SharePoint.Client.Publishing.Navigation.WebNavigationSettings $context, $Web
$navigationSettings.ResetToDefaults()
$navigationSettings.GlobalNavigation.Source = 1
$navigationSettings.CurrentNavigation.Source = 1
$navigationSettings.Update($taxonomySession)
Invoke-PnPQuery
Start-Sleep -Seconds 2
$context = Get-PnPContext
$Web = Get-PnPWeb
$navigationSettings = New-Object Microsoft.SharePoint.Client.Publishing.Navigation.WebNavigationSettings $context, $Web
$navigationSettings.CurrentNavigation.Source = "taxonomyProvider"
$navigationSettings.CurrentNavigation.TermStoreId = $TermStore.Id
$navigationSettings.CurrentNavigation.TermSetId = $navigationTermSet.Id
$navigationSettings.GlobalNavigation.Source = "taxonomyProvider"
$navigationSettings.GlobalNavigation.TermStoreId = $TermStore.Id
$navigationSettings.GlobalNavigation.TermSetId = $navigationTermSet.Id
$navigationSettings.Update($taxonomySession)
$Web.AllProperties["__IncludeSubSitesInNavigation"] = $True
#Show pages in global navigation
$Web.AllProperties["__IncludePagesInNavigation"] = $False
#Update Settings
$Web.Update()
$TermStore.CommitAll()
Invoke-PnPQuery
Write-Host "Done" -f Green
}
function Invoke-UpdateTaxonomyCache() {
$context = Get-PnPContext
Write-Host "Update taxonomy cache..." -f Yellow -NoNewline
$TaxonomySession = Get-PnPTaxonomySession
$TaxonomySession.UpdateCache()
$context.Load($TaxonomySession)
Invoke-PnPQuery
Write-Host "Done" -f Green
}
function Invoke-EnableTagging($termSetName) {
Write-Host "Enable tagging for termset..." -f Yellow -NoNewline
Start-Sleep -Seconds 5
$context = Get-PnPContext
$CurrentSite = Get-PnPSite
$taxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($context)
$TermStore = $taxonomySession.GetDefaultSiteCollectionTermStore();
$SiteCollectionTermGroup = $TermStore.GetSiteCollectionGroup($CurrentSite, $false)
$termsets = $SiteCollectionTermGroup.TermSets
$context.Load($taxonomySession)
$context.Load($SiteCollectionTermGroup)
$context.Load($termsets)
$context.Load($TermStore)
Invoke-PnPQuery
$navigationTermSet = $termsets | Where-Object { $_.Name -eq $termSetName }
$context.Load($navigationTermSet)
Invoke-PnPQuery
$navigationTermSet.IsOpenForTermCreation = $true
$navigationTermSet.IsAvailableForTagging = $true
$TermStore.CommitAll()
Start-Sleep -Seconds 2
Write-Host "Done" -f Green
}
function Invoke-LoadPnp() {
Write-Host "Load Libraries..." -f Yellow -NoNewline
$rootpath = $PSScriptRoot
$path = Join-Path -Path $rootpath -ChildPath "SharePointPnPPowerShell2019\3.29.2101.0\"
$sharepointPowershellModulePath = Join-Path -Path $path -ChildPath "SharePointPnPPowerShell2019.psd1"
if ($null -eq (Get-Module -Name "SharePointPnPPowerShell2019")) {
Import-Module $sharepointPowershellModulePath -DisableNameChecking
Disable-PnPPowerShellTelemetry -Force | Out-Null
}
#Load SharePoint CSOM Assemblies
Add-Type -Path "$path\Microsoft.SharePoint.Client.dll"
Add-Type -Path "$path\Microsoft.SharePoint.Client.Runtime.dll"
Add-Type -Path "$path\Microsoft.SharePoint.Client.Taxonomy.dll"
Add-Type -Path "$path\Microsoft.SharePoint.Client.Publishing.dll"
Write-Host "Done" -f Green
}
# -------------------------------------------------------------------------------------
# Load PnP and Client Libraries
# -------------------------------------------------------------------------------------
Invoke-LoadPnp
# -------------------------------------------------------------------------------------
# Connect
# -------------------------------------------------------------------------------------
$global:countTerms = 0
Connect-PnPOnline -Url $siteurl -CurrentCredentials
$Web = Get-PnPWeb -Includes Title, WebTemplate, Configuration
# -------------------------------------------------------------------------------------
# Test Template
# -------------------------------------------------------------------------------------
Write-Host "Site $($Web.Title): $($Web.WebTemplate)#$($Web.Configuration) it should be STS#0"
if ($Web.WebTemplate -ne "STS" -and $Web.Configuration -ne 0) {
Write-Host "Wrong template" -ForegroundColor Red
}
# -------------------------------------------------------------------------------------
# Setup Features
# -------------------------------------------------------------------------------------
Invoke-SetupFeatures
# -------------------------------------------------------------------------------------
# Setup Termset
# -------------------------------------------------------------------------------------
Invoke-SetupTermSet $termSetName
Invoke-UpdateTaxonomyCache
# -------------------------------------------------------------------------------------
# Setup Navigation
# -------------------------------------------------------------------------------------
Invoke-SetupNavigation $termSetName
Invoke-UpdateTaxonomyCache
# -------------------------------------------------------------------------------------
# Setup Tagging
# -------------------------------------------------------------------------------------
Invoke-EnableTagging $termSetName
# -------------------------------------------------------------------------------------
# Create Content
# -------------------------------------------------------------------------------------
$Ctx = Get-PnPContext
$PublishingWeb = [Microsoft.SharePoint.Client.Publishing.PublishingWeb]::GetPublishingWeb($Ctx, $Ctx.Web)
$Ctx.Load($PublishingWeb)
$Ctx.ExecuteQuery()
$PageLayoutItem = Get-PageLayoutItem 'ArticleLeft.aspx'
$context = Get-PnPContext
$CurrentSite = Get-PnPSite
$taxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($context)
$termStore = $taxonomySession.GetDefaultSiteCollectionTermStore();
$SiteCollectionTermGroup = $TermStore.GetSiteCollectionGroup($CurrentSite, $false)
$termsets = $SiteCollectionTermGroup.TermSets
$context.Load($taxonomySession)
$context.Load($SiteCollectionTermGroup)
$context.Load($termsets)
$context.Load($TermStore)
Invoke-PnPQuery
$termSet = $termsets | Where-Object { $_.Name -eq $termSetName }
$context.Load($termSet)
Invoke-PnPQuery
Invoke-CreateTerms -context $context -PublishingWeb $PublishingWeb -PageLayoutItem $PageLayoutItem -termStore $termStore -termObject $termSet -path "" -level 0 -maxlevel 3 -maxItemsPerLevel 30 -overallMax 3300
Invoke-UpdateTaxonomyCache