Ben Robb's SharePoint Blog

SharePoint Tales. Or SharePoint Ales. Take your pick…

Manipulating the Terms Store through Powershell

with 11 comments

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…

Advertisement

Written by Ben Robb

May 6, 2010 at 4:42 pm

Posted in Uncategorized

11 Responses

Subscribe to comments with RSS.

  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”.

    Joris

    June 24, 2010 at 7:18 am

    • 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.

      Ben Robb

      July 19, 2010 at 10:04 am

    • 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

      Jason

      August 20, 2010 at 7:42 am

  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

    Phil Childs

    June 29, 2010 at 3:10 pm

  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?

    James

    July 8, 2010 at 10:18 pm

    • 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.

      Paul Grimley

      November 12, 2010 at 2:50 pm

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

    David Remillard

    July 17, 2010 at 10:13 pm

  5. [...] – I could use XML and PowerShell as Ben Robb did. [...]

  6. 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

    brian

    November 11, 2010 at 6:26 pm

  7. 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()

    Mehul Bhuva

    January 20, 2011 at 5:20 pm

  8. # 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()

    Brian T

    February 8, 2011 at 1:36 pm


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 )

Connecting to %s

Follow

Get every new post delivered to your Inbox.