I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Wednesday, September 5, 2007

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.

1. gl-setnavigationnodes

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:

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":

1:<NodeTitle="Sub Sites"IsVisible="True">

2:<Url>/</Url>

3:<UrlFragment></UrlFragment>

4:<vti_navsequencechild>true</vti_navsequencechild>

5:<CreatedDate>10/15/2007 6:28:01 PM</CreatedDate>

6:<UrlQueryString></UrlQueryString>

7:<NodeType>Heading</NodeType>

8:<Audience></Audience>

9:<Target></Target>

10:<Description></Description>

11:<LastModifiedDate>10/15/2007 6:28:01 PM</LastModifiedDate>

12:<BlankUrl>True</BlankUrl>

13:<AutoAddSubSites/>

14:</Node>

2. gl-copynavigation

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:

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:

29 comments:

Frank
said...

Great work Gary. Thanks.After migrating WSS from SP 2003, this was exactly what I need.WSS(!). I got missing (MOSS)assembly messages. After (temporarilly) adding MOSS DLL's "Microsoft.Office.Server.dll" and "Microsoft.SharePoint.Publishing.dll" to the GAC the STSADM extension works like a charm (only checked the navigation extensions.

Question:I want to add a node like (had to remove the < and > for the post:Node Id="2006" Title="Home" IsVisible="True" Url /MySubSiteURL/default.aspx /Url vti_navsequencechild true /vti_navsequencechild/Nodeto a lot () of sites.Is there any way to replace the site url "/MySubSiteURL" by a parameter like e.g. [@CurrentURL] ?

I haven't done any testing on a pure WSS environment - my company is using MOSS Enterprise so that's been my focus (unfortunately the time or priority just isn't there for me to do any WSS testing).

I'm not entirely sure that I'm following what you are asking for with the parameters though - are you looking to be able to pass in a parameter and corresponding value into the command as arguments or are you wanting the code to detect known parameters (like @CurrentUrl) and then replace it with the server relative url of the current site? At the moment the code doesn't do any of that but it wouldn't be too difficult to modify it to do so (I'm not sure I'm seeing a lot of gain though but perhaps I'm just not understanding your situation).

Thanks for the feedback. Can you elaborate on what you mean by enabling global navigation? By default a sub-site uses the global navigation defined at the site collection level. I know there's a way via the browser to get the sub-site to not inherit the parents global navigation but I haven't looked to do it via code though I suspect it's just a property setting somewhere.

I might be using the wrong terms, but if I use stsadm to create a new sub-site, the top nav (global nav) only displays the Home tab. That is it doesn't inherit the navigation from it's parent site. You can change this through the browser afterwards under navigation, but that seems to defeat the use of using stsadm.

From what you are saying it sounds like you're not seeing this though?

The only place I've seen that behavior is when I've used the Fantastic 40 Application Templates (it's a setting that's saved with the template). It probably wouldn't be difficult to create a command to reset this. Assuming this is your issue one option would be to create your own version of the template (so create a web, set it up how you want it, and then export it out and re-import and then remove the original version).

I've created a new command which allows you to change the inheritance settings (and others all the other settings that you can set via the browser). The command is setnavigationsettings. Hopefully this helps address your issues.

I am trying to us the copynavigation function but it doesn't seem to be able to recreate a drop down navigation structure.

I have a top site collection, on it I created a heading in the global navigation called departments. I then added a link under that heading called HR and have it pointed to /departments/hr (which happens to be another site collection). On the main site collection the menus look good and work as intended. I want to copy that exact navigation to the HR site collection. When I do this I end up without my "home" tab and instead have a "hr" tab. Also the departments tab shows up but no longer has the drop down list of departments.

In the xml being created it shows a hr node embeded inside the departments node. So the export looks correct. I am guessing that for some reason the import process is not reading back in the embeded nodes.

Mike - not sure what the issue is - I have that exact same setup for my environment here and used the command to do what you are attempting but I haven't had any issues (the command handles the sub-nodes fine for me). Can you send my your xml so that I can attempt to reproduce? My email is gary at thelapointes dot com.

ve these utilities, great work. However for the gl-setnavigationnodes operation I found a bug. If the pubweb.GlobalNavigationNodes or pubweb.CurrentNavigationNodes is null (as I ran into ) the operation fails with an 'Object reference nto set' exception. I added a null check before accessing these properties and all's well.

I guess the real fix would be to determine under what conditions it's null and try to account for it (I'm assuming it's null because it's inheriting? That being the case we'd probably want to change it so that it doesn't inherit).

Hi gary,I have hide some pages from site settings Navigation.now i want to export this settings to xml.now i delete site and create the site back using your stsadm command now i want to hide those pages again using this xml file how can i do that.please help me out.

Hi Gary,I am continuing to use the commands as we progress with the deployment. It is amazing what you have done to the community. Now I am working with gl-setnavigationnodes. Does this set the global nav to all the site collections in the application. I have 176 site collection and just want to add the global nav. I was not clear with your update comments on 10/15/2007. I do not want the subsites to show up but just want to propagate the site collections with global nav.

Err there seems to be some hidden limitation that restricts values beyond what sharepoint allows in regard to the usage of non-lets-say-US characters.I have finnish, estonian, lithuanian, latvian, english, swedish and russian subsites, using the appropriate moss language packs.The gl-enumnavigation seems to be happy about exporting into an xml file characters not native to LCID 1033 (i mean it outputs them correctly), however the gl-setnavigationnodes bombs out with "Invalid character in the given encoding. Line 3, position 29.", where I have the letter ä in my simplest example navigation enumerated file. I could do a russian subsite, but since I cant even read the letters, we shall not go there.

I haven't yet analyzed your code, but the input XML doesnt specify any character space of any kind, apparently such xml markings are then not used, or tagged on as 1033 en-US perhaps?

I will proceed to look at your code and see if theres anything to be done.

TBH, there are instances within the sharepoint microsoft internal dll's, which if they were to be disassembled would show weak behavior particularly on the part of analyzing input values (using a field with non-us-ascii chars will fail site creation from site template when the provisioned aspx pages contain said field).

It seems that even though sharepoint has a utility namespace internally, which has correct methods for stripping "illegal" values, these are not used internally and I even ran across something so far from just stripping \ and other special marks, as to actually remove ALL non-us-ascii characters. Which is thousands of characters a different filter.(the us-ascii removes all chars except for a few hundred, when xml allows for hundreds of separate character sets, like russian, lithuanian, swedish etc)

Do you have any insight into whether it's your code or microsoft's code that is not accepting legally functional sharepoint navigation node title values?

Additional note: your code does indeed NOT suffer from NIH, and therefore fixing the output file is enough to support all legal XML characters in sharepoint, or at least scandinavian ones.A file with lots of ä for instance is fine to import using gl-setnavigationnodes as long as you add this declaration to the front of the created xml file from gl-enum navigation xml:gt ?xml version="1.0" encoding="ISO-8859-1"? lt(note i had to replace GT and LT for illegal html publish on the blog commentary)Your mileage may vary, and I use that encoding because it worked in this case with the output file containing raw [åäö], for other sharepoint operations one has to use utf-8 and uml encoded values. Such as 228; for ä

Does this command have a limitation with host header sites? I have a host header only site collection that I'm trying to use copynavigation with, and I'm having some trouble. When I use the copynavigation command for that site, it completes the command but it does not create the right backup.xml file or copy the nav. I'm trying to copy the nav to another site collection that is sitting within an explicit path on the same host header site.

I'm using the globalonly switch, and it's only copying the quick launch, which seems very strange. Also, the source nav is using publishing features, as well as the target site. The only different is that the source site is full blown publishing template, and the destination is just a team site with publishing features turned on.

Hi Gary - I migrated a SharePoint farm to a different domain but users have some hard-coded links in this farm. How do I go about changing them to point to that new farm? I'm somehow not comfortable updating the NavNodes table directly because it will leave my environment in an unsupported state. Any resolution?

My Custom Commands & Cmdlets

Use of any tools included in the various downloads found at this site is at your own risk. Gary Lapointe cannot be held liable for any damage done to your environment through the use of any code or downloadable tools found on this site.

You may not repackage or sell any of the downloadable tools or associated source code. Downloading of the various tools implies that you acknowledge these restrictions.