Friday, August 24, 2012

Upgrading SharePoint Content Types: General Guidelines, Ideas and Issues.

Microsoft introduced the "SharePoint Content Type Lifecycle Management" for better management of already deployed content types. Although simple in theory, in practice there are a number of issues to be aware of. The whole process of upgrading the Content Type Feature is done by following these steps.

(a) Create the New Fields. 

Personally I prefer to keep my Field Definitions elements.xml separate to my Content Type elements.xml. So whenever I update an existing project, I simply slot in a new manifest with all the new Field definitions (as per version).

Some prefer to add the new FieldRefs into the existing Content Types manifest, but this is not necessary.  However, this does make life simpler for new deployments.

In my example I've created a new Managed Metadata Field:
  <Field ID="{498D906E-1C7F-4493-8F28-3400654F4292}"
  Name="ArtifactClassification"
  StaticName="ArtifactClassification"
  DisplayName="Classification"
  Group="CII Projects Columns"
  Type="TaxonomyFieldType"
  ShowField="Term1033"
  EnforceUniqueValues="FALSE"
  Required="TRUE"
  Overwrite="TRUE"
  DisplaceOnUpgrade="TRUE">
    <Customization>
      <ArrayOfProperty>
        <Property>
          <Name>TextField</Name>
          <Value xmlns:q6="http://www.w3.org/2001/XMLSchema" p4:type="q6:string" xmlns:p4="http://www.w3.org/2001/XMLSchema-instance">{CFB09262-ED43-45b0-9E4F-A44D5256858C}</Value>
        </Property>
      </ArrayOfProperty>
    </Customization>
  </Field>
  <Field ID="{CFB09262-ED43-45b0-9E4F-A44D5256858C}"
    Name="ArtifactClassificationTaxHTField0"
    StaticName="ArtifactClassificationTaxHTField0"
    DisplayName="Classification_0"
    Group="CII Projects Columns"
    Type="Note"
    ShowInViewForms="FALSE"
    Required="FALSE"
    DisplaceOnUpgrade="TRUE"
    Overwrite="TRUE"
    Hidden="TRUE"
    CanToggleHidden="TRUE"
    RowOrdinal="0">
  </Field>

If you prefer to add the FieldRef to the Content Type, it would look something similar to this:
  
<ContentType ID="0x01010008a7b1b7ae814926b4a6a7754d7f9d08"
     Name="CII Projects Document Artifact"
     Group="CII Projects Content Types"
     Description="The CII Projects Document Artifact Content Type."
     Inherits="TRUE"
     Overwrite="TRUE"
     Version="0">
<FieldRefs>
<!-- FieldRefs omitted -->
    <FieldRef ID="{498D906E-1C7F-4493-8F28-3400654F4292}" Name="ArtifactClassification" Required="TRUE" DisplayName="Classification"/>
    <FieldRef ID="{CFB09262-ED43-45b0-9E4F-A44D5256858C}" Name="ArtifactClassificationTaxHTField0" />
</FieldRefs>
</ContentType>

(b) Modify the Existing Feature Element Manifest.

Now comes the bulk of the work. Update the existing content type deployment feature, by adding the UpgradeActions element to the manifest.  Right off the bat I can tell you that the intended functionality doesn't exactly work as intended.  By adding the PushDown="TRUE" property to the AddContentTypeField element, the Upgrade should push the added Fields down to any inheriting Content Types from Sites below the Parent.  This does not work.

The only way I got this working was by following Charles Chen's method of adding a CustomUpgradeAction and forcing the pushdown.  His excellent blog on SharePoint Content Type Lifecycle Management can be found here.  In his post, he explains that by deleting and re-adding the Field to the Content Type, the Field is finally pushed down.

So basically, my UpgradeActions element looks something like this:

<UpgradeActions
    ReceiverAssembly="CII.Projects.Provisioning, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5442323e733bd5e1" 
    ReceiverClass="CII.Projects.Provisioning.Features.CIIProjects_Provisioning.CIIProjects_ProvisioningEventReceiver">
    <VersionRange BeginVersion="0.0.0.0" EndVersion="1.0.0.0">
      <ApplyElementManifests>
        <ElementManifest Location="CIIProjects_Fields_v2\Elements.xml"/>
      </ApplyElementManifests>
      <AddContentTypeField ContentTypeId="0x01010008a7b1b7ae814926b4a6a7754d7f9d08" FieldId="{498D906E-1C7F-4493-8F28-3400654F4292}" PushDown="TRUE"/>
      <AddContentTypeField ContentTypeId="0x01010008a7b1b7ae814926b4a6a7754d7f9d08" FieldId="{CFB09262-ED43-45b0-9E4F-A44D5256858C}" PushDown="TRUE"/>
      <CustomUpgradeAction Name="AddFields">
          <Parameters>
             <Parameter Name="add.field.1">0x01010008a7b1b7ae814926b4a6a7754d7f9d08,{498D906E-1C7F-4493-8F28-3400654F4292}</Parameter>
             <Parameter Name="add.field.2">0x01010008a7b1b7ae814926b4a6a7754d7f9d08,{CFB09262-ED43-45b0-9E4F-A44D5256858C}</Parameter>
          </Parameters>
      </CustomUpgradeAction>
    </VersionRange>
</UpgradeActions>

The code from my ReceiverClass looks very similar to the one described in Chen's article, so I'm not going to repeat it here.

TIP : When adding the ReceiverClass via Visual Studio, the class references is automatically added to the <Feature> element and not the <UpgradeActions> element.  I could'nt trap any of my System.Diagnostic Traces in DebugView, and found that moving the ReceiverAssembly and ReceiverClass references manually to the <UpgradeActions> element was the only way I could get this working.  Does this indeed matter, or did I stuff up?  Comments welcome!

What is important however is to keep the version numbering constant.  In my case, the Version range starts with "0.0.0.0" (since I didn't include a version number with my original deployment) to "1.0.0.0".  Remember to set your Features' version number before packaging!


(c) Deploy the Update

After packaging, deploy the feature by whichever way is your poison.  My preferred method is a simple PowerShell cmdlet:
Update-SPSolution -GACDeployment -Identity "CII.Projects.Provisioning.wsp" -LiteralPath "E:\Deployment\SharePoint\CII\Projects\CII.Projects.Provisioning.wsp"

Be sure to check the 14-hive that your feature was indeed deployed!



(d) Activate the Upgrade

This is the part where everybody runs into issues [duh!].  After updating, the only way to enable the Feature Upgrade, is to get access to the QueryFeatures [SPFeatureQueryResultCollection] collection, and upgrade the features via their object model.

Two common way's we can accomplish this.  Either via C# code, or via a PowerShell cmdlet.  a Simple C# console application might look something like this :
static void Main(string[] args)
{
 using (SPSite currentSite = new SPSite("http://server"))
 {
  foreach (SPFeature feature in currentSite.QueryFeatures(SPFeatureScope.Site, true))
  {
   Console.WriteLine("Feature found : " + feature.DefinitionId.ToString());
   if (feature.Upgrade(true) != null)
   {
    Console.WriteLine("Upgrade " + feature.Definition.Name + " FAILED!");
   }
   else
   {
    Console.WriteLine("Upgrade " + feature.Definition.Name + " UPGRADED!");
   }
  }
 }
 Console.WriteLine("Press Enter to Continue...");
 Console.ReadLine();
} 

Or via a PowerShell cmdlet:
# This (as per 1000's of other examples) I couldn't get working
$featuresToUpgrade = $site.QueryFeatures("Site", $true)
foreach ($f in $featuresToUpgrade)
{
    Write-Host -ForegroundColor Yellow "Upgrading feature " $f.Definition.DisplayName -NoNewline
    $f.Upgrade( $false )
    Write-Host -ForegroundColor Green " DONE"
} 

# This worked
foreach ($f in $site.QueryFeatures("Site", $true)) {
   Write-Host -ForegroundColor Yellow "Upgrading feature " $f.Definition.DisplayName -NoNewline
   $f.Upgrade( $true ) 
   Write-Host -ForegroundColor Green " DONE"     
}

TIP : I ran into an issue where I update my upgraded feature, but couldn't access the feature via my PowerShell cmdlet (see above).  The QueryFeatures (true) collection simply did'nt return any of my upgraded features.  To make matters worse, this was a random occurrence. Sometimes everything ran perfectly smooth, and sometimes, no luck.  After 2 days of hair pulling I finally got to the actual reason...  I normally run the Update PowerShell cmdlet from the SharePoint 2010 Management Shell.  When the update completes, I run the Upgrade cmdlet from the SAME SHELL WINDOW.  This is when QueryFeatures "returns nothing".  Took me 2 whole days to figure out that the workaround this is to simply CLOSE THE SHELL WINDOW, and reopen it.  Voila.  My Upgraded feature starts showing up, and are ready for upgrading...  [facepalm!].

So that's how I got Content Type Upgrading working.  The client is happy, and my hair is coming back nicely.

Happy programming!

Important and Useful Links:



3 comments:

Sridhar said...

Kudos!! The best written article on the topic!

menna said...

الامانة كلين
خادمات بنظام الساعة فى الشارقة
افضل شركة نظافة بالساعة فى الشارقة

bath towels clearance said...

The code from my ReceiverClass looks very similar to the one described in Chen's article, so I'm not going to repeat it here.
rajai single bed ,
winter rajai price ,