MOSS MVP

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

More Site Navigation Settings Commands

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:

   1: /// <summary>
   2: /// Sets the navigation.
   3: /// </summary>
   4: /// <param name="web">The web site.</param>
   5: /// <param name="xmlDoc">The XML doc containing the navigation nodes.</param>
   6: /// <param name="showSubSites">if set to <c>true</c> [show sub sites].</param>
   7: /// <param name="showPages">if set to <c>true</c> [show pages].</param>
   8: /// <param name="deleteExistingGlobal">if set to <c>true</c> [delete existing global nodes].</param>
   9: /// <param name="deleteExistingCurrent">if set to <c>true</c> [delete existing current nodes].</param>
  10: public static void SetNavigation(SPWeb web, XmlDocument xmlDoc, bool showSubSites, bool showPages, bool deleteExistingGlobal, bool deleteExistingCurrent)
  11: {
  12:     PublishingWeb pubweb = PublishingWeb.GetPublishingWeb(web);
  13:  
  14:     // First need to set whether or not we show sub-sites and pages
  15:     pubweb.IncludeSubSitesInNavigation = showSubSites;
  16:     pubweb.IncludePagesInNavigation = showPages;
  17:     pubweb.Update();
  18:  
  19:     List<SPNavigationNode> existingGlobalNodes = new List<SPNavigationNode>();
  20:     List<SPNavigationNode> existingCurrentNodes = new List<SPNavigationNode>();
  21:     // We can't delete the navigation items until we've added the new ones so store the existing 
  22:     // ones for later deletion (note that we don't have to store all of them - just the top level).
  23:     // I have no idea why this is the case - but when I tried to clear everything out first I got
  24:     // all kinds of funky errors that just made no sense to me - this works so....
  25:     foreach (SPNavigationNode node in pubweb.GlobalNavigationNodes)
  26:         existingGlobalNodes.Add(node);
  27:     foreach (SPNavigationNode node in pubweb.CurrentNavigationNodes)
  28:         existingCurrentNodes.Add(node);
  29:  
  30:     XmlNodeList newGlobalNodes = xmlDoc.SelectNodes("//Navigation/Global/Node");
  31:     XmlNodeList newCurrentNodes = xmlDoc.SelectNodes("//Navigation/Current/Node");
  32:  
  33:  
  34:     // If we've got global or current nodes in the xml then the intent is to reset those elements.
  35:     // If we've also specified to delete any existing elements then we need to first hide all the
  36:     // sub-sites and pages (you can't delete them because they don't exist as a node).  Note that
  37:     // we are only doing this if showSubSites is true - if it's false we don't see them so no point
  38:     // in hiding them.  Any non-sub-site or non-page will be deleted after we've added the new nodes.
  39:     if (newGlobalNodes.Count > 0 && deleteExistingGlobal && showSubSites)
  40:     {
  41:         // Initialize the sub-sites (forces the provided XML to specify whether any should be visible)
  42:         foreach (SPWeb tempWeb in pubweb.Web.Webs)
  43:         {
  44:             pubweb.ExcludeFromNavigation(true, tempWeb.ID);
  45:         }
  46:         pubweb.Update();
  47:     }
  48:     if (newCurrentNodes.Count > 0 && deleteExistingCurrent && showSubSites)
  49:     {
  50:         foreach (SPWeb tempWeb in pubweb.Web.Webs)
  51:         {
  52:             pubweb.ExcludeFromNavigation(false, tempWeb.ID);
  53:         }
  54:         pubweb.Update();
  55:     }
  56:  
  57:     // Now we need to add all the global nodes (if any - if the collection is empty the following will just return and do nothing)
  58:     AddNodes(pubweb, true, pubweb.GlobalNavigationNodes, newGlobalNodes);
  59:     // Update the web as the above may have made modifications
  60:     pubweb.Update();
  61:  
  62:     // Now delete all the previously existing global nodes.
  63:     if (newGlobalNodes.Count > 0 && deleteExistingGlobal)
  64:     {
  65:         foreach (SPNavigationNode node in existingGlobalNodes)
  66:         {
  67:             node.Delete();
  68:         }
  69:     }
  70:  
  71:     // Now we need to add all the current nodes (if any)
  72:     AddNodes(pubweb, false, pubweb.CurrentNavigationNodes, newCurrentNodes);
  73:     // Update the web as the above may have made modifications
  74:     pubweb.Update();
  75:  
  76:     // Now delete all the previously existing current nodes.
  77:     if (newCurrentNodes.Count > 0 && deleteExistingCurrent)
  78:     {
  79:         foreach (SPNavigationNode node in existingCurrentNodes)
  80:         {
  81:             node.Delete();
  82:         }
  83:     }
  84: }
  85:  
  86:  
  87: /// <summary>
  88: /// Adds the nodes.
  89: /// </summary>
  90: /// <param name="pubWeb">The publishing web.</param>
  91: /// <param name="isGlobal">if set to <c>true</c> [is global].</param>
  92: /// <param name="existingNodes">The existing nodes.</param>
  93: /// <param name="newNodes">The new nodes.</param>
  94: private static void AddNodes(PublishingWeb pubWeb, bool isGlobal, SPNavigationNodeCollection existingNodes, XmlNodeList newNodes)
  95: {
  96:     if (newNodes.Count == 0)
  97:         return;
  98:  
  99:     for (int i = 0; i < newNodes.Count; i++)
 100:     {
 101:         XmlElement newNodeXml = (XmlElement)newNodes[i];
 102:         string url = newNodeXml.SelectSingleNode("Url").InnerText;
 103:         string title = newNodeXml.GetAttribute("Title");
 104:         NodeTypes type = NodeTypes.None;
 105:         if (newNodeXml.SelectSingleNode("NodeType") != null && !string.IsNullOrEmpty(newNodeXml.SelectSingleNode("NodeType").InnerText))
 106:             type = (NodeTypes)Enum.Parse(typeof(NodeTypes), newNodeXml.SelectSingleNode("NodeType").InnerText);
 107:  
 108:         bool isVisible = true;
 109:         if (!string.IsNullOrEmpty(newNodeXml.GetAttribute("IsVisible")))
 110:             isVisible = bool.Parse(newNodeXml.GetAttribute("IsVisible"));
 111:  
 112:         if (type == NodeTypes.Area)
 113:         {
 114:             // You can't just add an "Area" node (which represents a sub-site) to the current web if the
 115:             // url does not correspond with an actual sub-site (the code will appear to work but you won't
 116:             // see anything when you load the page).  So we need to check and see if the node actually
 117:             // points to a sub-site - if it does not then change it to "AuthoredLinkToWeb".
 118:             SPWeb web = null;
 119:             string name = url.Trim('/');
 120:             if (name.Length != 0 && name.IndexOf("/") > 0)
 121:             {
 122:                 name = name.Substring(name.LastIndexOf('/') + 1);
 123:             }
 124:             try
 125:             {
 126:                 // Note that pubWeb.Web.Webs[] does not return null if the item doesn't exist - it simply throws an exception (I hate that!)
 127:                 web = pubWeb.Web.Webs[name];
 128:             }
 129:             catch (ArgumentException)
 130:             {
 131:             }
 132:             if (web == null || !web.Exists || web.ServerRelativeUrl.ToLower() != url.ToLower())
 133:             {
 134:                 // The url doesn't correspond with a sub-site for the current web so change the node type.
 135:                 // This is most likely due to copying navigation elements from another site
 136:                 type = NodeTypes.AuthoredLinkToWeb;
 137:             }
 138:             else if (web.Exists && web.ServerRelativeUrl.ToLower() == url.ToLower())
 139:             {
 140:                 // We did find a matching sub-site so now we need to set the visibility
 141:                 if (isVisible)
 142:                     pubWeb.IncludeInNavigation(isGlobal, web.ID);
 143:                 else
 144:                     pubWeb.ExcludeFromNavigation(isGlobal, web.ID);
 145:             }
 146:  
 147:         }
 148:         else if (type == NodeTypes.Page)
 149:         {
 150:             // Adding links to pages has the same limitation as sub-sites (Area nodes) so we need to make
 151:             // sure it actually exists and if it doesn't then change the node type.
 152:             PublishingPage page = null;
 153:             try
 154:             {
 155:                 // Note that GetPublishingPages()[] does not return null if the item doesn't exist - it simply throws an exception (I hate that!)
 156:                 page = pubWeb.GetPublishingPages()[url];
 157:             }
 158:             catch (ArgumentException)
 159:             {
 160:             }
 161:             if (page == null)
 162:             {
 163:                 // The url doesn't correspond with a page for the current web so change the node type.
 164:                 // This is most likely due to copying navigation elements from another site
 165:                 type = NodeTypes.AuthoredLinkToPage;
 166:                 url = pubWeb.Web.Site.MakeFullUrl(url);
 167:             }
 168:             else
 169:             {
 170:                 // We did find a matching page so now we need to set the visibility
 171:                 if (isVisible)
 172:                     pubWeb.IncludeInNavigation(isGlobal, page.ListItem.UniqueId);
 173:                 else
 174:                     pubWeb.ExcludeFromNavigation(isGlobal, page.ListItem.UniqueId);
 175:             }
 176:         }
 177:  
 178:         // If it's not a sub-site or a page that's part of the current web and it's set to
 179:         // not be visible then just move on to the next (there is no visibility setting for
 180:         // nodes that are not of type Area or Page).
 181:         if (!isVisible && type != NodeTypes.Area && type != NodeTypes.Page)
 182:             continue;
 183:  
 184:         // Finally, can add the node to the collection.
 185:         SPNavigationNode node = SPNavigationSiteMapNode.CreateSPNavigationNode(
 186:             title, url, type, existingNodes);
 187:  
 188:        
 189:         // Now we need to set all the other properties
 190:         foreach (XmlElement property in newNodeXml.ChildNodes)
 191:         {
 192:             // We've already set these so don't set them again.
 193:             if (property.Name == "Url" || property.Name == "Node" || property.Name == "NodeType")
 194:                 continue;
 195:  
 196:             // CreatedDate and LastModifiedDate need to be the correct type - all other properties are strings
 197:             if (property.Name == "CreatedDate" && !string.IsNullOrEmpty(property.InnerText))
 198:             {
 199:                 node.Properties["CreatedDate"] = DateTime.Parse(property.InnerText);
 200:                 continue;
 201:             }
 202:             if (property.Name == "LastModifiedDate" && !string.IsNullOrEmpty(property.InnerText))
 203:             {
 204:                 node.Properties["LastModifiedDate"] = DateTime.Parse(property.InnerText);
 205:                 continue;
 206:             }
 207:  
 208:             node.Properties[property.Name] = property.InnerText;
 209:         }
 210:         // If we didn't have a CreatedDate or LastModifiedDate then set them to now.
 211:         if (node.Properties["CreatedDate"] == null)
 212:             node.Properties["CreatedDate"] = DateTime.Now;
 213:         if (node.Properties["LastModifiedDate"] == null)
 214:             node.Properties["LastModifiedDate"] = DateTime.Now;
 215:  
 216:         // Save our changes to the node.
 217:         node.Update();
 218:         node.MoveToLast(existingNodes); // Should already be at the end but I prefer to make sure :)
 219:         
 220:         XmlNodeList childNodes = newNodeXml.SelectNodes("Node");
 221:  
 222:         // If we have child nodes then make a recursive call passing in the current nodes Children property as the collection to add to.
 223:         if (childNodes.Count > 0)
 224:             AddNodes(pubWeb, isGlobal, node.Children, childNodes);
 225:     }
 226: }

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

   1: <Node Title="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:

   1: XmlDocument xmlDoc;
   2: // Get the XML from our source site.
   3: using (SPSite sourceSite = new SPSite(sourceurl))
   4: using (SPWeb sourceWeb = sourceSite.OpenWeb())
   5: {
   6:     PublishingWeb sourcePubWeb = PublishingWeb.GetPublishingWeb(sourceWeb);
   7:     xmlDoc = EnumNavigation.GetNavigationAsXml(sourcePubWeb);
   8: }
   9:  
  10: // If the user only wants to copy the global or current then remove the other element
  11: if (currentOnly)
  12:     xmlDoc.SelectSingleNode("//Navigation/Global").RemoveAll();
  13: if (globalOnly)
  14:     xmlDoc.SelectSingleNode("//Navigation/Current").RemoveAll();
  15:  
  16: // Set the navigation using the XML we got from the source.
  17: using (SPSite targetSite = new SPSite(targeturl))
  18: using (SPWeb targetWeb = targetSite.OpenWeb())
  19: {
  20:     if (keyValues.ContainsKey("backuptarget"))
  21:     {
  22:         PublishingWeb targetPubWeb = PublishingWeb.GetPublishingWeb(targetWeb);
  23:         XmlDocument xmlBackupDoc = EnumNavigation.GetNavigationAsXml(targetPubWeb);
  24:         xmlBackupDoc.Save(keyValues["backuptarget"]);
  25:     }
  26:     SetNavigationNodes.SetNavigation(targetWeb, xmlDoc, showSubSites, showPages, deleteExistingGlobal, deleteExistingCurrent);
  27: }

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>

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
/Node
to a lot () of sites.
Is there any way to replace the site url "/MySubSiteURL" by a parameter like e.g. [@CurrentURL] ?

Gary Lapointe said...

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).

Kale Davis said...

Gary,

Great site and great topic!

One question about global navigation. It seems like when I use "-o createweb" to create a new subsite, it doesn't enable global navigation. Do you know of a simple way to do this?

Thanks again.

Gary Lapointe said...

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.

kale davis said...

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?

Gary Lapointe said...

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).

Gary Lapointe said...

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.

Mike H said...

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.

Gary Lapointe said...

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.

Mike H said...

I figured out the issue. You need to have the publishing feature enabled for the drop downs to be displayed.

Denise said...

Hi there!
How do i install your package? Using the stsadm addsolution??
Denise :o)

Gary Lapointe said...

You've got it partly right - use stsadm's addsolution and deploysolution. Put the following in a batch file and you should be good to go:

SET SOLUTION_NAME="Lapointe.SharePoint.STSADM.Commands.wsp"
SET STSADM="C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\bin\stsadm"
%STSADM% -o addsolution -filename %SOLUTION_NAME%
%STSADM% -o deploysolution -local -allowgacdeployment -name %SOLUTION_NAME%

Michael Cummings said...

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.

Gary Lapointe said...

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).

Jayesh Prajapati said...

Hi gary,
I am getting object reference not set to instance of an object error while running this gl-setnavigation node command.
my Global node has no child.

-Jay

Jayesh Prajapati said...

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.

Thanks in advance,
Jayesh Prajapti

Sweet Sparky said...

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.

Gary Lapointe said...

The command only works for one site collection at a time. If you want to do more than one then I'd suggest you combine it with powershell.

Mats said...

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?

Mats said...

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 ä

Mike Ferrara said...

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.

Any help would be great. Love your work!

Gary Lapointe said...

I've not done any testing with host header sites so it is possible that there are issues.

Robin Meuré said...

Hi Gary,

you saved my day with this awesome code of yours buddy! :)

Robin

zeppy said...

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?

Gary Lapointe said...

Look at the various replace commands that I have - they were built specifically for this purpose.

Thiago said...

Hi Gary,
I was wondering if you could tell me where the navigation config file is located on the server? I mean, is it really a file or it is located in the DB?
Thanks

Gary Lapointe said...

There is no config file. The code reads the settings exposed via the object model.

Nico said...

Hello Gary,

I'm facing the following problem:

We migrated a SP2003 environment to a SP2007 environment, but the quick launch items didn't migrate.

I would like to try the copynavigation command. Will this work if i use the SP2003 as sourceURL and the sp2007 as the targetURL? or do both environments have to be SP2007?

Thanks!

I got an SP2003 sitecollection and a SP2007 sitecollection.

I wa

Gary Lapointe said...

Unfortunately none of my commands will work with 2003 and none will go across versions (so the 2007 stuff will only work with 2007 and the 2010 stuff will only work with 2010).