Friday, July 22, 2011

Refresh All Published Content Types On Next Update for All Site Collections.

Recently, I had an issue where some content types published to the content type hub did not get published on the first try. The reason is that I had site collections where the Document Set feature had not been enabled for the initial content type publish, so all content types derived from Document Set failed to be published.

It’s easy enough to activate the document set feature for all site collections, right? PowerShell. I can do that in one line:

   1:  (Get-spWebApplication <web app url>).Sites | % {enable-spfeature "DocumentSet" -url $_.url}

Done. But that’s not what this post is about.

Now that the Document Set feature has been enabled, I needed a way to republish the content types that failed. I didn’t want to modify the content types in any way, so my only real option was to go into the content type publishing settings for each site collection and click the “Refresh all published content types on next update” checkbox. This magically makes all the content types re-publish themselves to the site collection the next time the Content Type Subscriber timer job runs.

image

That’s all well and good if you have one or two site collections – go to each site collection, Site Actions –> Site Settings –> Content Type Publishing (under Site Collection Administration) –> Click the checkbox –> Click OK. It’s easy.

But I had a lot of site collections. This was my My Site web application. With personal site collections for a lot of users. 200 or so users. That’s a lot of clicking.

Unfortunately, I could find no apparent way to “click” this checkbox through PowerShell. There is no obvious member or property on either SPSite or SPWeb that seemed like it would do the trick. Frustrated, I turned to ILSpy.

ILSpy is what has replaced Reflector in my toolbox now that Reflector is no longer free. I highly recommend it. But this isn’t a post about ILSpy

The first step in trying to determine what SharePoint does when that checkbox is checked was to open the aspx file to find out where the code behind is located. Here’s the key line in ContentTypeSyndicationHubs.aspx in the Template\Layouts folder:

<%@ Page Language="C#" DynamicMasterPageFile="~masterurl/default.master" Inherits="Microsoft.SharePoint.Taxonomy.OM.CodeBehind.ContentTypeSyndicationHubsPage"       %>

Inherits attribute tells me the namespace I’m looking for, but not the assembly. First I checked Microsoft.SharePoint.dll, but when I didn’t find it there, I checked Microsoft.SharePoint.Taxonomy.dll (in the ISAPI folder). There I found what I was looking for:

image

So if the checkbox is checked, a method called Subscriber.RefreshAllTimeStamps is called for the current site. What does that function do? If I click on it, I get a warning message with the latest version of ILSpy (previous versions would just silently fail…)

image

With “Show internal types and members” selected under the View menu, I find this:

image

Ahhhhhh – now we’re getting somewhere. Even though this is an internal method that can’t be called from PowerShell, now that I know what I’m doing, it’s easy enough to convert:

Function RemoveAllTimeStamps([Microsoft.SharePoint.SPSite] $site)
{
    if ($site -eq $null) { return }
    $rootWeb = $site.RootWeb
    if ($rootWeb.Properties.ContainsKey("MetadataTimeStamp"))
    {
        $rootWeb.Properties["MetadataTimeStamp"] = [string]::Empty
       $rootWeb.Properties.Update()
    }
}

Now that we’ve got that, it’s just a matter of calling RemoveAllTimeStamps for each site collection in the web application.

$webApp = Get-spWebApplication <web app url>
 
$webApp.Sites | % {
    $site = $_
    RemoveAllTimeStamps $site
    $site.Dispose()
}
Now, simply run the Content Type Hub job, followed by the Content Type Subscriber job for your web application. If you have a lot of content types – be prepared to wait. I typed this up this entire blog post while my job went from 9% to 19%.