Invalid Terms in Managed Metadata Field

[Updated 2nd July, 2014: I’ve now created a conversation based mind map for this post so if you want to get a deep dive of what’s being done go here – I will be adding a comments section to this map in the future so go here if you want to post comments or ask questions]

MapSnapshotSmall

Recently I came across the problem where the terms inside managed metadata fields in a list become invalid after migrating a site. Even after re-linking the term sets to which the managed metadata fields were assigned, the values for each of the items were invalid. The stupid thing is when I went to edit the field the name would be found straight away in the term store and it was just a matter of clicking on the term again and saving and all would be fine.

After some investigating I found out that the IDs that were being referenced by the items had changed during the migration and thus needed to be updated. Obviously going through 4 columns on over 100+ items wasn’t going to be productive so I wrote up a powershell script to go through each item, find the appropriate term in the term store and update it with the new ID.

Before you use the script you will also need to go through and reconnect the managed metadata fields to the appropriate term sets. I did try to add this functionality into the script itself but unfortunately it appears as though only the GUIDs are stored inside the field meaning I have no idea which term set to use without having access to the original server. If you run the script in ‘test’ mode, it will flag if a field is currently connected to an invalid term set so this will help you narrow down which fields need to be updated.

Update: I’ve setup a CodePlex site at http://sptermstoreutilities.codeplex.com/ where you can download the following script.

Below is the code for the script but I recommend running it in test mode first and making sure that you aren’t receiving any peculiar errors or exceptions. From the SharePoint 2010 Management Shell, use it as so (by the way, I called my script ReloadTermSets.ps1 so just rename that part to whatever you call the script) –

.\ReloadTermSets.ps1 -web “http://localhost” -test -recurse

web – This is the web address to use.
test – This uses a ‘test’ mode that will output lines detailing found fields and the values found for them in the term store and won’t save the items.
recurse – This flag will cause the script to recurse through all child sites.

DISCLAIMER: Use the following script/advice at your own risk! By using/viewing/distributing it you accept responsibility for any loss/corruption of data that may be incurred by said actions. Myself and any contributors to this site accept no liability for any damage that may occur from the use of this information or any advice provided.


param
(
    [string]$web = "",
    [switch]$recurse,
    [switch]$test
)

[Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
[Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Taxonomy")

function UpdateTaxonomyFields
{
    param
    (
        [string]$webAddress = "",
        [switch]$recurse,
        [switch]$test
    )

    $web = Get-SPWeb $webAddress
    $taxonomySession = Get-SPTaxonomySession -Site $web.Site

    $taxonomyType = [Type]::GetType("Microsoft.SharePoint.Taxonomy.TaxonomyField, Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c", $true)

    foreach ($list in $web.Lists)
    {
        try
        {
            foreach ($field in $list.Fields)
            {
                $query = New-Object Microsoft.SharePoint.SPQuery
                $listItems = $list.GetItems($query)

                if ($field.GetType() -eq $taxonomyType)
                {
                    Write-Host "Entering List '", $list.Title, "' Located At:", $list.DefaultViewUrl, "On Field:", $field.Title

                    if (-not $field.IsTermSetValid)
                    {
                        Write-Host "Current Term Set Invalid:", $field.TermSetId, " Field Name:", $field.Title
                    }

                    foreach ($item in $listItems)
                    {
                        $currentTerms = $item[$field]

                        if (-not [string]::IsNullOrEmpty($currentTerms))
                        {
                            $newTerms = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection $field

                            if ($test)
                            {
                                Write-Host "Term Replacement Starting"
                            }

                            foreach ($term in $currentTerms)
                            {
                                Write-Host $term
                                $fullTermLabel = $term.Label

                                $lastIndexOfTermSeparator = $fullTermLabel.LastIndexOf(":")
                                $termLabel = $fullTermLabel.Substring($lastIndexOfTermSeparator + 1)

                                $existingTerms = $taxonomySession.GetTerms($termLabel, $false)

                                if ($existingTerms.Count -gt 1)
                                {
                                    if ($test)
                                    {
                                        Write-Host "Found Terms"
                                    }

                                    $termIsFound = $false

                                    $matchedTerm = ""

                                    foreach ($foundTerm in $existingTerms)
                                    {
                                        $foundTermPath = $foundTerm.GetPath().Replace(";", ":")

                                        if ($foundTermPath -eq $fullTermLabel)
                                        {
                                            Write-Host "Matching Term Found Found Term:", $foundTermPath, "Full Term:", $fullTermLabel
                                            $termIsFound = $true
                                            $matchedTerm = $foundTerm
                                        }
                                    }

                                    if (-not $termIsFound -and $test)
                                    {
                                        Write-Host "No exact term found"
                                    }
                                    elseif ($termIsFound)
                                    {
                                        Write-Host "Replacing Term:", $fullTermLabel, "New Term:", $matchedTerm.GetPath()
                                        $termValueAsString = "-1" + ";#" + $matchedTerm.GetPath() + [Microsoft.SharePoint.Taxonomy.TaxonomyField]::TaxonomyGuidLabelDelimiter + $matchedTerm.Id.ToString()
                                        Write-Host $termValueAsString
                                        $termValue = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue $termValueAsString
                                        $newTerms.Add($termValue)
                                    }

                                    if ($test)
                                    {
                                        Write-Host "End of Found Terms"
                                    }
                                }
                                elseif ($existingTerms.Count -le 0)
                                {
                                    Write-Host "Term For Item '", $item.Id, "' At List '", $list.Title, "' Located At", $list.DefaultViewUrl, "Has No Matching Term"
                                }
                                else
                                {
                                    if ($test)
                                    {
                                        try
                                        {
                                            Write-Host "    Old Term:", $term.Label, "New Term:", $existingTerms[0].GetPath()
                                        }
                                        catch
                                        {
                                            Write-Host "Error Occurred Full Term Label:", $fullTermLabel, "| Term Label:", $termLabel, "| Found Terms Count:", $existingTerms.Count
                                        }
                                    }
                                    else
                                    {
                                        Write-Host "Replacing Term:", $fullTermLabel, "New Term:", $existingTerms[0].GetPath()
                                        $termValueAsString = "-1" + ";#" + $existingTerms[0].GetPath() + [Microsoft.SharePoint.Taxonomy.TaxonomyField]::TaxonomyGuidLabelDelimiter + $existingTerms[0].Id.ToString()
                                        Write-Host $termValueAsString
                                        $termValue = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValue $termValueAsString
                                        $newTerms.Add($termValue)
                                    }
                                }
                            }

                            try
                            {
                                if ($test)
                                {
                                    Write-Host "Term Replacement Finished"
                                }
                                else
                                {
                                    if ($field.AllowMultipleValues)
                                    {
                                        $field.SetFieldValue($item, $newTerms)
                                        $item.Update()
                                    }
                                    elseif ($newTerms.Count -gt 0)
                                    {
                                        $field.SetFieldValue($item, $newTerms[0])
                                        $item.Update()
                                    }
                                }
                            }
                            catch [System.Management.Automation.ExtendedTypeSystemException]
                            {
                                Write-Host $_.Exception.ToString()
                            }
                        }
                        else
                        {
                            Write-Host "Item Is Empty:", $item.Id, $item.Title
                        }
                    }
                }
            }
        }
        catch [System.Management.Automation.ExtendedTypeSystemException]
        {
            Write-Host $_.Exception.ToString()
        }
    }

    if ($recurse)
    {
        foreach ($childWeb in $web.Webs)
        {
            if ($test)
            {
                Write-Host "Child Web:", $childWeb.Url
            }

            UpdateTaxonomyFields -webAddress $childWeb.Url -test:$test -recurse:$recurse
        }
    }
}

UpdateTaxonomyFields -webAddress $web -test:$test -recurse:$recurse

27 thoughts on “Invalid Terms in Managed Metadata Field

  1. Pingback: CleverWorkarounds » SP2010 – Migrating managed metadata term sets to another farm on another domain

  2. Hi Chris,
    thanks for the wonderful post. I want to migrate manage meta data from one farm to another farm.I have below queries

    Where the Script code need to be placed ? Also i would like to know whether i can use Export-SPMetadataWebServicePartitionData for this purpose . Please let me know

    Thanks in advance

    • Hi Jeni,

      Sorry I’ve been away for the last month. Yes you can use that command to export your managed metadata and use the Import version to obviously import or for import you can use the web interface.

      The script doesn’t need to be placed anywhere in particular but you may need to enable script execution with the “set-executionpolicy” command (http://technet.microsoft.com/en-us/library/ee176961.aspx).

      Hope that helps.

      Kind Regards,
      Chris

  3. I downloaded the tool and used it today; it worked great, thanks. My customer had >1900 items with 16,000 term references that needed to be re-linked after a migration from one server to another.

    When will I be able to rate it on CodePlex? Post Beta?

    • Hi generation12,

      Gee sorry no idea what is with that CodePlex rating tool. I’ve now upgraded it to full release, will have to see if that works.

      Kind Regards,
      Chris

  4. hi Chris,

    i executed the script provided by you in my environment using SharePoint Management Shell. but getting errors in the results.

    ERROR :

    Get-SPWeb : Cannot find an SPSite object that contains the following Id or Url: Empty or Null.
    At c:\Users\sp_farm\Desktop\ReloadTermSets.ps1:20 char:21
    + $web= Geet-SPWeb <<<< $webAddress
    + CategoryInfo : InvaliData: [Get-SPWeb], SPCmdletPipeBindException
    + FullyQualifiedErrorId : Microsoft.SharePoint.PowerShell.SPCmdletGetWeb

    Get-SPTaxonomySession : Cannot bind argument to parameter ‘Site’ because it is null.
    At c:\Users\sp_farm\Desktop\ReloadTermSets.ps1 char:51
    + $taxonomySession = Get-SPTaxonomySession -Site <<<< $web.Site
    +CategoryInfo : InvalidData: (:) [Get-SPTaxonomySession], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.SharePoint.Taxonomy.Cmdlet.SPCmdletGetTaxonomySession.

    You cannot call a method on a null-valued expression.

    Please help me in this…


    Regards,

    Deepak Semwal

    • Hi Chris,

      anyhow I was able to make the script executable, I think I getting the error because my content type was not attached to document library, but not sure about this solution hows the thing worked.

      But again I saw that script is attaching tags to documents which are directly inside library not attaching documents which are inside folder in document library.
      Can you help me in this..?


      Regards,

      Deepak Semwal

      • Hi Deepak,

        Sorry you caught me at a bad time and my week was pretty chaotic at best last week.

        The script queries for all items so I’m not really sure why it wouldn’t do folders inside a document library. Did you have to modify the script at all to get it to work?

        Kind Regards,
        Chris

  5. hi Chris,

    atleast you replied 🙂 i was aware that you must be somewhere outside and waiting for your reply check each day your comments 🙂
    yup Chris I need to modify script to attach documents that are inside folders of document library, which the script is not performing right now. it is only listing the the folders which are inside document library not visiting inside folders. 😦 😦

    Showing :
    Item is empty : 11 Folder1
    Item is empty : 12 Folder2

    I also tried changing document library view to display items without folders 😦 😦 but did’t get success, 😦 😦


    regards,
    Deepak Semwal

    • Hi Chris,

      also I realized one more issue 😦 if there are documents having metadata field with multiple tags(allow multiple values), then the script is also not attaching terms to that document.
      Kindly help me in this…


      Regards,
      Deepak Semwal

      • Deepak,

        I don’t know if you are still looking for an answer regarding Tags not being properly restored for documents inside Folders,

        But here is what i did to resolve it. Update lines 31 and 32 with the following lines.

        $Query = New-Object Microsoft.SharePoint.SPQuery
        $Query.Query = “1”
        $Query.ViewAttributes = “Scope = ‘Recursive'”
        $ListItems = $List.GetItems($Query)

        This will skip folders and retrieves only ListItems.

        But, there was another catch in my case, i had same Term in more than one TermSet, so i had few tags that weren’t still restored because the script only does a query on entire TermStore and not the exact TermSet.

        Hope this helps someone who is looking for an answer for this particular scenario.

  6. Excellent script Chris. That did exactly what I needed. Two notes for successful use with document libraries: 1) all documents must be checked in and 2) require document checkout must be disabled. Thanks again!

  7. Hi Chris,
    I have read your post, thanks for the wonderful post. I have a query regarding mapping my Metadata terms to a site column.

    Lets say we have a metadata mapping in the following hierarchy

    Company
    Finance
    ABC
    abcSub1
    abcSub2
    DEF
    defSub1
    defSub2
    HR
    HIJ
    KLM

    In some cases we would like to map the term ABC to the site column.
    I am able to achieve the mapping of TermSet to the site column , but
    mapping to an individual Term which has sub terms does not seem to work.
    Have you come across something similar?

    Below is the snippet that I use, or am trying to use

    I get the field Id initially – “77500658-53e0-4eec-8d71-3e2132991ae5”

    $site = Get-SPSite “http://server/”
    [Microsoft.SharePoint.Taxonomy.TaxonomyField]$field = $site.RootWeb.Fields | Where-Object { $_.Id -eq “77500658-53e0-4eec-8d71-3e2132991ae5” }

    $session = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site)
    $termstore = $session.TermStores[“Managed Metadata Service”]
    $group = $termstore.Groups | Where-Object { $_.Name -eq “Company” }
    $termSet = $group.TermSets[“Finance”]
    $terms = $termSet.GetTerms(100)
    $term = $terms | ?{$_.Name -eq “ABC”}
    $field.SspId = $termstore.Id
    $field.TermSetId = $term.Id
    # Update the field
    $field.Update()

    Some how , it does not set the value if I use a Term, but the if I use the TermSet ID and set it , it does work fine.

    Thanks,
    Ashwin

  8. Pingback: x-Managed Metadata- Configuration « My SharePoint Toolbox

    • Hi Surya,

      You are correct, if you have access to the original managed service application database you can do a restore using that. Unfortunately in my scenario, I didn’t have access to that original managed service application database, only a term set export.

      In scenarios such as yourself, I too would restore the managed service application using a database backup.

      Thanks for your comment.

  9. Hi Chris,
    Excellent script Chris , thanks for that. Seems script is not getting into Folders inside the List.
    01 Create a new view with Show all items without folders .
    02 change the code $list.Items
    03 Run the script.

    It did FIX some of the terms with Replacing new terms . but didn’t fix all the terms.
    Do you have update for the Script ?

    Appreciate it

  10. Pingback: Managed metadata field values red on restored site | Question and Answer

  11. Hi i run above script but on most of the places i’m getting following error.

    System.Management.Automation.MethodInvocationException: Exception calling “SetFieldValue” with “2” argument(s): “The given guid does not exist in the term store” —> Microsoft.SharePoint.SPFieldValidationException: The given guid does not exist in the term
    store

    Please suggest me some solution. Thanks in Advance.

    • Hi Manish,

      Unfortunately this is not an issue I’ve experienced myself.

      My only suggestion is to run the script through Powershell ISE and debug it. More information on debugging in Powershell ISE can be found at https://technet.microsoft.com/en-us/library/dd819480.aspx.

      If you aren’t sure how to do this, I can’t offer much help beyond providing the script. I don’t consider this script the best way to migrate managed metadata (go to the visual map that I have linked at the top of this page for an analysis on the subject) and it’s provided more as a guide for people with a strong SharePoint development background rather than an off-the-shelf solution as it is an extremely technical solution. If you look at the post here http://www.cleverworkarounds.com/2013/07/02/managed-metadata-fun-troubleshooting-the-taxonomy-update-scheduler/ you will find a better way to migrate managed metadata using the SharePoint Powershell cmdlets.

      Kind Regards,
      Chris Tomich

  12. Hey Chris,

    Would you be able to reconnect the field with the metadata term if the GUID was the same as the source farm? I’m able to export the term group from farm A to farm B with the GUIDs for all the terms, just need the fields in farm B to reconnect using script. You had mentioned you tried it but were unable to since you don’t have the same GUID. Would you be able to do that again?

    • Hi Craig,

      I’m not entirely sure I understand your question but hopefully the following may provide some clarity.

      The way the metadata field works is that it’s ultimately just a “lookup column” and it looks up to a hidden list located on the site that is populated with terms from the Managed Metadata service application. It’s because of this the terms stop detecting when you migrate the terms manually (if you are able to export the term store through the Powershell command line tools for the Managed Metadata service application you can avoid this whole situation altogether).

      What this script does is to match the term used in a list item or document, back to the correct term in the list.

      If you’re able to provide me a little more information about your case I might be able to provide some help, but my recommendation is that you read through the code yourself and observe the hidden list for yourself and make sure you understand the script before running it.

      As I strongly suggest in this post, this script is intended for those that could write it themselves as this could potentially corrupt data and you really need to understand the impact it could have for your environment. Also I’d recommend setting up a test environment and cloning your content database before you run this script in a production environment to make sure it acts as it is intended. Whilst I’ve tested this script in a few environments, I’ve seen so many SharePoint deployments that had weird configurations that I’d be concerned if anyone were to run this script on a production environment without first testing in a cloned test environment.

      Kind Regards,
      Chris Tomich

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