Friday, July 28, 2017

SharePoint export and import terms

SharePoint has a great feature to set terms which can be used to tag contents and enable terms based navigation and several other interesting purposes.

Often times we need to migrate terms from one environment to the other and there is not OOB option to export terms in SharePoint 2013. This is not very difficult with powershell however, and this excellent article makes the scripts available for us:

https://blogs.msdn.microsoft.com/thirusrinivasan1/2015/09/28/sharepoint-exportimport-managed-metadata-terms-through-csv/

While trying to use the above scripts I noticed that the import script checks for IsPinned property on the term to be added and in our environment this property was null rather than false. So I have modified that script slightly and reposting both the script for future reference.

Export Terms




# Outputs CSV of the specified termset from the specificed termstore/group
# Example call:
# .\ExportMMStoCsv.ps1 "https://contoso.com:12345" "Managed Metadata Service"
param ([string]$centralAdminUrl = "https://contoso.com:12345",
[string] $termStoreName = "Managed Metadata Service"
)
Add-PSSnapin microsoft.sharepoint.powershell

$maxLevels = 5

function Export-SPTermStoreGroupTerms()
{

    $isValid = $true;
    $message = "";

    if ($centralAdminUrl.Length -eq 0) { $message = "Please provide a central admin URL"; $isValid = $false; }

    if ($isValid -eq $false)
    {
        write-host "ERROR OCCURRED`t$message"
        write-host "NAME`tExport-SPTermStoreGroupTerms"
        write-host "SYNOPSIS`tReturns a CSV file containing a listing of term names and identifiers from the supplied term set."
        write-host "SYNTAX`tExport-SPTermStoreGroupTerms centralAdminUrl termStoreName termGroupName termSetName outPutDir"
        write-host "EXAMPLES Export-SPTermStoreGroupTerms ""http://sp2010"" ""Managed Metadata Service"" ""Enterprise Metadata"" ""Business Units"""
        return;
    }

    try
    {
        $ErrorActionPreference = "Stop";

        try
        {
            $site = Get-SPSite $centralAdminUrl;
            $taxSession = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site, $true);

            try
            {
                $termStore = $taxSession.TermStores[$termStoreName];

                if ($termStore -ne $null)
                {
                    try
                    {
                        $filename = $centralAdminUrl.Replace("https://","").Replace("/","").Replace(":","")
                        $time = (Get-Date).tostring("ddMMyyyy_hhmmss")
                        $outPutFile =  (Get-Location).Path + [string]::Format("\{0}_{1}.csv", $filename,$time);
                        $sb = new-object System.Text.StringBuilder;
                        $sb.Append("TermGroup, TermSet,Term,Level2, Level3,Level4,Level5,UpdatedTitle,Url,FriendlyUrl,CatalogTargetUrl,CatalogChildTargetUrl,TargetUrl,ChildTargetUrl,LastModified,Action,Status");

                        [Byte[]] $ampersand = 0xEF,0xBC,0x86;

                        foreach ($termGroup in $termStore.Groups)
                        {
                            $termgroupName =  $termGroup.Name.Replace([System.Text.Encoding]::UTF8.GetString($ampersand), "&")

                            if ($termgroupName.equals("system", [stringcomparison]::OrdinalIgnoreCase) -eq $false)
                            {
                                try
                                {
                                    foreach($termSet in $termGroup.TermSets)
                                    {
                                        $termsetName  = $termSet.Name.Replace([System.Text.Encoding]::UTF8.GetString($ampersand), "&")

                                        foreach ($term in $termSet.Terms)
                                        {
                                            write-host $termgroupName ","$termsetName","$termName
                                            $termName = $term.Name.Replace([System.Text.Encoding]::UTF8.GetString($ampersand), "&")

                                            $custProp1 = $term.LocalCustomProperties["_Sys_Nav_SimpleLinkUrl"]
                                            $custProp2 = $term.LocalCustomProperties["_Sys_Nav_FriendlyUrlSegment"]
                                            $custProp3 = $term.LocalCustomProperties["_Sys_Nav_CatalogTargetUrl"];
                                            $custProp4 = $term.LocalCustomProperties["_Sys_Nav_CatalogTargetUrlForChildTerms"];
                                            $custProp5 = $term.LocalCustomProperties["_Sys_Nav_TargetUrl"];
                                            $custProp6 = $term.LocalCustomProperties["_Sys_Nav_TargetUrlForChildTerms"];
                                            $custProp7 = $term.LastModifiedDate.ToString("MM/dd/yyyy HH:mm:ss");

                                            $sb1 = new-object System.Text.StringBuilder;
                                            addOutPutField -sb1 $sb1 -field $termgroupName
                                            addOutPutField -sb1 $sb1 -field $termsetName
                                            addOutPutField -sb1 $sb1 -field $termName
                                          
                                            $path = $sb1.ToString();
                                            addEmptyFields -sb1 $sb1 -count 5
                                           
                                            addOutPutField -sb1 $sb1 -field $custProp1
                                            addOutPutField -sb1 $sb1 -field $custProp2
                                            addOutPutField -sb1 $sb1 -field $custProp3
                                            addOutPutField -sb1 $sb1 -field $custProp4
                                            addOutPutField -sb1 $sb1 -field $custProp5
                                            addOutPutField -sb1 $sb1 -field $custProp6
                                            addOutPutField -sb1 $sb1 -field $custProp7
                                            addEmptyFields -sb1 $sb1 -count 2

                                            $sb.AppendLine();
                                            $sb.Append($sb1.ToString());
                                            $sb1.Clear()
                                            GetChildTerms -term $term -path $path -sb $sb
                                        }                                   
                                    }
                                }
                                catch
                                {
                                    "Unable to acquire the termset from the term group"
                                }
                            }
                        }

                        $sw = new-object system.IO.StreamWriter($outPutFile);
                        $sw.Write($sb.ToString());
                        $sw.close();
                        write-host "Your CSV has been created at $outPutFile";
                    }
                    catch
                    {
                        "Unable to acquire term store group"
                    }
                }
            }
            catch
            {
                "Unable to acquire term store"
            }
        }
        catch
        {
            "Unable to acquire session for the site $centralAdminUrl"
        }
    }
    catch
    {

    }
    finally
    {
        $ErrorActionPreference = "Continue";
    }
}

function addOutPutField($sb1, $field){
    $val = "";
    if ($field)
    {
        $val = $field;
    }

    if($sb1.Length -gt 0)
    {
        $sb1.AppendFormat(",{0}",$val);
    }
    else
    {
        $sb1.Append($val);
    }
}

function addEmptyFields($sb1, $count){
    for($i=1; $i -le $count; $i++)
    {
        $sb1.Append(",");
    }
}

function GetChildTerms($term, [object]$path, $sb){
    if ($term.TermsCount -gt 0)
    {
        foreach ($childterm in $term.terms)
        {
            $termName = $childterm.Name.Replace([System.Text.Encoding]::UTF8.GetString($ampersand), "&")
            $custProp1 = $childterm.LocalCustomProperties["_Sys_Nav_SimpleLinkUrl"]
            $custProp2 = $childterm.LocalCustomProperties["_Sys_Nav_FriendlyUrlSegment"]
            $custProp3 = $childterm.LocalCustomProperties["_Sys_Nav_CatalogTargetUrl"];
            $custProp4 = $childterm.LocalCustomProperties["_Sys_Nav_CatalogTargetUrlForChildTerms"];
            $custProp5 = $childterm.LocalCustomProperties["_Sys_Nav_TargetUrl"];
            $custProp6 = $childterm.LocalCustomProperties["_Sys_Nav_TargetUrlForChildTerms"];
            $custProp7 = $childterm.LastModifiedDate.ToString("MM/dd/yyyy HH:mm:ss");

            $sb11 = new-object System.Text.StringBuilder;
            addOutPutField -sb1 $sb11 -field $path
            addOutPutField -sb1 $sb11 -field $termName
            [object] $childpath = $sb11.ToString();
            $fields = $sb11.ToString().split(',')
            addEmptyFields -sb1 $sb11 -count ($maxLevels - $fields.length + 3)
            addOutPutField -sb1 $sb11 -field $custProp1
            addOutPutField -sb1 $sb11 -field $custProp2
            addOutPutField -sb1 $sb11 -field $custProp3
            addOutPutField -sb1 $sb11 -field $custProp4
            addOutPutField -sb1 $sb11 -field $custProp5
            addOutPutField -sb1 $sb11 -field $custProp6
            addOutPutField -sb1 $sb11 -field $custProp7
            addEmptyFields -sb1 $sb11 -count 2

            $sb.AppendLine();
            $sb.Append($sb11.ToString());
            write-host $childpath;
            $sb11.Clear()
            #$sw.writeline($towrite);
           
            GetChildTerms -term $childterm -path $childpath -sb $sb
        }
    }

    $path = "";
}

Export-SPTermStoreGroupTerms


Wednesday, July 26, 2017

Text truncate in bootstrap

The css property text-overflow: ellipsis; works well for containers having width setting other than percentage, however since most responsive frameworks like Bootstrap set widths as a percentage value, it becomes somewhat more difficult to achieve this behavior.

A very nice alternative as suggested at http://collaboradev.com/2015/03/28/responsive-css-truncate-and-ellipsis/#codesyntax_2  is to add another div inside our container element with css properties display: table;  and table-layout: fixed;


.truncate-ellipsis {
    display: table;
    table-layout: fixed;
    width: 100%;
    white-space: nowrap;
}
 
.truncate-ellipsis > * {
    display: table-cell;
    overflow: hidden;
    text-overflow: ellipsis;
}

<div class="my-container">
<div class="truncate-ellipsis">
<span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam porta tortor vitae nisl tincidunt varius. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel ullamcorper tortor. Nullam vel turpis a augue tempor posuere vel quis nibh. Nam ultrices felis turpis, at commodo ipsum tristique non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse in accumsan dui, finibus pharetra est. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque vitae velit eu dui rutrum pellentesque vel imperdiet sem. Morbi ut lacinia lacus, in commodo nibh. Sed cursus ante ut nunc molestie viverra. </span>
</div>
</div>

Tuesday, July 25, 2017

MDX Filter dimension members and attributes

Sometimes we need to filter members of a dimension based on value of one attribute and fetch value of another attribute of the same member.

This is relatively simple in a query, like so:

WITH
   MEMBER [Measures].[Label] AS [Geography].[Country].CURRENTMEMBER.MEMBER_CAPTION
SELECT {[Measures].[Label]} ON COLUMNS,
       NON EMPTY { ([Geography].[State-Province].&[FL]&[US] ) } ON ROWS

FROM  [Adventure Works]

However notice that we need to know the country code 'US' to specify state Florida's value. The query gets even more complex when we only know state code 'FL' and want to use the country label as a calculated measure. Here we need to filter the dimension members based on one attribute value and return caption of another attribute of the same dimension member.

So the query changes to:

WITH
   MEMBER [Measures].[Label] AS (filter(
   [Geography].[Country].children * [Geography].[State-Province].children, Instr( [Geography].[State-Province].currentmember.Properties( 'Member_Caption' ), 'FL')  > 0)
   ).item(0).item(0).membervalue

SELECT {[Measures].[Label]} ON COLUMNS
FROM  [Adventure Works]

And the result is:


More reference on querying dimension members:

Wednesday, July 19, 2017

Whole page scrollbar in sharepoint

Removing id="s4-workspace" in the masterpage is the easiest way to enable scrollbar on whole page in SharePoint. This works for SP 2013 as well.

Reference: https://stackoverflow.com/questions/2984849/having-whole-page-in-sharepoint-2010-scroll-including-header-and-ribbon

Secure micro services using jwt and ocelot

  Secure Microservices Using JWT With Ocelot in .NET Core (code-maze.com)