In a recent project we were migrating a large SharePoint 2007 intranet web application to SharePoint Server 2010.
All site collections in the application had custom content types containing lookup fields. And some custom code was written to maintain only one source list and replicate its content to all site collections, by using this piece of custom code, all content types would have the some choices available.
The lookup fields were there to allow users to tag publishing pages. It was a quite nice solution, given the options available in SharePoint 2007.
In SharePoint 2010 we can now use taxonomy fields with the Managed Metadata Service application to create fields for tagging.
One part in project of the migration to 2010, we had to:
- Create a term set for each list used by the lookup field
- Create a term for each item in those lists
- Create taxonomy fields (site columns) for each term set
- Add the taxonomy field to the content types
- Copy the values of the lookup fields to the taxonomy fields of all pages in the entire web application
Step 1 and 2: Create term sets and terms
I've written a powershell script for this.
In a 2007 environment I probably would have been a command line utility, but in 2010 the available powershell cmdlets make it so easy to script.
Part one of the first script:
Start-SPAssignment -Global
$url = "http://intranet"
$taxSession = Get-SPTaxonomySession -Site $url
if ($taxSession -ne $null)
{
$termStore = $taxSession.TermStores[0]
$termGroup = $termStore.Groups | where { $_.Name -eq "Keywords" }
if ($termGroup -eq $null)
{
$termGroup = $termStore.CreateGroup("Keywords")
$termStore.CommitAll()
}
$web = Get-SPWeb -Identity $url
MigrateListToTermSet $web "List A" $termStore $termGroup "Tagging set A"
MigrateListToTermSet $web "List B" $termStore $termGroup "Tagging set B"
}
Stop-SPAssignment -Global
Explanation:
- Create a taxonomy session within the context of the correct site collection
- Get the first available term store
- Try to get the term group and if it doesn’t exist, create it
- Then get the web opened
- Call the function to create the term set for a list, by providing it with the web, the name of the list, the term store, the term group and the name of the term set…
(Start/Stop-SPAssignment ensures correct disposal of SharePoint objects)Second part of the script:
function MigrateListToTermSet($web, $listName, $store, $group, $setName)
{
$list = $web.Lists.TryGetList($listName)
if ($list -ne $null)
{
$set = $group.TermSets | where { $_.Name -eq $setName }
if ($set -eq $null)
{
$set = $group.CreateTermSet($setName, $lcid)
$store.CommitAll()
}
$items = $list.Items
foreach ($item in $items)
{
$title = $item.Title
$terms = $set.GetTerms($title, 1033, $true)
if ($terms.Count -eq 0)
{
$term = $set.CreateTerm($title, 1033)
$store.CommitAll()
}
}
}
}
Explanation:
- Try to get the list from the web
- Get the term set from the term group, if it isn’t found: create the set
- Then loop through all list items and try to get a term from the term set and if you can’t find any: create a new term .
Step 3: Create taxonomy site columnsThe feature containing the site columns, allready contained some feature code to configure the lookup fields after they are provisioned.
Because we can't provision taxonomy field like every other field type (xml files), we've added additional methods to this feature receiver class to create the taxonoomy fields.Here is the code:
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPSite site = properties.Feature.Parent as SPSite;
if (site != null)
{
TaxonomyFieldProperties fieldProps =
new TaxonomyFieldProperties();
fieldProps.DisplayName = "Taxonomy field A";
fieldProps.Name = "TaxFieldA";
fieldProps.Group = "Custom fields";
fieldProps.Required = false;
fieldProps.TermStoreName = "Managed Metadata Service";
fieldProps.TermGroupName = "Keywords";
fieldProps.TermSetName = "Tagging set A";
TaxonomySession taxSession = new TaxonomySession(site);
SPFieldCollection fields = site.RootWeb.Fields;
if (!fields.ContainsField(fieldProps.DisplayName))
{
CreateTaxonomyField(taxSession, fields, fieldProps);
}
}
}
private static TermStore GetTermStore(TaxonomySession taxSession, string storeName)...
private static Group GetTermGroup(TermStore termStore, string groupName)...
private static TermSet GetTermSet(Group termGroup, string setName)...
private TaxonomyField CreateTaxonomyField(TaxonomySession taxSession, SPFieldCollection fields,
TaxonomyFieldProperties fieldProps)
{
TaxonomyField taxField = null;
TermStore termStore = GetTermStore(taxSession, fieldProps.TermStoreName);
Group termGroup = GetTermGroup(termStore, fieldProps.TermGroupName);
TermSet set = GetTermSet(termGroup, fieldProps.TermSetName);
TaxonomyField newField = fields.CreateNewField(fieldProps.FieldType, fieldProps.Name) as TaxonomyField;
if (newField != null)
{
// Set the properties of the field
// and add it to the collection.
newField.SspId = termStore.Id;
newField.TermSetId = set.Id;
newField.AllowMultipleValues = fieldProps.AllowMultipleValues;
newField.Group = fieldProps.Group;
newField.Required = fieldProps.Required;
fields.Add(newField);
// Get the newly added field in the collection
// and update the display name.
taxField = (TaxonomyField)fields[fieldProps.Name];
taxField.Title = fieldProps.DisplayName;
taxField.Update();
}
return taxField;
}
private struct TaxonomyFieldProperties
{
public string Name;
public string DisplayName;
public string Group;
public string TermStoreName;
public string TermGroupName;
public string TermSetName;
public bool AllowMultipleValues;
public bool Required;
public string FieldType
{
get
{
string type = "TaxonomyFieldType";
if (this.AllowMultipleValues)
{
type = "TaxonomyFieldTypeMulti";
}
return type;
}
}
}
Explanation FeatureActivated():
- Get the site from the feature
- Create a new instance the TaxonomyFieldProperties struct (you could store the properties in an XML file)
- Create a taxonomy session (just like in the powershell script :-)
- Get the fields collection from the root web and check for an existing field, not nothing is found, create it
Explanation CreateTaxonomyField():
- Get the term store, term group and term set
- Now create a new taxonomy field and set the properties
- Add the new field to the fields collection
- When that is done, get the newly added field from the collection, to update the title. If you set the title of the field you've created earlier, you will NOT update the field which was added to the collection! You really need to fetch the field from the collection.
Step 4: Add field to content typeAnd of course we have another feature to provision the content types, just add some more code to add the taxonomy field to a content type, here it is:
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPSite site = properties.Feature.Parent as SPSite;
if (site != null)
{
UpdateContentTypeMetadataFieldRefs(site, "0x010100C568DB52D9D0A14D9B2FDCC96666E9F2", "");
}
}
private static SPField GetFieldFromWebByStaticName(SPWeb web, string fieldName)...
private static void UpdateContentTypeMetadataFieldRefs(SPSite site, string id, string fieldName)
{
SPWeb targetWeb = site.RootWeb;
SPContentTypeId contentTypeID = new SPContentTypeId(id);
SPContentType contentType = targetWeb.ContentTypes[contentTypeID];
// Get the site column
SPField siteColumn = GetFieldFromWebByStaticName(targetWeb, fieldName);
// If the current content type doesn't include this field,
// add a field link,
if (!contentType.Fields.ContainsFieldWithStaticName(fieldName))
{
SPFieldLink link = new SPFieldLink(siteColumn);
contentType.FieldLinks.Add(link);
contentType.Update(true);
}
}
Explanation:
- I think the code in FeatureActivated speaks for it self...
- The method UpdateContentTypeMetadatafieldRefs:
- Get the root web of the site collection, containing the content types.
- Create a SPContentTypeId object get the corresponding content type from the collection
- Now a another method to get the taxonomy field by it's static name from the fields collection of the root web.
- Verify the content type doesn't all ready contains the field, before you add a field link to the content type.
- Calling the contentType.Update(true) method causes all deriving content types to update to (and lists that use this content type also)
Step 5: Copy field valuesAnd again I've done this useing a powershell script. This script opens up the web applications as processes all sites collections in the applications by calling the ProcessWeb function and passing in a taxonomy session and the root web of the site collection.
function ProcessWeb($taxSession, $web)
{
$listId = [Microsoft.SharePoint.Publishing.PublishingWeb]::GetPagesListId($web)
$list = $web.Lists[$listId]
$pages = $list.Items
foreach ($page in $pages)
{
$ct = $page.ContentType.Name
if ($ct -ne $null -and $ct.StartsWith('MyPrefix'))
{
$changed = $false;
$lupValues = $page["LookupFieldA"]
if ($lupValues -ne $null)
{
$mdField = $page.Fields.GetFieldByInternalname("TaxFieldA")
$termStore = $taxSession.TermStores[$mdField.SspId]
if ($termStore -ne $null)
{
$termSet = $termStore.GetTermSet($mdField.TermSetId)
if ($termSet -ne $null)
{
$newTerms = New-Object "System.Collections.ObjectModel.Collection``1[[Microsoft.SharePoint.Taxonomy.Term, Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c]]"
foreach ($lupValue in $lupValues)
{
$terms = $termSet.GetTerms($lupValue.LookupValue, 1033, $true)
if ($terms.Count -ne 0)
{
$newTerms.Add($terms[0])
}
else
{
Write-Output "Term '"$lupValue.LookupValue"' not found!"
}
}
$mdField.SetFieldValue($page, $newTerms)
$changed = $true
}
else
{
Write-Output "Term set not found!"
}
}
else
{
Write-Output "Term store not found!"
}
}
if ($changed)
{
$page.SystemUpdate()
}
}
}
foreach ($subWeb in $web.Webs)
{
ProcessWeb $taxSession $subWeb
$subWeb.Dispose()
}
}
Start-SPAssignment -Global
$url = "http://intranet"
$webApp = Get-SPWebApplication | where { $_.url -eq $url }
if ($webApp -ne $null -and $webApp.Sites.Count -ne 0)
{
$taxSession = Get-SPTaxonomySession -Site $url
foreach ($site in $webApp.Sites)
{
ProcessWeb $taxSession $site.RootWeb
$site.Dispose()
}
}
Stop-SPAssignment -Global
Explanation ProcessWeb:
- First web get the id of the publishing pages library, to get this list from the lists collection.
- Second we loop trough all items in the list
- For each item we look at the name of the content type, because we only what to update pages of our custom content type, which has a name that starts with 'MyPrefix'
- Then we get the lookup field (in our case a multi value lookup field) and if the has values we'll get the corrosponding taxonomy field.
- Using the properties of the taxonomy field we get the term store and term set from the taxonomy session.
- If all goes well we can now create a collection of term objects and loop trou the values in the lookup field.
- For each value try to get the corrosponding term(s) form the term set, when found: add the first term to the collection.
- When done, use the SetFieldValue method of the taxonomy field object to update the item field value and remember we've changed something.
- If we have changed something, do a SystemUpdate of the list item, this will not update the 'modified by' and 'modified date/time' values and we don't have to bother the current check-in and/or approval state.
And this is it! This is how we moved the lookup fields and values to taxonomy fields and terms!
In a large environment running the scripts may take a while, just like (re)activating the site columns and content type features.