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>