In a previous post, I described a feature that would take install and retract modifications to SPTHEMES.XML. Peter Seale suggested providing a method to reapply the changes without a deactivate/activate cycle, specifically for new servers added to a farm. It should be as simple as providing a user interface to call FeatureThemesJob.InstallThemes, but that presents a bit of a problem: InstallThemes expects the name of the themes file, which I declare in the feature receiver. So before we can work on a reapplication interface, let’s move that file name to a more accessible location.
The Revised Feature
A better way to store the theme file name would be as a Feature Property:
<Feature
Id="E2F8D046-607D-4BB6-93CC-2C04CF04099E"
Title="SPHOLS Themes"
Description="Installs SPHOLS and SPHOLSX themes on farm."
Version="1.0.0.0"
Scope="Farm"
ReceiverAssembly="MyBranding, ..."
ReceiverClass="MyBranding.MyThemesFeatureReceiver"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementFile Location="SPTHEMES.XML" />
</ElementManifests>
<Properties>
<Property Key="Solutionizing:ThemesFile" Value="SPTHEMES.XML" />
</Properties>
</Feature>
And we can remove references to THEMES_FILE from our receiver:
namespace MyBranding {
public class MyThemesFeatureReceiver : SPFeatureReceiver {
public override void FeatureActivated(SPFeatureReceiverProperties properties) {
if (properties == null)
throw new ArgumentNullException("properties");
FeatureThemesJob.InstallThemes(properties.Definition);
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
if (properties == null)
throw new ArgumentNullException("properties");
FeatureThemesJob.DeleteThemes(properties.Definition);
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties) { }
public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { }
}
}
Note that this code is now completely feature-agnostic, reusable for any themes feature.
FeatureThemesJob
Other than purging the logic to persist _themesFile, which I’ll leave as an exercise for the reader, we just need to update Execute to use our new feature property:
private const string PROP_THEMES_FILE = "Solutionizing:ThemesFile"
private const string SPTHEMES_PATH = @"TEMPLATE\LAYOUTS\1033\SPTHEMES.XML";
public override void Execute(Guid targetInstanceId) {
SPFeatureDefinition fDef = Farm.FeatureDefinitions[_featureID];
if (fDef != null) {
SPFeatureProperty themesFileProp = fDef.Properties[PROP_THEMES_FILE];
if(themesFileProp == null)
throw new SPException(string.Format("Feature '{0}' is missing property '{1}'.", fDef.DisplayName, PROP_THEMES_FILE));
DoMerge(SPUtility.GetGenericSetupPath(SPTHEMES_PATH), Path.Combine(fDef.RootDirectory, themesFileProp.Value));
}
}
But since we’re in a refactoring mood, we might as well extract the code to retrieve the themes file path:
internal const string PROP_THEMES_FILE = "Solutionizing:ThemesFile"
private const string ERR_FEATURE_NOT_FOUND = "Feature '{0}' not found in farm.";
private const string ERR_MISSING_PROPERTY = "Feature '{0}' is missing property '{1}'.";
internal string ThemesFilePath {
get {
SPFeatureDefinition fDef = Farm.FeatureDefinitions[_featureID];
if (fDef == null)
throw new SPException(string.Format(ERR_FEATURE_NOT_FOUND, _featureID));
SPFeatureProperty prop = fDef.Properties[PROP_THEMES_FILE];
if (prop == null)
throw new SPException(string.Format(ERR_MISSING_PROPERTY, fDef.DisplayName, PROP_THEMES_FILE));
return Path.Combine(fDef.RootDirectory, prop.Value);
}
}
Which makes Execute rather elegant:
private const string SPTHEMES_PATH = @"TEMPLATE\LAYOUTS\1033\SPTHEMES.XML";
public override void Execute(Guid targetInstanceId) {
DoMerge(SPUtility.GetGenericSetupPath(SPTHEMES_PATH), ThemesFilePath);
}
Now everything we need for the timer job is available from the feature definition. The next step is to build an interface to run the job on demand. Stay tuned!

