This command was created to fix an issue with our upgraded sites as well as issues I discovered with sites that had been imported to new farms. What happened was that after the upgrade (or an import) the Page Layout URL (page.ListItem[FieldId.PageLayout]) for various publishing pages was pointing to the wrong place. This didn’t prevent the page from loading as the Layout property of the page was referencing the correct PageLayout object – what failed was the editing of the page settings (so edit a page and click Page->Page Settings).

When I attempted to edit the page settings I’d get an error due to this url being incorrect (pointed to the old location). So rather than create something one off I figured I’d make this a command that I could use later as I know I’ll need it any time I decide to move a site to a new site collection or farm. The command I created, gl-fixpublishingpagespagelayouturl, is detailed below (apologies for the verbose name).

The code is pretty simple – I’m just resetting the URL based on what I know it should be (I’m using the existing page layout filename and just fixing the host and site collection path). You can also pass in a known page layout url or a regular expression search and replace string to use. I suggest you test this in a virtual environment or at least by using the test parameter to see what will change before running on your entire intranet. The core code is shown below (I left out the details about how I’m looping through the sites – download the code if you’d like to see that):

  1public static void FixPages(PublishingWeb publishingWeb, string pageName, string pageLayoutUrl, Regex searchRegex, string replaceString, bool verbose, bool fixContact, bool test)
  2{
  3    if (!PublishingWeb.IsPublishingWeb(publishingWeb.Web))
  4        return;
  5 
  6    PublishingPageCollection pages;
  7    int tryCount = 0;
  8    while (true)
  9    {
 10        try
 11        {
 12            tryCount++;
 13            pages = publishingWeb.GetPublishingPages();
 14            break;
 15        }
 16        catch (InvalidPublishingWebException)
 17        {
 18            // The following is meant to deal with a timing issue when using this method in conjuction with other commands.  When
 19            // used independently this should be unnecessary.
 20            if (tryCount > 4)
 21                throw;
 22            Thread.Sleep(10000);
 23            SPWeb web = publishingWeb.Web;
 24            SPSite site = web.Site;
 25            string url = site.MakeFullUrl(web.ServerRelativeUrl);
 26            site.Close();
 27            site.Dispose();
 28            web.Close();
 29            web.Dispose();
 30            publishingWeb.Close();
 31            site = new SPSite(url);
 32            web = site.OpenWeb(Utilities.GetServerRelUrlFromFullUrl(url));
 33            publishingWeb = PublishingWeb.GetPublishingWeb(web);
 34        }
 35    }
 36 
 37    foreach (PublishingPage page in pages)
 38    {
 39        if (!(string.IsNullOrEmpty(pageName) || page.Name.ToLower() == pageName.ToLower()))
 40            continue;
 41 
 42        if (verbose)
 43        {
 44            Log(string.Format("Begin processing {0}.", page.Url));
 45            Log(string.Format("Current layout set to {0}.", page.ListItem[FieldId.PageLayout]));
 46        }
 47 
 48        // Can't edit items that are checked out.
 49        if (Utilities.IsCheckedOut(page.ListItem))
 50        {
 51            if (verbose)
 52                Log("Page is already checked out - skipping.");
 53            continue;
 54        }
 55 
 56        SPFieldUrlValue url;
 57        if (string.IsNullOrEmpty(pageLayoutUrl))
 58        {
 59            if (searchRegex == null)
 60            {
 61                if (page.ListItem[FieldId.PageLayout] == null || string.IsNullOrEmpty(page.ListItem[FieldId.PageLayout].ToString().Trim()))
 62                {
 63                    if (verbose)
 64                        Log(string.Format("Current page layout is empty - skipping.  Use the 'pagelayout' parameter to set a page layout."));
 65 
 66                    continue;
 67                }
 68 
 69                // We didn't get a layout url passed in or a regular expression so try and fix the existing url
 70                url = new SPFieldUrlValue(page.ListItem[FieldId.PageLayout].ToString());
 71                if (string.IsNullOrEmpty(url.Url) ||
 72                    url.Url.IndexOf("/_catalogs/") < 0)
 73                {
 74                    if (verbose)
 75                        Log(string.Format("Current page layout does not point to a _catalogs folder or is empty - skipping.  Use the 'pagelayout' parameter to set a page layout  Layout Url: {0}",
 76                                url));
 77                    continue;
 78                }
 79 
 80 
 81                string newUrl = publishingWeb.Web.Site.ServerRelativeUrl.TrimEnd('/') +
 82                              url.Url.Substring(url.Url.IndexOf("/_catalogs/"));
 83 
 84                string newDesc = publishingWeb.Web.Site.MakeFullUrl(newUrl);
 85 
 86                if (url.Url.ToLowerInvariant() == newUrl.ToLowerInvariant())
 87                {
 88                    if (verbose)
 89                        Log("Current layout matches new evaluated layout - skipping.");
 90                    continue;
 91                }
 92                url.Url = newUrl;
 93                url.Description = newDesc;
 94            }
 95            else
 96            {
 97                if (page.ListItem[FieldId.PageLayout] == null || string.IsNullOrEmpty(page.ListItem[FieldId.PageLayout].ToString().Trim()))
 98                    if (verbose)
 99                        Log(string.Format("Current page layout is empty - skipping.  Use the pagelayout parameter to set a page layout."));
100 
101                // A regular expression was passed in so use it to fix the page layout url if we find a match.
102                if (searchRegex.IsMatch((string)page.ListItem[FieldId.PageLayout]))
103                {
104                    url = new SPFieldUrlValue(page.ListItem[FieldId.PageLayout].ToString());
105                    string newUrl = searchRegex.Replace((string)page.ListItem[FieldId.PageLayout], replaceString);
106                    if (url.ToString().ToLowerInvariant() == newUrl.ToLowerInvariant())
107                    {
108                        if (verbose)
109                            Log("Current layout matches new evaluated layout - skipping.");
110                        continue;
111                    }
112                    url = new SPFieldUrlValue(newUrl);
113                }
114                else
115                {
116                    if (verbose)
117                        Log("Existing page layout url does not match provided regular expression - skipping.");
118                    continue;
119                }
120            }
121        }
122        else
123        {
124            // The user passed in an url string so use it.
125            if (pageLayoutUrl.ToLowerInvariant() == (string)page.ListItem[FieldId.PageLayout])
126            {
127                if (verbose)
128                    Log("Current layout matches provided layout - skipping.");
129                continue;
130            }
131 
132            url = new SPFieldUrlValue(pageLayoutUrl);
133        }
134 
135        string fileName = url.Url.Substring(url.Url.LastIndexOf('/'));
136        // Make sure that the URLs are server relative instead of absolute.
137        if (url.Description.ToLowerInvariant().StartsWith("http"))
138            url.Description = Utilities.GetServerRelUrlFromFullUrl(url.Description) + fileName;
139        if (url.Url.ToLowerInvariant().StartsWith("http"))
140            url.Url = Utilities.GetServerRelUrlFromFullUrl(url.Url) + fileName;
141 
142        if (page.ListItem[FieldId.PageLayout] != null && url.ToString().ToLowerInvariant() == page.ListItem[FieldId.PageLayout].ToString().ToLowerInvariant())
143            continue; // No difference detected so move on.
144 
145        if (verbose)
146            Log(string.Format("Changing layout url from \"{0}\" to \"{1}\"", page.ListItem[FieldId.PageLayout], url));
147 
148 
149        if (fixContact)
150        {
151            SPUser contact = null;
152            try
153            {
154                contact = page.Contact;
155            }
156            catch (SPException)
157            {
158            }
159            if (contact == null)
160            {
161                if (verbose)
162                    Log(string.Format("Page contact ('{0}') does not exist - assigning current user as contact.", page.ListItem[FieldId.Contact]));
163                page.Contact = publishingWeb.Web.CurrentUser;
164 
165                if (!test)
166                    page.ListItem.SystemUpdate();
167            }
168        }
169 
170        if (test)
171            continue;
172 
173        page.CheckOut();
174        page.ListItem[FieldId.PageLayout] = url;
175        page.ListItem.UpdateOverwriteVersion();
176        PublishItems.Settings settings = new PublishItems.Settings();
177        settings.Test = test;
178        settings.Quiet = !verbose;
179        settings.LogFile = null;
180 
181        PublishItems.PublishListItem(page.ListItem, page.ListItem.ParentList, settings, "stsadm -o fixpublishingpagespagelayouturl");
182        //page.ListItem.File.CheckIn("Fixed URL to page layout.", SPCheckinType.MajorCheckIn);
183        //if (page.ListItem.ModerationInformation != null)
184        //    page.ListItem.File.Approve("Publishing changes to page layout.");
185    }
186}

The syntax of the command can be seen below:

C:\>stsadm -help gl-fixpublishingpagespagelayouturl

stsadm -o gl-fixpublishingpagespagelayouturl


Fixes the Page Layout URL property of publishing pages which can get messed up during an upgrade or from importing into a new farm.

Parameters:
        -url <url>
        -scope <WebApplication | Site | Web | Page>
        [-pagename <if scope is Page, the name of the page to update>]
        {[-pagelayout <url of page layout to retarget page(s) to (format: "url, desc")>] /
         [-regexsearchstring <search pattern to use for a regular expression replacement of the page layout url>]
         [-regexreplacestring <replace pattern to use for a regular expression replacement of the page layout url>]}
        [-verbose]
        [-test]

To fix all the pages on a given web application you would use the following syntax:

stsadm –o gl-fixpublishingpagespagelayouturl –url "http://intranet/" -scope webapplication

Update 2/17/2008: I’ve made quite a few changes to this command. Note that if you have a previous version the syntax of the command has changed a lot (content above has been updated). You can now pass in a single url parameter along with a scope parameter. Also – you can now pass in a test parameter to simulate what changes would occur. The verbose switch will tell you what it’s doing. If you know you want to set a specific page to you can pass in the pagename parameter. Similarly if you want to set a specific page layout url use the pagelayout parameter or you can use the regex parameters for doing a search and replace. Note that if you pass in the pagelayout parameter you want to use the format [url], [desc] – for example:

stsadm -o gl-fixpublishingpagespagelayouturl -url "http://intranet" -scope site -pagelayout "http://intranet/_catalogs/masterpage/WelcomeLinks.aspx, /_catalogs/masterpage/WelcomeLinks.aspx".