So I’ve been moving my web sites all around, completely changing the hierarchy, creating new site collections, etc., and I discovered that whenever I hit a link that still pointed to an old bucket web (http://intranet/c16/ebusiness instead of http://intranet/topics/divisions/ebusiness) it loaded up a page called spsredirect.aspx which was supposed to redirect me to the new, correct link.

Unfortunately because I’ve moved things around the page was returning a 404. After disassembling the spsredirect page (Microsoft.SharePoint.Portal.WebControls.SPSRedirectPage) I found that what is happening is that the page is looking at a special hard-coded list that gets created when you are doing an upgrade. The list has a fixed name: “98d3057cd9024c27b2007643c1”. Joel Oleson has a brief article which touches upon this list: http://blogs.msdn.com/joelo/archive/2007/03/17/areas-bucketwebs-upgrade-and-redirects.aspx. The list contains the old bucket web links and the new links. What I found was that if I updated the V3ServerRelativeUrl field I could now move my webs around (within the same web application – more on that in a bit) and then simply update the corresponding field entry in this list and now any old bucket web links will still work.

What’s irritating is that the spsredirect.aspx page expects all the URLs to be server relative so if you’re moving a web to a new web app then you’re out of luck (unless you create your own custom redirect page and then set the links to use that page and pass in the actual URL – that way spsredirect will redirect to your custom redirect page which will then redirect to the actual page – I decided to just limit my command to working with a single web app rather than try to deal with this scenario).

Of course changing the entries within here doesn’t actually fix the links, but it will enable you to move things around and have the old v2 links still redirect correctly. I’ve created this new command to handle changing the new V3 URL and I’ve also modified my gl-moveweb and gl-convertsubsitetositecollection commands to utilize this functionality as well so if you run either of those two commands it will attempt to fix the links in this list if it can. The core code to do all this is detailed below:

 1/// <summary>
 2/// Fixes the upgraded area URL mappings.  This overload is used when moving a web within a single site collection.
 3/// </summary>
 4/// <param name="site">The site.</param>
 5/// <param name="sourceWebUrl">The source web URL.</param>
 6/// <param name="targetWebUrl">The target web URL.</param>
 7private static void FixUpgradeAreaUrlMappings(SPSite site, string sourceWebUrl, string targetWebUrl)
 8{
 9 SPList list = GetUpgradeAreaUrlMappingsList(site);
10 
11 if (list == null)
12  return;
13 
14 FixUpgradeAreaUrlMappings(list, sourceWebUrl, targetWebUrl);
15}
16 
17/// <summary>
18/// Gets the upgrade area URL mappings list.
19/// </summary>
20/// <param name="site">The site.</param>
21/// <returns></returns>
22internal static SPList GetUpgradeAreaUrlMappingsList(SPSite site)
23{
24 try
25 {
26  // 98d3057cd9024c27b2007643c1 is a special hard coded name for a list that Microsoft uses to store the mapping
27  // of URLs from v2 to v3 (maps the bucket urls to the new urls).
28  return Utilities.GetListByUrl(site.RootWeb, "98d3057cd9024c27b2007643c1", false);
29 }
30 catch (Exception)
31 {
32  return null;
33 }
34 
35}
36/// <summary>
37/// Fixes the upgraded Area URL mappings.
38/// </summary>
39/// <param name="list">The list.</param>
40/// <param name="sourceWebUrl">The source web URL.</param>
41/// <param name="targetWebUrl">The target web URL.</param>
42private static void FixUpgradeAreaUrlMappings(SPList list, string sourceWebUrl, string targetWebUrl)
43{
44 targetWebUrl = targetWebUrl.TrimEnd('/');
45 sourceWebUrl = sourceWebUrl.TrimEnd('/');
46 
47 using (SPSite rootSite = list.ParentWeb.Site)
48 {
49  foreach (SPListItem item in list.Items)
50  {
51   string v3Url = item["V3ServerRelativeUrl"] as string;
52   if (string.IsNullOrEmpty(v3Url))
53    continue;
54 
55   if (v3Url.ToLowerInvariant().StartsWith(sourceWebUrl.ToLowerInvariant()))
56   {
57    v3Url = targetWebUrl + v3Url.Substring(sourceWebUrl.Length);
58    item["V3ServerRelativeUrl"] = v3Url;
59 
60 
61    SPSite targetSite;
62    try
63    {
64     targetSite = new SPSite(rootSite.MakeFullUrl(v3Url));
65    }
66    catch (Exception)
67    {
68     targetSite = null;
69    }
70    if (targetSite != null)
71    {
72     using (SPWeb targetWeb = targetSite.AllWebs[v3Url])
73     {
74      if (targetWeb.Exists)
75      {
76       item["V3WebId"] = targetWeb.ID.ToString();
77      }
78     }
79    }
80 
81    item.Update();
82   }
83  }
84 }
85}

The command I created is detailed below (the name is really verbose but I couldn’t think of anything shorter that would be clear as to it’s intentions).

The command, gl-updatev2tov3upgradeareaurlmappings, is very straightforward – it expects the web application that the upgrade list resides on and the source and target URLs. Keep in mind that these URLs though can be specified as absolute they will be converted to server relative and they must correspond to webs on the specified web application (note that I made the code sensitive enough so that you could specify a non-existent source or target in the event that you have already moved your site or are about to move it – it’s always better though if the target actually exists so that it can set the web ID properly).

The syntax of the command can be seen below:

C:\>stsadm -help gl-updatev2tov3upgradeareaurlmappings

stsadm -o gl-updatev2tov3upgradeareaurlmappings

Updates the server relative URL corresponding to the source URL to reflect the new target URL in the Upgrade Area URL Mappings list: "http://portal/Lists/98d3057cd9024c27b2007643c1/AllItems.aspx".  All sites below the site are also updated.

Parameters:
        -webapp <web application>
        -sourceurl <source V3 url (this is not the V2 bucket URL)>
        -targeturl <target V3 URL>

Here’s an example of how to change the redirect URL for a given web:

stsadm –o gl-updatev2tov3upgradeareaurlmappings -webapp "http://intranet/" -sourceurl "/topics/divisions/humanresources" -targeturl "/hr"