Thursday, December 24, 2015

Search Navigation for SharePoint Online using CSOM and Powershell

Our company has just migrated from SharePoint 2013 to SharePoint Online, so I am wrestling with modifying all my Powershell scripts. One such scenario is around the Small Search Box. By default it searches the current site only, although you can can change the scope, and we have a search Centre site. In practice, users just type their query into the search box.

You can change the search configuration under 'Site Settings -> Search Settings' to add links, so I add in a link to the Search Centre ('Everything'), and 'this site'. This is a per-site setting, not per-site collection, so it is a pain to do manually.

The Scenario

So here is the scenario:

  1. Loop through each site
  2. check if the Search Navigation is already set, and delete it if necessary
  3. Add the two search Navigation Nodes

Sounds simple? not until you try and do it. I pieced this together from a number of different posts, as I got stuck on different stages. I assume you know something about CSOM , and connecting to your tenant via Powershell.

The Execution

Lets load the existing nodes:

write-Host "Setting Search Urls for: " $web.Title " Url: " $web.Url
# Set the Title and Url of the two nodes we want to add
$nodeEverythingTitle = "Everything"
$nodeEverythingUrl = "/search/Pages/results.aspx"
$nodeThisSiteTitle = "this site"
$nodeThisSiteUrl = "/_layouts/15/osssearchresults.aspx"
$addEverything = $true
$addThisSite = $true

#Get the list of Search Navigation Nodes
$nav = $web.Navigation
$clientContext.Load($nav)
$clientContext.ExecuteQuery()
$srchNav = $nav.GetNodeById(1040);
[Microsoft.SharePoint.Client.NavigationNodeCollection] $sNodes = $srchNav.Children
$clientContext.Load($srchnav)
$clientContext.Load($sNodes)
$clientContext.ExecuteQuery()

Important points:

  1. The navigation Nodes are loaded through $web.Navigation. Search Navigation (Navigation.SearchNav) is not implemented Client-side. However, $nav.GetNodeById(1040)  will get you the Search Navigation.
  2. Server Side you work with SPNavigationNodes. Client-side you work with NavigationNodes.

The next part checks if the Nodes exist and whether they should be deleted.

# Check the existing Nodes
# Need to iterate through rather than foreach since we may delete some Nodes
for ($nodeID=0;$nodeID -lt $sNodes.Count;$nodeID++){
$nNode = $sNodes[$nodeID];
if ($nNode.Title -eq $nodeEverythingTitle) {

if ($nNode.Url -eq $nodeEverythingUrl) {
#Node is unchanged, don't need to delete it, or add it again
write-host "Everything Node is unchanged"
$addEverything = $false
}
else {
#Node Url is different; delete this Node and add it again
write-host "Deleting Everything Node"
$nNode.DeleteObject()
}

}
if ($nNode.Title -eq $nodeThisSiteTitle) {

if ($nNode.Url -eq $nodeThisSiteUrl) {
#Node is unchanged, don't need to delete it, or add it again
write-host "This Site Node is unchanged"
$addThisSite = $false
}
else {
#Node Url is different; delete this Node and add it again
write-host "Deleting This Site Node"
$nNode.DeleteObject()
}

}
}
#Update the Client Context with the changes
$clientContext.ExecuteQuery()

Notes:

  1. You can't use foreach to loop through the Nodes, as you may modify the Collection.
  2. If the Node title matches one we are interested in, and the Url is different, we delete it, using Node.DeleteObject()

I like to load the updated Navigation Nodes again, just to make sure:

#Load the Navigation again just to be sure
$nav = $web.Navigation
$clientContext.Load($nav)
$clientContext.ExecuteQuery()
$srchNav = $nav.GetNodeById(1040);
[Microsoft.SharePoint.Client.NavigationNodeCollection] $sNodes = $srchNav.Children
$clientContext.Load($srchnav)
$clientContext.Load($sNodes)
$clientContext.ExecuteQuery()

Now we Add the Nodes:

# Add the Nodes if they are not there already
if ($addEverything) {
write-host "Adding Everything Node"
$nodeEverything = New-Object Microsoft.SharePoint.Client.NavigationNodeCreationInformation
$nodeEverything.Title = "Everything"
$nodeEverything.Url = "/search/Pages/results.aspx"
$nodeEverything.AsLastNode = $false
# IsExternal is really important or it tries to resolve the url and fails.
$nodeEverything.IsExternal = $true
$clientContext.Load($sNodes.Add($nodeEverything))
}
if ($addThisSite) {
write-host "Adding This Site Node"
$nodeThisSite = New-Object Microsoft.SharePoint.Client.NavigationNodeCreationInformation
$nodeThisSite.Title = "this site"
$nodeThisSite.Url = "/_layouts/15/osssearchresults.aspx"
$nodeThisSite.AsLastNode = $true
#IsExternal is really important or it tries to resolve the url and fails.
$nodeThisSite.IsExternal = $true
$clientContext.Load($sNodes.Add($nodeThisSite))
}
$clientContext.ExecuteQuery()

Notes:

  1. The really important one here is the $nodeThisSite.IsExternal = $true  (even if it isn't). If you set the Node to be an internal node, SharePoint Online will attempt to resolve it internally, and it always seems to fail, even when you explicitly set it to an internal Url, or a shortened one like here. if you set it to an External Node, it doesn't mess about with the Url. This stumped me for ages.

So that is the essence of the solution, which I have in a function setSearchUrls ($web)  ProcessWeb invokes this for each subsites of a given site:

function processWeb($web)
{
$clientContext.Load($web)
$clientContext.ExecuteQuery()
Write-Host "Processing Web URL is" $web.Url
setSearchUrls( $web)

#Process all sub-sites from this site
$childWebs = $web.Webs
$clientContext.Load($childWebs)
$clientContext.Load($web)
$clientContext.ExecuteQuery()
foreach ($childWeb in $childWebs)
{
processWeb($childWeb)
}
}

I then retrieve the list of Site Collections that I want to process from a text file, and process each one in turn

Get-Content C:\siteslist.txt | Foreach-Object {
write-Host "Procesing Site Url: " $_
$webUrl = $_

$clientContext = Get-Context -Url $webUrl -Credentials $credentials

#get the Starting site to set Search Urls
$rootWeb = $clientContext.Web
$clientContext.Load($rootWeb)
$clientContext.ExecuteQuery()

processWeb $rootWeb
}

This can be run if new sites are added, as it will only add the Navigation to a site where it is not already there.

Hopefully this will help someone else.


by Ciaran Fletcher via Everyone's Blog Posts - SharePoint Community

No comments:

Post a Comment