In my previous post I showed you how to Export your Taxonomy from SharePoint, either on premise or Online. It was a PowerShell script that using CSOM reads in the Taxonomy and saves the information to an XML file.
However to import this XML file back into SharePoint, you will require the Import PowerShell script too. This blog post will explain the separate functions within the Import PowerShell. At the end of this blog post you can download the full PowerShell file from my OneDrive. When this PowerShell is run it will check to ensure that the Group, TermSet, Term is not already there, if it cannot find it then it will add it. This can be useful if you were trying to merge (assuming ID’s are the same), or restore terms that may have been deleted from an Export you done as a back up.
Breaking the PowerShell file down.
Parameters
There are 5 different parameters and they are all Manatory.
AdminUser
The user who has administrative access to the term store. (e.g., On-Prem: Domain\user or for 365: user@sp.com)
AdminPassword
The password for the Admin User.
AdminUrl
The URL of Central Admin for On-Prem or Admin site for 365
FilePathOfExportXMLTerms
The path of the Exported XML file, which you have created using the Export Script.
PathToSPClientdlls
The script requires to call the following dlls:
Microsoft.SharePoint.Client.dll
Microsoft.SharePoint.Client.Runtime.dll
Microsoft.SharePoint.Client.Taxonomy.dll
(e.g., C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI) Or you can download SharePoint Client SDK which will have the dll’s
Load and Connect to SharePoint Function
This function will return a SPContext, it will use CSOM to connect to the SharePoint Admin site. There is a line of code that checks if the URL contains “.sharepoint.com”, we can assume that if it does then it is an online SharePoint tenant and will require to connect using a different sign in method. The function requires the SharePoint admin URL, User, Password and path to the client dlls. This is also the same function and code used in the Export script.
function LoadAndConnectToSharePoint($url, $user, $password, $dllPath){ #Convert password to secure string $securePassword = ConvertTo-SecureString $password -AsPlainText -Force #Get SPClient Dlls Path $spClientdllsDir = Get-Item $dllPath #Add required Client Dlls Add-Type -Path "$($spClientdllsDir.FullName)\Microsoft.SharePoint.Client.dll" Add-Type -Path "$($spClientdllsDir.FullName)\Microsoft.SharePoint.Client.Runtime.dll" Add-Type -Path "$($spClientdllsDir.FullName)\Microsoft.SharePoint.Client.Taxonomy.dll" $spContext = New-Object Microsoft.SharePoint.Client.ClientContext($url) if($url.Contains(".sharepoint.com")) # SharePoint Online { $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($User, $securePassword) } else # SharePoint On-premises { $networkCredentials = new-object -TypeName System.Net.NetworkCredential $networkCredentials.UserName = $user.Split('\')[1] $networkCredentials.Password = $password $networkCredentials.Domain = $user.Split('\')[0] [System.Net.CredentialCache]$credentials = new-object -TypeName System.Net.CredentialCache $uri = [System.Uri]$url $credentials.Add($uri, "NTLM", $networkCredentials) } #See if we can establish a connection $spContext.Credentials = $credentials $spContext.RequestTimeOut = 5000 * 60 * 10; $web = $spContext.Web $site = $spContext.Site $spContext.Load($web) $spContext.Load($site) try { $spContext.ExecuteQuery() Write-Host "Established connection to SharePoint at $Url OK" -foregroundcolor Green } catch { Write-Host "Not able to connect to SharePoint at $Url. Exception:$_.Exception.Message" -foregroundcolor red exit 1 } return $spContext }Get TermStore Info
This function using the SPContext will return the TermStore. If you have multiple termstores (which you might have in your on prem environment) you might need to modify the code not to just bring back the termstore at index 0. This is also the same function and code used in the Export script.
function Get-TermStoreInfo($spContext){ $spTaxSession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($spContext) $spTaxSession.UpdateCache(); $spContext.Load($spTaxSession) try { $spContext.ExecuteQuery() } catch { Write-host "Error while loading the Taxonomy Session " $_.Exception.Message -ForegroundColor Red exit 1 } if($spTaxSession.TermStores.Count -eq 0){ write-host "The Taxonomy Service is offline or missing" -ForegroundColor Red exit 1 } $termStores = $spTaxSession.TermStores $spContext.Load($termStores) try { $spContext.ExecuteQuery() $termStore = $termStores[0] $spcontext.Load($termStore) $spContext.ExecuteQuery() Write-Host "Connected to TermStore: $($termStore.Name) ID: $($termStore.Id)" } catch { Write-host "Error details while getting term store ID" $_.Exception.Message -ForegroundColor Red exit 1 } return $termStore }Get Terms To Import
This function loads the XML file into a Linq XDocument and then returns the file. This will allow us to read in and loop through the XML.
function Get-TermsToImport($xmlTermsPath){ [Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq") | Out-Null try { $xDoc = [System.Xml.Linq.XDocument]::Load($xmlTermsPath, [System.Xml.Linq.LoadOptions]::None) return $xDoc } catch { Write-Host "Unable to read ExportedTermsXML. Exception:$_.Exception.Message" -ForegroundColor Red exit 1 } }Create Groups
This function get passed the SPContext, the TermStore and the XML file. From the XML file it grab all Group nodes and iterates over them, by grabbing relevant information such as Name, Description, ID. Once it has the ID it calls the SharePoint TermStore you are connecting to, and attempts to get the Group by ID. If nothing is returned then it means the group doesn’t exist and the group is added to the TermStore. If it already exist it moves directly onto creating the TermSets for the group.
function Create-Groups($spContext, $termStore, $termsXML){ foreach($groupNode in $termsXML.Descendants("Group")) { $name = $groupNode.Attribute("Name").Value $description = $groupNode.Attribute("Description").Value; $groupId = $groupNode.Attribute("Id").Value; $groupGuid = [System.Guid]::Parse($groupId); Write-Host "Processing Group: $name ID: $groupId ..." -NoNewline $group = $termStore.GetGroup($groupGuid); $spContext.Load($group); try { $spContext.ExecuteQuery(); } catch { Write-host "Error while finding if " $name " group already exists. " $_.Exception.Message -ForegroundColor Red exit 1 } if ($group.ServerObjectIsNull) { $group = $termStore.CreateGroup($name, $groupGuid); $spContext.Load($group); try { $spContext.ExecuteQuery(); write-host "Inserted" -ForegroundColor Green } catch { Write-host "Error creating new Group " $name " " $_.Exception.Message -ForegroundColor Red exit 1 } } else { write-host "Already exists" -ForegroundColor Yellow } Create-TermSets $termsXML $group $termStore $spContext } try { $termStore.CommitAll(); $spContext.ExecuteQuery(); } catch { Write-Host "Error commiting changes to server. Exception:$_.Exception.Message" -foregroundcolor red exit 1 } }Create TermSets
This function is called from within the Create Groups function. The XML file, current Group, TermStore and SharePoint Context is passed into the function. Firstly from the XML file we grab all TermSets that are related to the current Group. Then we iterate over these TermSets. For the given TermSet we grab all the information in the XML, and then try and get the TermSet using the ID from the TermStore. As with the groups function, if the TermSet doesn’t exists, then the TermSet is created. If when the TermSet is being created an error occurs, a flag is marked so that we can inform the user that an error occurred, but not affect the running and importing of the rest of the XML. If the TermSet does exist then it skips the creation, and checks to see if there are any Terms and call the Create-Term function.
function Create-TermSets($termsXML, $group, $termStore, $spContext) { $termSets = $termsXML.Descendants("TermSet") | Where { $_.Parent.Parent.Attribute("Name").Value -eq $group.Name } foreach ($termSetNode in $termSets) { $errorOccurred = $false $name = $termSetNode.Attribute("Name").Value; $id = [System.Guid]::Parse($termSetNode.Attribute("Id").Value); $description = $termSetNode.Attribute("Description").Value; $customSortOrder = $termSetNode.Attribute("CustomSortOrder").Value; Write-host "Processing TermSet $name ... " -NoNewLine $termSet = $termStore.GetTermSet($id); $spcontext.Load($termSet); try { $spContext.ExecuteQuery(); } catch { Write-host "Error while finding if " $name " termset already exists. " $_.Exception.Message -ForegroundColor Red exit 1 } if ($termSet.ServerObjectIsNull) { $termSet = $group.CreateTermSet($name, $id, $termStore.DefaultLanguage); $termSet.Description = $description; if($customSortOrder -ne $null) { $termSet.CustomSortOrder = $customSortOrder } $termSet.IsAvailableForTagging = [bool]::Parse($termSetNode.Attribute("IsAvailableForTagging").Value); $termSet.IsOpenForTermCreation = [bool]::Parse($termSetNode.Attribute("IsOpenForTermCreation").Value); if($termSetNode.Element("CustomProperties") -ne $null) { foreach($custProp in $termSetNode.Element("CustomProperties").Elements("CustomProperty")) { $termSet.SetCustomProperty($custProp.Attribute("Key").Value, $custProp.Attribute("Value").Value) } } try { $spContext.ExecuteQuery(); } catch { Write-host "Error occured while create Term Set" $name $_.Exception.Message -ForegroundColor Red $errorOccurred = $true } write-host "created" -ForegroundColor Green } else { write-host "Already exists" -ForegroundColor Yellow } if(!$errorOccurred) { if ($termSetNode.Element("Terms") -ne $null) { foreach ($termNode in $termSetNode.Element("Terms").Elements("Term")) { Create-Term $termNode $null $termSet $termStore $termStore.DefaultLanguage $spContext } } } } }Create Term
The Create Term can be called from Create Terms or recursively from itself. It is passed the XML Term, the parent term (which is used for recursive calls, otherwise null), the TermSet that the Term is from, the TermStore, language of the Term and the SPContext. This script just uses your TermStore default language, but with some small code change (I’ll leave that for you) you can allow it to use any language. This function reads in the Terms XML values, then tries and get the Term by ID from the TermStore. If the Term doesn’t exist it will process the term and add it to SharePoint either to the TermSet, or to another Term as a SubTerm. If the Term already exists then it will skip the creation and check to see if it has any subterms in the XML and recursively call itself, otherwise it will return up the stack for the next Term/TermSet/Group.
function Create-Term($termNode, $parentTerm, $termSet, $store, $lcid, $spContext){ $id = [System.Guid]::Parse($termNode.Attribute("Id").Value) $name = $termNode.Attribute("Name").Value; $term = $termSet.GetTerm($id); $errorOccurred = $false $spContext.Load($term); try { $spContext.ExecuteQuery(); } catch { Write-host "Error while finding if " $name " term id already exists. " $_.Exception.Message -ForegroundColor Red exit 1 } write-host "Processing Term $name ..." -NoNewLine if($term.ServerObjectIsNull) { if ($parentTerm -ne $null) { $term = $parentTerm.CreateTerm($name, $lcid, $id); } else { $term = $termSet.CreateTerm($name, $lcid, $id); } $customSortOrder = $termNode.Attribute("CustomSortOrder").Value; $description = $termNode.Element("Descriptions").Element("Description").Attribute("Value").Value; $term.SetDescription($description, $lcid); $term.IsAvailableForTagging = [bool]::Parse($termNode.Attribute("IsAvailableForTagging").Value); if($customSortOrder -ne $null) { $term.CustomSortOrder = $customSortOrder } if($termNode.Element("CustomProperties") -ne $null) { foreach($custProp in $termNode.Element("CustomProperties").Elements("CustomProperty")) { $term.SetCustomProperty($custProp.Attribute("Key").Value, $custProp.Attribute("Value").Value) } } if($termNode.Element("LocalCustomProperties") -ne $null) { foreach($localCustProp in $termNode.Element("LocalCustomProperties").Elements("LocalCustomProperty")) { $term.SetLocalCustomProperty($localCustProp.Attribute("Key").Value, $localCustProp.Attribute("Value").Value) } } try { $spContext.Load($term); $spContext.ExecuteQuery(); write-host " created" -ForegroundColor Green } catch { Write-host "Error occured while create Term" $name $_.Exception.Message -ForegroundColor Red $errorOccurred = $true } } else { write-host "Already exists" -ForegroundColor Yellow } if(!$errorOccurred) { if ($termNode.Element("Terms") -ne $null) { foreach ($childTermNode in $termNode.Element("Terms").Elements("Term")) { Create-Term $childTermNode $term $termSet $store $lcid $spContext } } } }How to Import XML into SharePoint with the PowerShell command.
This example use a SharePoint online account/site, but you can easily point it to an on premise site. To view all details of the PowerShell file has a help file which can be called ./Import-Taxonomy.ps1 – help. This will import the entire XML into your given SharePoint site, it will add any information that is missing from the given TermStore.
./Import-Taxonomy.ps1 -AdminUser user@sp.com -AdminPassword password -AdminUrl https://sp-admin.onmicrosoft.com -FilePathOfExportXMLTerms c:\myTerms\exportedterms.xml -PathToSPClientdlls "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI"Screenshot as Importing to SharePoint Online.
Screen shot of Terms in my On Premise environment.
Screen shot of the same terms imported into my SharePoint Online environment.
Link to the PowerShell file can be found on my One Drive.
Link to blog about Exporting Taxonomy from SharePoint.
Again, lastly I would like to thank tow of my colleagues who without their initial work I wouldn’t have been able to create this final PS1 file. Kevin Beckett and Luis Manez (http://geeks.ms/blog/imanez)