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.
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;
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;
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;
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:
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;
- http://[server]/c1 (still v0.0.0.0)
- http://[server]/c2 (also still v0.0.0.0)
- http://[server]/c3 (new, v1.1.0.0)
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:
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:
Out of interest, running it against case site 3 which is already at v1.1.0.0 does this;
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
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;
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;
- Add another element manifest
- Increase the version of the feature (1.2)
- 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.
- Deploy it
- 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.
- Put the new web part in the main manifest file (for new sites)
- 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/