Audit your Content Type Hierarchy

Shortly after you deploy a SharePoint site you probably start to wonder how your carefully designed content types are actually being used. And abused. More importantly I want to know if any of my feature based content types have been disconnected (unghosted) from the XML source file.

To that end I looked into extracting and visualizing the content type hierarchy to get the overview – and I found that it was already built by some nice chaps at codeplex (can be found here)! The community works like never before 😉

I changed it in a number of ways (in my mind improved it):

The original only showed the content types declared at the site collection root, now I use a completely different way of finding inherited content types through the SPContentTypeUsage classes, to be able to show derived content types in sub sites as well

Using the SPContentTypeUsage will also give you derived list content types, so they can be displayed as well. There might be a large number of lists, so you can switch the list content type display on and off. Default is off.

Caveat: To figure out whether a given (list) content type is a direct descendent of another I have to open the actual list content type and check. Some lists are special (like _catalogs) and will be rendered slightly off in the hierarchy (hierarchy “missing” but the list content type location will be there)

Added my very own detection of the ghosting/unghosting status of any given content type. There are three states: ghosted (connected to xml source), unghosted (disconnected from xml source) and “DB only” (created through the UI or OM, not from a feature)

At the end of the day pretty much all of the code has been changed, while keeping the layout. I’m still grateful for the very nice first step provided and I will post this code back to the codeplex site. Hopefully the guys there will accept the modified code.
[Updated April 14: Patch posted now]

Detecting Ghosted/Unghosted State

I did a lot of digging to figure out how to get at the ghosting status of a content type. You can have a look in the wss content database (table “ContentTypes”) to see if your content types are disconnected or not (column “definition” is null – see my other blog on this). There’s even a dedicated stored procedure that will tell you the ghosting state of a given content type (named “proc_IsContentTypeGhosted”).

I really did not want to directly call stored procedures on the database, so I had a good long hard look at the properties available through the SPContentType class (and many others through reflector) and came up with the following conditions for detecting the state:

Is it a feature based content type, by checking “FeatureID != null”, if not mark it as “DB only”. Note that this is actually a private field, so I have to go the long way around and fetch it through reflection.

If it is feature based on the version > 0 then it has been unghosted. The version number seem to work well for this. It will always be 0 for ghosted content types and be incremented by one every time somebody modifies (and thereby unghost it). According to the xml schema you can specify this field in your content type definition, but it will (fortunately) not make it to the database so the detection seems sound.

If you make new versions of the xml file, the server won’t really notice (see post here).

Disclaimer: This algorithm works well for me, seems reasonable sensible, but there might be an edge case that I’m not aware of.

The Result

Here is what it looks like:

The page is rather long – you are seeing about half of it. It’s also slow, but that’s hardly a performance issue you should care to fix 😉

The Code

You need to create a feature to deploy the page, the feature.xml I use:

Remember to reference the files in your solution manifest file and you should be good to go 🙂

Summary

It works and I’m very happy with it. It is an invaluable tool to debug all sorts of content type related problems. Big thanks to the guys at codeplex for letting me hit the ground running on this.

What’s not in it? Site columns. Didn’t find a good way for it and the need seems limited.

Finally understand that this is not really an audit in the normal SharePoint sense, so you should also consider to enable auditing on content type changes as well. Even when you have locked down security properly accidents do happen (only for your fellow admins of course).

Related

16 Responses to Audit your Content Type Hierarchy

Thanks for posting this, we are finding it valuable in troubleshooting our content types. We are experiencing an error however that we’re hoping you can assist with. After the Annoucment CType we receive the following error:

Error:System.NullReferenceException: Object reference not set to an instance of an object. at ASP._layouts_contenttypehierarchy_aspx.ShowContentType(SPContentType currenttype) in c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\LAYOUTS\ContentTypeHierarchy.aspx:line 117Error:System.NullReferenceException: Object reference not set to an instance of an object. at ASP._layouts_contenttypehierarchy_aspx.ShowContentType(SPContentType currenttype) in c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\LAYOUTS\ContentTypeHierarchy.aspx:line 117Error:System.NullReferenceException: Object reference not set to an instance of an object. at ASP._layouts_contenttypehierarchy_aspx.ShowContentType(SPContentType currenttype) in c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\LAYOUTS\ContentTypeHierarchy.aspx:line 117

When looking at our CTypes with another analyzer tool we are relatively certain it is failing on the “Link” CType. Looking at the code it appears that it’s failing somewhere around setting SPContentType or ShowContentType method. If you have any tips on what to look into, I would appreciate it.

I have not seen the error you describe. However if it fails somewhere in the ShowContentType method, I would guess that it would be in the beginning (as you got no html output for that CType, right?)

I would do two things:
1. Check the line
OutputHtml(“<a class=’ms-topnav'” …

at the beginning of the method. I refer to “.scope.TrimEnd()”. Add a null check around for the scope, e.g. (currenttype.scope == null ? “” : currenttype.scope.TrimEnd )

2. Add some more try/catch statements. Dissect the method in 2, one for the top (output of the current type) and one for the bottom (recurse). Make sure that your catch statement makes it easy to identify where it failed.