I’d thought about building an STSADM command to enable setting the master page of a site for quite a while but had opted not to do it simply out of principle – it’s generally a better idea to do this via a Feature and I didn’t really want to promote a bad practice. Ultimately though I had to concede that there are administrators who will not have the luxury of having developers who can create Feature that can be deployed to enable consistent application of their master page across site collections.
So what I came up with was a command, called gl-setmasterpage
, which allows the user to set the site and system master page URLs and, this is the cool part, copy a master page from a source location to the destination site. So consider that you have 10 different site collections on your main portal and you want all those site collections to use the same master page as the root site collection – you could accomplish this by running the following for each site collection (or by wrapping in a loop using PowerShell as shown further down):
stsadm -o gl-setmasterpage -url "http://portal/division1" -sitemaster "/division1/_catalogs/masterpage/custom.master" -systemmaster "/division1/_catalogs/masterpage/custom.master" –sitesource "http://portal/_catalogs/masterpage/custom.master"
The code to set the master page is pretty simple – there’s two core properties of the SPWeb
object: CustomMasterUrl
and MasterUrl
. The CustomMasterUrl
property corresponds to the “Site Master Page” value when editing the settings via the browser and the MasterUrl
property corresponds to the “System Master Page” value. The bulk of the code for this command is in the validation and copying of the source master page:
1using System;
2using System.Collections.Generic;
3using System.Collections.Specialized;
4using System.IO;
5using System.Text;
6using Lapointe.SharePoint.STSADM.Commands.Lists;
7using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
8using Lapointe.SharePoint.STSADM.Commands.SPValidators;
9using Microsoft.SharePoint;
10using Microsoft.SharePoint.Deployment;
11
12namespace Lapointe.SharePoint.STSADM.Commands.SiteCollectionSettings
13{
14 public class SetMasterPage : SPOperation
15 {
16 /// <summary>
17 /// Initializes a new instance of the <see cref="SetMasterPage"/> class.
18 /// </summary>
19 public SetMasterPage()
20 {
21 SPParamCollection parameters = new SPParamCollection();
22 parameters.Add(new SPParam("url", "url", true, null, new SPUrlValidator(), "Please specify the web url."));
23 parameters.Add(new SPParam("sitemaster", "sitemp", false, null, new SPNonEmptyValidator()));
24 parameters.Add(new SPParam("systemmaster", "sysmp", false, null, new SPNonEmptyValidator()));
25 parameters.Add(new SPParam("resetsubsites", "reset"));
26 parameters.Add(new SPParam("systemsource", "syssrc", false, null, new SPUrlValidator()));
27 parameters.Add(new SPParam("sitesource", "sitesrc", false, null, new SPUrlValidator()));
28
29 StringBuilder sb = new StringBuilder();
30 sb.Append("\r\n\r\nSets the site and/or system master page for the given web.\r\n\r\nParameters:");
31 sb.Append("\r\n\t-url <web URL>");
32 sb.Append("\r\n\t[-sitemaster <server relative URL to the site master page>]");
33 sb.Append("\r\n\t[-systemmaster <server relative URL to the system master page>]");
34 sb.Append("\r\n\t[-resetsubsites]");
35 sb.Append("\r\n\t[-systemsource <URL to a source system master page file to copy to the target>");
36 sb.Append("\r\n\t[-sitesource <URL to a source site master page file to copy to the target>");
37 Init(parameters, sb.ToString());
38 }
39
40 #region ISPStsadmCommand Members
41
42 /// <summary>
43 /// Gets the help message.
44 /// </summary>
45 /// <param name="command">The command.</param>
46 /// <returns></returns>
47 public override string GetHelpMessage(string command)
48 {
49 return HelpMessage;
50 }
51
52 /// <summary>
53 /// Runs the specified command.
54 /// </summary>
55 /// <param name="command">The command.</param>
56 /// <param name="keyValues">The key values.</param>
57 /// <param name="output">The output.</param>
58 /// <returns></returns>
59 public override int Execute(string command, StringDictionary keyValues, out string output)
60 {
61 output = string.Empty;
62 Verbose = true;
63
64 string url = Params["url"].Value.TrimEnd('/');
65 string siteMaster = null;
66 string systemMaster = null;
67 string siteSource = null;
68 string systemSource = null;
69 bool recurse = Params["resetsubsites"].UserTypedIn;
70
71 if (Params["sitemaster"].UserTypedIn)
72 siteMaster = Params["sitemaster"].Value;
73
74 if (Params["systemmaster"].UserTypedIn)
75 systemMaster = Params["systemmaster"].Value;
76
77
78 if (Params["sitesource"].UserTypedIn)
79 siteSource = Params["sitesource"].Value;
80
81 if (Params["systemsource"].UserTypedIn)
82 systemSource = Params["systemsource"].Value;
83
84
85 SetMasterPages(url, siteSource, systemSource, siteMaster, systemMaster, recurse);
86
87 return OUTPUT_SUCCESS;
88 }
89
90
91
92 /// <summary>
93 /// Validates the specified key values.
94 /// </summary>
95 /// <param name="keyValues">The key values.</param>
96 public override void Validate(StringDictionary keyValues)
97 {
98 base.Validate(keyValues);
99
100 if (!Params["sitemaster"].UserTypedIn && !Params["systemmaster"].UserTypedIn)
101 {
102 throw new SPSyntaxException("You must provide at least one of the sitemaster or systemmaster parameters.");
103 }
104 }
105
106 #endregion
107
108 /// <summary>
109 /// Validates the master page URL.
110 /// </summary>
111 /// <param name="site">The site.</param>
112 /// <param name="masterPageUrl">The master page URL.</param>
113 /// <param name="source">The source.</param>
114 private static void ValidateMasterPageUrl(SPSite site, ref string masterPageUrl, string source)
115 {
116 if (!string.IsNullOrEmpty(masterPageUrl))
117 {
118 masterPageUrl = masterPageUrl.ToLowerInvariant();
119 if (masterPageUrl.IndexOf("_catalogs/masterpage") < 0)
120 throw new ArgumentException(string.Format("The specified master page url is not in the '_catalogs/masterpage' gallery: {0}", masterPageUrl));
121 if (!masterPageUrl.EndsWith(".master"))
122 throw new ArgumentException(string.Format("The specified master page url does not end with '.master': {0}", masterPageUrl));
123
124 if (!string.IsNullOrEmpty(source) && site.MakeFullUrl(masterPageUrl).ToLowerInvariant() == source.ToLowerInvariant())
125 {
126 Log("WARNING: Source file and target are the same. Source will not be copied: {0}", source);
127 source = null;
128 }
129 SPFile sourceFile = null;
130 string sourceList = null;
131 if (!string.IsNullOrEmpty(source))
132 {
133 source = source.ToLowerInvariant();
134 if (source.IndexOf("_catalogs/masterpage") < 0)
135 throw new ArgumentException(string.Format("The specified source master page url is not in the '_catalogs/masterpage' gallery: {0}", source));
136 if (!source.EndsWith(".master"))
137 throw new ArgumentException(string.Format("The specified source master page url does not end with '.master': {0}", source));
138
139 string sourceFileName = source.Substring(source.LastIndexOf('/') + 1);
140 string targetFileName = masterPageUrl.Substring(masterPageUrl.LastIndexOf('/') + 1);
141 if (sourceFileName != targetFileName)
142 throw new ArgumentException(string.Format("The specified source filename ({0}) does not match the master page settings filename ({1}).", sourceFileName, targetFileName));
143
144 // Get the source file to copy to the target.
145 string sourceWebUrl = source.Substring(0, source.IndexOf("_catalogs/masterpage")).TrimEnd('/');
146 using (SPSite sourceSite = new SPSite(sourceWebUrl))
147 using (SPWeb sourceWeb = sourceSite.OpenWeb())
148 {
149 sourceFile = sourceWeb.GetFile(Utilities.GetServerRelUrlFromFullUrl(source));
150 if (!sourceFile.Exists)
151 throw new FileNotFoundException(string.Format("The specified source file does not exist: {0}", source));
152 sourceList = sourceSite.MakeFullUrl(sourceFile.Item.ParentList.RootFolder.ServerRelativeUrl);
153 }
154 }
155
156 // Get the web associated with the passed in master page (we can't use OpenWeb(masterPageUrl, false) because it will throw an exception as it doesn't allow opening webs using this url).
157 string masterWebUrl = masterPageUrl.Substring(0, masterPageUrl.IndexOf("_catalogs/masterpage")).TrimEnd('/');
158 using (SPWeb masterWeb = site.OpenWeb(masterWebUrl))
159 {
160 try
161 {
162 if (sourceFile != null)
163 {
164 SPList masterPageGallery = masterWeb.GetList(masterWebUrl + "/_catalogs/masterpage");
165 string targetList = site.MakeFullUrl(masterPageGallery.RootFolder.ServerRelativeUrl);
166 CopyListItem copyCmd = new CopyListItem();
167 List<int> ids = new List<int> {sourceFile.Item.ID};
168 Log("Progress: Copying source file ({0}) to target ({1})...", source, targetList);
169 copyCmd.CopyItem(ids, sourceList, targetList, false, false, true, SPIncludeVersions.CurrentVersion, SPIncludeDescendants.Content, SPUpdateVersions.Append);
170 }
171 // Try to locate the file specified.
172 SPFile file = masterWeb.GetFile(masterPageUrl);
173 if (!file.Exists)
174 throw new FileNotFoundException();
175
176 // The master page settings page is case sensitive so we need to make sure that the case of the string matches the actual file name.
177 // This is crude but it works (we have to use the file.Item.Url property instead of file.ServerRelativeUrl because the latter returns
178 // whatever we provided it.
179 masterPageUrl = masterWeb.ServerRelativeUrl.TrimEnd('/') + "/" + file.Item.Url;
180 }
181 catch
182 {
183 throw new FileNotFoundException(string.Format("The specified master page could not be found: {0}", masterPageUrl));
184 }
185
186 }
187 }
188 }
189
190 /// <summary>
191 /// Sets the master pages.
192 /// </summary>
193 /// <param name="url">The URL.</param>
194 /// <param name="siteMaster">The site master.</param>
195 /// <param name="systemMaster">The system master.</param>
196 /// <param name="recurse">if set to <c>true</c> [recurse].</param>
197 public static void SetMasterPages(string url, string siteMaster, string systemMaster, bool recurse)
198 {
199 SetMasterPages(url, null, null, siteMaster, systemMaster, recurse);
200 }
201
202 /// <summary>
203 /// Sets the master pages.
204 /// </summary>
205 /// <param name="url">The URL.</param>
206 /// <param name="siteSource">The site source.</param>
207 /// <param name="systemSource">The system source.</param>
208 /// <param name="siteMaster">The site master.</param>
209 /// <param name="systemMaster">The system master.</param>
210 /// <param name="recurse">if set to <c>true</c> [recurse].</param>
211 public static void SetMasterPages(string url, string siteSource, string systemSource, string siteMaster, string systemMaster, bool recurse)
212 {
213 using (SPSite site = new SPSite(url))
214 using (SPWeb web = site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(url)])
215 {
216 Log("Progress: Processing Site Collection {0}...", url);
217
218 // If the source files are the same then clear the system source so that we only do the copying once.
219 if (siteSource != null && systemSource != null && siteSource.ToLowerInvariant() == systemSource.ToLowerInvariant())
220 systemSource = null;
221
222
223 // Because of the way the validation code works and because we don't want to have to specify the source twice (thus copying
224 // the file twice) then we have to make sure that if a source is not set then it is done last.
225 if ((siteSource == null && systemSource == null) || (siteSource != null))
226 {
227 ValidateMasterPageUrl(site, ref siteMaster, siteSource);
228 ValidateMasterPageUrl(site, ref systemMaster, systemSource);
229 }
230 else
231 {
232 ValidateMasterPageUrl(site, ref systemMaster, systemSource);
233 ValidateMasterPageUrl(site, ref siteMaster, siteSource);
234 }
235
236 SetMasterPages(web, siteMaster, systemMaster, recurse);
237 }
238 }
239
240 /// <summary>
241 /// Sets the master pages.
242 /// </summary>
243 /// <param name="web">The web.</param>
244 /// <param name="siteMaster">The site master.</param>
245 /// <param name="systemMaster">The system master.</param>
246 /// <param name="recurse">if set to <c>true</c> [recurse].</param>
247 private static void SetMasterPages(SPWeb web, string siteMaster, string systemMaster, bool recurse)
248 {
249 Log("Progress: Processing Web {0}...", web.Url);
250 if (!string.IsNullOrEmpty(siteMaster) && web.CustomMasterUrl != siteMaster)
251 {
252 Log("Progress: Changing Site Master from '{0}' to '{1}'.", web.CustomMasterUrl, siteMaster);
253 web.CustomMasterUrl = siteMaster;
254 }
255
256 if (!string.IsNullOrEmpty(systemMaster) && web.MasterUrl != systemMaster)
257 {
258 Log("Progress: Changing System Master from '{0}' to '{1}'.", web.MasterUrl, systemMaster);
259 web.MasterUrl = systemMaster;
260 }
261
262 if (recurse)
263 {
264 foreach (SPWeb subWeb in web.Webs)
265 {
266 try
267 {
268 SetMasterPages(subWeb, siteMaster, systemMaster, recurse);
269 }
270 finally
271 {
272 subWeb.Dispose();
273 }
274 }
275 }
276 web.Update();
277 }
278 }
279}
The help for the command is shown below:
C:\>stsadm -help gl-setmasterpage
stsadm -o gl-setmasterpage
Sets the site and/or system master page for the given web.
Parameters:
-url <web URL>
[-sitemaster <server relative URL to the site master page>]
[-systemmaster <server relative URL to the system master page>]
[-resetsubsites]
[-systemsource <URL to a source system master page file to copy to the target>
[-sitesource <URL to a source site master page file to copy to the target>
The following table summarizes the command and its various parameters:
Command Name | Availability | Build Date |
---|---|---|
gl-setmasterpage | WSS 3.0, MOSS 2007 | Released: 2/7/2009 |
Parameter Name | Short Form | Required | Description | Example Usage |
---|---|---|---|---|
url | Yes | URL of the web or site collection to update. | -url "http://portal" | |
sitemaster | sitemp | Yes if systemmaster is not provided or sitesource is provided | The server relative URL to the master page. | -sitemaster "/_catalogs/masterpage/default.master" , -sitemp "/_catalogs/masterpage/default.master" |
systemmaster | sysmp | Yes if sitemaster is not provided or systemsource is provided | The server relative URL to the master page. | -systemmaster "/_catalogs/masterpage/default.master" , -sysmp "/_catalogs/masterpage/default.master" |
resetsubsites | reset | No | If specified all sub-sites of the passed in URL will be configured to use the specified master page. | -resetsubsites , -reset |
systemsource | syssrc | No | The absolute URL to the source master page to copy to the master page gallery of the web specified in the URL parameter. | -systemsource "http://portal/_catalogs/masterpage/default.master" , -syssrc "http://portal/_catalogs/masterpage/default.master" |
sitesource | sitesrc | No | The absolute URL to the source master page to copy to the master page gallery of the web specified in the URL parameter. | -sitesource "http://portal/_catalogs/masterpage/default.master" , -sitesrc "http://portal/_catalogs/masterpage/default.master" |
Now that we have the command we can easily combine this with a simple bit of PowerShell that will enable us to copy a master page from a single source site to all sites that match our filter criteria and set the master page settings to use this new master page. The code to do this is shown in lines 1-6 below along with some sample output in the following lines:
PS C:\> $sites = get-spsite-gl -url http://portal*
PS C:\> foreach ($site in $sites) {
>> $siteMaster = $site.ServerRelativeUrl.TrimEnd('/') + "/_catalogs/masterpage/gary.master"
>> stsadm -o gl-setmasterpage -url ($site.Url) -sitemaster $siteMaster -sitesource http://portal/_catalogs/masterpage/gary.master -reset
>> }
>>
Progress: Processing Site Collection http://portal...
WARNING: Source file and target are the same. Source will not be copied: http://portal/_catalogs/masterpage/gary.master
Progress: Processing Web http://portal...
Progress: Changing Site Master from '/_catalogs/masterpage/default.master' to '/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/Docs...
Progress: Changing Site Master from '/_catalogs/masterpage/default.master' to '/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/News...
Progress: Changing Site Master from '/_catalogs/masterpage/default.master' to '/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/Reports...
Progress: Changing Site Master from '/_catalogs/masterpage/default.master' to '/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/SearchCenter...
Progress: Changing Site Master from '/_catalogs/masterpage/default.master' to '/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/SiteDirectory...
Progress: Changing Site Master from '/_catalogs/masterpage/default.master' to '/_catalogs/masterpage/gary.master'.
Operation completed successfully.
Progress: Processing Site Collection http://portal/sites/Test...
Progress: Copying source file (http://portal/_catalogs/masterpage/gary.master) to target (http://portal/sites/test/_catalogs/masterpage)...
Progress: Processing Web http://portal/sites/Test...
Progress: Changing Site Master from '/sites/Test/_catalogs/masterpage/default.master' to '/sites/test/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/sites/Test/Docs...
Progress: Changing Site Master from '/sites/Test/_catalogs/masterpage/default.master' to '/sites/test/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/sites/Test/News...
Progress: Changing Site Master from '/sites/Test/_catalogs/masterpage/default.master' to '/sites/test/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/sites/Test/Reports...
Progress: Changing Site Master from '/sites/Test/_catalogs/masterpage/default.master' to '/sites/test/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/sites/Test/SearchCenter...
Progress: Changing Site Master from '/sites/Test/_catalogs/masterpage/default.master' to '/sites/test/_catalogs/masterpage/gary.master'.
Progress: Processing Web http://portal/sites/Test/SiteDirectory...
Progress: Changing Site Master from '/sites/Test/_catalogs/masterpage/default.master' to '/sites/test/_catalogs/masterpage/gary.master'.
Operation completed successfully.
PS C:\>