Setting the navigation is another common post upgrade/deployment task that should be easy to script. One of the challenges in using the commands I created was that it’s necessary to know the node ID of the navigational elements. There’s probably ways to change this to use the name rather than the ID but you can run into trouble when dealing with items with the same name. To set the navigation using the web interface go to your top level site ->Site Settings->Navigation.

This one took me a while to figure out as the code that Microsoft wrote to handle this was cryptic at best. It was very difficult to wade through some of the oddness. In the end I figured out that you simply had to get a PublishingWeb object and manipulate the GlobalNavigationNodes collection or the CurrentNavigationNodes collection depending on your intentions. In order to add a new node you need to know the existing node IDs so I created an enumerate command to get that information (again there’s probably another way to approach this but this worked for my needs). The two commands I created are called gl-enumnavigation and gl-addnavigationnode.

gl-enumnavigation

The code for this turned out to be pretty simple. The main part of the code is a recursive routine which goes through all the nodes and their children. I added some indenting to give it more of a tree layout (Update 9/4/2007: I added an xml switch to allow the output to be specified as XML instead of flat text – the results can also be dumped to an output file specified via an outputfile parameter).

 1public int Run(string command, StringDictionary keyValues, out string output)
 2{
 3    ...
 4    string url = keyValues["url"];
 5    using (SPSite site = new SPSite(url))
 6    {
 7        using (SPWeb web = site.OpenWeb())
 8        {
 9            PublishingWeb pubweb = PublishingWeb.GetPublishingWeb(web);
10
11            StringBuilder sb = new StringBuilder();
12            sb.Append("Global Navigation:\r\n");
13            EnumerateCollection(pubweb.GlobalNavigationNodes, ref sb, 1);
14                
15            sb.Append("Current Navigation:\r\n");
16            EnumerateCollection(pubweb.CurrentNavigationNodes, ref sb, 1);
17
18            output += sb.ToString();
19        }
20    }
21    ...
22}
23private static void EnumerateCollection(SPNavigationNodeCollection nodes, ref StringBuilder sb, int level)
24{
25    if (nodes == null || nodes.Count == 0)
26        return;
27 
28    string indent = "";
29 
30    for (int i = 0; i < level; i++)
31    {
32        indent += "  ";
33    }
34    if (level > 0)
35        indent += "- ";
36 
37    foreach (SPNavigationNode node in nodes)
38    {
39        sb.AppendFormat("{0}{1}: {2}\r\n", indent, node.Id, node.Title);
40        foreach (DictionaryEntry d in node.Properties)
41            sb.Append(indent + "   :" + d.Key + "=" + d.Value + "\r\n");
42        EnumerateCollection(node.Children, ref sb, level+1);
43    }
44}

As you can see from the code above, the EnumerateCollection static method does most of the work. It takes in an SPNavigationNodeCollection and iterates through the collection, passing any children back into the same method recursively for processing. The syntax of the command can be seen below:

C:\>stsadm -help gl-enumnavigation

stsadm -o gl-enumnavigation

Returns the site navigation hierarchy.

Parameters:
        -url <site collection>
        [-xml]
        [-outputfile <file to output results to>

Here’s an example of how to return the site navigation:

stsadm –o gl-enumnavigation –url "http://intranet/"

The results of running the above command can be seen below (note that I’m also output all the properties associated with each node object which can be useful when trying to determine how the various NodeType enums are assigned:

C:\>stsadm -o gl-enumnavigation -url "http://intranet/

Global Navigation:
  - 2018: Topics
        :NodeType=Area
        :vti_navsequencechild=true
  - 2019: News
        :NodeType=Area
        :vti_navsequencechild=true
  - 2021: Sites
        :NodeType=Area
        :vti_navsequencechild=true
Current Navigation:
  - 2013: Topics
        :NodeType=Area
        :vti_navsequencechild=true
  - 2014: News
        :NodeType=Area
        :vti_navsequencechild=true
  - 2016: Sites
        :NodeType=Area
        :vti_navsequencechild=true

gl-addnavigationnode

Figuring out how to add a new node took a little more digging. Turns out that you need to use SPNavigationSiteMapNode.CreateSPNavigationNode() which is a static method that returns back an SPNavigationNode object. You pass into this static method the name, link, type, and nodes collection that the new node belongs to (global or current). Once the node is created you set the remaining properties and call Update(). At this point the node exists as the last item – now you simply move it to where you want (no need to update again):

 1string url = keyValues["url"];
 2string linkUrl = keyValues["linkurl"];
 3string name = keyValues["name"];
 4string description = keyValues["description"];
 5NodeTypes type = (NodeTypes)Enum.Parse(typeof(NodeTypes), keyValues["type"]);
 6bool global = keyValues.ContainsKey("global");
 7bool current = keyValues.ContainsKey("current");
 8bool addAsFirst = keyValues.ContainsKey("addasfirst");
 9bool addAsLast = keyValues.ContainsKey("addaslast");
10bool addAfter = keyValues.ContainsKey("addafter");
11bool newWindow = keyValues.ContainsKey("newwindow");
12string previousNodeID = string.Empty;
13if (addAfter)
14    previousNodeID = keyValues["addafter"];
15 
16using (SPSite site = new SPSite(url))
17{
18    using (SPWeb web = site.OpenWeb())
19    {
20        PublishingWeb pubweb = PublishingWeb.GetPublishingWeb(web);
21 
22        SPNavigationNodeCollection nodes;
23        if (global)
24            nodes = pubweb.GlobalNavigationNodes;
25        else if (current)
26            nodes = pubweb.CurrentNavigationNodes;
27        else
28            throw new ApplicationException("Unknown error occured.");
29 
30        SPNavigationNode node = SPNavigationSiteMapNode.CreateSPNavigationNode(
31            name, linkUrl, type, nodes);
32        
33        node.Properties["CreatedDate"] = DateTime.Now;
34        node.Properties["LastModifiedDate"] = DateTime.Now;
35        node.Properties["Description"] = description;
36        if (newWindow)
37            node.Properties["Target"] = "_blank";
38        else
39            node.Properties["Target"] = string.Empty;
40        
41        node.Update();
42 
43        if (addAsFirst)
44            node.MoveToFirst(nodes);
45        else if (addAsLast)
46            node.MoveToLast(nodes);
47        else if (addAfter)
48        {
49            SPNavigationNode previousNode = web.Navigation.GetNodeById(int.Parse(previousNodeID));
50            if (previousNode == null)
51            {
52                output = "Previous node was not found.";
53                return 0;
54            }
55            node.Move(nodes, previousNode);
56        }
57    }
58}

The syntax of the command can be seen below:

C:\>stsadm -help gl-addnavigationnode

stsadm -o gl-addnavigationnode

Adds a new node to the site hierarchy.

Parameters:
        -url <site collection url>
        -name <navigation name>
        -linkurl <link url>
        -type <link type: None | Area | Page | List | ListItem | PageLayout | Heading | AuthoredLinkToPage | AuthoredLinkToWeb | AuthoredLinkPlain | AuthoredLink | Default | Custom | All>
        -global / -current
        -addasfirst / -addaslast / -addafter <previous node id>
        [-newwindow]
        [-description <description text>]

Here’s an example of how to add a new node after an existing node:

stsadm –o gl-addnavigationnode–url "http://intranet/" -name "Help Desk" -linkurl "http://helpdesk/" -type AuthoredLinkPlain -global -addafter 2019 -newwindow -description "Corporate Help Desk Application"