Branding a MOSS Corporate Intranet Portal, Part 2: Site & System Pages
Introduction
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.
BrandingFeatures Project
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 Manifest
File name |
Project Location |
Installation Scope |
Purpose |
Notes |
BrandingStapler |
BrandingStapler |
Web App |
Folder for BrandingStapler feature |
|
GenericBrandingStaplee |
GenericBrandingStaplee |
Web |
Folder for GenericBrandingStaplee feature |
|
MeetingBrandingStaplee |
MeetingBrandingStaplee |
Web |
Folder for MeetingBrandingStaplee feature |
|
Custom_default.master |
GenericBrandingStaplee |
Web |
Master page for all site pages for non-meeting workspace sites. |
Common element file for all associated features. |
Custom_MWSDefault.master |
MeetingBrandingStaplee |
Web |
Master page for all site pages for meeting workspace sites. |
Common element file for all associated features. |
Feature.xml |
BrandingStapler |
Web App |
Feature specification file |
|
Elements.xml |
BrandingStapler |
Web App |
Element manifest. |
Defines Feature Site Template Associations for all Branding Staplee features. |
Feature.xml |
GenericBrandingStaplee |
Web |
Feature specification file |
Includes properties for use by Feature Receiver (MySiteCreate). |
Elements.xml |
GenericBrandingStaplee |
Web |
Element manifest. |
Defines installation location for master page and MySiteStaplee.xml. |
Feature.xml |
MeetingBrandingStaplee |
Web |
Feature specification file |
Includes properties for use by Feature Receiver (MySiteCreate). |
Elements.xml |
MeetingBrandingStaplee |
Web |
Element manifest. |
Defines installation location for master page and MySiteStaplee.xml. |
MySiteStaplee.xml |
GenericBrandingStaplee |
Web |
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. |
MySiteStaplee.xml |
MeetingBrandingStaplee |
Web |
Defines web part deployment instructions for the home page for all site definitions associated with the MeetingBrandingStaplee feature. |
No web part deployment instructions for these site definitions. |
MySiteCreate.cs |
{root} |
Farm |
Feature receiver for all branding staplee features. |
MySiteCreate.cs
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.
Public Methods
Name |
Type |
Purpose |
Notes |
FeatureActivated |
Void |
Sets the master page and theme for the site. Optionally sets the web part check flag for the MySiteCreate control. |
|
FeatureDeactivating |
Void |
Not implemented. See SPFeatureReceiver specification. |
|
FeatureInstalled |
Void |
Not implemented. See SPFeatureReceiver specification. |
|
FeatureUninstalling |
Void |
Not implemented. See SPFeatureReceiver specification. |
|
Private Methods
Name |
Type |
Purpose |
Notes |
UpdateLog |
Void |
Writes entries to the Windows Application event log. |
|
Custom_default.master
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
Custom_MWSdefault.master
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:
· 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
BrandingStapler Feature
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.
BrandingStaplee Features
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.
Custom Properties
Name |
Type |
Purpose |
Notes |
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 |
String |
Used by MySiteCreate feature receiver. Theme ID for the theme to be set on the site. |
|
OriginalMasterName |
String |
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.
BrandingUpdate
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.
File Manifest
File name |
Project Location |
Installation Scope |
Purpose |
Notes |
Program.cs |
{root} |
n/a |
Main application. |
|
BrandMap.cs |
{root} |
n/a |
Structure for storing the attributes of featuresitetemplate elements in the configuration file. |
|
webAppSectionhandler.cs |
{root} |
n/a |
Extension of IConfigurationSectionHandler for processing the custom <webAppSection/> configuration file element. |
|
masterMapSectionHandler.cs |
{root} |
n/a |
Extension of IConfigurationSectionHandler for processing the custom <mappingSection/> configuration file element. |
|
App.config |
{root} |
n/a |
Application configuration file. |
|
Configuration Settings
The Branding Update application uses the configuration settings specified in the table below. Settings in bold red are required.
Name |
Section |
Domain |
Purpose |
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 |
appSettings |
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 |
appSettings |
Boolean – true or false |
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 |
false |
filterPattern |
appSettings |
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. |
(none) |
FeatureSiteTemplateAssociation (One or more entries) |
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. |
(none) |
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
Program.cs is the main application class for the BrandingUpdate application.
Protected Methods
Name |
Type |
Purpose |
Notes |
Main |
Void |
Main method. |
|
Private Methods
Name |
Type |
Purpose |
Notes |
DoLog |
Void |
Writes entries to the Windows Application event log and/or the console. |
Log entries appear with a source of “BrandingUpdate” |
BrandingParts
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.
File Manifest
File name |
Project Location |
Installation Scope |
Purpose |
Notes |
PartCheck.cs |
{root} |
Farm |
Manipulates the web parts deployed on the default.aspx page for a given site. |
|
PartCheck.cs
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.
LAYOUTS Customizations
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
Code Snippets and Additional Comments
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.
BrandingStapler
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="https://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="https://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>
GenericBrandingStaplee
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:
<Feature
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="https://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementFile Location="custom_default.master"/>
<ElementFile Location="MySiteStaplee.xml"/>
<ElementManifest Location="element.xml"/>
</ElementManifests>
<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>
</Feature>
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 Receiver Assembly
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))
{
using (SPWeb curWeb = (SPWeb)properties.Feature.Parent)
{
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 +
". MasterUrl: " + curWeb.MasterUrl + "; CustomMasterUrl: " +
curWeb.CustomMasterUrl + "; Theme: " + curWeb.Theme + ".",
EventLogEntryType.Information);
*/
}
}
}
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)
{
try
{
System.Diagnostics.EventLog.WriteEntry("My Site Created Handler", Message, msgType);
}
catch
{
//ignore
}
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
/*
try
{
string originalMaster = string.Empty;
//look at the properties collection to get the new master page name
originalMaster = properties.Feature.Properties["OriginalMaster"].Value;
if ((originalMaster != null) && (originalMaster != string.Empty))
{
using (SPWeb curWeb = (SPWeb)properties.Feature.Parent)
{
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 revert branding for web " + curWeb.Title +
". MasterUrl: " + curWeb.MasterUrl + "; CustomMasterUrl: " +
curWeb.CustomMasterUrl + "; Theme: " + curWeb.Theme + ".",
EventLogEntryType.Information);
curWeb.MasterUrl = curMaster.Substring(0, ++masterPathEnd) + originalMaster;
curWeb.CustomMasterUrl = curCustomMaster.Substring(0, ++customMasterPathEnd) + originalMaster;
curWeb.ApplyTheme(ORIGINALTHEME);
curWeb.Update();
UpdateLog("Reverted branding for web " + curWeb.Title +
". MasterUrl: " + curWeb.MasterUrl + "; CustomMasterUrl: " +
curWeb.CustomMasterUrl + "; Theme: " + curWeb.Theme + ".",
EventLogEntryType.Information);
}
}
}
catch (Exception ex)
{
//try writing to event log
UpdateLog("Error in handler. Message: " + ex.Message + ". Full Exception: " + ex.ToString(), EventLogEntryType.Error);
}
*/
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
}
}
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
BrandingUpdate
The behavior of Program.cs is described in detail above. Here’s the code:
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Text;
using System.Xml;
using System.Configuration;
using Microsoft.SharePoint;
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();
try
{
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);
}
try
{
//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)
{
try
{
DoLog("Activating branding feature " + featureGuid.ToString() + " for web " + web.Url , EventLogEntryType.Information, 500);
if (brandingActivated) web.Features.Remove(featureGuid);
web.Features.Add(featureGuid);
}
catch (Exception ex)
{
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
}
}
catch (Exception e)
{
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))
writeEntry = false;
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>
<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"/>
</mappingSection>
</brandingSectionGroup>
<appSettings>
<add key="loglevel" value="0"/>
<add key="debug" value="true"/>
<add key="brandAll" value="true"/>
<add key="filterPattern" value=""/>
</appSettings>
</configuration>
Comments
Anonymous
November 13, 2007
If you're into MOSS customizations, check out Brett Geoffroy's MSDN blog. He's done a sweet job organizingAnonymous
November 17, 2007
We’re finally starting to tie a little bow around the branding effort for my customer’s MOSS intranetAnonymous
December 16, 2007
Ein sehr ausführliche Best-Practice Anleitung zum Theme SharePoint Anpassung mit vielen HintergrundinformationAnonymous
January 09, 2008
Eine sehr ausführliche Best-Practice Anleitung zum Theme SharePoint Anpassung mit vielen HintergrundinformationenAnonymous
January 18, 2008
Brett,Great posts on Feature Stapling! I was able to activate the branding features for a specific site collection, but it seems that the features are available (visible through Site Settings) to other web apps and their respective site collections as well.The following features I am trying to implement:PublishLayouts (staplee) - Scope: Site. Adds custom master page to file system & uploads to master pages galleryCustomBranding (staplee) - Scope: Site. Applies custom master page to site collectionCustomBrandingStaple (stapler) - Scope: WebApplication. Associates the above features (GUIDs) to a Team Site Def (STS#0) and to a specified Web Application.And installed & activated in this order using stsadm:CustomBrandingStaple feature to a specific web appPublishLayouts feature to a specific site collection w/in the above web appCustomBranding feature to a specific site collection w/in the above web appWe also tested with swapping step #1 to the end, but no luck.Would you have any insight as to what the issue may be or any suggestions to try?Anonymous
January 18, 2008
Re: vchan's issues:Sounds like you're stumbling on a couple of minor implementation issues and one big conceptual issue.Implementation issues:PublishLayouts - Set Hidden="TRUE" to prevent users from being able to activate/deactivate the feature manually. This is in the <Feature/> element - see the feature.xml for GenericBrandingStaplee above for an example.CustomBranding - This doesn't need to be a distinct feature that you install in the 12 hive. This should be a feature receiver that you install in the GAC and register as a feature receiver assembly for PublishLayouts. In this way, CustomBranding would function similarly to MySiteCreate.cs above. You might even be able to reuse the MySiteCreate.cs feature receiver and the element.xml & feature.xml structures for GenericBrandingStaplee after ripping out the snippets that update the theme. Conceptual issue:Keep in mind that by implementing Web application-scoped feature stapling for a Web-scoped feature, you're APPLYING THE FEATURE TO ALL NEW WEBS INSTANTIATED FROM THE SITE TEMPLATES SPECIFIED IN THE FEATURE STAPLER.So in your case, the intent captured by your feature stapler is, "apply this feature to all Team Sites on the Web applications where this feature stapler is activated". This seems to conflict with your objective of applying branding to a single site collection. If you just need to install and associate a new master page with a single site collection (or a handful of them), use SharePoint Designer. It's much faster and doesn't incur the labor costs of feature development and deployment.Anonymous
January 21, 2008
Thanks for clarifying the use of a feature stapler. One of the items we were trying to figure out was if a feature stapler can be used for a specific site collection, but it appears that it is not meant to be used that way. The use of a stapler allows for applying feature(s) to a specified scope (farm, web application, site, web) based on a site definition.Definitely agree on using SP Designer to apply a new master page is much easier than developing a feature for it. We were looking into feature stapling for other reasons in addition to applying a master page like applying a theme, and be able to apply updates to those themes/master pages seamlessly with the least effort to many site collections within a single web application (it looks like your BrandingUpdate program would be very helpful in this regard). The issue we came accross was to determine how to apply a feature stapler to site collection ABC using site def N, where there was another site collection XYZ also using site def N. It looks like there's no way for a feature stapler to tell the difference between the two since they are using the same site defintion. A custom site definition would need to be created that only site collection ABC uses, and the stapler associates with the custom site definition.Many thanks for all your help. This has pointed us in the right direction. Thanks again! :)Anonymous
January 22, 2008
Sounds like you've got the concepts down.Only addendum to my previous comment is based on the new information you provided that you want to apply these hidden customizations to multiple site collections (though not necessarily all SCs created from a given site template). This still rules out feature stapling as a good idea, but you might iwant nstall a web-scoped feature w/o stapling and activate it on the specific site collections to which it applies. That would save the considerable duplication of effort incurred by performing the same SPD customizations over and over...Anonymous
March 15, 2008
Hey where is the code? It would be easier to follow this blog if I could see the finished project. Thanks!