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.

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.

Using PowerShell in your C# code

SharePoint standard comes with a lot of cmdlets ready for use. Sometimes it might be handy to be able to use those cmdlets in your serverside code. For instance, using Merge-SPLogFile to combine the logs from all the servers into one single file.

To start using PowerShell inside your C# code, add a reference to the System.Management.Automation.dll library.

add reference

You can find it under: C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0

At the using section of your code page add:

using System.Management.Automation;
using System.Management.Automation.Runspaces;

Now it’s time to start creating our PowerShell objects. It’s important to load the Microsoft.SharePoint.PowerShell dll, otherwise we’re unable to execute any SharePoint cmdlets. Do so by using the following codeblock:

InitialSessionState iss = InitialSessionState.CreateDefault();
PSSnapInException warning;
iss.ImportPSSnapIn("Microsoft.SharePoint.PowerShell", out warning);

After we created our sessionstate, we need a runspace where we can execute pipelines:

Runspace runspace = RunspaceFactory.CreateRunspace(iss);
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();

The next step is building the command to be executed by PowerShell. We have to specify a path for storing the merged log file. By using the temporary folder, and a guid for the name of the file, we can assume that there will be no conflicts. If the file already exists, the -OverWrite parameter will make overwrite the existing merged log. It’s always possible to add more parameters to the Merge-SPLogFile cmdlet.

string savePath = String.Format("{0}{1}.log", System.IO.Path.GetTempPath(), System.Guid.NewGuid().ToString());
string command = String.Format("Merge-SPLogFile -OverWrite -Path '{0}'", savePath);

By adding our script, and invoking our pipeline, we let PowerShell do its thing.

pipeline.Commands.AddScript(command);
try
{
    pipeline.Invoke();
}
catch (Exception ex)
{
    throw ex;
}

Now that we’re ready with the PowerShell execution it’s time to close things up.

runspace.Close();

That’s it! Now if you put everything in your codefile and let it run, you should find a merged log file at your temporary location (C:\Users\AppData\Local\Temp) with a “guid”.log

If you wish to clean up your temporary file after working with it you can use the following method:

System.IO.File.Delete(savePath);

I’ve implemented this method in a solution I created. You can find it at codeplex, and here’s a direct link to the sourcecode where I use this code.

SPFarm.Local – Object reference not set to an instance of an object

While writing a console application for SharePoint 2010, I kept getting “Object reference not set to an instance of an object” when using the SPFarm.Local object.

SPFarm oFarm = SPFarm.Local;

The solution was putting the “Platform Target” configuration of my console application from x86 to x64 or Any CPU:

platform target

After that, my application ran fine.

Updating a list item field using its internal name

While looking up examples of eventreceivers for updating list item fields, I noticed a high usage of display names. List fields were often referenced using their display name, like:

properties.ListItem["Title"] = "something";

It works, but, when there are multiple languages used on the SharePoint site, it might get broken. A solution to this is using the internal field name. This can be obtained by GetFieldByInternalName(). By using the internal field name, SharePoint will never be confused by changes. Imagine someone changing the name of a column and breaking your code. That’s not good. The internal field name will always stay the same, unless someone changes it programmatically of course 😉

I used the following code snippet to add text to the title of a list item:

       public override void ItemAdded(SPItemEventProperties properties)
       {
           base.ItemAdded(properties);
           Guid oId = properties.ListItem.Fields.GetFieldByInternalName("Title").Id;
           properties.ListItem[oId] += " and something added to the title field";
           properties.ListItem.Update();
       }

Finding the internal field name is pretty simple. By using Visual Studio 2010’s server explorer, we can access almost all the information of our SharePoint site.

Open up the Server Explorer by going to view and clicking the Server Explorer:

open server explorer

With the Server Explorer view open click on the Add SharePoint Connection button and add your site:

add sharepoint connection

Now we have access to our site properties within Visual Studio 2010. Please note that everything here is read only.

Expand the Explorer View to a desired list field and click on it:

server explorer expanded

The Properties window will now show the associated properties of the field. Here we can find back the internal field name:

list field properties

Other useful information like the GUID of the field can also be found here.