Category Archives: SharePoint

MOSS 2007 – How Web.Config Modifications Work

Background

When adding custom development functionality to MOSS/SharePoint 2007, making changes to Web.Configs is not as straightforward as a regular ASP.NET application. The reason for this is that MOSS uses a multiple-server model, and even if you have only one server, it’s still considered a Web Farm.

So, in the same way that MOSS automatically configures IIS sites, host names, authentication, and more, MOSS also has a centralized concept for modifying Web.Config settings.

Why Web.Configs?

A SharePoint site, as glamorous as it may make itself out to be, is nothing more than an ASP.NET web site running on top of IIS. Sure, there’s tons of functionality in it, but at the end of the day, it’s just an ASP.NET web site. With that in mind, it too will have a web.config file, where certain settings can be adjusted, custom application settings can be added, and so on. To give one example, assembly bindings can be specified (and even re-mapped to alternate versions of assemblies) through web.config file to make your application work.

The Problem

Let’s say you have 5 front-end web servers that serve up your SharePoint site. That means you have 5 copies of IIS, and 5 copies of the SharePoint web application. In turn, you have 5 web.config files to keep up with. If you were to manage this manually, you would have to make sure that whenever you made a change, you manually copy web.config files to each server.

Now, let’s say you have 10 SharePoint web applications running on each of those servers. You then have 10 web.configs per server, so a total of 50 web.configs to manage across your farm.

And finally, let’s say you add a 6th server to your farm. You would have to make sure your 10 web.configs were added to the 6th server, on top of now having 60 web.configs to manage and keep in sync.

Microsoft has solved this problem with the centralized concept of web.config changes.

General  Concept

The general idea of this concept revolves around the fact that a Web.Config is nothing more than XML, and that with XML XPath support, certain blocks of HTML can be found with accuracy and reliability, and then changed.

So, if there were just one place where you could store your “changes”, the rest could be automated and kept in sync with the other servers. And when a new server was provisioned, it could automatically be sync’d with this copy of the changeset. If we only had a SharePoint concept, called, oh, I don’t know…

SPWebConfigModification

The SPWebConfigModification class in SharePoint (as of WSS version 3.0) allows you to specify changes with the following information:

  1. What “ownership” group should this settings change go under? (To group certain settings together for easy management.)
  2. What is the XML Node path you want to start at? (for example, /configuration/system.web)
  3. What is the XPath to the specific node you want to match?
  4. What is the block of XML that you wish to go under the matched node?

These changes are stored in a centralized repository per-web application, and are automatically sync’d with all SharePoint web applications in the farm at regular intervals.

Below is a screenshot of what this “central repository” looks like when viewed through an internal tool I developed for this purpose (blurred to exclude possible sensitive info):

image

How Changes are Applied

Changes are applied one of two ways:

  1. Manually (on-demand), through the SharePoint object model.
  2. Automatically, at a regular interval.

When a web.config changeset synchronization occurs, the following things happen:

  1. A timer job is created to synchronize web.config settings, specified to run on all farm servers.
  2. Within 30 seconds, each server picks up its timer job to synchronize its own web.config.
  3. Each server reads its own web.config, compares the XML nodes with the central modification repository entries, and ensures that all values match up. If a repository entry doesn’t exist in the web.config, it will be created, and if it does exist in the web.config with a different value, the web.config value will be updated.
  4. Depending on what was changed, this can cause a recycle of the SharePoint web application.

WARNING: SharePoint, by default, schedules this to happen automatically at regular intervals (at least once a day). This is why it is important NOT to make “manual” changes to web.config files (i.e. by editing them in a text editor). If your manual changes happen to match a repository entry’s XPath, your manual change will be overwritten as soon as this synchronization occurs.

Additionally, if the XPath is only ‘halfway’ correct, it can cause multiple entries of the same node. For an example of this, please read the previous post MOSS Web.Config Modification – Beware the XPath.

Managing Web.Config Changes for Structured Deployments

Unfortunately, there is not a good way within SharePoint’s out of the box Solution Deployment to handle web.config change deployments. As far as I know, you will need to roll your own. However, the team working on this particular project came up with a pretty clever way of managing these changes, and pushing them out in a structured manner.

First, a XML file that is more or less referred to as a web.config Changeset File:

<?xml version="1.0" encoding="utf-8" ?>
<WebConfigChanges xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <!-- This change set is for unit test web applications -->
  <ChangeSet Name="UnitTesting" Status="Active" CanUpdate="True">
    <ChangeItem Name="trust" Path="configuration/system.web" Sequence="0" Environment="Development">
      <trust level="Full" originUrl="" />
    </ChangeItem>
  </ChangeSet>
 
  <!-- This change set is for development web applications -->
  <ChangeSet Name="Development" Status="Active" CanUpdate="True">
    <ChangeItem Name="compilation" Path="configuration/system.web" Sequence="2" Environment="Development">
      <compilation batch="false" debug="true">
        <assemblies>
          <add assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
        </assemblies>
        <expressionBuilders>
          <remove expressionPrefix="Resources" />
          <add expressionPrefix="Resources" type="Microsoft.SharePoint.SPResourceExpressionBuilder, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
          <add expressionPrefix="SPHtmlEncodedResources" type="Microsoft.SharePoint.SPHtmlEncodedResourceExpressionBuilder, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
          <add expressionPrefix="SPSimpleFormattingEncodedResources" type="Microsoft.SharePoint.SPSimpleFormattingEncodedResourceExpressionBuilder, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
          <add expressionPrefix="SPUrl" type="Microsoft.SharePoint.Publishing.WebControls.SPUrlExpressionBuilder, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
        </expressionBuilders>
      </compilation>
    </ChangeItem>
  </ChangeSet>
 
  <!-- Used to configure error log page-->
  <ChangeSet Name="ErrorLogging" Status="Active" CanUpdate="True">
    <ChangeItem Name="SafeMode" Path="configuration/SharePoint" Sequence="0">
      <SafeMode MaxControls="300" CallStack="true" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false">
        <PageParserPaths>
        </PageParserPaths>
      </SafeMode>
    </ChangeItem>
    <ChangeItem Name="customErrors" Path="configuration/system.web" Sequence="1">
      <customErrors mode="On" defaultRedirect="~/_layouts/error.aspx" />
    </ChangeItem>
  </ChangeSet>
</WebConfigChanges>

This file encapsulates what will go into a SPWebModification repository entry, and specifies a couple critical things:

  1. The changeset name (translated into the “Owner” in SPWebConfigModification)
  2. The environment that should be applied to each item (this provides a way to specify different settings for Development, Staging, Production, All, etc, for example, connection strings).
  3. The XPath and Sequence of the modification entry, directly translated into the SPWebModification entry.

A straightforward deployment tool parses this XML, takes a Web Application as an argument, identifies the current environment (via a MOSS Farm-level Property Bag setting), and translates relevant entries into SPWebModification entries for the specified web application.

This provides a structured way to deploy and manage web.config changes, specific to the environment. Although you can still get in a great deal of trouble if XPaths are not absolutely perfect, this provides a great way to eliminate human error to deploy web.config changes in a similar fashion as normal MOSS .WSP package deployments.

MOSS Content Type Propagation – Part 2 – The Code

(This is a follow-up to the previous post: Propagating Content Type Changes in MOSS 2007)

So far, I have propagated a few Content Type fields in a live production environment of ~65,000 webs using this method. Mind you, this does not mean that I have propagated a content type to 65,000 lists or items, just that the environment is large.

The whole game here revolves around Field Links (see the SPFieldLink class on MSDN). And to recap, the goal here is to be able to maintain your Content Types in in a Solution (WSP) feature. MOSS does not support updating content types “declaratively via XML” (i.e. through a solution package).

Here’s what MOSS will do: In most cases, MOSS will add new fields to the root / site collection content type (make sure you do an RDAD deployment – Retract / Delete / Add / Deploy). What this actually means (and this is important) – is that MOSS will add the field and create a SPFieldLink which links the field to the root content type. This is why when you create a new list item based on the content type, that it will show up, but there are no field links automatically created for existing items.

So here’s the work we need to do to propagate the field:

  1. If working with a large site collection (more than 1,000 webs), determine the usage count of the content type you’re updating. This will give you an approximate idea of how long this is going to take to propagate (more on this below).
  2. Break the SPFieldLink at the root level, so that the field is no longer linked to the content type, and update the content type.
  3. Add the SPFieldLink to the content type, but this time, call .Update(true) on the content type, so that SharePoint will propagate the changes to all inherited items (including lists items).

Let’s step through these…

1. Determine Usage Count – This determines where the content type is actually used, and therefore, the # of updates that will take place. A content type usage is defined as the # of items that actually use / inherit the content type.

This is simple – if you already have a SPWeb object called “web” and the Content Type name string called “contentTypeName”, just use:

int contentTypeUsages = Microsoft.SharePoint.SPContentTypeUsage.GetUsages(web.ContentTypes[contentTypeName]).Count;

In testing so far, here are my performance findings as to how long it will take to propagate. Bear in mind that for smaller usage counts there is still some overhead time in establishing a context, retrieving web, etc. However, the results are not very linear, and I couldn’t tell you why. This will just give you a very general and vague idea to set your expectations.

  • 1 usage – 0.66 seconds per web (Total Time: 28 seconds)
  • 6 usages – 0.01 seconds per web (Total Time: 19 seconds)
  • 1,876 usages – 0.64 seconds per web (Total Time: 1192 seconds / 19 min, 52 sec)
  • 13,138 usages – 0.53 seconds per web (Total Time: 1003 seconds / 16 min, 43 sec)
  • 15,031 usages – 0.66 seconds per web (Total Time: 1234 seconds / 20 min, 34 sec)

2. Break the SPFieldLink at the root level – If you already see the field when you go to Site Collection Settings > Content Types, then you will need to break the SPFieldLink. Find the field link by looking in the SPContentType.FieldLinks collection (in the below example, this is the selectedFieldLink object, and selectedSiteColl is the SPSite site collection object).

SPFieldCollection fieldColl = selectedSiteColl.RootWeb.Fields;

this.selectedContentType.FieldLinks.Delete(selectedFieldLink.Id);
this.selectedContentType.Update(true);

fieldColl.Delete(selectedFieldLink.Name);
selectedSiteColl.RootWeb.Update();

3. Propagate the field – Example below – In this case I have created a FieldLinkInstance class that just holds some properties like the Site Collection, Content Type name, etc, but you should be able to see the general idea here. The most critical thing is the last line where you call c.Update(true).

public void PropagateFieldToSiteCollection(FieldLinkInstance fieldLink)
{
SPContentTypeCollection contTypes = fieldLink.SiteCollection.RootWeb.ContentTypes;
SPFieldCollection fieldColl = fieldLink.SiteCollection.RootWeb.Fields;
SPContentType c = contTypes[fieldLink.FieldLink.ContentType.ContentTypeName];
SPField newField = new SPField(fieldColl, fieldLink.FieldLink.Field.FieldType, fieldLink.FieldLink.Field.Name);
newField.Title = fieldLink.FieldLink.Field.DisplayName;
newField.StaticName = fieldLink.FieldLink.Field.StaticName;
newField.PushChangesToLists = true;

string strNewColumn = fieldColl.Add(newField);
SPField targetField = fieldColl.GetFieldByInternalName(strNewColumn);

SPFieldLink oFieldLink = new SPFieldLink(targetField);
c.FieldLinks.Add(oFieldLink);

c.Update(true);
}

Hopefully this will give you an idea and a little code to propagate content types using this method. I have created a class library that actually parses WSP solution packages to auto-detect unpropagated content types between a Solution and live MOSS farm and propagate them, but can’t share the source code for that at the moment. In general, you can extract the WSP package as a CAB, then parse the XML files to extract the Content Types.

MOSS Web.Config Modification – Beware the XPath

Today I finally got around to diagnosing an interesting and simple (but critical) problem with web.config settings. Here’s the scenario:

Every time web.config modifications are done, multiple entries get added to the web.config file. In this case, to retrieve data from a web service, the MOSS farm uses a <proxy> entry to specify the outbound proxy server for the application.

However, every time SPWeb.ApplyWebConfigModifications() was called, an additional <proxy> entry is added to the web.config file. You can’t have more than one proxy entry, and therefore causes these components (or the entire requests) to fail because of an invalid web.config.

(For more information about Web.Config Modifications in MOSS – http://sharepointsolutions.blogspot.com/2006/12/using-spwebconfigmodificat_116736917110571614.html)

Well, the MOSS web.config modification entries looked something like this:

Owner: ProxySettings
Name: proxy[@autoDetect=’true’]
Value: <proxy proxyaddress=”http://someproxy:8080&#8243; />

The problem lies in the fact that MOSS does not do any validation to make sure that the value you’re putting in matches the name’s XML Xpath. So, MOSS says “OK, got a new modification for me? Alright… I’ll put it in. No problem.” and it works fine.

The next time SPWeb.ApplyWebConfigModifications() is called, MOSS does the following:

  1. Looks at the existing web.config file, and makes an XML DOM inventory of what is already in it.
  2. Enumerates through the Web.Config Modifications (which, by the way, are persistently stored in the SharepointConfig database) that should be applied.
  3. Gets to this proxy modification entry, and tries to match the name – proxy[@autoDetect=’true’] – which in XML-ese is slang for: “Let me see if I already have a tag (element) that starts with <proxy and has an attribute in it of autoDetect=’true’.

    So, if our modification value looked like this: <proxy autoDetect=’true’ /> then MOSS would have found this entry and not added a new one. The problem is, our value has no autoDetect=’true’ attribute inside it, so every time MOSS tries to match the value, it never will match.

  4. Match fails, so MOSS assumes that the entry doesn’t exist yet, and adds another one.
  5. MOSS site goes down in flames due to multiple proxy entries, users scream, riots happen, etc.

Really, the setting should have looked like this:

Owner: ProxySettings
Name: proxy
Value: <proxy proxyaddress=”http://someproxy:8080&#8243; />

That way, any element/tag starting with <proxy… would be matched and correctly handled.

Unfortunately, even before or after this is corrected, there is one more important thing: The original never matched, so it’s now orphaned and MOSS will not remove it. Once the web.config modifications are removed from the persisted MOSS object model, the entries are still there in the web.config. The proper way to do this is as follows:

  1. Delete the SPWebConfigModification entry in the object model.
  2. Apply web config modification settings with SPWeb.ApplyWebConfigModifications().
  3. Manually remove all entries (in this case, all entries starting with <proxy…) from the web.config file and save.
  4. Add the correct SPWebConfigModification entry and apply modifications again.

Now, the web.config modifications should be matched by MOSS.

MOSS Farm Monitoring – Everybody online?

Before a deployment, I’m working on a “health check” to determine whether or not some common ‘gotcha’s in a farm are going to be a problem. One problem in particular that I needed to solve is determining whether or not a MOSS Farm Member server is actually online and functioning – specifically its Timer Service.

(Slight detour – Solution Deployment in a farm environment totally relies on Timer Jobs – when you deploy a solution, MOSS creates a Solution Deployment Timer Job that every server in the farm executes. If a server is offline, that solution deployment job will hang indefinitely until the offline server comes back – by design, so that you don’t consider a solution deployment “successful” without all servers receiving the deployment.)

There are obviously the normal “Windows” ways to figure this out – can you ping each member, access a file share, etc. But I actually wanted to know that the member was not only running, but actively communicating with MOSS.

My goal was to come up with, for lack of a better term, a “Farm Ping” — Whereas I would kick off some kind of “dummy” timer job that all farm members would need to run in order to consider the “ping” completed. If I queued this job, and all member servers ran it fine, then I would know everybody was online. If it didn’t finish, then I’d know I have a problem.

The only issue with this technique is that I would need to create some type of custom timer job, package it up in a solution, and deploy it whenever I wanted to test this. But really, that just adds another moving piece to my equation. However, looking around, I found a great alternative that MOSS already does – the Config Refresh Timer Job.

After watching my Timer Job Monitoring Utility for a couple minutes, I noticed a pattern. Every farm member that runs Timer Jobs runs the “Config Refresh” job every 30 seconds or so. If I stop the Timer service on a member server, the Config Refresh job will continue to age. Start the Timer service again, and within a minute or so the Config Refresh job gets run and back on its 30-second schedule.

What’s this mean? Well, we can simply get SPFarm.Local.TimerService.RunningJobs, take a look at the “Config Refresh” jobs in that collection, and if they’re more than 30 seconds old, we know that the server it belongs to is not online. Simple, easy, and surprisingly accurate.

MOSS Object Guids – Case Matters

Learned something today – the hard way.

In my deployment utility (which is a glorified way of simply deploying a solution by using the MOSS API), when I do an upgrade, I get the existing solution by ID via GetSolutionByID(string id) so that I can use the SPSolution object to perform the necessary actions.

However, in the middle of deployment, to my surprise, MOSS started yelling back at me, saying “Can’t find solution with ID <solutionID>”, but then declares “A solution with the name <SolutionName> already exists”.

It appears that MOSS cares whether or not the ID you pass into GetSolutionByID() is lowercase. Otherwise, MOSS won’t find the solution. I would have thought that this would be case insensitive, but it is apparently not.

Timer Job Monitoring Utility

One of the problems that seems to repeat itself over and over again in MOSS deployments / pushes, is that eventually you have trouble with Timer Jobs. The way to know if something is working or not is to keep refreshing the Jobs list in Central Administration, which gets to be a really clunky task, especially with more than a couple servers in the farm.

Soren Nielsen again has written an excellent post about how to fix them.

So yesterday I put together a little utility in Windows Forms that is designed to just give a little better visualization over what’s happening across the MOSS farm with Timer Jobs and SharePoint Services.

Timer Job Monitoring/Dashboard Utility

This utility simply connects to the local farm, categorizes all the running timer jobs (from SPFarm.Local.TimerService.RunningJobs) and puts them into one of 3 lists – Running / Incomplete / Idle. Also, I’ve tried to take an additional step out of problem diagnosis by showing the status of all SharePoint services across all servers in the farm. This is done through WMI by querying the Win32_Service class.

Everything is updated in 2-second intervals (since the point of this is to detect problems quickly in a deployment process).

If you’d like to try this out, you can download it here. (Just make sure you have the .NET Framework installed – which you should, if you’re running MOSS – and run from one of the farm servers.)

SPFarm.Local is null… what?

This comes from the “Captain Obvious” department, but took me a few minutes nonetheless. When running a utility application (which will probably be the next post), SPFarm.Local didn’t throw any exceptions but simply returned as null.

A quick Google for “SPFarm null” brings you to the conclusion that this is a 64-bit problem. And indeed, I was trying to use a .NET application compiled in a 32-bit environment (under “Any CPU” target) on a 64-bit server. However, changing the target to 64-bit and recompiling did not fix the problem.

As it turns out, the account I was running it under didn’t have permissions to access the farm. I would have expected SharePoint to throw some kind of UnauthorizedAccess exception, however in this scenario SPFarm.Local simply returns null. A quick “Run as…” and it came up fine, regardless of the target CPU.

Lesson learned – if you start seeing “Object reference not set to an instance of an object” exceptions with the SPFarm object, check the account permissions that you’re running under.

Propagating Content Type Changes in MOSS 2007

First, a short intro – I’m starting this blog for 2 reasons – 1, I’m learning a lot of stuff right now that if not used regularly, I’ll forget – and need a quick way to document some of it, and 2, if it weren’t for the other people out there doing the same, I wouldn’t have learned a lot of what I have. So hopefully my experiences will help someone else out there, too.

Now, on to the good stuff. The problem I’ve been tackling this week is in MOSS 2007 – specifically, the deployment and propagation of Content Types in an enterprise environment, using the Features & Solutions Framework. Here’s the background:

  1. MOSS is being used as a publishing platform for a very large project. (Hundreds of thousands of webs).
  2. Following best practices for enterprise MOSS architecture, Content Types are being deployed through Features, and are inherited by pages (list items) in subwebs.

The challenge is that MOSS really doesn’t support updates to content types through the Features & Solutions framework (i.e., you are using XML Based Content Types). Once you’ve deployed your feature, it’s going to be a little bit of a fight to update a content type by upgrading the solution. On the other hand, MOSS’s editing capabilities of content types through the UI works extremely well. So, my goal has been to come up with a way to replicate the “UI” behavior within the confines of the Features & Solutions framework.

The first thing I discovered is that if you Deactivate your feature, Retract it, Delete it, Install it, Deploy it, and Activate your feature again, new fields that you’ve added to the Content Type in XML do, in fact, appear. The only problem is, everything that inherits from that Content Type (let’s say, oh, a few million list items?) does not see any of the changes. This is because a new “list” Content Type is created any time you assign a content type to a list, and the new List Content Type inherits from the “real” one that you deployed through your feature. Soren Nielsen describes this briefly in his blog post.

So, at this point, the problem that still remains is that we need a way to propagate changes from a Parent Content Type down to its Child Content Types (in this case, pages in a publishing web that inherit from it). Microsoft’s MSDN article on Updating Child Content Types pretty much spells this out for us:

“You cannot add columns to an existing site content type declaratively, in other words, by updating the Feature XML files.”

Unfortunately, we simply can’t create a business requirement in an enterprise MOSS installation that says “once we deploy content types, they can never change.”

There are a few blogs out there that have proposed the same solution, as well as a Microsoft engineer that worked with me on this issue – use the MOSS Object Model to recurse individually through each subweb, check each list, compare fields, and programmatically add the field links that don’t exist. Some have even integrated this into a STSADM command. I gave this a shot, and it takes between 0.2 – 0.4 seconds to check and compare fields on a list item. This may not sound like a lot (and will work fine for a smaller MOSS install), but for this particular environment, this technique would take at least 66 hours (2.7 days) to propagate Content Type fields.

You may notice, though, that MOSS 2007 out of the box has a special little radio button in the UI where you add a new column to a content type – “Update all content types that inherit from this content type” (yes/no). Propagating new columns/fields this way works dramatically faster – about 0.04 seconds per WEB. This is much more acceptable.

Kicking this around with a team member, we eventually understood what MOSS was actually doing. MOSS, out of the box, during a Content Type update, creates a list of changes to a content type, and then will propagate those changes via the SPContentType.Update() method. The cool thing, is that .Update() takes an optional boolean parameter – “UpdateChildren”. This will propagate the current set of changes to child content types (which in our case are lists). Tested it, and it works! Essentially, there’s no way to make the Features & Solutions framework call .Update(true) – if we can find a way to do this, we have our solution.

At the end of the day, we need to update the Content Type XML files, but programmatically add or make changes to the fields ourselves in the Object Model. So I’ve modified the steps to deploy a solution with a small utility to go along with it. Here is the procedure:

  1. Upgrade the revised solution in-place (no deactivating features). At this point you will NOT see any new fields/columns in the content type.
  2. Compare the fields in your revised solution (WSP) file with the fields and field links that currently exist in your “Live” Content Type as it currently exists in the MOSS farm. I wrote a utility to accomplish this by extracting and parsing the contents of the WSP package and make a class model of the fields.
  3. For any fields that don’t exist, create the field (SPField) and add a field link (SPFieldLink) to the Content Type.
  4. Call SPContentType.Update(true).

As long as you can make field/column changes programmatically and .Update(true) them in one shot, MOSS does the rest (and much faster than enumerating through every list).