Tuesday, February 22, 2011

Assembly redirection in OWSTimer

You may have read my older post on how to add BindingRedirect for assemblies into web.config in SharePoint, which allows you to change the DLL version number of your SharePoint customizations.

One thing I did not mention was, what if you need to use one of these redirected DLL’s in an SPTimerJob?

You will notice that assembly BindingRedirect does not apply to OWSTimer.exe, and it will throw errors such as “Could not load file or assembly 'AssmeblyName, Version=1.2.60.0, Culture=neutral, PublicKeyToken=xxxxxxxxx' or one of its dependencies. The system cannot find the file specified”, while you alraedy have a newer version deployed.

So, how do I make OWSTimer.exe aware of the new version and have my SPTimerJobs running with the latest version every time?

First, we need to understand why this doesn’t work.

Timer jobs run from OWSTimer.exe file, which is not effected by the web.config settings, so our BindingRedirect in web.config file will not help our code to find the correct assembly when running of the OWSTimer.exe file.

Now, for the solution

The bad news are that although a WSP natively allows you to add BindingRedirect to your web.config, it does not allow the same for other config files.

So, we need to do this manually.

Not to fear though! There is a rather simple way of doing that, still within the WSP with no need to come out of the WSP deployment.

It is a little know fact, that a farm feature event handler, with the “FeatureInstalled” event will have 2 special effects:

1. It will run for each web front end server, and not only on one of them

2. It will allow sufficient access to add files to the SharePoint Root.

Just make sure you do not edit / remove any of the out of the box files!

Well, taking this into consideration makes our problem a very simple one to solve.

We just need to create a config file for OWSTimer.exe and add our BindingRedirect statements there, and that’s it – the OWSTimer will know which version of our DLL to look for.

Here is a code example on how to add or update 2 BindingRedirect nodes in that config file, the same method can apply to any other config file during WSP deployment stage:

public void UpdateOWSTimerBindingRedirect()
{
try
{
string configFile = SPUtility.GetGenericSetupPath("TEMPLATE").ToLower().Replace("\\template", "\\bin") + "\\OWSTIMER.EXE.CONFIG";
//string XMLData = System.IO.File.ReadAllText(configFile, Encoding.UTF8);
XmlDocument config = new XmlDocument();
config.Load(configFile);

//ensure assemblyBinding exists
XmlNode assemblyBinding = config.SelectSingleNode("configuration/runtime/*[local-name()='assemblyBinding' and namespace-uri()='urn:schemas-microsoft-com:asm.v1']");

if (assemblyBinding == null)
{
assemblyBinding = config.CreateNode(XmlNodeType.Element, "assemblyBinding", "urn:schemas-microsoft-com:asm.v1");
config.SelectSingleNode("configuration/runtime").AppendChild(assemblyBinding);
}

//Delete old entrees if exist
XmlElement current = assemblyBinding.FirstChild as XmlElement;
while (current != null)
{
XmlElement elmToRemove = null;
if (current.FirstChild != null)
{
var asmIdn = (current.FirstChild as XmlElement);
if (asmIdn.GetAttribute("name").ToLower().Equals("kwizcom.sharepoint.foundation") ||
asmIdn.GetAttribute("name").ToLower().Equals("kwizcom.foundation"))
elmToRemove = current;
}

current = current.NextSibling as XmlElement;

if (elmToRemove != null)
assemblyBinding.RemoveChild(elmToRemove);
}

XmlElement dependentAssembly = null;
if (dependentAssembly == null)//create it
{
dependentAssembly = config.CreateElement("dependentAssembly");
dependentAssembly.InnerXml = "<assemblyIdentity name=\"KWizCom.SharePoint.Foundation\" publicKeyToken=\"30fb4ddbec95ff8f\" culture=\"neutral\" />"+
"<bindingRedirect oldVersion=\"1.0.0.0-20.0.0.00\" newVersion=\"13.2.62.0\" />";
assemblyBinding.AppendChild(dependentAssembly);
}

dependentAssembly = null;
if (dependentAssembly == null)//create it
{
dependentAssembly = config.CreateElement("dependentAssembly");
dependentAssembly.InnerXml = "<assemblyIdentity name=\"KWizCom.Foundation\" publicKeyToken=\"30fb4ddbec95ff8f\" culture=\"neutral\" />" +
"<bindingRedirect oldVersion=\"1.0.0.0-20.0.0.00\" newVersion=\"13.2.62.0\" />";
assemblyBinding.AppendChild(dependentAssembly);
}

config.LoadXml(config.OuterXml.Replace("xmlns=\"\"",""));
config.Save(configFile);
}
catch { }
}

Simply call this method during the FeatureInstalled event and update the code with your DLL name and version number, and you are done.

Hope this helps you with file versioning on SharePoint, which can be a rather difficult task sometimes.


Thanks, Shai.

9 comments:

Vasu said...

Thanks for this info. We have a similar problem, but I do not want to touch the wsp.

There is already a file owstimer.exe.config in bin directory, why can't I edit that file directly? There is nothing else in that file and this is a dedicated environment and if possible we could make a change to the config file. Is there any side-effect if we make change to owstimer.exe.config directly?

Appreciate your help!

Shai Petel said...

Hi Vasu,
Editing the config file directly should be fine, as long as you update all servers in farm.

I was showing how to do that within a WSP installation package, which is what I recommend for the intergrity of your package purposes.

Vasu said...

@Shai

Thanks for the confirmation. We recently updated from 13.2.40.0 to 13.2.50.0. I also found that Kwizcom automatically makes the owstimer.exe.config redirect change in the templates/bin directory in each of the wfes. But it still did not work and I was getting the same error.

I observed that Kwizcom by default adds the xmlns="" attribute for the tag. In your code you are explicitly nullifying it. I removed the xmlns="" in the config file too and then it worked fine.

Thanks once again.

Shai Petel said...

Yes, you are correct.

This was fixed in newer versions, and also cleans up the wrong entree with xmlns="".

Our latest foundation version is 13.2.71 was released just now, and it has some more performence enhancements and other fixes.
I suggest you upgrade at your earliest convinent time.

We are working on a "KWizCom Update" tool that will notify you on updates and allow you to install them quickly.

hemant.risbud@yec.yk.ca said...

I am getting following error -

Event manager error: Could not load file or assembly 'KWizCom.SharePoint.Features.SLFEFeature, Version=1.0.0.0, Culture=neutral, PublicKeyToken=30fb4ddbec95ff8f' or one of its dependencies. The system cannot find the file specified.

I am not using this component at all... Please let me know how to fix this

Shai Petel said...

Hi hemant.risbud@yec.yk.ca,

This blog does not offer support for KWizCom products.

Please contact support at kwizcom dot com, I am sure our support team would be happy to assist you.

Please do not leave support comments on this blog, it is not monitored by our support team.

If you get this error in your log it means you installed the product, even if you are not using it. Quite possibly this points to a problem that occurred during the installation or retraction stage.

Shai Petel said...

Hi hemant.risbud@yec.yk.ca,

This blog does not offer support for KWizCom products.

Please contact support at kwizcom dot com, I am sure our support team would be happy to assist you.

Please do not leave support comments on this blog, it is not monitored by our support team.

If you get this error in your log it means you installed the product, even if you are not using it. Quite possibly this points to a problem that occurred during the installation or retraction stage.

Anonymous said...

In my testing FeatureInstalled is running on one, arbitrary WFE or the APP server. The feature is Farm scoped on SP2010. Is there perhaps some setting that will cause it to run on all WFEs, or is that claim wrong?

Shai Petel said...

It seems that there is a flaw or a bug in the FeatureInstalled event.
When you install a new feature on the server it behaves as described and runs the FeatureInstalled on each server in the farm.
But it seems that once you retract and deploy a new version of the package (WSP) the FeatureInstalled sometimes run on all servers, and in other cases runs on some servers only.

I found this to be a non-reliable mechanism.

One more thing: I can confirm it is in most cases running on all servers. If you get consistent behavior where it runs only on 1 server, that's not what I'm seeing.

I will look for updated documentation, perhaps a service pack changed this behavior.