Sue Hernandez's SharePoint Blog

SharePoint and Related Stuff

MOSS – Global Custom Master Template

Objective

The Problem

Let’s say you want to force your entire organization to use your custom master page. You may wish to have certain contact information, legal disclaimers, utilities, or other functionality on your page that is standard company-wide. The problem we face is how to get this feature automatically activated on each and every site, of every type, when newly created.

The Solution

We will create 2 features – one to install the Custom Master Page into the Master Page Gallery of each site, as well as activate that page as the currently used master page; and one feature, called “Feature Stapling”, to have that first feature automatically activate.

References

The code in this article is taken in large part from other articles out there in blogLand – PLEASE at least skim over these articles to see the similarities and differences to this post. Also, there is a lot more explanation of the code itself (what it does) in Becky Bertram’s article.

Why is this Different from the first referenced article?

It isn’t all that much different from Becky Bertram’s code – let me please give credit where due. However, I found that in some circumstances the URL to the custom master page did not work; and most importantly, this only worked for Sub Sites already added to the Site Collection. I wanted to get a feature which activated on each and every NEW site and site collection. As for the existing sites, well, we’ll just use CorasWorks Design Migrator to push out the master page to those sites.

Alternatively, once the feature is installed, you can activate it on every existing site. If you don’t want to do this on every site, and just want to do this for a site collection down, then take Becky Bertram’s original code with the ProcessSubWebs method which pushes the changes to every child.

Finally, I wanted to write an article that not only had the code, but also the complete steps to create the solution package, which I find is not all that intuitive if you’re not used to it. For the second feature, we won’t even bother to create a solution – we’ll just use the elements and feature files in a folder and run an stsadm command line on them.

Danger

A word of warning – test your master page thoroughly before turning on these features. You could potentially break every page in your site if you’re not careful. Always keep the url /_layouts/settings.aspx in handy reach in case you get an error on the page and need to go back to turn the feature off. Sometimes you will get a “File Not Found” or other error if the custom URL path is off, and you can’t even navigate around the regular pages of the site.

Also, NEVER MESS WITH default.master IN THE 12 HIVE GLOBAL DIRECTORY.  Service packs will overwrite your changes.

FEATURE # 1 – Master Page with Feature Receiver

Install WSPBuilder
Create the Visual Studio Project
  • File à New à Project
  • WSPBuilder à WSPBuilder Project
  • Under the 12 folder, create a folder named Template
  • Under the Template folder, create a folder named Features
  • Under the Features folder, create a folder named YourCompanyCustomMaster
  • Under the YourCompanyCustomMaster folder, create a folder named MasterPages
  • Under the YourCompanyCustomMaster folder, create a file named feature.xml
  • Under the YourCompanyCustomMaster folder, create a file named elements.xml
  • Under the MasterPages folder, create a master page file named YourCompanyCustomMaster.master
  • Under the root of your project, create a code file named FeatureReceiver.cs.
  • Your project should now resemble the following:
     

    • File Structure

      File Structure

      Paste the code from below into the Feature.xml File, the Elements.xml file, and the FeatureReceiver.cs file.

    • Create a new Guid for your Feature ID (In Visual Studio, choose Tools à Create GUID) and paste it in to the Feature ID. Be sure to remove any { } braces from the guid, such that the end result resembles: Id=F647BBF6-5277-4118-9FA8-87D3E7C2059C“.  NOTE: The ID you create here is the ID you will use in the elements file in the second feature.
    • In order to get a Public Key for the Receiver Assembly, temporarily register this file into your GAC. I find the easiest way to do this is to open up C:\Windows\Assembly and just drag the file in. Then right-click on the file and choose Properties. You can copy the public key token right from the message box. The project has to have a key file associated with it to be registered into the GAC, but WSPBuilder already did that for you.
       

      Assembly Key

      Assembly Key

    • Compile your code. Right-click on the project and choose WSPBuilder à Build WSP. This builds the WSP file and places it in the root directory.
       

      Build WSP

      Build WSP

    • If you are developing on your Development SharePoint machine, or your Production Machine (although I would NOT recommend deploying this straight into production), you can use the WSPBuilder command “Deploy” (see above picture) to move the WSP file into the SharePoint environment. If you do not have WSPBuilder, or you are not on your SharePoint box, then copy the WSP file over to the SharePoint machine and run the following commands from STSADM (Typically located in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN)
          stsadm -o addsolution -filename <file_path>\YourCompanyCustomMaster.wsp, where <file_path> is the location of your wsp file
          stsadm -o deploysolution -name YourCompanyCustomMaster.wsp -allowgacdeployment
Feature File

<Feature Id=<a new guid> Title=YourCompany Custom Master Page
   Scope=Web Version=1.0.0.0 Hidden=FALSE DefaultResourceFile=core
   xmlns=http://schemas.microsoft.com/sharepoint/
   Description
=This Feature contains the YourCompany’s Custom Master Page
   ReceiverAssembly=YourCompanyCustomMaster, Version=1.0.0.0, Culture=neutral,     
      PublicKeyToken=<your public key>

   ReceiverClass=YourCompanyCustomMaster.FeatureReceiver>
   <ElementManifests>
      <ElementManifest Location=elements.xml />
      <ElementFile Location=MasterPages\YourCompanyCustomMaster.master />
   </ElementManifests>
</Feature>

Elements File

<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
   <Module Name=YourCompanyCustomMaster Url=_catalogs/masterpage Path=MasterPages
      RootWebOnly
=TRUE

      <File Url=YourCompanyCustomMaster.master Name=YourCompanyCustomMaster.master
         Type
=GhostableInLibrary >

         <Property Name=ContentType
            Value
=$Resources:cmscore,contenttype_masterpage_name; />

      </File>
   </Module>
</Elements>  

Feature Receiver

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
 
namespace YourCompanyCustomMaster
{
   public class FeatureReceiver : SPFeatureReceiver
   {
      // Code adapted from:
      // http://www.beckybertram.com/oldblog/index.php?p=33&more=1&c=1&tb=1&pb=1
 
      // Master Page Feature adapted from:
      // http://sharepointmagazine.net/technical/development/deploying-the-master-page
 
      const string defaultMasterUrl = “/_catalogs/masterpage/default.master”;
      const string customizedMasterUrl = “/_catalogs/masterpage/AKGCustomMaster.master”;
 
      public override void FeatureInstalled(SPFeatureReceiverProperties properties) { }
 
      public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { }
 
      public override void FeatureActivated(SPFeatureReceiverProperties properties)
      {
         try
         {
            using (SPWeb web = (SPWeb)properties.Feature.Parent)
            {
               string customURLtoUse = customizedMasterUrl;
 
               customURLtoUse = web.ServerRelativeUrl + customizedMasterUrl;
               customURLtoUse = customURLtoUse.Replace(“//”, “/”);
 
               // Store the old Master URL’s and Custom Master URL’s
               web.AllProperties[“OldMasterUrl”] = web.MasterUrl; 
               web.AllProperties[“OldCustomMasterUrl”] = web.CustomMasterUrl; 
 
               // Assign the Master URL to both properties
               web.MasterUrl = customURLtoUse;
               web.CustomMasterUrl = customURLtoUse;
 
               // Update the Web
               web.Update(); 
            } 
         }
         catch { } 
      }
 
      private void DeactivateWeb(SPWeb web)
      {
         try
         {
            if (web.AllProperties.ContainsKey(“OldMasterUrl”))
            {
               // Change the MasterURL and CustomMasterURL back to
               // old versions, if the property exists
               string oldMasterUrl = web.AllProperties[“OldMasterUrl”].ToString();
               try
               { 
                  bool fileExists = web.GetFile(oldMasterUrl).Exists;
                  web.MasterUrl = oldMasterUrl;
               }
               catch (ArgumentException)
               {
                  web.MasterUrl = defaultMasterUrl;
               }
 
               string oldCustomUrl = web.AllProperties[“OldCustomMasterUrl”].ToString();
               try
               {
                  bool fileExists = web.GetFile(oldCustomUrl).Exists;
                  web.CustomMasterUrl = web.AllProperties[“OldCustomMasterUrl”].ToString();
               }
               catch (ArgumentException)
               {
                  web.CustomMasterUrl = defaultMasterUrl;
               }
 
               // Remove the custom properties
               web.AllProperties.Remove(“OldMasterUrl”);
               web.AllProperties.Remove(“OldCustomMasterUrl”); 
            } 
            else
            {
               // Otherwise, change back to default
               web.MasterUrl = defaultMasterUrl;
               web.CustomMasterUrl = defaultMasterUrl;
            } 
         }
         catch
         {
            try
            {
               web.MasterUrl = defaultMasterUrl;
               web.CustomMasterUrl = defaultMasterUrl;
            } 
            catch { }
         }
      }
 
      public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
      {
         try
         {
            using (SPWeb web = (SPWeb)properties.Feature.Parent)
            {
               DeactivateWeb(web);
               web.Update(); 
 
               string customURLtoUse = web.ServerRelativeUrl + customizedMasterUrl;
               customURLtoUse = customURLtoUse.Replace(“//”, “/”);
 
               // Delete the file manually from the master page gallery
               if (web.MasterUrl != customURLtoUse)
               {
                  try
                  {
                     bool fileExists = web.GetFile(customURLtoUse).Exists;
                     SPFile file = web.GetFile(customURLtoUse);
                     SPFolder masterPageGallery = file.ParentFolder; 
  
                     // Unfortunately, there seems to be an issue in SharePoint where 
                     // you can not delete a master page that was installed by a
                     // feature, even if no one is referencing that master page.
                     // You have to move it to another folder then delete that folder. 
                     SPFolder temp = masterPageGallery.SubFolders.Add(“Temp”);
                     file.MoveTo(temp.Url + “/” + file.Name);
                     temp.Delete(); 
                  }
                  catch (ArgumentException)
                  {
                     return;
                  } 
               } 
            } 
         }
         catch { } 
      }
   }
}
 

FEATURE # 2 – Stapling the first feature to every new web

What is Feature Stapling?

A basic explanation of feature stapling is that it is used to attach one or more features to a SharePoint site definition without modifying an existing site definition or creating a new site definition. For a more detailed explanation I recommend reading Chris Jonson’s blog on “Feature Stapling in WSS V3”.

Create the files in the Feature folder

For this exercise, we will not even bother creating a solution through Visual Studio. Let’s just put the simple files directly into a Feature Folder on the 12 hive and add the solution.

  • Navigate to the 12 hive, usually at C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\FEATURES.
  • Create a new folder, and name it YourCompanyMasterStapling.
  • In the YourCompanyMasterStapling folder, create 2 files – elements.xml and feature.xml.
  • Paste the code from below into the Feature.xml File, and the Elements.xml file.
  • Create a new Guid for your Feature ID (In Visual Studio, choose Tools à Create GUID) and paste it in to the Feature ID. Be sure to remove any { } braces from the guid, such that the end result resembles: Id=F647BBF6-5277-4118-9FA8-87D3E7C2059D“.  NOTE: The ID you create here is NOT the ID you’re using in the Elements file – rather use the Feature ID from the FIRST feature.
  • Run the following commands from STSADM (Typically located in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN)
        stsadm -o installfeature -filename YourCompanyMasterStapling\feature.xml
        stsadm -o activatefeature -filename YourCompanyMasterStapling\feature.xml
  • NOTE: You may receive a message stating that the feature has already been activated to the farm.
Feature File

<?xml version=1.0 encoding=utf-8 ?>
<Feature Id=<a new guid>
   Title=YourCompany Custom Master Page Feature Stapling
   Description=YourCompany Custom Master Page Feature Stapling – activates the YourCompany
      Master Page feature for all new sites

   Version=1.0.0.0
   Scope=Farm
   Hidden=TRUE
   xmlns=http://schemas.microsoft.com/sharepoint/
   <ElementManifests>
      <ElementManifest Location=elements.xml/>
   </ElementManifests
</Feature>

Elements File

<Elements xmlns=http://schemas.microsoft.com/sharepoint/>
    <FeatureSiteTemplateAssociation Id=<the guid from feature 1> TemplateName=GLOBAL /> ** Read update below
</Elements>

UPDATE:  Do not use GLOBAL – there is a bug:   if you create a meeting workspace from a recurring Calendar event, if you try to staple a feature to the new meeting workspace site, it breaks the scripting on the page such that you cannot switch dates.  Instead staple to all templates EXCEPT Meeting Workspaces:

<elements xmlns="http://schemas.microsoft.com/sharepoint/">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="STS#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="STS#1">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="STS#2">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="WIKI#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="BLOG#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="BDR#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="EAWF#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="OFFILE#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="OFFILE#1">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="PWA#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="PWS#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPS#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSMSITE#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSTOC#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSTOPIC#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSNEWS#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSNHOME#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSSITES#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSBWEB#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSCOMMU#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSREPORTCENTER#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SPSPORTAL#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="SRCHCEN#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="PROFILES#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="CMSPUBLISHING#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="BLANKINTERNET#0">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="BLANKINTERNET#1">
<featuresitetemplateassociation id="F647BBF6-5277-4118-9FA8-87D3E7C2059C" templatename="BLANKINTERNET#2">
</elements>

 

Testing

Make sure you test your creation thoroughly. Test the following areas:

Root Web Site

The Root Web of the Root site – typically the portal site (the one without “sites” in the url).

Site Collections

Create a new Site Collection and check its Root Web.

Sub Sites

Create and test each type of Sub Site – create a Team Site, a Blog Site, a Meeting Workspace, a Publishing Portal, and a Document Center Portal at the very least

Advertisements

One response to “MOSS – Global Custom Master Template

  1. Neil September 19, 2009 at 4:58 am

    Your meeting workspace fails to work because its missing

    From the master page, add it to the master page before
    <HTML dir="” runat=”server” xmlns:o=”urn:schemas-microsoft-com:office:office”>

    also add

    To the masterpage body as:

    That works.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: