Manipulating the Terms Store through Powershell

I really like Powershell, for its repeatability, the fact that it means I know that that once I’ve worked out how to do something I can save it in my stash of modules for later, and the way that I can spin up lots of different scenarios very quickly.

For my session on “Build a website in 60 minutes” at SP Evo, I spent a fair amount of time pulling together scripts for manipulating term store data quickly. I could, I guess, have just used the out of the box “import CSV file” option from the UI. But that won’t cut it when going between multiple environments in the real world. I need to be able to do it from my build scripts, and I couldn’t find any way via the API to import from a CSV – it seems that code is locked into the web UI, not baked into the core APIs.

There are other posts out there for how to set up the term store in the first place, both manually and via Powershell, so I’ll not repeat them here. But assuming you have a term store available, here is some very straightforward Powershell to start populating it:

Assuming you have an XML file in this format:

<?xml version="1.0"?>
<termstore name="Managed Metadata Service Application Proxy">  
  <group name="SPEvo">
    <termset name="Services">
      <term name="Customer Engagement">
        <term name="Analytics">
        </term>
        <term name="Search Engine Optimisation">
        </term>
        <term name="Engagement Strategies">
        </term>
      </term>
      <term name="Technology">
        <term name="Technical Build and Delivery">
        </term>
        <term name="Technical Consultancy">
        </term>
        <term name="Technical Design">
        </term>
      </term>
      <term name="User Experience">
        <term name="Creative Design">
        </term>
        <term name="Information Architecture">
        </term>
      </term>
    </termset>
  </group>
</termstore>

You should be able to manipulate that like this:

 $TermStoreData = [xml] (Get-Content ($xmlpath))
 $site = Get-SPSite $url
 $session = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site)
 $termstore = $session.TermStores[$TermStoreData.termstore.name]
 $TermStoreData.termstore.group |
 ForEach-Object {
  ## create the group
  if ($termstore.Groups[$_.name] -eq $null)
  {
   $group = $termstore.CreateGroup($_.name);
   Write-Host -ForegroundColor Cyan "Added group $_.name"
   $_.termset |
   ForEach-Object {
    ## create the termset
    $termset = $group.CreateTermSet($_.name)
    Write-Host -ForegroundColor Cyan "Added termset $_.name"
    SetTermsRecursive -termsetitem $termset -parentnode $_
   }
  }
 }
 $termstore.CommitAll()

Some of the “power” of “Powershell” is in its ability to load up standard .NET objects; in this case there are no out of the box cmdlets to add term store data, so we have to make use of “new-object” to load up the TaxonomySession object. From that we are into native SharePoint code – walking down the XML and creating Term Store Groups, Term Sets and then ultimately Terms. The terms in this example are deployed in a recursive function:

function SetTermsRecursive ([Microsoft.SharePoint.Taxonomy.TermSetItem] $termsetitem, $parentnode)
{
 $parentnode.term |
 ForEach-Object {
  ## create the term
  $newterm = $termsetitem.CreateTerm($_.name, 1033)
  Write-Host -ForegroundColor Cyan "Added term $_.name"
  SetTermsRecursive($termsetitem, $_)
 }
}

Pretty neat? Some core Powershell abilities are showcased here:

  • Walking through XML DOM structures via the “dot” shorthand notation. For example, $TermStoreData.termstore.group is equivalent to $TermStoreData.SelectNodes(“/termstore/group”) – the dot notation means less typing and more readable code.
  • Pipelines – for example, the ForEach-object and pipeline associated $_ syntax.

These can be difficult to read in examples, but I strongly advise anyone starting out in Powershell to get to grips with these, particularly the piping model.

Anyway, the end result is that in a few lines of fairly simple Powershell you can take an XML file and build up a term store with data as part of your deployment model. I’ll post later about why that is important if you ever want to use those term sets in, say a site column declaration…

About these ads
Leave a comment

11 Comments

  1. Your code seems to fail on the following line SetTermsRecursive($termsetitem,$_) within the function with the following error message

    SetTermsRecursive : Cannot process argument transformation on parameter ‘termsetitem’. Cannot convert the “System.Object[]” value of type “System.Object[]” to type “Microsoft.SharePoint.Taxonomy.TermSetItem”.

    Reply
    • Hi Joris / James – I wrote this on RC rather than RTM, so there may be a change to the API. I’ll have a look and post an update.

      Reply
    • Thx for the great tip, Ben!

      Joris, I think a small tweak in the SetTermsRecursive will fix things right up. The method(var1, var2) is c# syntax; calling a powershell method that way makes it look like an array of objects since () only represents order of precedence groupings. Added a null check, otherwise the foreach of the recursive call will try to act on a $null value (see comment / link below). Finally, changed the recursive call from $termsetitem to $newterm.

      if ($_.term -ne $null)
      {
      #powershell treats $null as explicit placeholder; see http://technet.microsoft.com/en-us/library/dd347608.aspx
      SetTermsRecursive $newterm $_
      }

      Best,
      Jason

      Reply
  2. Hi Ben,

    You can use Get-SPTaxonomySession to grab the term store and Import-Csv to create a CSV format of your own. The format provided with SharePoint 2010 doesn’t support synonyms so I have used this method to populate the IPSV terms (11,000+), which are provided as a CSV download to begin with.

    Phil

    Reply
  3. This looks like exactly what i need however I’m getting the following error when i run this:

    Cannot index into a null array.
    At C:\_dev\Scripts\metadata.ps1:39 char:24
    + if ($termstore.Groups[ <<<< $_.name] -eq $null)
    + CategoryInfo : InvalidOperation: (MyGroup:String) [], RuntimeException
    + FullyQualifiedErrorId : NullArray

    Thoughts?

    Reply
    • James, I had the same problem and figured out this was to do with PowerGui. I ran the script in native PowerShell and all worked fine! very strange.

      Reply
  4. Great post, Ben! I love practical, elegant solutions!

    Reply
  5. has anyone been able to successfully put TermStore.CommitAll() anywhere in C# code on a sharepoint site without it breaking? it seems to be difficult to find examples of it, I need a little chat to get the appropriate permissions to use it there

    Reply
  6. Updated Code which resolves the error:Cannot process argument transformation on parameter

    Add-PsSnapin Microsoft.SharePoint.PowerShell

    function SetTermsRecursive ([Microsoft.SharePoint.Taxonomy.TermSetItem] $termsetitem, $parentnode)
    {
    $parentnode.term |
    ForEach-Object {
    ## create the term
    if($_ -ne $null)
    {
    $newterm = $termsetitem.CreateTerm($_.name, 1033)
    Write-Host -ForegroundColor Cyan “Added term $_.name”
    SetTermsRecursive $newterm $_
    }
    }
    }

    #Do not modify anything in the script from here onwards
    function Get-ScriptDirectory
    {
    $Invocation = (Get-Variable MyInvocation -Scope 1).Value
    Split-Path $Invocation.MyCommand.Path
    }

    #Solutions to Deploy
    $XMLName = “ManagedMetaDataTermSets.xml”
    $XMLPath = Join-Path (Get-ScriptDirectory) $XMLName

    echo “Extracting information from the $XMLPath”

    #Site Collection URL – Give your site collection url in quotation marks
    $TaxonomySiteUrl = “http://localhost”

    #Access the TermStore data
    [xml]$TermStoreData = Get-Content ($XMLPath)

    $site = Get-SPSite $TaxonomySiteUrl
    $session = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site)
    $termstore = $session.TermStores[$TermStoreData.termstore.name]

    $TermStoreData.termstore.group |
    ForEach-Object {
    ## create the group
    if ($termstore.Groups[$_.name] -eq $null)
    {
    $group = $termstore.CreateGroup($_.name);
    Write-Host -ForegroundColor Cyan “Added group $_.name”
    $_.termset |
    ForEach-Object {
    ## create the termset
    $termset = $group.CreateTermSet($_.name)
    Write-Host -ForegroundColor Cyan “Added termset $_.name”
    #SetTermsRecursive -termsetitem $termset -parentnode $_
    SetTermsRecursive -termsetitem $termset -parentnode $_
    }
    }
    }
    $termstore.CommitAll()

    Reply
  7. Brian T

     /  February 8, 2011

    # Some fixes for you

    function SetTermsRecursive ([Microsoft.SharePoint.Taxonomy.TermSetItem] $termsetitem, $parentnode, $tab)
    {
    if($parentnode.ChildNodes.Count -gt 0) {
    $parentnode.term | ForEach-Object {
    ## create the term
    $newterm = $termsetitem.CreateTerm($_.name, 1033)
    Write-Host -ForegroundColor Cyan “$tab`t” $_.name

    if($_.ChildNodes.Count -gt 0) {
    SetTermsRecursive $newterm $_ “$tab`t”
    }
    }
    }
    }

    $TermStoreData = [xml] (Get-Content ($ConfigFile))
    $session = Get-SPTaxonomySession -Site $CentralAdminUrl
    Write-host “Count of termstores is ” $session.TermStores.Count
    $termstore = $session.TermStores[$TermStoreData.termstore.name]

    $TermStoreData.termstore.group | ForEach-Object {
    ## create the group
    write-host “Processing group:” $_.name

    if ($termstore.Groups -eq $null -or $termstore.Groups[$_.name] -eq $null)
    {
    Write-Host “Creating group:” $_.name
    $group = $termstore.CreateGroup($_.name);

    $_.termset | ForEach-Object {
    ## create the termset
    Write-Host “Creating termset:” $_.name
    $termset = $group.CreateTermSet($_.name)

    $openSet=[bool]$($_.Open)
    $available=[bool]$($_.AvailableForTagging)

    $termset.IsOpenForTermCreation = $openSet
    $termset.IsAvailableForTagging = $available

    SetTermsRecursive $termset $_ “”
    }
    }
    }
    $termstore.CommitAll()

    Reply
  1. Programmatically: Create many term sets from one csv file « Norpoint's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: