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