Finally back in the blogging saddle, keep stacking ideas and post embryos but never time to finish them. This post is about how you create custom Health Rules for SharePoint 2010 and this health rule is of particular interest since it checks for debug build assemblies in all installed farm solutions.

Health Rules

Health Rules in SharePoint 2010 is a great way to make administrators aware of possible problems and issues with your farm; such as running out of disk space, living up to best practices etc. SharePoint 2010 comes with a bunch of out-of-the-box rules (of which some are good and some not that good). The best thing about the Health Rules is that you can write your own to fully satisfy your needs and live up to your governance standards.

MSDN contains a guide on how to create these custom Health Rules in the “Developing and Deploying a Custom Health Rule” articles. Even though it covers the topic good it is not done using the Visual Studio 2010 SharePoint Developer Tools. This article actually uses MakeCab.exe and DDF files - like we did five years ago!

I’m going to show you how to do it in a much easier way, but first something about the rule…

Assemblies in Debug Mode

This Health Rule that we are going to build will check all existing farm solutions for assemblies built in debug mode. Having assemblies built in debug mode in your production farm can severely hurt performance and even lock your processes down (think Assert dialogs on the server). I’ve seen debug builds on several production farms throughout the years (I have even put them there myself). It’s kinda easy to miss to switch from Debug to Release when deploying a solution in a rush.

Building the Health Rule

So let’s get to action and build our Health Rule. First of all you need Visual Studio 2010 and SharePoint Foundation 2010 or higher. Create a new Empty SharePoint project and select to deploy it as a farm solution. The Health Rule is implemented as a class, so go ahead and add a new class file to the project.

First of all you need to derive the class from the SPHealthAnalysisRule:

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

public class DebugAssemblies : SPHealthAnalysisRule

Then you need to override a couple of methods and properties.

The Summary property is the summary of the rule and is shown in Rule Definition (red box in image below):

public override string Summary {
    get {
        return "One or more features contains assemblies built in Debug mode";
    }
}

Performance Rules

The AutomaticExecutionParameters property defines the schedule and scope of the rule as well as if it should repair the “problem” automatically:

public override SPHealthAnalysisRuleAutomaticExecutionParameters AutomaticExecutionParameters {
    get {
        SPHealthAnalysisRuleAutomaticExecutionParameters parameters = 
            new SPHealthAnalysisRuleAutomaticExecutionParameters { 
                Schedule = SPHealthCheckSchedule.Daily, 
                Scope = SPHealthCheckScope.Any, 
                RepairAutomatically = false, 
                ServiceType = typeof(SPTimerService) };
        return parameters;
    }
}

This rule is set to check for problems Daily and the checks should be done on any server where the Timer Service service is hosted. And no, we’re not repairing this problem automatically

The Category and ErrorLevel properties tells what category the rule ends up in and what severity a failed check causes:

public override SPHealthCategory Category {
    get {
        return SPHealthCategory.Performance;
    }
}
public override SPHealthCheckErrorLevel ErrorLevel {
    get {
        return SPHealthCheckErrorLevel.Warning;
    }
}

For this rule we place it in the Performance category and only issue an warning when the check fails.

The actual Health Rule check is done in the Check method, which we will look at in a second. First we define a custom class to store information about debug assemblies and a private property which contains all debug assembly information like this:

List m_debuAssemblies = new List();
class DebugAssembly{
    public string AssemblyName;
    public string SolutionName;
    public Guid SolutionId;
}

Now it’s time to attack the Check method, which is the method that is executed when the rule is invoked. Here’s the code for that one:

public override SPHealthCheckStatus Check() {
    foreach (SPSolution solution in SPFarm.Local.Solutions) {
        // 1) Save file in temp
                
        string tmp = System.IO.Path.GetTempPath();
        Guid guid = Guid.NewGuid();
        tmp = String.Format(@"{0}{1:D}\n", tmp, guid);
        System.IO.Directory.CreateDirectory(tmp);

        solution.SolutionFile.SaveAs(tmp + solution.Name);

        // 2) extract assemblies
        CabLib.Extract extractor = new CabLib.Extract();
        extractor.ExtractFile(tmp + solution.Name, tmp);
                
        // 3) Open manifest
        NameTable nt = new NameTable();
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt);
        nsmgr.AddNamespace("sp", "http://schemas.microsoft.com/sharepoint/");
        XmlDocument manifest = new XmlDocument();
        manifest.Load(tmp + "manifest.xml");

        foreach(XmlNode assembly in 
            manifest.SelectNodes("//sp:Assemblies/sp:Assembly", nsmgr)){

            XmlElement elm = assembly as XmlElement;
            // 4) Inspect assembly
            if (IsDebugBuild(tmp + elm.GetAttribute("Location"))) {
                m_debuAssemblies.Add(new DebugAssembly() { 
                    AssemblyName = elm.GetAttribute("Location"),
                    SolutionId = solution.Id,
                    SolutionName = solution.Name
                });
            }
        }
    }
    // 4) Return
    if (m_debuAssemblies.Count == 0) {
        return SPHealthCheckStatus.Passed;
    }
    else {
        return SPHealthCheckStatus.Failed;
    }
}

What this method actually does it that it retrieves all farm solutions, copies the WSP to the temp directory and then uses CabLib.dll and extract all the files. Once the files are extracted it looks in the solution manifest for all assemblies and run the IsDebugBuild method to check if it is a debug build. All debug build assemblies are stored in the local property. Finally if it finds any debug assemblies it returns a failure message and if not it says that the rule was ok.

To be able to compile this code you need to add a reference to Cablib.dll (x64) and also make sure that you include that assembly in your SharePoint Solution Package (Advanced tab in the Package editor)

The IsDebugBuild method is a derivative of Scott Hanslemans old blog post “How to Programmatically Detect if an Assembly is Compiled in Debug or Release mode”. Where I only changed it to return true or false.

If the rule fails we need someway to inform the administrators. First we override the Remedy property which informs the administrators on how to act on this problem:

public override string Remedy {
    get {
        return "Redeploy features built in Debug mode as Release builds";
    }
}

Then we also need to include some detailed explanation on what solutions and assemblies that causes this rule to fail. This is done using the Explanation property:

public override string Explanation {
    get {
        StringBuilder sb = new StringBuilder();
        if (m_debuAssemblies.Count > 0) {
            sb.Append("The following assemblies are built in debug mode:\n");
            foreach (var assembly in m_debuAssemblies) {
                sb.AppendFormat("Assembly {0} in solution {1}/{2},\n",
                    assembly.AssemblyName, assembly.SolutionName, assembly.SolutionId);

            }
            return sb.ToString();
        }
        else {
            return "No assemblies built in debug mode could be detected.";
        }
    }
}

This property retrieves all assemblies from the local property and writes a nice message for the administrators.

Result of a failed Health Check rule

Build the farm feature and register the rule

Once we’re finished with the rule we need to deploy it to our farm and register the rule with the Health Analyzer. Add a feature to your project and set the scope to farm. By default all farm features are Activated on Default - I prefer to change it to not activate on default and instead manually enable the feature in Central Admin. Add a feature receiver to your newly added feature and copy the FeatureActivated and FeatureDeactivated code from the MSDN article.

Deploy and activate

All is now ready for compilation, packaging and deployment. Do it! Then browse to Central Administration > System Settings > Manage Farm features and activate your feature:

Activate!

Your rule(s) should then appear in Central Administration > Monitoring > Review Rule definitions and you can (if you can’t wait for it to automatically run) click on the rule and choose Run Now. Failed checks will appear in Monitoring > Review Problems and Solutions.

Note: To debug your rule you need to attach the debugger to the OWSTimer.exe process - which is the timer service.

Download

If you think this rule is something that could be useful (and you should do), then download it here.

Summary

You’ve now seen how easy it is to implement and deploy a custom Health Rule. You should implement Health Rules that complies with your governance plans to make it easier and faster for the administrators and the governance-police.