Fixing taxonomy terms after a Metalogix migration

When migrating from SharePoint 2010 to 2013 using the Metalogix Content Matrix 7.0.0.1 migration tool, a taxonomy issue was encountered. The individual term from the SP2010 source was not applied to the SP2013 destination and associated with the correct termset and term.
The destination taxonomy field appeared empty in every sense. It was not displayed when viewing the Item, nor in the list view.
However one remnant of the term did appear in a surprising place. Within the read-only property bag of the item (item.XML) there was a field with what appears to be a GUID devoid of dashes as property name, with a value that seems somewhat usable.
The property value in this case was “a466a2acd62b498291c78829c2bb5fe3”.
item.GetFormattedValue(“a466a2acd62b498291c78829c2bb5fe3”) gets the value, as does item[“a466a2acd62b498291c78829c2bb5fe3”]
This is the GUID of the target field associated with the termset.
To make this more generic, I set the targetField to the field name we are looking for, and derive the GUID generically this way.

[Microsoft.SharePoint.Taxonomy.TaxonomyField]$taxonomyField = $item.Fields.GetField($targetField)

Then stripped off the dashes, to get it into the format that appears in the property bag:

$FieldGUID = $taxonomyField.id.Guid.tostring().replace("-","")

Then I derive the value for the property:

$FQterm = $item[$fieldGUID]

The value appears in the internal format, so I strip off any text appearing after a pipe:

$fqTerm = $FQterm.Substring(0,$FQterm.IndexOf("|"))

To match to the appropriate term, we need to handle the situation where a text value can appear under multiple parents. So we walk the hierarchy to find the single correct match. First we split the term into colon delimited segments:

$termHierarchy = $FQterm.Split(":")

Then check parent term matching to narrow to one appropriate matching term. This assumes checking two levels finds the match. It can be extended to match 7 levels deep if needed:

if ( $TC.count -gt 1)
{
$termIdx=-1;
if ($termHierarchy.count -ge 2)
{
$ParentTerm = $termHierarchy[$termHierarchy.count-2];
}
else
{
$ParentTerm = $null;
}
for ($ti=0; $ti -lt $TC.count; $ti++) #loop to ensure parent is right one
{
if ($TC[$ti].parent.getdefaultlabel(1033) -eq $parentTerm)
{
$termIdx = $ti;
}
}
}

Programmatically Configuring Metadata Navigation

Metadata navigation offers a quite clever way to navigate documents within a library. It cuts across folders, and allows drill-in for taxonomies as configured hierarchies, and also allows for a combination of fields for key filters. One can configure it manually for a library, but how can one do this programmatically? Below are the steps:

# get the SPWeb:
$web = Get-SPWeb "http://WhateverTheSPWebSiteIs"
# get the library:
$JPLib = $web.Lists.TryGetList("WhateverTheListIsCalled")
# Here's the XML object we are coding against:
$listNavSettings = [Microsoft.Office.DocumentManagement.MetadataNavigation.MetadataNavigationSettings]::GetMetadataNavigationSettings($JPLib)
# You can output the XML settings and easily see its configuration each step along the way with this:
$listnavSettings.SettingsXml
# Here's how to clear both Configured Hierarchies, and Key Filters:
$listNavSettings.ClearConfiguredHierarchies()
$listNavSettings.ClearConfiguredKeyFilters()
[Microsoft.Office.DocumentManagement.MetadataNavigation.MetadataNavigationSettings]::SetMetadataNavigationSettings($JPLib, $listNavSettings, $true)
# Let's get ready for a Content Type Hierarchy
$ctHierarchy = [Microsoft.Office.DocumentManagement.MetadataNavigation.MetadataNavigationHierarchy]::CreateContentTypeHierarchy()
$listnavSettings.AddConfiguredHierarchy($ctHierarchy)
# Add a configured Hierarchy:
$listNavSettings.AddConfiguredHierarchy($JPLib.Fields["Field Name"])
# Add a Content Type Key Filter; I chose this on purpose, as using "Content Type" will not work, the field to use here is "Content Type ID":
$listNavSettings.AddConfiguredKeyFilter($JPLib.Fields["Content Type ID"])
# Now the party ends happily with an update; note no $list.update() or $web.update() is needed:
[Microsoft.Office.DocumentManagement.MetadataNavigation.MetadataNavigationSettings]::SetMetadataNavigationSettings($JPLib, $listNavSettings, $true)

Metadata Warehouse of SharePoint Content

I often write custom reports to be able to analyze huge data from a SharePoint farm. I can ansewr questions such as:
– Number of documents updated per user per month over time.
– What are the metadata fields and values in use, and their frequency by department.

I typically export as a CSV, using pipe delimited format (to avoid misinterpreting the ubiquitous comma), and import into a spreadsheet.

What happens when the dataset is too large for Excel? The data I analyze often pushes the limits of Excel or my machine. In that case, I’ll import into SQL Server. To make it repeatable, I’ll create a import process, that drops and recreates the target table, defines the fields, and how to handle errors for each, then export as an SSIS DTSX package.

To run a DTSX package, I’ll import into Business Intelligence Studio as a package and run from there.

Once in SQL Server, one can handle larger datasets, with all the tools and techniques of SQL for dealing with really big data.

The data can be exposed as an SSRS report.

Fixing bad SharePoint taxonomy term references

How to fix bad Taxonomy Terms in SharePoint automatically

A given document using managed metadata can have a term orphaned from the termset. This can happen due to bad references to the intermediary site collection cached terms list, which is in each site collection and hidden, under the list name “HiddenTaxonomyList”, here’s how to recreate this URL: [siteurl]/Lists/TaxonomyHiddenList/AllItems.aspx

While it’s easy to extend this to check the health of each document term, for simplicity let’s imagine we want to fix a single document, and have the URL and know the name of the taxonomy field. Let’s set the basics before we really get started:

$docurl = "http://WebApp/path/lib/doc.xlsx" #URL to correct
$site = New-Object Microsoft.SharePoint.SPSite($docurl)  #grab the SPSite
$web = $site.OpenWeb() #grab the SPWeb
$item = $web.GetListItem($docurl) #grab the SPItem
$targetField = "MMS Field Name" # let's establish the name of the field
$TermValueToReplace = $item[$targetField].label;  #this is the termset value we want to re-assign correctly

Now let’s get a taxonomy session, with proper error detection:

try
{
Write-Host "getting Tax Session for $($Site.url)..." -NoNewline
$taxonomySession = Get-SPTaxonomySession -Site $site  #get one session per site collection you are in
$termStore = $taxonomySession.TermStores[0]  #We need to move the  get Termset to above the lib level too!
Write-Host "Got Tax Session. " 
}
catch
{
Write-Host "Tax session acquisition problem for $($site.url)"
$ok=$false;
}

Given the field, let’s get the correct termset.

[Microsoft.SharePoint.Taxonomy.TaxonomyField]$taxonomyField = $item.Fields.GetField($targetField)  
$termSetID=$taxonomyField.TermSetId
$termSet = $termStore.GetTermSet($taxonomyField.TermSetId)  #if we ever loop, move to outside item loop, this takes a long time!

Now we do a lookup for the term in the Managed Metadata Service. We expect precisely one match, but we’ll check for that later

#"true" parameter avoids untaggable terms, like parent term at higher tier that should not be selected
[Microsoft.SharePoint.Taxonomy.TermCollection] $TC = $termSet.GetTerms($TermValueToReplace,$true)  

Now let’s populate a TaxonomyFieldValue, and assign it to the SPItem, and save it without changing timestamp or author by using SystemUpdate()

$taxonomyFieldValue = new-object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue($taxonomyField)
$t1=$TC[0]  #use the first result
$taxonomyFieldValue.set_TermGuid($t1.get_Id())  #this assigns the GUID
$taxonomyFieldValue.set_Label($TermValueToReplace)  #this assigns the value
$taxonomyField.SetFieldValue($Item,$taxonomyFieldValue)  #let's assign to the SPItem
$item.systemupdate()

That’s the meat of it. Let’s put it all together with error handling:

$ok=$true;
$docurl = "http://WebApp/path/lib/doc.xlsx" #URL to correct
$site = New-Object Microsoft.SharePoint.SPSite($docurl)
$web = $site.OpenWeb()
$item = $web.GetListItem($docurl)
$targetField = "FieldName"
$TermValueToReplace = $item[$targetField].label;
try
{
Write-Host "getting Tax Session for $($Site.url)..." -NoNewline
$taxonomySession = Get-SPTaxonomySession -Site $site  #get one session per site collection you are in
$termStore = $taxonomySession.TermStores[0]  #We need to move the  get Termset to above the lib level too!
Write-Host "Got Tax Session. " 
}
catch
{
Write-Host "Tax session acquisition problem for $($site.url)"
$ok=$false;
}
[Microsoft.SharePoint.Taxonomy.TaxonomyField]$taxonomyField = $item.Fields.GetField($targetField)  
$termSetID=$taxonomyField.TermSetId
$termSet = $termStore.GetTermSet($taxonomyField.TermSetId)  #Move to outside item loop, this takes a long time! 	
[Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue]$taxonomyFieldValue = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue($taxonomyField)  
[Microsoft.SharePoint.Taxonomy.TermCollection] $TC = $termSet.GetTerms($TermValueToReplace,$true)  #true avoids untaggable terms, like parent company at higher tier
if ($TC.count -eq 0)
{
Write-Host -ForegroundColor DarkRed "Argh, no Taxonomy entry for term $($TermValueToReplace)"
$ok=$false;
}
else
{
if ( $TC.count -gt 1)
{
Write-Host -ForegroundColor DarkRed "Argh, $($TC.count) Taxonomy entries for Claim term $($TermValueToReplace)"
$ok=false; #we can't be sure we got the right claim!
}
$taxonomyFieldValue = new-object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue($taxonomyField)
$t1=$TC[0]
$taxonomyFieldValue.set_TermGuid($t1.get_Id())
#$taxonomyFieldValue.ToString()
#$targetTC.add($taxonomyFieldValue)
}
try
{
$taxonomyFieldValue.set_Label($TermValueToReplace)
$taxonomyField.SetFieldValue($Item,$taxonomyFieldValue)  
}
catch
{
Write-Host -ForegroundColor DarkRed "Argh, can't write Tax Field for $($Item.url)"
$ok=$false;
}
if ($ok)
{
$item.systemupdate()
write-host "Fixed term for item"
}
else
{
Write-Host -ForegroundColor DarkRed "Did not fix term for item"
}

Each TaxonomyFieldValue has three important properties; these appear often as pipe separated values:
Label : It is the property of the Label selected by the user from the Labels property of the Term object
TermGuid : it is the Id (Guid) property of the Term (inherited from TaxonomyItem)
WssId : Reference back to the TaxonomyHiddenList, actually ID of the list entry in the site collection

Add a Taxonomy Field into Every Site Collection and to a Content Type

Add a Taxonomy Field into Every Site Collection

I recently had to add a Taxonomy Site Column into a range of Site Collections, and then add the new Site Column into a Content Type.  Lastly, I needed to update all libraries in the site collection to reflect the updated Content Type.  How might we do this?

Step by step

Let’s get a web application, and its sites.  Note that this is not the most efficient approach, as a collection of SPSites can be large.  A more efficient way could be to pass the Sites through a pipeline.

$webApp=Get-Spwebapplication "http ://SharePoint" #your web app url
$sites=$webApp.sites;

Let’s loop through the SPSites, and get the SPWeb to work with.  The SPSite actually has no content, nor does it have Site Columns or Content Types.  The root web is where Site Columns and Content Types are managed.

for ($i=0; $i -lt $sites.count; $i++)
{
$site= $sites[$i];
$JPweb=$site.rootweb;
}

To do anything with Managed Metadata requires opening a Taxonomy session.   This can be done directly through the Service Application.  For simplicity, I open the session at the Site level.  This code assumes only one Managed Metadata Service association (index offset of zero).  If you have more than one MMS service application, query the collection of termstores to ensure you are grabbing the correct one.  Note below there is a “group name” and “Term Set Name” required.  Adjust these to match your environment.  Lastly there’s a check below to ensure you grabbed a termset, and aren’t holding a null reference:

$taxSession = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site, $true);
$termStore = $taxSession.TermStores[0];
$TermSet = $termStore.Groups["Your Term Group"].TermSets["TermSetName"]
if ($TermSet -eq $null)
{
Write-Host -ForegroundColor DarkRed "Termset does not exist"
continue;
}

Now let’s create a new field. Note we’ll need to know the display name for the field, as well as define a static (internal) name.  As with all Site Columns, I highly recommend using a static name that avoids spaces and other characters that lead to control escape sequences in the code (%20 for space, etc).  Note the termset that is “latched” to this field refers to the specific term store (it could point to any of your Managed Metadata Service Applications) and to the specific termset.  These are GUIDs.  Note that each Managed Metadata Service Application is associated with a dedicated database.  That’s where your termsets are stored.  You’ll want to select a “group” for where the site column will be displayed.  We’ll add the field, and update the SPWeb object.

$taxonomyField = $JPweb.Fields.CreateNewField("TaxonomyFieldType", $FieldToAddDisplayName)
$taxonomyField.SspId = $termSet.TermStore.Id
$taxonomyField.TermSetId = $termSet.Id
$taxonomyField.AllowMultipleValues = $false
$taxonomyField.Group = "Site Column Group"
$taxonomyField.StaticName = $FieldToAddStaticName
$taxonomyField.ShowInEditForm = $true
$taxonomyField.ShowInNewForm = $true
$taxonomyField.Hidden = $false
$taxonomyField.Required = $false
$JPweb.Fields.Add($taxonomyField);
$JPweb.Update();

Now let’s grab the set of Content Types, find our target Content Type and field, and update the Content Type.  We’ll turn off ReadOnly for the Content Type, and do the following special object update to force propagation of the Content Type into all the libraries in the site collection: $ct.UpdateIncludingSealedAndReadOnly()

$cts=$JPWeb.ContentTypes
$ct=$cts["Specific Content Type"] # replace with your content type
$ct.set_ReadOnly($false)
$fields=$ct.fields
$fs=$JPWeb.Fields
$favField=$fs.get_Item($FieldToAddDisplayName)
if ($favField -eq $null)
{
Write-Host -ForegroundColor DarkRed "Cannot find $($FieldToAdd) in web $($JPWeb.url)"
continue;
}
$link = new-object Microsoft.SharePoint.SPFieldLink $favField
$ct.FieldLinks.Add($link)
$ct.UpdateIncludingSealedAndReadOnly($true)
$ct.set_ReadOnly($true)

Let’s now put it all together into one neat script that includes some extra error handling:

$FieldToAddStaticName = "InternalFieldName"
$FieldToAddDisplayName = "Field Display Name"
$webApp=Get-Spwebapplication "http ://SharePoint" #your web app url
$sites=$webApp.sites;
for ($i=0; $i -lt $sites.count; $i++)
{
$site= $sites[$i];
$JPweb=$site.rootweb;
if ($site.url -notlike "http ://SharePoint/MyPreferredPath/*")
{
continue;
}
$taxSession = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site, $true);
$termStore = $taxSession.TermStores[0];
$TermSet = $termStore.Groups["Your Term Group"].TermSets["TermSetName"]
if ($TermSet -eq $null)
{
Write-Host -ForegroundColor DarkRed "Termset does not exist"
continue;
}
$taxonomyField = $JPweb.Fields.CreateNewField("TaxonomyFieldType", $FieldToAddDisplayName)
$taxonomyField.SspId = $termSet.TermStore.Id
$taxonomyField.TermSetId = $termSet.Id
$taxonomyField.AllowMultipleValues = $false
$taxonomyField.Group = "Site Column Group"
$taxonomyField.StaticName = $FieldToAddStaticName
$taxonomyField.ShowInEditForm = $true
$taxonomyField.ShowInNewForm = $true
$taxonomyField.Hidden = $false
$taxonomyField.Required = $false
$JPweb.Fields.Add($taxonomyField);
$JPweb.Update();
$cts=$JPWeb.ContentTypes
$ct=$cts["Specific Content Type"] # replace with your content type
if ($ct -eq $null)
{
Write-Host -ForegroundColor DarkRed "Cannot add field to Content Type in web $($JPWeb.url)"
continue;
}
$ct.set_ReadOnly($false)
$fields=$ct.fields
$fs=$JPWeb.Fields
$favField=$fs.get_Item($FieldToAddDisplayName)
if ($favField -eq $null)
{
Write-Host -ForegroundColor DarkRed "Cannot find $($FieldToAdd) in web $($JPWeb.url)"
continue;
}
$link = new-object Microsoft.SharePoint.SPFieldLink $favField
$ct.FieldLinks.Add($link)
$ct.UpdateIncludingSealedAndReadOnly($true)
$ct.set_ReadOnly($true)
}

Customized Taxonomy Creation from a feedfile

Creating a customized Taxonomy from a feedfile

There’s an native SharePoint 2013 capability to import termsets using a CSV. However it’s limited in structure, produces no logs, and if it encounters a problem, it fails without indicating where it failed, and it won’t continue the import after the problem entry.

For industrial strength Taxonomy loads, rolling your own is the only way.

Here’s a script that is easily adapted and extended. It detects the first letter of the term, and files the terms by the first letter. Then uses two more levels to create a 3 level hierarchy

When loading terms, terms are committed in batches. The larger the batch, the faster the load. However for most one-off loads, I recommend using a batch size of 1, so any errors are immediately addressable and localized to one term.

Taxonomy basics

First, grab a taxonomy session

$taxonomySession = Get-SPTaxonomySession -Site $TaxSite

Now let’s grab the termstore for our target Service Application

$termStore = $taxonomySession.TermStores[$ServiceName]

Finally, we can grab our target group

$group = $termStore.Groups[$GroupName]

Lastly, we can grab our target termset if it exists

$termSet = $group.TermSets | Where-Object { $_.Name -eq $termSetName }

Or create a new termset:

$termSet = $group.CreateTermSet($termSetName)
$termStore.CommitAll()

Let’s grab one or more matching terms. Setting the value below to $true avoids untaggable terms, like parent company at higher tier, but we want to find unavailable tags here

[Microsoft.SharePoint.Taxonomy.TermCollection] $TC = $termSet.GetTerms($CurrentLetter,$false)

Let’s see what matching terms we have:

if ($TC.count -eq 0)
{
write-host "No Matching Terms Found!"
}
else
{
write-host "$($TC.Count) Matching Terms Found!"
}

Let’s create a Term2 beneath an existing Term1, then set a description value:

$Lev2TermObj=$Lev1TermObj.createterm($Lev2Term,1033);
$Lev2TermObj.SetDescription($Description,1033)

That covers some of the basics. Let’s put it together into a useful script:

#CREATES  AND POPULATES A FULL HIERARCHICAL TERMSET
#Later we can add details to termset
#term.SetDescription()
#term.CreateLabel
#KNOWN PROBLEM: batch will fail if there are duplicate names in the batch, preventing clean restart unless batch size = 1
$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
if ($snapin -eq $null)
{
Add-PSSnapin Microsoft.Sharepoint.PowerShell
}
$env="Prod"
$termSetName = "YourTermset"
$SourceCSV="L:PowerShellTaxTabDelimitedTermsetFeed.txt"
#set the batch size; 100+ for speed, reduce to 1 to catch errors
$batchSize=1;
$BatchNum=0;
if ($env -eq "Dev")
{
$TaxSite = "http ://SharePointDev"
$ServiceName="Managed Metadata Service"
$GroupName="TermGroupName"
}
elseif ($env -eq "Prod")
{
$ServiceName="Managed Metadata Services"
$TaxSite = "http ://SharePoint"
$GroupName="TermGroupName"
}
try
{
$StartTime=get-date;
Write-Host -ForegroundColor DarkGreen "Reading CSV...$($StartTime)"
$Terms=Import-Csv $SourceCSV -Delimiter "`t"
$ReadSourceTime=Get-Date;
$Duration=$ReadSourceTime.subtract($StartTime)
Write-Host -ForegroundColor DarkGreen "Read in $($Terms.count) items from $($SourceCSV) in $($duration.TotalSeconds) Seconds"
}
catch
{
Write-Host -ForegroundColor DarkRed "Could not read in $($SourceCSV)"
}
#first let's grab a taxonomy session
$taxonomySession = Get-SPTaxonomySession -Site $TaxSite
#plural Now let's grab the termstore for our target Service Application
$termStore = $taxonomySession.TermStores[$ServiceName]
#Finally, we can grab our target group
$group = $termStore.Groups[$GroupName]
$termSet = $group.TermSets | Where-Object { $_.Name -eq $termSetName }
if($termSet -eq $null)  # will have to create a new termset
{
try
{
$termSet = $group.CreateTermSet($termSetName)
$termStore.CommitAll()
Write-Host "Created Successfully $($termSetName) TermSet"
}
catch
{
Write-Host "Whoops, could not create $($termSetName) TermSet"
}
}
else #termset already exists
{
Write-Host "Nice, termset $($TermSetName) already exists"
}
$CurrentLetter=$LastParentTerm=$null; # track previous parent, to determine whether to create a parent
for ($i=0; $i -lt $Terms.count; $i++)
{
$Lev1Term=$Terms[$i]."Level 1 Term"
$Lev2Term=$Terms[$i]."Level 2 Term"
if ($LastParentTerm -ne $Lev1Term)
{
$LastParentTerm=$Lev1Term;
if ($LastParentTerm[0] -ne $CurrentLetter)  #create a new letter!
{
$CurrentLetter=$LastParentTerm[0];
#setting to $true avoids untaggable terms, like parent company at higher tier, but we want to find unavailable tags here
[Microsoft.SharePoint.Taxonomy.TermCollection] $TC = $termSet.GetTerms($CurrentLetter,$false)
if ($TC.count -eq 0)
{
$CurrentLetterTerm=$termSet.createterm($CurrentLetter,1033);
$CurrentLetterTerm.set_IsAvailableForTagging($false);
}
else
{
$CurrentLetterTerm=$TC[0]
}
}
#first try to find existing level1 term before trying to create the term.  This is needed for incremental loads
[Microsoft.SharePoint.Taxonomy.TermCollection] $TC = $termSet.GetTerms($Lev1Term,$false)
if ($TC.count -ge 1)  #Term found.  So use it
{   #assume only one hit possible, if more than one found, just use first, as precise parent is less important in this case
$Lev1TermObj=$TC[0];
}
else # no term found, so create it
{   #in this case, all parent terms are not available, this logic is for extensibility only
$Lev1TermObj=$CurrentLetterTerm.createterm($Lev1Term,1033);
if ($Terms[$i]."available" -eq "FALSE")  #careful, if term2 has a new term1, the term1 will be created as available for tagging
{
$Lev1TermObj.set_IsAvailableForTagging($false);
}
else
{  #we choose not to tag this level as available, so force level1 to always unavailable.
$Lev1TermObj.set_IsAvailableForTagging($false);
}
}
} #term1 unchanged, so this above was handling new terms or finding terms, below is just term2 handling. Note hole, in case term is being loaded that exists already
try
{
if ($Lev2Term.get_length() -ne 0)  #bypasses my habit of new parent terms with empty level 2, can be zero length and not null
{
$Lev2TermObj=$Lev1TermObj.createterm($Lev2Term,1033);
$Description=$Terms[$i]."Description"
if ($Description.get_Length() -ne 0)
{
try
{
$Lev2TermObj.SetDescription($Description,1033)
}
catch
{
Write-Host -ForegroundColor DarkRed "Failed to set description on $($i)"
}
}
}
}
catch
{
Write-Host -ForegroundColor DarkRed "Could not create $($terms[$i])"
}
if (($i % $batchSize) -eq ($batchSize-1))   #some quick modulus math
{
$BatchNum++;
try
{
$termStore.CommitAll();
Write-Host -ForegroundColor darkgreen "Committed terms in batch: $($BatchNum)"
}
catch
{
Write-Host -ForegroundColor darkred "FAILED commiting terms in batch: $($BatchNum), Index: $($i)"
}
}
}
$termStore.CommitAll();  #in subsequent ophase, try to commit a batch at a time

Observations

1. CSV loads fast, and is cached, so subsequent loads are extremely fast
2. Batching speeds things, but not as much as one might imagine
3. Once a batch fails due to a duplicate name, the whole process is messed up and the script needs re-running

Tips and tricks

1. Sort source CSV by Term1 () then by Available
2. Eliminate leading blanks for terms using Trim()
3. Ensure no dups in advance
4. Sort so all term levels are grouped together, otherwise an attempt to create the second set of Term1 will fail

Enjoy!

Wiping Taxonomy Values

Wiping MMS Taxonomy Values

Ever want to wipe out Taxonomy values in a Managed Metadata field in a library? Assigning a null won’t cut it. My good friend Brett Parker found the solution:

for ($i=0; $i -lt $count; $i++)
{
$doc = $items[$i]
$field = [Microsoft.SharePoint.Taxonomy.TaxonomyField]$lib.Fields[$CompanyMyGroup]
$empty = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue($field)
$field.SetFieldValue($doc, $empty)
$doc.SystemUpdate()
}

When users can’t access a taxonomy

When users cannot access a MMS taxonomy

The Managed Metadata Service is great; but what do you do when users can’t view some taxonomy entries? This occurs when the user does not have access to the HiddenTaxonomyList, native to each Site Collection.

You can view the list using SharePoint Manager from CodePlex, navigating the Object Model, or simply by modifying this URL to reflect your site collection URL:
http ://SharePoint/sites/SiteCollection/Lists/TaxonomyHiddenList/AllItems.aspx where “http ://SharePoint/sites/SiteCollection” is replaced with your site collection URL.

I recently found a situation where permissions by default were empty. Best would be to allocate all Authenticated users access. However what does one do if there are many site collections within a given Web Application? Here’s a script that will iterate through Site Collections, and grant the access:

$WebApp = "http ://SharePoint" #replace with your own web app
$webapp = get-spwebapplication $webapp
function AddPerm ([Microsoft.SharePoint.SPList] $TargetObj, [string] $RoleValue, [string] $RoleGroup)
{ #SPWeb is implied and not passed as parms for efficiency!
if ((!$RoleValue) -or (!$RoleGroup))
{
return; #race to be efficient on NullOp
}
try
{
$user = $SPWeb.ensureuser($RoleGroup)
$roledef = $SPWeb.RoleDefinitions[$RoleValue]
$roleass = New-Object Microsoft.SharePoint.SPRoleAssignment($user)
$roleass.RoleDefinitionBindings.Add($roledef)
$TargetObj.RoleAssignments.Add($roleass)  
}
catch
{
Write-Host -ForegroundColor DarkRed "ERR: Can't Assign $($RoleGroup)"
}
}
for ($i=0; $i -lt $WebApp.Sites.Count; $i++)
{
$site=$webapp.Sites[$i];
$SPWeb=$site.rootweb;
$list = $SPWeb.Lists["TaxonomyHiddenList"]
addPerm $list "Read" "SharePointNT Authenticated Users"
}