Health analyzer rule registration requires that the {0} assembly be registered in the global assembly cache.

When developing custom health rules, you might bump into this error:

Health analyzer rule registration requires that the {0} assembly be registered in the global assembly cache.

Odd, because when you check the GAC you see that your .dll gets registered correctly.

The only solution step I’ve found so far is the following:

  1. Retract your solution fully, either by using Visual Studio, the Central Administration or PowerShell
  2. Change the site URL of your project to another, valid URL (it has to differ from the last deployment)
    change site url project
  3. Deploy

This process can be quite cumbersome to do every time you wish to redeploy. There’s a workaround though, you can always use CKSDev‘s Quick Deploy commands which don’t seem to be affected by this weird behaviour.

Advertisements

Deep dive into SharePoint Timer Jobs and Health Rules

In a previous post, I raised an issue regarding a SQL stored procedure not being created upon the creation of the ASP.NET Session State Service Application. This stored procedure takes care of the deletion of the expired sessions. When we check our timer jobs, we also see we have a definition which is called “State Service Delete Expired Sessions”. This obviously is connected to the regular State Service, but let’s take a closer look on how this timer job works.

Before we go deeper into the fundamentals, you’ll need a tool, which is free, called ILSpy. It’s a .NET decompiler, so if you already have one, you’re good to go. When you’ve downloaded ILSpy, extract it somewhere and you can fire it up. The main screen looks like this:

ilspy main window

Select any open libraries and remove them. This gives us a clean start:

ilspy remove standard libs

Next, we’re gonna look at the timer job itself. We need to know in which library it’s located so we can decompile it with ILSpy. Leave ILSpy open and fire up the SharePoint 2010 Management Shell. Type in the following command:

Get-SPTimerJob | ?{$_.Title -eq "State Service Delete Expired Sessions"} | Select TypeName

This will give you the following output:

Microsoft.Office.Server.Administration.StateServiceExpiredSessionJobDefinition

Great! Let’s go back to ILSpy and open the Microsoft.Office.Server library from the GAC:

ilspy open from gac

Next select the Microsoft.Office.Server library and click “Open”:

ilspy open microsoft office server from gac

Expand the library to view all the namespaces. Expand the Administration namespace and scroll down until you find the StateServiceExpiredSessionJobDefinition class. When you decompile this class, you can expand any of the methods and properties. We’re interested in the Execute method, cause that’s what fires when the timer job is executed:

ilspy decompile class

So all it does is call the static DeleteExpiredSessions(current) method on the StateSqlSession class. When we go deeper and click on the method, we can see what happens behind the scene:

ilspy click on method

Seems that all that happens is call a SQL Stored Procedure “proc_DeleteExpiredItems” from this Timer Job:

ilspy deep into method

Deep diving into Timer Jobs can give you a lot of insight into the engine of SharePoint. The following PowerShell command lists all the Titles and TypeNames from the registered Timer Job Definitions so you can go even deeper:

Get-SPTimerJob | Select Title, TypeName | fl

You’ll notice that health rule jobs are registered differently. Getting their TypeName is also a bit different. You have to use the local SPHealthRulesList:

[Microsoft.SharePoint.Administration.Health.SPHealthRulesList]::Local.Items | Select Title, @{ Label="HealthRuleType"; Expression= { $_["HealthRuleType"] } } | fl

Automate your SharePoint farm maintenance using Health Rules

By default, SharePoint comes with a broad set of health rules and remedies which check your farm periodically. These, however, don’t always cover specific situations. For instance, your governance document may state that there may only be 10 site collections in one content database. When there are multiple administrators, it’s good practice to implement some sort of extra check, and if possible fix the error. If a newly created content database doesn’t have any limitations set, it’s good to trigger a warning or error for all of your administrators to read.

Start out by creating an empty SharePoint project. I name the solution below “CustomHealthRules”. Deploy the solution as a farm solution.

new sharepoint project customhealthrules

After the empty project is loaded, add a new class named “CheckContentdbs.cs” and inherit SPHealthAnalysisRule.

customhealthrules add new class checkcontentdbs

using Microsoft.SharePoint.Administration.Health;
using Microsoft.SharePoint.Administration;
using System.Collections.Generic;
using System.Text;

namespace CustomHealthRules
{
    class CheckContentdbs : SPHealthAnalysisRule
    {
    }
}

Because we derive from the SPHealthAnalysisRule, we override a few properties to identify our custom rule.

class CheckContentdbs : SPHealthAnalysisRule
{
    public override SPHealthCategory Category
    {
        // Categorise the health rule under configuration in the health rules definition list
        get { return SPHealthCategory.Configuration; }
    }
    public override SPHealthCheckErrorLevel ErrorLevel
    {
        // Define the health as error when the check fails
        get { return SPHealthCheckErrorLevel.Error; }
    }
    public override string Summary
    {
        // Summary of the rule
        get { return "Contentdatabases are misconfigured"; }
    }
}

Before we can make the rule pass or fail, we have to check something. Override the Check() method and identify any problems. Keep track of faulty contentdatabases in a list.

List<SPContentDatabase> faultyContentDbs = new List<SPContentDatabase>();

public override SPHealthCheckStatus Check()
{
    // loop through all web applications
    SPServiceCollection services = SPFarm.Local.Services;
    foreach (SPService curService in services)
    {
        if (curService is SPWebService)
        {
            SPWebService webService = (SPWebService)curService;
            foreach (SPWebApplication webApp in webService.WebApplications)
            {
                // for every web application, check content databases
                foreach (SPContentDatabase contentDB in webApp.ContentDatabases)
                {
                    if (contentDB.MaximumSiteCount != 10)
                    {
                        faultyContentDbs.Add(contentDB);
                    }
                }
            }
        }
    }
    if (faultyContentDbs.Count == 0)
        return SPHealthCheckStatus.Passed;
    else
        return SPHealthCheckStatus.Failed;
}

Whenever your check fails, you must give a remedy to your administrators.

public override string Remedy
{
    get { return "Set the warning and maximum site collection limit on the content database"; }
}

public override string Explanation
{
    get
    {
        if (faultyContentDbs.Count > 0)
        {
            StringBuilder sBuild = new StringBuilder();
            sBuild.Append("The following databases are incosistent:\n");
            foreach (SPContentDatabase contentDB in faultyContentDbs)
            {
                sBuild.AppendFormat("db {0} on webapp {1}\n", contentDB.Name, contentDB.WebApplication.Name);
            }
            return sBuild.ToString();
        }
        else
            return "No faulty contentdatabases have been found.";
    }
}

Finally, add some default configuration settings. You may also choose to manually configure your health rule. In that case you don’t need to override this property.

public override SPHealthAnalysisRuleAutomaticExecutionParameters AutomaticExecutionParameters
{
    get
    {
        SPHealthAnalysisRuleAutomaticExecutionParameters retval = new SPHealthAnalysisRuleAutomaticExecutionParameters();
        retval.Schedule = SPHealthCheckSchedule.Hourly;
        retval.Scope = SPHealthCheckScope.All;
        retval.ServiceType = typeof(SPTimerService);
        retval.RepairAutomatically = false;
        return retval;
    }
}

Our CheckContentdbs class file is complete. All that remains is to register the rule inside a farm feature. Add a new feature to your project, name it and add a description. You’ll see that you can’t just select your codefile in the project. Registering a health rule is slightly different than your average web part. Add a new feature EventReceiver and add the code you find on MSDN. Make sure you Resolve unadded Usings.

customhealthrules farm feature

customhealthrules feature event receiver

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    Assembly a = Assembly.GetExecutingAssembly();
    IDictionary<Type, Exception> exceptions = SPHealthAnalyzer.RegisterRules(a);

    if (exceptions != null)
    {
        string logEntry = a.FullName;
        if (exceptions.Count == 0)
        {
            logEntry += " All rules were registered.";
        }
        else
        {
            foreach (KeyValuePair<Type, Exception> pair in exceptions)
            {
                logEntry += string.Format(" Registration failed for type {0}. {1}",
                                            pair.Key, pair.Value.Message);
            }
        }
        System.Diagnostics.Trace.WriteLine(logEntry);
    }
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    Assembly a = Assembly.GetExecutingAssembly();
    IDictionary<Type, Exception> exceptions = SPHealthAnalyzer.UnregisterRules(a);

    if (exceptions != null)
    {
        string logEntry = a.FullName;
        if (exceptions.Count == 0)
        {
            logEntry += " All rules were unregistered.";
        }
        else
        {
            foreach (KeyValuePair<Type, Exception> pair in exceptions)
            {
                logEntry += string.Format(" Unregistration failed for type {0}. {1}",
                                            pair.Key, pair.Value.Message);
            }
        }
        System.Diagnostics.Trace.WriteLine(logEntry);
    }
}

Deploy the solution. When you activate the farm feature in your central admin, you can check the health rules definition list (under Monitoring) and you’ll see your custom health rule is successfuly deployed!

customhealthrules registered rule

After the rule is ran, you’ll get an error in your health reports where you can check for faulty content databases.

customhealthrules rule triggered error

So what’s next? If you wish to debug your solution, remember to attach your debugger to the OWSTIMER.EXE. Health rules are timer jobs after all.

Also check the SPRepairableHealthAnalysisRule class. If you derive from this one, you can override the Repair() method where you can implement default “fixes” for your health rule.

You can also implement some Health Rule configuration for your Check() method. Think of propertybags for instance. You can put your preferred default values in the feature activated method.