SharePoint 2013 search open in client


Issue

SharePoint 2013 search results uses Excel Calculation Services to open workbooks found in the search results, despite having "open in client" specified on the Document Library and/or the Site Collection level. Notice the URL pointing to _layouts/xlviewer.aspx at the bottom of the screen. In this scenario I only have SharePoint Server 2013 installed without any Office Web Apps servers.
Searching a workbook

 

Initial solution

In my previous encounter with this issue I used a custom Search Result Type with Display Template in order to control the link that is rendered for the search result item.

Item_Excel_Client.html

<html xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"> 
<head>
<title>Excel Client Item</title>

<!--[if gte mso 9]><xml>
<mso:CustomDocumentProperties>
<mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden>
<mso:MasterPageDescription msdt:dt="string">Displays a result tailored for Microsoft Excel documents.</mso:MasterPageDescription>
<mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106603</mso:ContentTypeId>
<mso:TargetControlType msdt:dt="string">;#SearchResults;#</mso:TargetControlType>
<mso:HtmlDesignAssociated msdt:dt="string">1</mso:HtmlDesignAssociated>
<mso:ManagedPropertyMapping msdt:dt="string">'Title':'Title','Path':'Path','Description':'Description','EditorOWSUSER':'EditorOWSUSER','LastModifiedTime':'LastModifiedTime','CollapsingStatus':'CollapsingStatus','DocId':'DocId','HitHighlightedSummary':'HitHighlightedSummary','HitHighlightedProperties':'HitHighlightedProperties','FileExtension':'FileExtension','ViewsLifeTime':'ViewsLifeTime','ParentLink':'ParentLink','FileType':'FileType','IsContainer':'IsContainer','ServerRedirectedURL':'ServerRedirectedURL','ServerRedirectedEmbedURL':'ServerRedirectedEmbedURL','ServerRedirectedPreviewURL':'ServerRedirectedPreviewURL'</mso:ManagedPropertyMapping>
</mso:CustomDocumentProperties>
</xml><![endif]-->
</head>
<body>
    <div id="Item_Excel_Client">
<!--#_ 
        if(!$isNull(ctx.CurrentItem) && !$isNull(ctx.ClientControl)){
            var id = ctx.ClientControl.get_nextUniqueId();
            var itemId = id + Srch.U.Ids.item;
            var hoverId = id + Srch.U.Ids.hover;
            var hoverUrl = "~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_Excel_HoverPanel.js";
            $setResultItem(itemId, ctx.CurrentItem);
            ctx.CurrentItem.csr_Icon = Srch.U.getIconUrlByFileExtension(ctx.CurrentItem);
            ctx.CurrentItem.csr_OpenApp = "excel";
            ctx.CurrentItem.csr_Path = ctx.CurrentItem.Path;
            ctx.currentItem_ShowHoverPanelCallback = Srch.U.getShowHoverPanelCallback(itemId, hoverId, hoverUrl);
            ctx.currentItem_HideHoverPanelCallback = Srch.U.getHideHoverPanelCallback();
_#-->
            <div id="_#= $htmlEncode(itemId) =#_" name="Item" data-displaytemplate="ExcelItem" class="ms-srch-item" onmouseover="_#= ctx.currentItem_ShowHoverPanelCallback =#_" onmouseout="_#= ctx.currentItem_HideHoverPanelCallback =#_">
                _#=ctx.RenderBody(ctx)=#_                
                <div id="_#= $htmlEncode(hoverId) =#_" class="ms-srch-hover-outerContainer"></div>
            </div>
<!--#_ 
        } 
_#-->
    </div>
</body>
</html>

Item_Excel_Client.js

/* This file is currently associated to an HTML file of the same name and is drawing content from it.  Until the files are disassociated, you will not be able to move, delete, rename, or make any other changes to this file. */

function DisplayTemplate_89a1689efe684e93be8c5bdaa1e46b07(ctx) {
  var ms_outHtml=[];
  var cachePreviousTemplateData = ctx['DisplayTemplateData'];
  ctx['DisplayTemplateData'] = new Object();
  DisplayTemplate_89a1689efe684e93be8c5bdaa1e46b07.DisplayTemplateData = ctx['DisplayTemplateData'];

  ctx['DisplayTemplateData']['TemplateUrl']='~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Excel_Client.js';
  ctx['DisplayTemplateData']['TemplateType']='Item';
  ctx['DisplayTemplateData']['TargetControlType']=['SearchResults'];
  this.DisplayTemplateData = ctx['DisplayTemplateData'];

  ctx['DisplayTemplateData']['ManagedPropertyMapping']={'Title':['Title'], 'Path':['Path'], 'Description':['Description'], 'EditorOWSUSER':['EditorOWSUSER'], 'LastModifiedTime':['LastModifiedTime'], 'CollapsingStatus':['CollapsingStatus'], 'DocId':['DocId'], 'HitHighlightedSummary':['HitHighlightedSummary'], 'HitHighlightedProperties':['HitHighlightedProperties'], 'FileExtension':['FileExtension'], 'ViewsLifeTime':['ViewsLifeTime'], 'ParentLink':['ParentLink'], 'FileType':['FileType'], 'IsContainer':['IsContainer'], 'ServerRedirectedURL':['ServerRedirectedURL'], 'ServerRedirectedEmbedURL':['ServerRedirectedEmbedURL'], 'ServerRedirectedPreviewURL':['ServerRedirectedPreviewURL']};
  var cachePreviousItemValuesFunction = ctx['ItemValues'];
  ctx['ItemValues'] = function(slotOrPropName) {
    return Srch.ValueInfo.getCachedCtxItemValue(ctx, slotOrPropName)
};

ms_outHtml.push('',''
); 
        if(!$isNull(ctx.CurrentItem) && !$isNull(ctx.ClientControl)){
            var id = ctx.ClientControl.get_nextUniqueId();
            var itemId = id + Srch.U.Ids.item;
            var hoverId = id + Srch.U.Ids.hover;
            var hoverUrl = "~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_Excel_HoverPanel.js";
            $setResultItem(itemId, ctx.CurrentItem);
            ctx.CurrentItem.csr_Icon = Srch.U.getIconUrlByFileExtension(ctx.CurrentItem);
            ctx.CurrentItem.csr_OpenApp = "excel";
            ctx.CurrentItem.csr_Path = ctx.CurrentItem.Path;
            ctx.currentItem_ShowHoverPanelCallback = Srch.U.getShowHoverPanelCallback(itemId, hoverId, hoverUrl);
            ctx.currentItem_HideHoverPanelCallback = Srch.U.getHideHoverPanelCallback();
ms_outHtml.push(''
,'            <div id="', $htmlEncode(itemId) ,'" name="Item" data-displaytemplate="ExcelItem" class="ms-srch-item" onmouseover="', ctx.currentItem_ShowHoverPanelCallback ,'" onmouseout="', ctx.currentItem_HideHoverPanelCallback ,'">'
,'                ',ctx.RenderBody(ctx),'                '
,'                <div id="', $htmlEncode(hoverId) ,'" class="ms-srch-hover-outerContainer"></div>'
,'            </div>'
); 
        } 
ms_outHtml.push(''
,'    '
);

  ctx['ItemValues'] = cachePreviousItemValuesFunction;
  ctx['DisplayTemplateData'] = cachePreviousTemplateData;
  return ms_outHtml.join('');
}
function RegisterTemplate_89a1689efe684e93be8c5bdaa1e46b07() {

if ("undefined" != typeof (Srch) &&"undefined" != typeof (Srch.U) &&typeof(Srch.U.registerRenderTemplateByName) == "function") {
  Srch.U.registerRenderTemplateByName("Item_Excel_Client", DisplayTemplate_89a1689efe684e93be8c5bdaa1e46b07);
}

if ("undefined" != typeof (Srch) &&"undefined" != typeof (Srch.U) &&typeof(Srch.U.registerRenderTemplateByName) == "function") {
  Srch.U.registerRenderTemplateByName("~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Excel_Client.js", DisplayTemplate_89a1689efe684e93be8c5bdaa1e46b07);
}

}
RegisterTemplate_89a1689efe684e93be8c5bdaa1e46b07();
if (typeof(RegisterModuleInit) == "function" && typeof(Srch.U.replaceUrlTokens) == "function") {
  RegisterModuleInit(Srch.U.replaceUrlTokens("~sitecollection\u002f_catalogs\u002fmasterpage\u002fDisplay Templates\u002fSearch\u002fItem_Excel_Client.js"), RegisterTemplate_89a1689efe684e93be8c5bdaa1e46b07);
}

 

Then I pushed the Display Template and Result Type automatically to all Site Collections, because I wanted a uniform experience.

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction:SilentlyContinue

function DropFile([Microsoft.SharePoint.SPWeb]$spweb, [string]$fileName, [byte[]]$fileContents)
{
    $spfolderUrl = $spweb.Url + "/_catalogs/masterpage/Display Templates/Search";
    $spfolder = $spweb.GetFolder($spfolderUrl);
    $spfile = $spweb.GetFile("$spfolderUrl\$fileName");
    
    if ($spfile.Exists)
    {
        $spfile.CheckOut();
    }

    $spfile = $spfolder.Files.Add($fileName, $fileContents, $true);
    
    if ($spfile.CheckOutType -ne "None")
    {
        $spfile.CheckIn("");
    }

    $spfile.Publish("");
    $spfile.Approve("");
    
    Write-Host " > Display Template Provisioned."
    
    return $spfile;
}

function CreateSearchResultType([Microsoft.SharePoint.SPWeb]$spweb, [string]$spfileUrl)
{
    $ssa = Get-SPEnterpriseSearchServiceApplication
    $owner = Get-SPEnterpriseSearchOwner -Level SPSite -SPWeb $spweb
    $excelRIT = Get-SPEnterpriseSearchResultItemType -Owner $owner -SearchApplication $ssa | Where Name -eq "Microsoft Excel"
    $rules = $excelRIT.Rules
    $dispProps = $excelRIT.DisplayProperties
    $sptemplateUrl = "~sitecollection/$spfileUrl";
    
    Write-Host $sptemplateUrl;

    Get-SPEnterpriseSearchResultItemType -Owner $owner -SearchApplication $ssa | Where Name -eq "Microsoft Excel (Client)" | Remove-SPEnterpriseSearchResultItemType -Owner:$owner -SearchApplication $ssa -Confirm:$false
    $rit = New-SPEnterpriseSearchResultItemType -Name "Microsoft Excel (Client)" -Owner $owner -SearchApplication $ssa -Rules $rules -DisplayProperties $dispProps -DisplayTemplateUrl $sptemplateUrl
    
    Write-Host " > Search Result Type Created."
    
    return $rit;
}

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

$cd = gl
$fileNames = @("Item_Excel_Client.js", "Item_Excel_Client.html");

Get-SPWebApplication "http://mywebapp" | Get-SPSite -Limit ALL | ForEach {
    $spsite = $_;
    $spweb = $spsite.RootWeb;

    Write-Host $spsite.Url

    # Upload File(s)
    $fileNames | foreach {
        $fileName = $_;
        $fileContents = [System.IO.File]::ReadAllBytes("$cd\$fileName");
        $spfile = DropFile $spweb $filename $fileContents;
    }

    # Register Search Result Type
    $fileUrl = $spfile.Url.Replace(".html", ".js");
    CreateSearchResultType $spweb $fileUrl;
}

Better solution

Recently I stumbled upon the “Preferences” link at the bottom of the search result page. This allows each user to control how links in the search results should be opened.

Search preferences

 

This actually changes the experience for the given user immediately… awesome! I did some digging and it seems to be saved globally to the Search Service Application (Proxy), so all Web Applications and Site Collections making use of the same SSA should give the user a uniform experience.

The question still stands whether this setting can be pushed out programmatically to a specific user or group of users. I was thinking along the lines of the following script, but no luck so far.

$web = Get-SPWeb http://intranet
$ctx = [Microsoft.SharePoint.SPContext]::GetContext($web)
$pref =[Microsoft.Office.Server.Search.Administration.UserPreference]::GetUserPreference($false, $ctx)
$pref

HTH

 


Links to this post

Comments

Tuesday, 1 Apr 2014 02:26 by Marcus
Hi, I finally found a solution. But it's only possible in SharePoint solution because the "UserPreference" checks for "SPContext.Current" and throws exceptions if it's not set. There is my method. It only works for current user: Namespace: Microsoft.Office.Server.Search.Administration private void EnsureOpenInClient() { UserPreference preference = UserPreference.GetUserPreference(); if (!preference.IsSettingEnabled(UserPreference.Settings.OpenDocumentsInClient)) { preference.EnableSetting(UserPreference.Settings.OpenDocumentsInClient); UserPreference.SetUserPreference(preference); } }

CAPTCHA Image Validation