{"id":967,"date":"2012-01-20T06:54:06","date_gmt":"2012-01-20T06:54:06","guid":{"rendered":"https:\/\/poiseddevelopers.com\/reality-tech\/?p=967"},"modified":"2024-04-26T12:47:16","modified_gmt":"2024-04-26T12:47:16","slug":"customized-taxonomy-creation-from-a-feedfile","status":"publish","type":"post","link":"https:\/\/poiseddevelopers.com\/reality-tech\/customized-taxonomy-creation-from-a-feedfile\/","title":{"rendered":"Customized Taxonomy Creation from a feedfile"},"content":{"rendered":"<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_65 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title \" >Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 eztoc-toggle-hide-by-default' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/poiseddevelopers.com\/reality-tech\/customized-taxonomy-creation-from-a-feedfile\/#Observations\" title=\"Observations\">Observations<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/poiseddevelopers.com\/reality-tech\/customized-taxonomy-creation-from-a-feedfile\/#Tips_and_tricks\" title=\"Tips and tricks\">Tips and tricks<\/a><\/li><\/ul><\/nav><\/div>\n<h4>Creating a customized Taxonomy from a feed file<\/h4>\n<p>There\u2019s an native SharePoint 2013 capability to import termsets using a CSV. However it\u2019s limited in structure, produces no logs, and if it encounters a problem, it fails without indicating where it failed, and it won\u2019t continue the import after the problem entry.<\/p>\n<p>For industrial strength Taxonomy loads, rolling your own is the only way.<\/p>\n<p>Here\u2019s a script that is easily adapted and extended. It detects the first letter of the term, and files the terms by the first letter. Then uses two more levels to create a 3 level hierarchy<\/p>\n<p>When loading terms, terms are committed in batches. The larger the batch, the faster the load. However for most one-off loads, I recommend using a batch size of 1, so any errors are immediately addressable and localized to one term.<\/p>\n<p>Taxonomy basics<br \/>\nFirst, grab a taxonomy session<\/p>\n<div>\n<div id=\"highlighter_895318\" class=\"syntaxhighlighter powershell\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td class=\"gutter\">\n<div class=\"line number1 index0 alt2\">1<\/div>\n<\/td>\n<td class=\"code\">\n<div class=\"container\">\n<div class=\"line number1 index0 alt2\"><code class=\"powershell variable\">$taxonomySession<\/code> <code class=\"powershell plain\">= <\/code><code class=\"powershell functions\">Get-SPTaxonomySession<\/code> <code class=\"powershell color1\">-Site<\/code> <code class=\"powershell variable\">$TaxSite<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>Now let\u2019s grab the termstore for our target Service Application<\/p>\n<div>\n<div id=\"highlighter_526408\" class=\"syntaxhighlighter powershell\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td class=\"gutter\">\n<div class=\"line number1 index0 alt2\">1<\/div>\n<\/td>\n<td class=\"code\">\n<div class=\"container\">\n<div class=\"line number1 index0 alt2\"><code class=\"powershell variable\">$termStore<\/code> <code class=\"powershell plain\">= <\/code><code class=\"powershell variable\">$taxonomySession<\/code><code class=\"powershell plain\">.TermStores[<\/code><code class=\"powershell variable\">$ServiceName<\/code><code class=\"powershell plain\">]<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>Finally, we can grab our target group<\/p>\n<div>\n<div id=\"highlighter_104444\" class=\"syntaxhighlighter powershell\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td class=\"gutter\">\n<div class=\"line number1 index0 alt2\">1<\/div>\n<\/td>\n<td class=\"code\">\n<div class=\"container\">\n<div class=\"line number1 index0 alt2\"><code class=\"powershell variable\">$group<\/code> <code class=\"powershell plain\">= <\/code><code class=\"powershell variable\">$termStore<\/code><code class=\"powershell plain\">.Groups[<\/code><code class=\"powershell variable\">$GroupName<\/code><code class=\"powershell plain\">]<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>Lastly, we can grab our target termset if it exists<\/p>\n<div>\n<div id=\"highlighter_578107\" class=\"syntaxhighlighter powershell\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td class=\"gutter\">\n<div class=\"line number1 index0 alt2\">1<\/div>\n<\/td>\n<td class=\"code\">\n<div class=\"container\">\n<div class=\"line number1 index0 alt2\"><code class=\"powershell variable\">$termSet<\/code> <code class=\"powershell plain\">= <\/code><code class=\"powershell variable\">$group<\/code><code class=\"powershell plain\">.TermSets | <\/code><code class=\"powershell functions\">Where-Object<\/code> <code class=\"powershell plain\">{ <\/code><code class=\"powershell variable\">$_<\/code><code class=\"powershell plain\">.Name <\/code><code class=\"powershell operator value\">-eq<\/code> <code class=\"powershell variable\">$termSetName<\/code> <code class=\"powershell plain\">}<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>Or create a new termset:<\/p>\n<div>\n<div id=\"highlighter_195438\" class=\"syntaxhighlighter powershell\">\n<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td class=\"gutter\">\n<div class=\"line number1 index0 alt2\">1<\/div>\n<div class=\"line number2 index1 alt1\">2<\/div>\n<\/td>\n<td class=\"code\">\n<div class=\"container\">\n<div class=\"line number1 index0 alt2\"><code class=\"powershell variable\">$termSet<\/code> <code class=\"powershell plain\">= <\/code><code class=\"powershell variable\">$group<\/code><code class=\"powershell plain\">.CreateTermSet(<\/code><code class=\"powershell variable\">$termSetName<\/code><code class=\"powershell plain\">)<\/code><\/div>\n<div class=\"line number2 index1 alt1\"><code class=\"powershell variable\">$termStore<\/code><code class=\"powershell plain\">.CommitAll()<\/code><\/div>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p data-uw-rm-sr=\"\">Let\u2019s grab one or more matching terms. Setting the value below to $true avoids untaggable terms, like parent company at higher tier, but we want to find unavailable tags here<\/p>\n<pre lang=\"php\">   [Microsoft.SharePoint.Taxonomy.TermCollection] $TC = $termSet.GetTerms($CurrentLetter,$false)<\/pre>\n<p>Let\u2019s see what matching terms we have:<\/p>\n<pre lang=\"php\">   if ($TC.count -eq 0)\r\n{\r\n  write-host \"No Matching Terms Found!\"\r\n}\r\nelse\r\n{\r\n  write-host \"$($TC.Count) Matching Terms Found!\"\r\n}<\/pre>\n<p>Let\u2019s create a Term2 beneath an existing Term1, then set a description value:<\/p>\n<pre lang=\"php\"> $Lev2TermObj=$Lev1TermObj.createterm($Lev2Term,1033);\r\n        $Lev2TermObj.SetDescription($Description,1033)<\/pre>\n<p>That covers some of the basics. Let\u2019s put it together into a useful script:<\/p>\n<pre lang=\"php\"> #CREATES  AND POPULATES A FULL HIERARCHICAL TERMSET\r\n#Later we can add details to termset\r\n#term.SetDescription()\r\n#term.CreateLabel\r\n#KNOWN PROBLEM: batch will fail if there are duplicate names in the batch, preventing clean restart unless batch size = 1\r\n \r\n$snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}\r\n    if ($snapin -eq $null)\r\n    {\r\n      Add-PSSnapin Microsoft.SharePoint.PowerShell\r\n    }\r\n \r\n$env=\"Prod\"\r\n$termSetName = \"YourTermset\"\r\n \r\n$SourceCSV=\"L:PowerShellTaxTabDelimitedTermsetFeed.txt\"\r\n \r\n#set the batch size; 100+ for speed, reduce to 1 to catch errors\r\n$batchSize=1;\r\n$BatchNum=0;\r\n \r\nif ($env -eq \"Dev\")\r\n{\r\n    $TaxSite = \"http :\/\/SharePoint Dev\"\r\n    $ServiceName=\"Managed Metadata Service\"\r\n    $GroupName=\"TermGroupName\"\r\n}\r\nelseif ($env -eq \"Prod\")\r\n{\r\n    $ServiceName=\"Managed Metadata Services\"\r\n    $TaxSite = \"http :\/\/SharePoint\"\r\n    $GroupName=\"TermGroupName\"\r\n}\r\n \r\ntry\r\n{\r\n$StartTime=get-date;\r\nWrite-Host -ForegroundColor DarkGreen \"Reading CSV...$($StartTime)\"\r\n$Terms=Import-Csv $SourceCSV -Delimiter \"`t\"\r\n$ReadSourceTime=Get-Date;\r\n$Duration=$ReadSourceTime.subtract($StartTime)\r\nWrite-Host -ForegroundColor DarkGreen \"Read in $($Terms.count) items from $($SourceCSV) in $($duration.TotalSeconds) Seconds\"\r\n}\r\ncatch\r\n{\r\nWrite-Host -ForegroundColor DarkRed \"Could not read in $($SourceCSV)\"\r\n}\r\n    #first let's grab a taxonomy session\r\n    $taxonomySession = Get-SPTaxonomySession -Site $TaxSite\r\n    #plural Now let's grab the termstore for our target Service Application\r\n    $termStore = $taxonomySession.TermStores[$ServiceName]\r\n    #Finally, we can grab our target group\r\n    $group = $termStore.Groups[$GroupName]\r\n \r\n    $termSet = $group.TermSets | Where-Object { $_.Name -eq $termSetName }\r\n    if($termSet -eq $null)  # will have to create a new termset\r\n    {\r\n        try\r\n        {\r\n            $termSet = $group.CreateTermSet($termSetName)\r\n            $termStore.CommitAll()\r\n            Write-Host \"Created Successfully $($termSetName) TermSet\"\r\n        }\r\n        catch\r\n        {\r\n            Write-Host \"Whoops, could not create $($termSetName) TermSet\"\r\n        }\r\n \r\n    }\r\n    else #termset already exists\r\n    {\r\n    Write-Host \"Nice, termset $($TermSetName) already exists\"\r\n    }\r\n \r\n$CurrentLetter=$LastParentTerm=$null; # track previous parent, to determine whether to create a parent\r\n \r\nfor ($i=0; $i -lt $Terms.count; $i++)\r\n{\r\n$Lev1Term=$Terms[$i].\"Level 1 Term\"\r\n$Lev2Term=$Terms[$i].\"Level 2 Term\"\r\n \r\nif ($LastParentTerm -ne $Lev1Term)\r\n{\r\n    $LastParentTerm=$Lev1Term;\r\n    if ($LastParentTerm[0] -ne $CurrentLetter)  #create a new letter!\r\n    {\r\n        $CurrentLetter=$LastParentTerm[0];\r\n        #setting to $true avoids untaggable terms, like parent company at higher tier, but we want to find unavailable tags here\r\n        [Microsoft.SharePoint.Taxonomy.TermCollection] $TC = $termSet.GetTerms($CurrentLetter,$false)\r\n        if ($TC.count -eq 0)\r\n        {\r\n            $CurrentLetterTerm=$termSet.createterm($CurrentLetter,1033);\r\n            $CurrentLetterTerm.set_IsAvailableForTagging($false);\r\n        }\r\n        else\r\n        {\r\n            $CurrentLetterTerm=$TC[0]\r\n        }\r\n \r\n    }\r\n \r\n    #first try to find existing level1 term before trying to create the term.  This is needed for incremental loads\r\n    [Microsoft.SharePoint.Taxonomy.TermCollection] $TC = $termSet.GetTerms($Lev1Term,$false)\r\n    if ($TC.count -ge 1)  #Term found.  So use it\r\n    {   #assume only one hit possible, if more than one found, just use first, as precise parent is less important in this case\r\n        $Lev1TermObj=$TC[0];\r\n    }\r\n    else # no term found, so create it\r\n    {   #in this case, all parent terms are not available, this logic is for extensibility only\r\n        $Lev1TermObj=$CurrentLetterTerm.createterm($Lev1Term,1033);\r\n        if ($Terms[$i].\"available\" -eq \"FALSE\")  #careful, if term2 has a new term1, the term1 will be created as available for tagging\r\n        {\r\n            $Lev1TermObj.set_IsAvailableForTagging($false);\r\n        }\r\n        else\r\n        {  #we choose not to tag this level as available, so force level1 to always unavailable.\r\n        $Lev1TermObj.set_IsAvailableForTagging($false);\r\n        }\r\n    }\r\n} #term1 unchanged, so this above was handling new terms or finding terms, below is just term2 handling. Note hole, in case term is being loaded that exists already\r\n    try\r\n    {\r\n    if ($Lev2Term.get_length() -ne 0)  #bypasses my habit of new parent terms with empty level 2, can be zero length and not null\r\n        {\r\n        $Lev2TermObj=$Lev1TermObj.createterm($Lev2Term,1033);\r\n \r\n        $Description=$Terms[$i].\"Description\"\r\n        if ($Description.get_Length() -ne 0)\r\n        {\r\n            try\r\n            {\r\n                $Lev2TermObj.SetDescription($Description,1033)\r\n            }\r\n            catch\r\n            {\r\n            Write-Host -ForegroundColor DarkRed \"Failed to set description on $($i)\"\r\n            }\r\n        }\r\n \r\n        }\r\n    }\r\n    catch\r\n    {\r\n    Write-Host -ForegroundColor DarkRed \"Could not create $($terms[$i])\"\r\n    }\r\n    if (($i % $batchSize) -eq ($batchSize-1))   #some quick modulus math\r\n    {\r\n    $BatchNum++;\r\n        try\r\n        {\r\n        $termStore.CommitAll();\r\n        Write-Host -ForegroundColor darkgreen \"Committed terms in batch: $($BatchNum)\"\r\n        }\r\n        catch\r\n        {\r\n        Write-Host -ForegroundColor darkred \"FAILED commiting terms in batch: $($BatchNum), Index: $($i)\"\r\n        }\r\n    }\r\n}\r\n \r\n$termStore.CommitAll();  #in subsequent ophase, try to commit a batch at a time\r\n\r\n<\/pre>\n<h2><span class=\"ez-toc-section\" id=\"Observations\"><\/span>Observations<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>1. CSV loads fast, and is cached, so subsequent loads are extremely fast<br role=\"presentation\" data-uw-rm-sr=\"\" \/>2. Batching speeds things, but not as much as one might imagine<br role=\"presentation\" data-uw-rm-sr=\"\" \/>3. Once a batch fails due to a duplicate name, the whole process is messed up and the script needs re-running<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Tips_and_tricks\"><\/span>Tips and tricks<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>1. Sort source CSV by Term1 () then by Available<br role=\"presentation\" data-uw-rm-sr=\"\" \/>2. Eliminate leading blanks for terms using Trim()<br role=\"presentation\" data-uw-rm-sr=\"\" \/>3. Ensure no dups in advance<br role=\"presentation\" data-uw-rm-sr=\"\" \/>4. Sort so all term levels are grouped together, otherwise an attempt to create the second set of Term1 will fail<\/p>\n<p>Enjoy!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Creating a customized Taxonomy from a feed file There\u2019s an native SharePoint 2013 capability to import termsets using a CSV. However it\u2019s limited in structure, produces no logs, and if it encounters a problem, it fails without indicating where it failed, and it won\u2019t continue the import after the problem entry. For industrial strength Taxonomy [&hellip;]<\/p>\n","protected":false},"author":10,"featured_media":991,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[26],"tags":[],"class_list":["post-967","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell"],"acf":[],"_links":{"self":[{"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/posts\/967","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/users\/10"}],"replies":[{"embeddable":true,"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/comments?post=967"}],"version-history":[{"count":1,"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/posts\/967\/revisions"}],"predecessor-version":[{"id":992,"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/posts\/967\/revisions\/992"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/media\/991"}],"wp:attachment":[{"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/media?parent=967"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/categories?post=967"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/poiseddevelopers.com\/reality-tech\/wp-json\/wp\/v2\/tags?post=967"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}