Monday, February 10, 2014

SharePoint Online Taxonomy Term Group Copy in PowerShell/CSOM

I had a dream late in the night of Friday, February 7th, 2014. I dreamed I was creating a PowerShell/CSOM script - in a mad scientist fashion - that copied every asset, configuration, and data row from one SharePoint Online instance to another. It was going very smoothly in my dream, and the script came to life like the robotic personal assistant in the Spike Jonze movie, “Her”. I fell in love with my script, and I gave her cyan and magenta Write-Host lines to communicate with me. Her name was Elise. The script became my girlfriend and went to a bar with me to play darts. We were just about to kiss when I woke up.

I mused about the script for the rest of the weekend and arrived at work on Monday eager to start the script development process.  Below is my first pass of a script that copies a named Term Group from one Office365 tenant to another.  To test it, I created two 30-day demo Office 365 Enterprise tenancies. In my source tenancy, I opened the term store manager of the SharePoint Online instance and added a group, some term sets, and some terms:

elsie - source term tree

My overall goal was to migrate the “Wef Test” Term Group from one Office 365 to another.  The absolute first thing to do before you accomplish this is to to grant your O365 “Company Administrator” permissions to your term store:

1) Hit the “SharePoint admin center” of your tenant.

2) Click the “term store” link on the left navigation menu.

3) Under the “GENERAL” tab find the people picker for “Term Store Administrators”

4) Search for “Company” in the people picker control, and select “Company Administrator”.  Hit OK.

5) You can confirm that a random SID appears in the “Term Store Administrators” box

6) Hit “Save” at the bottom of the page. (The “Save” button was below the fold for me…)

After both tenancies have the “Company Administrator” account set with a the term store administrator permission, you can start to edit variables in the script.  You need source and destination URLs, names of your administrator accounts, and the passwords of these accounts. (Note: This is your Office 365 User ID. Sample naming structures are in the script below.)  You also need the name of the Term Group you wish to run the migration against. In the script below, I have this variable set to “Wef Test”, but you should use your own term group name.

Picture of the results from running the script (notice the cyan and magenta output):

elise-run

The script worked like a charm and preserved Term Set and Term GUIDs, which is important if you are using any sandbox features that deploy site columns or content types.

Final note  - The output line that says: “Default Term Store connected” contains the GUID of the Term Store, which is handy for taxonomy field site column definitions. This is the elusive “SspId” property of the “TaxonomyFieldType”.

 

Here’s the script:

#######################

#

# "Elise", a script to migrate a single O365 SharePoint Online Taxonomy Term group across O365 instances

#

# Copyright 2014, John Wefler, RightPoint Consulting, LLC.

#

#######################



# Change the following to reflect your environments



# 1) Source Site

$sUrl = "https://wefnet069.sharepoint.com/"

$sAdmin = "[email protected]"

$sPwd = "yourmomwears69combatboots"



# 2) Destination Site

$dUrl = "https://wefnet070.sharepoint.com/"

$dAdmin = "[email protected]"

$dPwd = "yourmomwears70combatboots"



# 3) What Term Group do you want to synchronize?

$sTermGroupName = "Wef Test"



## Stop here

$lcid = "1033"



$sSecurePwd = ConvertTo-SecureString $sPwd -AsPlainText -Force

$dSecurePwd = ConvertTo-SecureString $dPwd -AsPlainText -Force



# these aren't required for the script to run, but help to develop

Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"

Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"

# doh

Add-Type -Path "c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Taxonomy.dll"



# connect/authenticate to SharePoint Online and get ClientContext object.. 

$sCtx = New-Object Microsoft.SharePoint.Client.ClientContext($sUrl)

$sCredentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($sAdmin, $sSecurePwd)

$sCtx.Credentials = $sCredentials



$dCtx = New-Object Microsoft.SharePoint.Client.ClientContext($dUrl)

$dCredentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($dAdmin, $dSecurePwd)

$dCtx.Credentials = $dCredentials



$continue = 0



if (!$dCtx.ServerObjectIsNull.Value)

{

    Write-Host "Connected to DESTINATION SharePoint Online site: " $dCtx.Url "" -ForegroundColor Green

    $dTaxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($dCtx)

    $dTaxonomySession.UpdateCache()

    $dCtx.Load($dTaxonomySession)

    $dCtx.ExecuteQuery()

    if (!$dTaxonomySession.ServerObjectIsNull)

    {

        Write-Host "Destination Taxonomy session initiated: " $dTaxonomySession.Path.Identity "" -ForegroundColor Green



        $dTermStore = $dTaxonomySession.GetDefaultSiteCollectionTermStore()

        $dCtx.Load($dTermStore)

        $dCtx.ExecuteQuery()



        if ($dTermStore.IsOnline) 

        {

            Write-Host "...Default Term Store connected:" $dTermStore.Id "" -ForegroundColor Green

            # $termStoreId will be the SspId in the taxonomy column configs



            $continue = 1

        }

    }

}

 

if (!$sCtx.ServerObjectIsNull.Value -and $continue -eq 1) 

{ 

    Write-Host "Connected to the SOURCE SharePoint Online site: " $sCtx.Url "" -ForegroundColor Green

    

    $sTaxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($sCtx)

    $sTaxonomySession.UpdateCache()

    $sCtx.Load($sTaxonomySession)

    $sCtx.ExecuteQuery()

    if (!$sTaxonomySession.ServerObjectIsNull)

    {

        Write-Host "Source Taxonomy session initiated: " $sTaxonomySession.Path.Identity "" -ForegroundColor Green



        $sTermStore = $sTaxonomySession.GetDefaultSiteCollectionTermStore()

        $sCtx.Load($sTermStore)

        $sCtx.ExecuteQuery()



        if ($sTermStore.IsOnline) 

        {

            Write-Host "...Default Term Store connected:" $sTermStore.Id "" -ForegroundColor Green

            # $termStoreId will be the SspId in the taxonomy column configs

            

            $sCtx.Load($sTermStore.Groups)

            $sCtx.ExecuteQuery()



            foreach ($sTermGroup in $sTermStore.Groups)

            {

                if ($sTermGroup.Name -eq $sTermGroupName)

                {

                    Write-Host ".....Term Group loaded: " $sTermGroup.Name "-" $sTermGroup.Id "" -ForegroundColor Cyan

                    $sCtx.Load($sTermGroup.TermSets)

                    $sCtx.ExecuteQuery()



                    #create this group in the destination context

                    $newGroup = $dTermStore.CreateGroup($sTermGroup.Name, $sTermGroup.Id)

                    $dCtx.Load($newGroup)

                    $dCtx.ExecuteQuery()

                    Write-Host ".....Term Group copied to destination:" $newGroup.Name "" -ForegroundColor Magenta



                    foreach($sTermSet in $sTermGroup.TermSets)

                    {

                        Write-Host ".......Term Set found: " $sTermSet.Name "-" $sTermSet.Id "" -ForegroundColor Cyan

                        $sCtx.Load($sTermSet.Terms)

                        $sCtx.ExecuteQuery()



                        #create new term set in destination context

                        $newTermSet = $newGroup.CreateTermSet($sTermSet.Name, $sTermSet.Id, $lcid)

                        $dCtx.Load($newTermSet)

                        $dCtx.ExecuteQuery()

                        Write-Host ".......Term Set copied to destination:" $newTermSet.Name "" -ForegroundColor Magenta



                        foreach($sTerm in $sTermSet.Terms)

                        {

                            Write-Host ".........Term found: " $sTerm.Name "-" $sTerm.Id "" -ForegroundColor Cyan



                            #create new term in destination context

                            $newTerm = $newTermSet.CreateTerm($sTerm.Name, $lcid, $sTerm.Id)

                            $dCtx.Load($newTerm)

                            $dCtx.ExecuteQuery()

                            Write-Host ".........Term copied to destination:" $newTerm.Name "" -ForegroundColor Magenta

                        }

                    }

                }

            }

        }

    }

}