Last month I created a couple pretty basic commands to help me with setting the navigational elements of my site: "gl-enumnavigation" and "gl-addnavigationnode". You can find information about those commands here: Site Navigation Settings.
As I worked more on my upgraded site I found that what I really needed was a way to make drastic changes using an XML file rather than trying to add or remove one item at a time (note that I haven't created anything to allow removing a single item but I suspect after what I've just done that wouldn't be too hard). To address my needs I first needed to modify my original gl-enumnavigation command so that it could output XML instead of the flat list that it previously did (probably should have done that to begin with but...).
This was pretty easy to do except for one stumbling block - you'd think it would be easy to determine whether a site or page was hidden and that this info would be part of the SPNavigationNode object - unfortunately that's not the case - it took me a bit of digging to realize that I had to "find" the correct PublishingWeb or PublishingPage object and then checks it's IncludeInGlobalNavigation or IncludeInCurrentNavigation properties.
The second thing I needed to do was to create a new command which would be able to take the generated XML from the gl-enumnavigation command and use it to "rebuild" the navigation. This works pretty well in the sense that you can export using gl-enumnavigation, modify the generated XML to meet whatever custom needs you have, and then import using the gl-setnavigationnodes command that I created.
For my purposes this has enabled me to do a test upgrade - work the navigation to how by business users stipulated, save that out to a file and then re-run my upgrade any number of times and simple reset the navigation using the previously generated file. Another use could be to help get around the fact that there's no approval process for navigation changes - an administrator could make the proposed changes in a test site (either in a test farm or on the same production farm), get stakeholder approval, and then import the changes to the production site (and gl-enumnavigation could be used to make a backup of the existing navigation in the event that it becomes necessary to roll-back).
Once I had these two commands created I realized that it wasn't a big step to create a copy command. So I created gl-copynavigation which really just combines gl-enumnavigation and gl-setnavigationnodes but doesn't require you to deal with creating the file (though it does have an option to backup the target in case you want to revert the site). The two commands are detailed below.
I'd like to say that this was real easy to do - it should have been - but as it turned out it was much more difficult than I thought it would be (I'm beginning to detect a trend). I have two main methods which do all the work - the first, SetNavigation() does all the prep and cleanup work; the second, AddNodes() actually does the adding of the nodes to the appropriate collection. The real difficult part of all this was that what you see in the browser is not what you get when you query the GlobalNavigationNodes and CurrentNavigationNodes collections. These collections will not show the various sub-sites and pages that show up in the navigation unless you've explicitly set some property (by moving a node for example). So I had to take look in more than once place for everything and the logic of it all is really bizarre. The code is well documented so I won't go through it again here:
The syntax of the command can be seen below:
C:\>stsadm -help gl-setnavigationnodes stsadm -o gl-setnavigationnodes Rebuilds the Global and/or Current navigation based on a passed in XML file which can be generated by the gl-enumnavigation command and then modified (note that the Id attribute is ignored). Parameters: -url <site collection url> -inputfile <xml file to use as input> [-showsubsites <true (default) | false>] [-showpages <true | false (default)>] [-deleteexistingglobal <true (default) | false>] [-deleteexistingcurrent <true (default) | false>] [-backuptarget <filename>]
Here’s an example of how to set the navigation replacing both the global (Top Nav) and current (Quick Launch) navigation with that specified in the input file "input.xml":
stsadm –o gl-setnavigationnodes –url "http://intranet/hr" -inputfile "c:\input.xml" -backuptarget "c:\backup.xml"
If you want to do more of a merge (add everything from the XML file to the end of the navigation list) you would do the following:
stsadm –o gl-setnavigationnodes –url "http://intranet/hr" -inputfile "c:\input.xml" -backuptarget "c:\backup.xml" -deleteexistingglobal false -deleteexistingcurrent false
Doing the above would be useful if you've got a certain set of navigational elements that you want to have on all your site collections - you could define the XML file once and then use setnavigationnodes to add those elements to each site collection without having to do it manually.
Update 10/15/2007: I enhanced this command slightly. If you add the XML tag "<AutoAddSubSites />" to the source XML the code will replace this with nodes corresponding to all the sub-sites for the target web. This is useful if you've got a global navigation that you want to propagate to various site collections but you want the site collections sub-sites listed in addition to what you are importing. In my case I added the following XML to my source so that all sub-sites would appear under a heading called "Sub Sites":
I basically got this command for free - all I'm doing is utilizing what I'd already done for gl-enumnavigation and gl-setnavigationnodes. I haven't actually needed to use this myself but I thought someone might find it useful and as it was simple to create (which has been a nice change) I didn't really spend much time on it. Here's the core of the code:
As you can see from the above - it's really quite simple. The syntax of the command can be seen below:
C:\>stsadm -help gl-copynavigation stsadm -o gl-copynavigation Copies the Global and/or Current navigation from one site collection to another. Parameters: -sourceurl <source site collection url> -targeturl <target site collection url> [-globalonly / -currentonly] [-showsubsites <true (default) | false>] [-showpages <true | false (default)>] [-deleteexistingglobal <true (default) | false>] [-deleteexistingcurrent <true (default) | false>] [-backuptarget <filename>]
Here’s an example of how to copy both the global (Top Nav) and current (Quick Launch) navigation from the root site collection to a site collection at the managed path "hr":
stsadm –o gl-copynavigation –sourceurl "http://intranet/" -targeturl "http://intranet/hr" -backuptarget "c:\backup.xml"
If you'd like to just copy the global navigation and leave the quick launch (current) then you'd do the following:
stsadm –o gl-copynavigation –sourceurl "http://intranet/" -targeturl "http://intranet/hr" -backuptarget "c:\backup.xml" -globalonly
Setting deleteexistingglobal or deleteexistingcurrent to false would result in the source items being added to the existing navigation rather than replacing the existing navigation.
Update 11/2/2007: I've modified the gl-setnavigationnodes command so that it now allows you to add XML nodes to the passed in XML which will be replaced dynamically. The two nodes are <SiteCollectionUrl /> and <WebUrl />. If either (or both) of these two nodes appear in the XML they will be replaced with the appropriate server relative URL of the specified web or site collection. For example - you could have the following Node element in your XML:
<Node Id="2030" Title="Document Center" IsVisible="True"> <Url><SiteCollectionUrl />/Docs</Url> <vti_navsequencechild>true</vti_navsequencechild> <UrlQueryString></UrlQueryString> <NodeType>Area</NodeType> <Description>Main Document Center for Site Collection</Description> <UrlFragment></UrlFragment> </Node>