This post covers a sample technical design for the most common branding task you’ll encounter for site & system pages – adapting ALL site & system pages on ALL sites to your customer’s look-and-feel standards. We accomplish this with the following components, each of which are covered in detail:
· Branding “Staplee” Features – the Features that contain a custom master page and (optionally) custom pages, page manipulation specifications, and other SPWeb-level components to be applied to all sites created from one or more site definitions
· Branding ”Stapler” Feature – the Feature that staples all of the Branding Features to their respective site definitions
· MySiteCreate Receiver Assembly – the Feature receiver assembly used to set custom site & system master pages, a custom theme, and manage page manipulation execution
· PartCheck Web Part – the Page Manipulation web part that is used to alter the default home/welcome page for sites associated with a particular Branding Feature
· BrandingUpdate – a console application used to apply/reapply branding features to existing sites
Customizations to components found in the LAYOUTS directory are described in Part 3. The closing section of this article will reference specific dependencies between customizations to site/system pages and LAYOUTS components.
The Branding Features project contains all SharePoint features relating to the custom intranet branding. This includes a number of “Staplee” features which are used to install master pages and a theme on sites, and a single “Stapler” feature that associates the Staplees with specific site definitions.
The branding features described below are intended to be deployed in a standard manner across ALL web applications. Should new requirements make separate branding schemes necessary for individual web applications, developers can create a unique Branding Stapler feature for each web application and (if necessary) additional Branding Staplee features.
File name
Project Location
Installation Scope
Purpose
Notes
BrandingStapler
Web App
Folder for BrandingStapler feature
GenericBrandingStaplee
Web
Folder for GenericBrandingStaplee feature
MeetingBrandingStaplee
Folder for MeetingBrandingStaplee feature
Custom_default.master
Master page for all site pages for non-meeting workspace sites.
Common element file for all associated features.
Custom_MWSDefault.master
Master page for all site pages for meeting workspace sites.
Feature.xml
Feature specification file
Elements.xml
Element manifest.
Defines Feature Site Template Associations for all Branding Staplee features.
Includes properties for use by Feature Receiver (MySiteCreate).
Defines installation location for master page and MySiteStaplee.xml.
MySiteStaplee.xml
Defines web part deployment instructions for the home page for all site definitions associated with the GenericBrandingStaplee feature.
No web part deployment instructions for these site definitions.
Defines web part deployment instructions for the home page for all site definitions associated with the MeetingBrandingStaplee feature.
MySiteCreate.cs
{root}
Farm
Feature receiver for all branding staplee features.
Microsoft.IW.MySiteCreate is a modified version of the feature receiver from the MySite customization article described in the references section. This feature receiver executes the following actions for each site created from a site definition associated with the feature:
· Sets the master page and custom master page URL for the site to the value specified in the MasterName property.
· Sets the theme for the site to the value specified in the ThemeID property.
· Optionally sets a flag on the site for later processing by the PartCheck control to ignore web part deployment instructions based on the value of the CheckPart property.
Name
Type
FeatureActivated
Void
Sets the master page and theme for the site. Optionally sets the web part check flag for the MySiteCreate control.
FeatureDeactivating
Not implemented. See SPFeatureReceiver specification.
FeatureInstalled
FeatureUninstalling
UpdateLog
Writes entries to the Windows Application event log.
The Custom_default.master file is a customized version of the default.master SharePoint master page.
Sample customizations that could be found in Custom_default.master are described below:
· Header region
o Moved Welcome control from right side of header to left side of header
o Moved Search controls to same row as Welcome control
o Removed User-customizable Site Logo
o Added Contoso logo
· Footer region
o Added custom footer
§ Footer contains {WebPart X} and several hyperlinks to policies
· HIDDEN
o Added PartCheck control to page
The Custom_MWSdefault.master file is a customized version of the MWSdefault.master SharePoint master page.
Sample customizations that could be found in Custom_MWSdefault.master are described below:
The BrandingStapler feature associates each of the branding staplees with one or more site templates.
Refer to the webtemp*.xml files in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\1033\XML to identify the common names for the site templates described in the feature site template associations.
The branding staplees are hidden features – which cannot be disabled/enabled by site owners – that are activated whenever a web of the type associated with the feature is created. Each branding staple accomplishes the following tasks upon activation in a web:
· Installs the master page specified by the feature in the _catalogs/masterpages directory
· Installs the web part deployment instructions specified by the feature in the _catalogs/masterpages directory. This file must always be named MySiteCreate.xml.
· Calls the MySiteCreate feature receiver assembly.
CheckPart
Boolean
Specifies whether or not the PartCheck control needs to execute web part deployment instructions on the site’s home page.
Optional. PartCheck will log an error event if it processes a malformed MySiteStaplee.xml file or cannot access the default.aspx page.
MasterName
String
Used by MySiteCreate feature receiver. File name for master page to be set on the site.
ThemeID
Used by MySiteCreate feature receiver. Theme ID for the theme to be set on the site.
OriginalMasterName
Intended for future use by MySiteCreate feature receiver. NOT IMPLEMENTED
Comments: The branding stapler doesn’t include any deactivation methods. Depending on your scenario, you may or may not want to include deactivation logic, but that can get complicated. Do you restore the site to the original theme, site master page, system master page, and home page configuration? Do you restore the master pages to default.master & the theme to the “Default” theme and leave the page alone? What about changes that the user has made since the branding was applied? Regardless, lots of planning and design has to go into any branding updates and/or deactivation.
The Branding Update application is used to apply branding features to existing sites. It can be used for both initial branding deployment and future updates to branding features, or even for installing other features unrelated to branding that are associated with webs via FeatureSiteTemplateAssociations.
Program.cs
n/a
Main application.
BrandMap.cs
Structure for storing the attributes of featuresitetemplate elements in the configuration file.
webAppSectionhandler.cs
Extension of IConfigurationSectionHandler for processing the custom <webAppSection/> configuration file element.
masterMapSectionHandler.cs
Extension of IConfigurationSectionHandler for processing the custom <mappingSection/> configuration file element.
App.config
Application configuration file.
The Branding Update application uses the configuration settings specified in the table below. Settings in bold red are required.
Section
Domain
Default Value
Loglevel
appSettings
Integer – 0 or greater
Describes application log verbosity.
0 = no event logging
1 = Error events only
2 = Error & Warning events
3 or more = All events
1
Debug
Boolean – true or false
Determines whether log entries should be written to the console.
True - write to console
False - do not write to console
false
brandAll
Determines scope of branding feature activation.
False - branding will only be applied to sites without branding features activated
True – branding will be applied (or reapplied) to all sites
filterPattern
Any string value
(should match one or more site names)
Case-insensitive pattern for restricting the scope of sites against which the Branding Update application will run. This is a “starts with” filter pattern, not a substring match. An empty filter pattern is interpreted as a wildcard filter.
(none)
webApp
(One or more entries)
webAppSectio
Any name for a web application in the farm
Case-insensitive name for a web application. Branding updates will be applied to any web applications listed in this section.
FeatureSiteTemplateAssociation
mappingSection
Any FeatureSiteTemplateAssociation element from the BrandingStapler’s elements.xml file.
Branding updates will only be applied to sites created from templates with associations listed in this element.
Note that BrandingUpdate employs progressive filtering via the configuration file. The priority is effectively as follows:
1. WebApp determines the set of web applications
2. FilterPattern determines the applicable sites within those applications
3. BrandAll determines whether to apply branding to those sites without branding features activated or to all sites
Program.cs is the main application class for the BrandingUpdate application.
Main
Main method.
DoLog
Writes entries to the Windows Application event log and/or the console.
Log entries appear with a source of “BrandingUpdate”
The BrandingParts project contains web parts used in Contoso intranet branding.
Comment: This example only shows the PartCheck part, but you might also have other web parts that are used to deploy custom functionality as part of branding efforts.
PartCheck.cs
Manipulates the web parts deployed on the default.aspx page for a given site.
PartCheck extends the System.Web.UI.WebControls.WebControl class. Refer to that specification for inherited members. This component is borrowed from the MySite branding blog entry described in the references section. Refer to that reference and code comments for full documentation.
The Customizations project contains a number of files that need to be installed in the IIS home directories of user-facing web applications and the “12” directory.
Some of the LAYOUTS components that your site & system page branding may depend on include:
· Images
· Theme Definitions
· Stylesheets
· CSS files
I’ll be posting most of the code shortly, once I have time to sanitize everything. Below are snippets of the key portions of the solution with a few comments. For the PartCheck code and explanation, see Steve Peschka’s post.
The stapler’s Feature.xml is very simple, just referencing the elements.xml. You could make this Farm scope instead of WebApp scope.
<Feature
Id="247385FE-AAAA-BBBB-980C-5517B7609D58"
Title="Branding Stapler"
Scope="WebApplication"
xmlns="http://schemas.microsoft.com/sharepoint/" >
<ElementManifests>
<ElementManifest Location="elements.xml" />
</ElementManifests>
</Feature>
The elements.xml is where the action is. You’ll see the feature GUIDs for the meeting and generic branding staplees referenced in this file. Of course, you can have site definition-specific branding features if your requirements demand them. Note that the list below includes all the MOSS Enterprise templates – you won’t find all of them in WSSv3 or MOSS Standard.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/" >
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="STS#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="STS#1"/>
<FeatureSiteTemplateAssociation Id="D373E781-AAAA-BBBB-9B08-998766EE558C" TemplateName="MPS#3"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="STS#2"/>
<FeatureSiteTemplateAssociation Id="D373E781-AAAA-BBBB-9B08-998766EE558C" TemplateName="MPS#0"/>
<FeatureSiteTemplateAssociation Id="D373E781-AAAA-BBBB-9B08-998766EE558C" TemplateName="MPS#1"/>
<FeatureSiteTemplateAssociation Id="D373E781-AAAA-BBBB-9B08-998766EE558C" TemplateName="MPS#2"/>
<FeatureSiteTemplateAssociation Id="D373E781-AAAA-BBBB-9B08-998766EE558C" TemplateName="MPS#4"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="WIKI#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="BLOG#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="OFFILE#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="OFFILE#1"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="BDR#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SRCHCENTERLITE#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SRCHCENTERLITE#1"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSPERS#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSMSITE#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="CMSPUBLISHING#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="BLANKINTERNET#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="BLANKINTERNET#1"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="BLANKINTERNET#2"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSNHOME#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSSITES#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSREPORTCENTER#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSPORTAL#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SRCHCEN#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="PROFILES#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="BLANKINTERNETCONTAINER#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSMSITEHOST#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPS#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSTOC#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSTOPIC#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSNEWS#0"/>
<FeatureSiteTemplateAssociation Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798" TemplateName="SPSCOMMU#0"/>
</Elements>
The branding staplee features are essentially the same as Steve Peschka’s MySiteStaplee feature. Only significant differences are in the feature.xml file, which appears below:
Id="6FA5083C-AAAA-BBBB-84E6-EDD5187DB798"
Title="Generic Site Branding Feature"
Scope="Web"
ReceiverAssembly="MySiteCreate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=AAAABBBBCCCCDDDD"
ReceiverClass="Microsoft.IW.MySiteCreate"
Hidden="TRUE"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementFile Location="custom_default.master"/>
<ElementFile Location="MySiteStaplee.xml"/>
<ElementManifest Location="element.xml"/>
<Properties>
<Property Key="MasterName" Value="custom_default.master"/>
<Property Key="ThemeID" Value="contoso"/>
<Property Key="CheckParts" Value="false"/>
<Property Key="OriginalMaster" Value="default.master"/>
</Properties>
You’ll see that my feature.xml also identifies the theme (we used a custom one) and, thinking ahead to potential deactivation logic which I ended up not using, the original master page for the site definition.
MySiteCreate.cs code:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;
using System.Diagnostics;
namespace Microsoft.IW
{
public class MySiteCreate : SPFeatureReceiver
const string KEY_CHK = "Microsoft.IW.PartCheck";
const string ORIGINALTHEME = "simple";
public override void FeatureActivated(SPFeatureReceiverProperties properties)
try
string newMaster = string.Empty;
string newTheme = string.Empty;
bool checkParts = false;
//look at the properties collection to get the new master page name
newMaster = properties.Feature.Properties["MasterName"].Value;
newTheme = properties.Feature.Properties["ThemeID"].Value;
checkParts = Convert.ToBoolean(properties.Feature.Properties["CheckParts"].Value);
if (!checkParts)
//no need to check the web parts - set the part check flag preemptively
using (SPWeb curWeb = (SPWeb)properties.Feature.Parent)
if (!curWeb.Properties.ContainsKey(KEY_CHK))
curWeb.Properties.Add(KEY_CHK, "true");
curWeb.Properties.Update();
}
if ((newMaster != null) && (newMaster != string.Empty) &&
(newTheme != null) && (newTheme != string.Empty))
string curMaster = curWeb.MasterUrl;
string curCustomMaster = curWeb.CustomMasterUrl;
//got the current site and root web in site, now set the master Url
//to our master page that should have been uploaded as part
//of our feature
int masterPathEnd = curMaster.LastIndexOf('/');
int customMasterPathEnd = curCustomMaster.LastIndexOf('/');
/*
UpdateLog("Preparing to update branding for web " + curWeb.Title +
". MasterUrl: " + curWeb.MasterUrl + "; CustomMasterUrl: " +
curWeb.CustomMasterUrl + "; Theme: " + curWeb.Theme + ".",
EventLogEntryType.Information);
*/
curWeb.MasterUrl = curMaster.Substring(0, ++masterPathEnd) + newMaster;
curWeb.CustomMasterUrl = curCustomMaster.Substring(0, ++customMasterPathEnd) + newMaster;
curWeb.ApplyTheme(newTheme);
curWeb.Update();
UpdateLog("Updated branding for web " + curWeb.Title +
catch (Exception ex)
//try writing to event log
UpdateLog("Error in handler. Message: " + ex.Message + ". Full Exception: " + ex.ToString(), EventLogEntryType.Error);
private void UpdateLog(string Message, EventLogEntryType msgType)
System.Diagnostics.EventLog.WriteEntry("My Site Created Handler", Message, msgType);
catch
//ignore
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
string originalMaster = string.Empty;
originalMaster = properties.Feature.Properties["OriginalMaster"].Value;
if ((originalMaster != null) && (originalMaster != string.Empty))
UpdateLog("Preparing to revert branding for web " + curWeb.Title +
curWeb.MasterUrl = curMaster.Substring(0, ++masterPathEnd) + originalMaster;
curWeb.CustomMasterUrl = curCustomMaster.Substring(0, ++customMasterPathEnd) + originalMaster;
curWeb.ApplyTheme(ORIGINALTHEME);
UpdateLog("Reverted branding for web " + curWeb.Title +
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
//throw new Exception("The method or operation is not implemented.");
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
Comments:
· This class is a novel extension of Steve Peschka’s code. Only differences are that I’m setting both master pages and the theme.
· Note that I started down the path of including a Deactivating event, but thought better of it due to the issues described earlier… J
The behavior of Program.cs is described in detail above. Here’s the code:
using System.Collections.Specialized;
using System.Xml;
using System.Configuration;
using Microsoft.SharePoint.Administration;
using System.Collections;
namespace BrandingUpdate
class Program
static int logLevel = 1;
static bool debug = false;
static bool brandAll = false;
static bool upload = false;
static string logSource = "BrandingUpdate";
static string logDestination = "Application";
static string filterPattern = "";
static void Main(string[] args)
//Retrieve Configuration Settings
ArrayList webApps = null;
ArrayList brandMaps = null;
StringDictionary staples = new StringDictionary();
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ConfigurationSectionGroupCollection webApp = config.SectionGroups;
NameValueCollection appSettings = ConfigurationManager.AppSettings;
logLevel = Convert.ToInt32(appSettings["loglevel"]);
debug = Convert.ToBoolean(appSettings["debug"]);
brandAll = Convert.ToBoolean(appSettings["brandAll"]);
filterPattern = appSettings["filterPattern"];
webApps = (ArrayList)ConfigurationManager.GetSection("brandingSectionGroup/webAppSection");
brandMaps = (ArrayList)ConfigurationManager.GetSection("brandingSectionGroup/mappingSection");
if (webApps == null || brandMaps == null) throw new ConfigurationException("Missing required arguments.");
foreach (BrandMap map in brandMaps)
staples.Add(map.templateName, map.id);
catch (Exception e)
DoLog("Error reading configuration file:" + e.ToString(), EventLogEntryType.Error, 100);
//Retrieve list of web apps
SPFarm mySPFarm = SPWebService.ContentService.Farm;
SPWebApplicationCollection mySPWebAppCollection = SPWebService.ContentService.WebApplications;
if (mySPWebAppCollection != null)
//Iterate through web applications
foreach (SPWebApplication mySPWebApp in mySPWebAppCollection)
DoLog("WebApp: " + mySPWebApp.Name, EventLogEntryType.Information, 250);
if (webApps.Contains(mySPWebApp.Name.ToUpper()))
//Iterate through site collections
foreach (SPSite site in mySPWebApp.Sites)
//Iterate through webs
foreach (SPWeb web in site.AllWebs)
//check to see if web matches filter pattern
if (filterPattern.Equals("") || web.Url.StartsWith(filterPattern, true, null))
string configurationID = web.WebTemplate + "#" + web.Configuration;
string featureStaple = staples[configurationID];
if (featureStaple != null)
bool brandingActivated = false;
Guid featureGuid = new Guid(featureStaple);
//see if branding feature is activated on this web
foreach (SPFeature feature in web.Features)
if (feature.DefinitionId.Equals(featureGuid))
brandingActivated = true;
//apply branding to everything if branding feature is not activated
if (brandAll || !brandingActivated)
DoLog("Activating branding feature " + featureGuid.ToString() + " for web " + web.Url , EventLogEntryType.Information, 500);
if (brandingActivated) web.Features.Remove(featureGuid);
web.Features.Add(featureGuid);
DoLog("Error activating branding feature " + featureGuid.ToString() + " for web " + web.Url + ". Exception: " + ex.ToString(), EventLogEntryType.Error, 500);
web.Close();
}//close web loop
site.Close();
} //Close site collection loop
}//Close webapp loop
DoLog("Error examining site collections:" + e.ToString(), EventLogEntryType.Error, 400);
private static void DoLog(string msg, EventLogEntryType eventType, int code)
bool writeEntry = true;
if (debug) Console.WriteLine("Event Type: {0}; ID: {1}; Message: {2}", eventType.ToString(), code, msg);
if (logLevel > 0)
if (!EventLog.SourceExists(logSource))
EventLog.CreateEventSource(logSource, logDestination);
if (eventType.Equals(EventLogEntryType.Warning) && (logLevel < 2))
writeEntry = false;
if (eventType.Equals(EventLogEntryType.Information) && (logLevel < 3))
if (writeEntry) EventLog.WriteEntry(logSource, msg, eventType, code);
App.config is also described above. Only comment-worthy feature is that the masterMapSectionHandler implements the same structure for a FeatureSiteTemplateAssociation as you have in the elements.xml, which makes adding all this configuration info very easy:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="brandingSectionGroup">
<section name="webAppSection"
type="BrandingUpdate.webAppSectionHandler,BrandingUpdate"/>
<section name="mappingSection"
type="BrandingUpdate.masterMapSectionHandler,BrandingUpdate"/>
</sectionGroup>
</configSections>
<brandingSectionGroup>
<webAppSection>
<webApp>MOSS.LITWAREINC.COM</webApp>
<webApp>SharedServices1</webApp>
</webAppSection>
<mappingSection>
</mappingSection>
</brandingSectionGroup>
<appSettings>
<add key="loglevel" value="0"/>
<add key="debug" value="true"/>
<add key="brandAll" value="true"/>
<add key="filterPattern" value=""/>
</appSettings>
</configuration>