Friday, 28 September 2012

Using SharePoint 2010 feature upgrades to update existing sites (Part II)

For the background to this, read my earlier post – feature upgrades part 1.

To summarise, we have an existing SharePoint portal, used for managing cases, where each case has it’s own site. We now need to update all case sites to add a web part to the landing page, and to add a new page entirely. Existing sites should be updated, and new sites being provisioned should have the new part and page.

For the source code, go grab it from codeplex here: http://featureupgradesample.codeplex.com/. There are 3 folders within the source, representing the solution at the beginning (001 - Start), after adding the web part upgrade (002 – Adding a web part to existing pages), and after adding the new page upgrade (003 – Adding a new page).

The starting point

The solution at the beginning is a simplified version of my client’s solution – it has a site definition for a case site, which references several standard features (publishing etc), and our “CaseSiteContent” feature.

image

The case site content feature contains a module – which in turn provisions a landing page on the case site with some prescribed web parts.

On our SharePoint portal, for the purposes of this exercise, I’ve provisioned a publishing site as the host, and within that, two case sites based on this starting solution.

A case site presently looks like this;

image

Exciting huh? The site contains one page – the landing page, with one content editor web part at the moment.

Adding the new web part

What we need to do now is the first part of the problem – add a new web part to these existing sites, and any future ones. Again, I’ll just be adding a content editor web part, to keep life simple.

Note, we aren’t going to touch the site definition. It’s already in use, and therefore shouldn’t be changed. We’re going to apply the updates against the feature.

We start by adding an extra element manifest to the case site content module. At the moment, the elements.xml in this module looks like this;

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Module Name="CaseSiteContent" Url="$Resources:cmscore,List_Pages_UrlName;" Path="CaseSiteContent\Pages">
        <File Url="default.aspx" Type="GhostableInLibrary">
            <Property Name="Title" Value="Landing Page" />
            <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/BlankWebPartPage.aspx" />
            <Property Name="ContentType" Value="$Resources:cmscore,contenttype_welcomepage_name;" />
 
            <AllUsersWebPart WebPartZoneID="Header" WebPartOrder="1">
                <![CDATA[
                <WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">
                  <Title>Hello World</Title>
                  <Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
                  <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
                  <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor">
                    <p>This is a simple case site example with a <strong>Hello World</strong> webpart in it. Soon it will be updated
                       via upgrades to add more pages and web parts</p>
                  </Content>
                </WebPart>
              ]]>
            </AllUsersWebPart>
        </File>
    </Module>
</Elements>

Now we could just add a new web part in there, and that would work for new sites being provisioned, but wouldn’t help us with the upgrade of existing sites. So, we’re going to put the new web part in a separate manifest, included in this module (for new sites), but also reference that separate manifest from the upgrade process.

Copy (just ctrl-drag) the elements.xml to elements.01.01.xml, and make sure you set it’s deployment type to ElementManifest;

image

The content of this new manifest should be as follows (it looks kinda similar to the original, because we’ve just added a slightly different content editor web part).

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Module Name="CaseSiteContent" Url="$Resources:cmscore,List_Pages_UrlName;" Path="CaseSiteContent\Pages">
        <File Url="default.aspx" Type="GhostableInLibrary">
            <Property Name="Title" Value="Landing Page" />
            <Property Name="PublishingPageLayout" Value="~SiteCollection/_catalogs/masterpage/BlankWebPartPage.aspx" />
            <Property Name="ContentType" Value="$Resources:cmscore,contenttype_welcomepage_name;" />
 
            <AllUsersWebPart WebPartZoneID="Header" WebPartOrder="2">
                <![CDATA[
                <WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">
                  <Title>New web part</Title>
                  <Assembly>Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
                  <TypeName>Microsoft.SharePoint.WebPartPages.ContentEditorWebPart</TypeName>
                  <Content xmlns="http://schemas.microsoft.com/WebPart/v2/ContentEditor">
                    <p>This is a whole new web part</p>
                  </Content>
                </WebPart>
              ]]>
            </AllUsersWebPart>
        </File>
    </Module>
</Elements>

Now, the one thing I’m not sure about here is the impact of defining the same file (default.aspx) in the manifest. When we deploy this, it works as expected, but I’m not sure if there are any other ramifications of doing this. If there are, then using the API to add a web part would probably be a better solution.

After adding the new manifest, you should find that it is included in the containing feature automatically by visual studio. Confirm this by opening the case site content feature, and clicking manifest at the bottom – it should look like this;

<Feature xmlns="http://schemas.microsoft.com/sharepoint/" Description="Provisions the content necessary for a case site definition." Id="48002b3b-317b-4224-bb9d-b1716de3bcdd" Scope="Web" Title="CaseSiteContent">
  <ElementManifests>
    <ElementManifest Location="CaseSiteContent\Elements.xml" />
    <ElementFile Location="CaseSiteContent\Pages\default.aspx" />
    <ElementManifest Location="CaseSiteContent\Elements.01.01.xml" />
  </ElementManifests>
</Feature>

If we deployed now, and created a new site, it would work, but we’re not done yet. First of all we need to add a version number to the feature, and then define how to get from the current version (which will be 0.0.0.0) to our new version.

Open the feature again, and in the properties inspector, set the version property to 1.1.0.0;

image

Next, we’ll tell SharePoint how to go from version 0.0.0.0 to our current version. From the feature designer, select “Manifest”, then click “Edit Options” from the bottom of the screen, which presents some XML that will be merged with the main feature manifest. Click “Open in XML Editor” to make your life easy and enter the following;

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/">
    <UpgradeActions>
        <VersionRange BeginVersion="0.0.0.0" EndVersion="1.1.0.0">
            <ApplyElementManifests>
                <ElementManifest Location="CaseSiteContent/Elements.01.01.xml"/>
            </ApplyElementManifests>
        </VersionRange>
    </UpgradeActions>
</Feature>

This is saying, when this feature is upgraded from any version between 0.0.0.0 and up to, but not including 1.1.0.0, apply the new element manifest. So now we have a scenario where, new sites will run this new element manifest in anyway, and set it’s version to 1.1.0.0, and existing sites that aren’t patched up, will, during the upgrade process, run in just this extra manifest.

If we deploy this to SharePoint now, we can create a new case site and it contains our web part:

image

However, our existing sites still aren’t upgraded, and that’s because, the upgrade process doesn’t happen automatically. We need to get each of the sites that uses this feature, get the feature itself and call upgrade on it. We can do this in a number of ways – using powershell, writing a console application, or using Chris O’Briens feature upgrade toolkit. For the purposes of this demo though, we’re going to upgrade sites individually using powershell.

Let’s take a look at the features on the case sites so far. I now have 3 case sites at;

We can confirm this with powershell – the following script will allow you to list all the features on a given site, along with the running version and the feature version.

Param(
    [Parameter(Mandatory=$true)]
    [string] $site,
    [string] $filter)

# Load sharepoint powershell if not already loaded
$spSnapIn = Get-PSSnapIn | where {$_.Name -eq "Microsoft.SharePoint.PowerShell"}
if( $spSnapIn -eq $null )
{
    Add-PSSnapIn Microsoft.SharePoint.PowerShell
}

# Get the sharepoint web
$web = get-spweb $site
if( $filter ) 
{ 
    $features = $web.features | where {$_.Definition.DisplayName -match $filter} 
}
else 
{ 
    $features = $web.features 
}

$features | select @{Name="Feature";Expression={$_.Definition.DisplayName}}, Version, @{Name="Feature Version";Expression={$_.Definition.Version}}

Save the above as “ListFeatureVersionsOnSite.ps1” (It’s also in the source code), and run it as follows;

.\ListFeatureVersionsOnSite.ps1 http://[server]/c1

You can also pass a filter parameter to only show features containing the filter value. So in our case, we run it with;

.\ListFeatureVersionsOnSite.ps1 http://[server]/c1 CaseSite

And we get this:

image

As you can see, they all report the feature version as being 1.1, but the first two sites are presently at 0.0.0.0, whilst the new one is up to date. As expected.

So, to upgrade the site, we just need to get the SPWeb object, get the appropriate SPFeature and then call upgrade on it. We can do that with this powershell script.

Param(
    [Parameter(Mandatory=$true)]
    [string] $site,
    [Parameter(Mandatory=$true)]
    [string] $filter
    )

# Load sharepoint powershell if not already loaded
$spSnapIn = Get-PSSnapIn | where {$_.Name -eq "Microsoft.SharePoint.PowerShell"}
if( $spSnapIn -eq $null )
{
    Add-PSSnapIn Microsoft.SharePoint.PowerShell
}

# Get the sharepoint web
$web = get-spweb $site

# Get our feature
$feature = $web.features | where {$_.Definition.DisplayName -match $filter}
if( -not($feature))
{
    Write-Host "Feature $filter not found"
}
else
{
    # Does it need an upgrade?
    if( $feature.Version -lt $feature.Definition.Version )
    {
        Write-Host "Upgrading $feature.Definition.DisplayName from" $feature.Version "to" $feature.Definition.Version
        $feature.Upgrade($false)
    }
    else
    {
        Write-Host "$feature.Definition.DisplayName is already up to date at" $feature.Version
    }
}

Run it as UpgradeFeatureOnSite.ps1 [site] [featurefilter]. Here’s it running for case site one, which needed an upgrade:

image

Out of interest, running it against case site 3 which is already at v1.1.0.0 does this;

image

We run the script in against all of our sites, and then we can confirm the feature versions again using the script above. All the sites should report as now being at version 1.1.0.0

image

If we then take a look at our older case sites – we see the new element manifest has been applied and our new web part appears;

image

Adding the new page

The process to add a new page would be the same, I’ve gone ahead and demonstrated that in the source code, but I’m not going to detail it here. It’s exactly the same;

  1. Add another element manifest
  2. Increase the version of the feature (1.2)
  3. Add a new version range to the upgrade manifest, starting at 1.1 and ending at the new version (1.2), and reference the same element manifest.
  4. Deploy it
  5. Run the upgrade process across the existing sites

Source code

Is available here: http://featureupgradesample.codeplex.com/

Footnotes and disclaimers

Here’s the “it works on my machine” bit!

  • I’m still not 100% about the duplicate file being put in the pages library across separate manifests. I’m hoping someone can assure me this is ok, otherwise, my plan would be to instead;
    • Put the new web part in the main manifest file (for new sites)
    • Use the custom upgrade action to trigger the feature upgrading code in a feature receiver, which will use the limited web part manager to inject the new web part.

  • A caveat.
    • If you have sites with a customised page that is checked out/not published yet which the upgrade process is targeting, the upgrade manifest won’t work. If you have this scenario, it might be an idea to use code, to ensure the page is checked in and published before it changes it.

And lastly, for upgrading features en-masse, take a look at the feature upgrade tool from Chris O’Brien – it’s on codeplex too, here: http://spfeatureupgrade.codeplex.com/

Using SharePoint 2010 feature upgrades to update existing sites.

One of my clients has a large SharePoint application, used for managing cases. As each case is raised, a new SharePoint site is provisioned and there, collaborators work together to move the case through to a resolution. Ultimately, the SharePoint site is used as the collaboration portal, the document store, and to surface an MVC application that provides the bulk of the functionality and process management through K2 workflow.

The system does a lot more than that, but for the purposes of this post, that’s enough background. Suffice it to say, it’s 3+ years old now and contains ten’s of thousands of case sites, and they need some changes.

Two of these changes are actually what I’d imagine are quite common scenarios. They need to;

  • Create some new functionality, and provision a new web part on all existing case sites (and have the new web part on new one’s too of course)
  • Add a new page to all case sites, with another set of web parts.

I wasn’t sure how to do this initially. If you know me, you’re aware that my speciality is application development, so the MVC part of this project, and I know enough SharePoint to get by, but this one had me stumped for a day.

The structure of the SharePoint solution is quite straight forward – for a case site, we have;

  • A “case site” site definition.
  • Several “features” associated with the site definition (not stapled, just each feature activated through the site definition).
  • One of these features contains a module, which provisions the pages of the case site and the web parts contained therein.

 

The first experiment

The first thing I tried, was just updating the page provisioning module to add the web parts. As I figured, this failed – it worked fine for new sites, but existing sites didn’t have the new web parts added to them, so I started reading up on the feature upgrade capabilities of SharePoint. I found these articles useful:

 

Solving it

So, armed with my new-found knowledge (ha), I finally got this working. In my next post I’ll address two scenarios, and provide all the source code that demonstrates the approach I settled on.

  • Adding a web part to a sharepoint page, and upgrading the one’s already in use.
  • Adding a new page to a sharepoint site, and adding it to existing sites.