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=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>


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=http://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 (9)

  1. If you’re into MOSS customizations, check out Brett Geoffroy’s MSDN blog. He’s done a sweet job organizing

  2. We’re finally starting to tie a little bow around the branding effort for my customer’s MOSS intranet

  3. Ein sehr ausführliche Best-Practice Anleitung zum Theme SharePoint Anpassung mit vielen Hintergrundinformation

  4. Eine sehr ausführliche Best-Practice Anleitung zum Theme SharePoint Anpassung mit vielen Hintergrundinformationen

  5. vchan says:

    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 gallery

    CustomBranding  (staplee) – Scope: Site.  Applies custom master page to site collection

    CustomBrandingStaple (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 app

    PublishLayouts feature to a specific site collection w/in the above web app

    CustomBranding feature to a specific site collection w/in the above web app

    We 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?

  6. bgeoffro says:

    Re: vchan’s issues:

    Sounds like you’re stumbling on a couple of minor implementation issues and one big conceptual issue.

    Implementation issues:

    1. 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.
    2. 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.

  7. vchan says:

    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! 🙂

  8. bgeoffro says:

    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…

  9. inasolutions says:

    Hey where is the code? It would be easier to follow this blog if I could see the finished project. Thanks!