Top Link Bar Navigation To XML

Applies To: SharePoint

Continuing in my series on SharePoint’s Top Link Bar (sometimes called the Tab Bar) I want to show you how to serialize a site collection’s Global Navigation Nodes to XML using PowerShell. This isn’t quite the same as just using the Export-Clixml command since we are interested in very specific properties and their format.

In my previous post, Multi-Level Top Link Bar Navigation (Sub Menus), I showed you how to enable additional sub menus using the native control in SharePoint by editing a simple attribute in the Master Page. However, it quickly became clear that the native navigation editor (Site Actions > Site Settings > Navigation) won’t allow you to edit anything greater than 2 levels despite the control’s support for it. In this post I’ll show you how to output a site’s navigation to XML. In my next post I’ll show how to edit it and then import it to create multiple levels.

The Script

Copy and paste the code below into notepad and save it as NavigationToXML.ps1

$xmlFile = "d:\scripts\navigation\ExportedNavigation.xml"
$sourceweb = "http://somesite.com"
$keepAudiences = $true

Function ProcessNode($nodeCollection) {
    $parentCollection = @()
    foreach($node in $nodeCollection) {
       if ($node.Properties.NodeType -ne [Microsoft.SharePoint.Publishing.NodeTypes]::Area -and $node.Properties.NodeType -ne [Microsoft.SharePoint.Publishing.NodeTypes]::Page) {
	   $nHash = New-Object System.Object
           $nHash | Add-Member -type NoteProperty -name IsNode -value "yes"
           $nHash | Add-Member -type NoteProperty -name Title -value $node.Title
           $nHash | Add-Member -type NoteProperty -name Url -value $node.Url
           $nHash | Add-Member -type NoteProperty -name NodeType -value $node.Properties.NodeType
           $nHash | Add-Member -type NoteProperty -name Description -value $node.Properties.Description
           if($keepAudiences){
                $nHash | Add-Member -type NoteProperty -name Audience -value $node.Properties.Audience
           } else {
                $nHash | Add-Member -type NoteProperty -name Audience -value ""
           }
           $nHash | Add-Member -type NoteProperty -name Target -value $node.Target
           if($node.Children.Count -gt 0){
                $nHashChildren = ProcessNode($node.Children)
                $nHash | Add-Member -type NoteProperty -name Children -value $nHashChildren
           } else {
                $nHash | Add-Member -type NoteProperty -name Children -value @()
           }
           $parentCollection += $nHash
       }
    }
    $parentCollection
}

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

ProcessNode($psw.Navigation.GlobalNavigationNodes) | Export-Clixml $xmlFile

#cleanup
$sw.dispose()

What it Does and How to Use It

There are 3 parameters at the top of the script you will need to change:

  • $xmlFile: The path to use for the XML output
  • $sourceweb: The URL of the site whose navigation you are serializing
  • $keepAudiences: When $true audience values are serialized, when $false they are left blank

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 run it by typing: .\NavigationToXml.ps1

RunNavigationToXMLScript

The script will create an array of custom objects that correspond to the $sourceweb‘s navigation nodes and then use standard PowerShell serialization to create a simplified XML document at the location specified by $xmlFile. For instance, given the navigation shown here:

IMG_0347

The XML output will look similar to this:

<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Object</T>
    </TN>
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Some Sites</S>
      <S N="Url">/personal/ckent/navtest</S>
      <S N="NodeType">Heading</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Obj N="Children" RefId="1">
        <TN RefId="1">
          <T>System.Object[]</T>
          <T>System.Array</T>
          <T>System.Object</T>
        </TN>
        <LST>
          <Obj RefId="2">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">theChrisKent</S>
              <S N="Url">http://thechriskent.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Obj N="Children" RefId="3">
                <TNRef RefId="1" />
                <LST />
              </Obj>
            </MS>
          </Obj>
          <Obj RefId="4">
            <TNRef RefId="0" />
            <ToString>System.Object</ToString>
            <MS>
              <S N="IsNode">yes</S>
              <S N="Title">Microsoft</S>
              <S N="Url">http://microsoft.com</S>
              <S N="NodeType">AuthoredLinkPlain</S>
              <S N="Description"></S>
              <S N="Audience"></S>
              <Nil N="Target" />
              <Ref N="Children" RefId="3" />
            </MS>
          </Obj>
        </LST>
      </Obj>
    </MS>
  </Obj>
  <Obj RefId="5">
    <TNRef RefId="0" />
    <ToString>System.Object</ToString>
    <MS>
      <S N="IsNode">yes</S>
      <S N="Title">Google</S>
      <S N="Url">http://google.com</S>
      <S N="NodeType">AuthoredLinkPlain</S>
      <S N="Description"></S>
      <S N="Audience"></S>
      <Nil N="Target" />
      <Ref N="Children" RefId="3" />
    </MS>
  </Obj>
</Objs>

For those familiar with the Export-Clixml PowerShell command this shouldn’t look too crazy. For the rest of us, I will go into detail about this in my next post. Regardless of how complicated that may look to you, it is much simpler than if we had just called Export-Clixml on the Global Navigation Nodes object for the site.

How it Works

The main code begins at line 33 where the $sourceweb is retrieved and then the ProcessNode function (Lines 5-31) is called on the GlobalNavigationNodes collection. This generates an array of custom objects that are then serialized to the $xmlFile using Export-Clixml.

The ProcessNode function creates a custom object for every node and captures the Title, Url, NodeType, Description, Target, and when $keepAudiences is $true the audience information. Every custom object is also given a Children property (lines 21-26) and this is set to an array recursively for all child nodes that exist. This has been written to capture any number of levels of children. (This script does, however, skip all Area and Page node types since these are automatic and shouldn’t be edited manually)

 

So what do we do with this XML document? In my next post I’ll show you how to read, edit and ultimately import these nodes back into the site collection. This can be helpful to copy nodes from one site colleciton to another (although my post on SharePoint Top Link Bar Synchronization provided a much cleaner approach for this). More importantly this will provide you an easy way to have multiple sub menus in the SharePoint Top Link Bar.