Reapplying changes from a customized file originally deployed through features

In most if not all scenarios, you will be deploying your files (Page Layouts, Master pages, CSS, JavaScript, etc.) through a Feature.  If you leave them as is, they are called “Uncustomized” and will reference the files on disk.  Any updates to the files – which you would normally do through a solution upgrade – would automatically be available live (it takes an application pool recycles, which the upgrade solution does) to all site collections that has that feature activated.

 

In some scenarios, especially in larger IT groups where creating the solution package for “Containers” (artefacts between Code (IT) and Content (Business) – but it’s really closer to the Business) may take some time, updates may be done online to one of those files to fix a problem.  These updates could be done through the Web UI or through SharePoint Designer, albeit the later is rarely allowed in production environments. 

 

Change Management is still recommended and testing should be done prior to updating any files in production – this is just a different deployment vehicle – one that is already available if you have enough permissions on the web site.

 

The problem is that once this update occurs, the file becomes “customized” – this is well known.  If you were to go in SharePoint Designer, you could see a blue icon next to the file (to be honest, with SP2, I had that on any files but Page Layouts) which tells you it’s customized.  You can right-click and “revert to definition” through SharePoint Designer, but as mentioned earlier, SPD is probably not available on the production environment.  What happens the next time you update your Feature is that the file on disk will contain your updates, but the site where customization occurred will not be updated – all of this is well known and was as such with SharePoint 2003.

 

If you’d like to force your Feature to be the ‘master’ and remove any customizations, you can use a Feature Receiver that will, on activation, validate each files in the Feature manifest and use the SPFile.RevertToContentStream() method.  This is essentially what the option in SharePoint Designer calls.

 

    1: using System;
    2: using System.Collections.Generic;
    3: using System.Xml;
    4: using System.Text;
    5: using System.Globalization;
    6: using Microsoft.SharePoint;
    7: using Microsoft.SharePoint.Administration;
    8: using Microsoft.SharePoint.Utilities;
    9:  
   10: namespace MB.SharePoint.FeatureReceivers
   11: {
   12:     class RevertFilesToDefinitionFeatureReceiver : SPFeatureReceiver
   13:     {
   14:         public override void FeatureActivated(SPFeatureReceiverProperties properties)
   15:         {
   16:             try
   17:             {
   18:                 SPWeb web = ((SPSite)properties.Feature.Parent).RootWeb;
   19:                 CultureInfo ci = System.Globalization.CultureInfo.InvariantCulture;
   20:                 SPElementDefinitionCollection elementFiles = properties.Definition.GetElementDefinitions(ci);
   21:  
   22:                 foreach (SPElementDefinition elementFile in elementFiles)
   23:                 {
   24:                     try
   25:                     {
   26:                         XmlNode content = elementFile.XmlDefinition;
   27:                         XmlDocument contentDoc = new XmlDocument();
   28:                         contentDoc.LoadXml(content.OuterXml);
   29:  
   30:                         XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(contentDoc.NameTable);
   31:                         xmlNamespaceManager.AddNamespace("ns", "http://schemas.microsoft.com/sharepoint/");
   32:  
   33:  
   34:  
   35:                         //check for an elementfile
   36:                         if (content.Name == "Module")
   37:                         {
   38:                             XmlNode xmlModule = elementFile.XmlDefinition;
   39:                             SPFolder spFolder = web.GetFolder("/" + xmlModule.Attributes["Url"].Value);
   40:                             SPDocumentLibrary spDocumentLibrary = (SPDocumentLibrary)web.Lists[spFolder.ContainingDocumentLibrary];
   41:  
   42:                             foreach (XmlNode xmlFile in xmlModule.SelectNodes("ns:File", xmlNamespaceManager))
   43:                             {
   44:                                 SPFile spFile = web.GetFile(String.Concat(xmlModule.Attributes["Url"].Value, "/", xmlFile.Attributes["Url"].Value));
   45:                                 if (spFile != null && spFile.CustomizedPageStatus == SPCustomizedPageStatus.Customized)
   46:                                     spFile.RevertContentStream();
   47:                             }
   48:                         }
   49:                     }
   50:                     catch (Exception ex)
   51:                     {
   52:                         //absorbing error (so that other files can try), you'll want to log here
   53:                     }
   54:                 }
   55:             }
   56:             catch (Exception ex)
   57:             {
   58:                 //absorbing error, you'll want to log here
   59:             }
   60:         }
   61:  
   62:         public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
   63:         {
   64:         }
   65:  
   66:         public override void FeatureInstalled(SPFeatureReceiverProperties properties)
   67:         {
   68:         }
   69:  
   70:         public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
   71:         {
   72:         }
   73:  
   74:     }
   75: }

 

Simply package this in your libraries and add the Feature Receiver Assembly & Class in your original feature definition.  You can download the latest version of my public code solution here :

Disclaimer : Note, this code is provided *AS IS* and no support is available from Microsoft.  While I will do what I can – when I can – to help you find a problem in the code, this code should be tested thoroughly in your environment before sending in production.

 

Sincerely,

 

Maxime