Follow me on Twitter @AntonioMaio2

Monday, November 6, 2017

Set a SharePoint Online Managed Metadata (Taxonomy) Field Value on a Document or Item

Just last week I had an interesting little project I was helping a client with where we needed to use PowerShell to set 2 metadata field values for a document in a SharePoint Online Document Library. I've done this before many times. However, the challenge here was that the metadata fields were managed metadata fields, configured as part of a corporate taxonomy using the Managed Metadata Service.

My preferred method to accomplish this would have been SharePoint PnP PowerShell module using Set-PnPTaxonomyFieldValue (reference: https://msdn.microsoft.com/en-us/pnp_powershell/setpnptaxonomyfieldvalue). Unfortunately, for various reasons that wasn't possible in my client's environment so we had to resort to writing the PowerShell script ourselves.

As many of you know, you cannot set managed metadata field values the same way you would a regular metadata field. In searching the web for some guidance on which methods to use, I found that out of the blogs and documentation available, much of it was either incomplete or incorrect. So, with the help of a few kind folks on Twitter, namely Erwin Van Hunen (@erwinvanhunen) and Chris Kent (@thechriskent), I was able to work out a solution. I'd like to share here how that was accomplished so that others might benefit from Erwin's and Chris' assistance and from my experience. I'll try to be as complete as possible here, so that you have a full solution.

An important step is to download and install the latest SharePoint Online Client Components SDK. At the time of publishing, Microsoft had recently released a new version for September 2017, which can be downloaded from here: https://www.microsoft.com/en-ca/download/details.aspx?id=42038. This must be installed on the computer that will be running this script. Now onto our PowerShell code:

First, we'll set some basic variables:
$UserCredentials = Get-Credential
$webUrl = "https://mytenant.sharepoint.com/sites/mySiteCol/mySubsite/"
$listName = "MyList"
$itemToEdit = "/sites/mySiteCol/mySubsite/MyList/MyDocument.docx"
$termGroupName = "My Term Group"
$targetField1Name = "Field 1"
$targetField2Name = "Field 2"
$targetField1Value = "Value 1"
$targetField2Value = "Value 2"

Now, we setup our paths to the SharePoint Online Client Components SDK. Specifically, notice that we are using Microsoft.SharePoint.Client.Taxonomy.dll. These are the default paths where these DLLs should be installed on your computer.
$sCSOMPath = "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI"
$sCSOMRuntimePath=$sCSOMPath +  "\Microsoft.SharePoint.Client.Runtime.dll"
$sCSOMTaxonomyPath=$sCSOMPath +  "\Microsoft.SharePoint.Client.Taxonomy.dll"
$sCSOMPath=$sCSOMPath +  "\Microsoft.SharePoint.Client.dll"
Add-Type -Path $sCSOMPath
Add-Type -Path $sCSOMRuntimePath
Add-Type -Path $sCSOMTaxonomyPath

Next, we create our context and authenticate:
$context = New-Object Microsoft.SharePoint.Client.ClientContext($WebUrl)
$context.AuthenticationMode = [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserCredentials.UserName, $UserCredentials.Password)
$context.Credentials = $credentials

Now, we retrieve our managed metadata fields from our Document Library and create our Taxonomy fields:
#get the collection of lists in our site and find our list by name
$lists = $context.web.Lists
$context.Load($lists)
$list = $lists.GetByTitle($listName)

#get our target field objects
$field1 = $list.Fields.GetByInternalNameOrTitle($targetField1Name)
$field2 = $list.Fields.GetByInternalNameOrTitle($targetField2Name)
$context.Load($field1)
$context.Load($field2)
$Context.ExecuteQuery()

$txField1 =[Microsoft.SharePoint.Client.ClientContext].GetMethod("CastTo").MakeGenericMethod([Microsoft.SharePoint.Client.Taxonomy.TaxonomyField]).Invoke($Context, $field1)
$txField2 = [Microsoft.SharePoint.Client.ClientContext].GetMethod("CastTo").MakeGenericMethod([Microsoft.SharePoint.Client.Taxonomy.TaxonomyField]).Invoke($Context, $field2)

Next, we create a session with the Managed Metadata Service and retrieve the terms we wish to set. This allows us to validate that the term values we're trying to set actually do exist in the term store.
$session = $spTaxSession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($context)
$session.UpdateCache();
$context.Load($session)

$termStores = $session.TermStores
$context.Load($termStores)
$Context.ExecuteQuery()

$termStore = $TermStores[0]
$context.Load($termStore)
$Context.ExecuteQuery()

$groups = $termStore.Groups
$context.Load($groups)
$Context.ExecuteQuery()

$groupReports = $groups.GetByName($termGroupName)
$context.Load($groupReports)
$context.ExecuteQuery()

$termSetField1 = $groupReports.TermSets.GetByName($targetField1Name)
$termSetField2 = $groupReports.TermSets.GetByName($targetField2Name)
$context.Load($termSetField1)
$context.Load($termSetField2)
$context.ExecuteQuery()

$termsField1 = $termSetField1.GetAllTerms()
$termsField2 = $termSetField2.GetAllTerms()
$context.Load($termsField1)
$context.Load($termsField2)
$context.ExecuteQuery()

foreach($term1 in $termsField1)
{
    if($term1.Name -eq $targetField1Value)
    {
        Write-Host "Found our term in the termset: $($term1.Name) with id: $($term1.id)"
        break
    }
}

foreach($term2 in $termsField2)
{
    if($term2.Name -eq $targetField2Value)
    {
        Write-Host "Found our term in the termset: $($term2.Name) with id: $($term2.id)"
        break
    }
}

if(($term1.Name -ne $targetField1Value) -or ($term2.Name -ne $targetField2Value))
{
    Write-Host "Missing term set values.  Double check that the values you are trying to set exit in the termstore. Exiting."
    exit
}

Finally, find the item we want to edit, check it out (if file checkout is required), update the taxonomy metadata fields and check the document back in:

#get all items in our list
$listItems = $list.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
$context.Load($listitems)
$context.ExecuteQuery()  
$itemFound = $false

foreach($item in $listItems)  
{        
    If($($item["FileRef"]) -eq $ItemToEdit)
    {
        Write-Host "Found our item..."
        $itemFound = $true

        $context.Load($item.File) 
        $context.ExecuteQuery();

        #Checkout the item and assign the content type to the document
        if($item.File.CheckOutType -eq "None")
        {
            $item.File.CheckOut();
            Write-Host "Item has been checked out"
        }      

        #create a brand new field and set our Year managed metadata field
        #you cannot simply reuse the term object you found above in the term store
        $txField1value = New-Object Microsoft.SharePoint.Client.Taxonomy.TaxonomyFieldValue 
        $txField1value.Label = $term1.Name           # the label of your term 
        $txField1value.TermGuid = $term1.Id          # the guid of your term 
        $txField1value.WssId = -1                    # the default value

        $txField1.SetFieldValueByValue($item,$txField1value)

        #create a brand new field and set our Period managed metadata field
        #you cannot simply reuse the term object you found above in the term store
        $txField2value = New-Object Microsoft.SharePoint.Client.Taxonomy.TaxonomyFieldValue 
        $txField2value.Label = $term2.Name           # the label of your term 
        $txField2value.TermGuid = $term2.Id          # the guid of your term 
        $txField2value.WssId = -1                    # the default value

        $txField2.SetFieldValueByValue($item,$txField2value)

        #update the item with all changes
        $item.Update()
        $item.File.CheckIn("Item metadata values have been updated", 1)  

        Write-Host "Item has been checked in with updated metadata values"

        $context.ExecuteQuery();

        break;
    }
}

if($itemFound)
{
    Write-Host "Updating the item's metadata is complete."
}
else
{
    Write-Host "Item not found: $itemToEdit"
}

I'm sure there are ways to make some of this more efficient, and of course the most efficient method of all would be to use the SharePoint PnP PowerShell module using Set-PnPTaxonomyFieldValue (reference: https://msdn.microsoft.com/en-us/pnp_powershell/setpnptaxonomyfieldvalue) if you're environment allows it at the time. However, I wanted to paint a complete picture of what's involved in building such a script if you have to create it from scratch in PowerShell yourself.

Enjoy.
-Antonio

No comments:

Post a Comment