After installing a languagepack help contents should be available when you create a site collection in that language. Sometimes however SharePoint gives you a message that the files have not been installed when you click the help button or go to the Help-settings page for your sitecollection.

You can force SharePoint to install the files using the hcinstal.exe tool in the bin folder under the SharePoint root (c:\program files\common files\microsoft shared\web server extensions\14\bin):

hcinstal.exe /act InstallAllHCs /loc 1043

This command will install all available help content for LCID 1043 (Dutch)

After this running this command (which can take up to 10-15 minutes easily) you need to run the SharePoint Products and Configuration Wizard on each front-end server.

I think you can also use the following 2 PowerShell commands, but I wasn’t able to test these after the first solution worked:

  • Install-SPHelpCollection
  • Install-SPApplicationContent

When you use the out-of-the box components for searching for people, a generic search with a keyword that is included in an added Profile property will not give the results you might expect.

This is especially a problem with the out-of-the-box webpart that shows information about a person:

Profile properties webpart

When you click on one of the values in this webpart, you will be redirected to the people search results page with the value you clicked passed in as the search keyword:

Profile search generic

But this will not give you any results. This post by kgreame outlines the same problems and some of the steps he tried to solve this issue. In the comments to that post, a workaround is mentioned: map the matching crawled properties to the ContentsHidden Managed Property.

There is however an other way. In this post by Larry Kuhn, he explains how the DEFAULTPROPERTIES within the SharePoint Search work. By setting a weight of a Managed Property, you will include it in the ranking model SharePoint uses and the property is therefore added to the default properties that are searched. SharePoint 2010 introduces the concept of Ranking Models, so the solution mentioned in that blogpost doesn’t work. We can however create our own Ranking Model. I’ve copied the information from the Ranking Model that SharePoint uses for People Search and added my own Managed Property, Expertise, with a weight of 1.0:

<?xml version="1.0" encoding="utf-8"?>
<rankingModel name="CustomPeopleRanking" id="5EA2750C-8165-4f65-BD12-6E6DAAD45FE0" description="Custom People Ranking" xmlns="http://schemas.microsoft.com/office/2009/rankingModel">
  <queryDependentFeatures>
    <queryDependentFeature pid="177" name="RankingWeightName" weight="0.5" lengthNormalization="0" />
    <queryDependentFeature pid="19" name="PreferredName" weight="1.0" lengthNormalization="0" />
    <queryDependentFeature pid="24" name="JobTitle" weight="2.0" lengthNormalization="0" />
    <queryDependentFeature pid="39" name="Responsibilities" weight="1.0" lengthNormalization="5" />
    <queryDependentFeature pid="179" name="RankingWeightLow" weight="0.2" lengthNormalization="5" />
    <queryDependentFeature pid="175" name="ContentsHidden" weight="0.1" lengthNormalization="5" />
    <queryDependentFeature pid="35" name="Memberships" weight="0.25" lengthNormalization="5" />
    <queryDependentFeature pid="178" name="RankingWeightHigh" weight="2.0" lengthNormalization="0" />
    <queryDependentFeature pid="180" name="Pronunciations" weight="0.05" lengthNormalization="0" />
    <queryDependentFeature pid="408" name="Expertise" weight="1.0" lengthNormalization="0" />
  </queryDependentFeatures>
</rankingModel>

We can then add this Ranking Model to SharePoint with Powershell:

Get-SPEnterpriseSearchServiceApplication | New-SPEnterpriseSearchRankingModel

And paste in the XML of this Ranking Model when Powershell prompts you for it.

But how do we force SharePoint to use this Ranking Model? I’ve outlined one of the solutions in a previous post.

So after adding a web part that sets the ranking model we can perform the search again:

Profile search after ranking model

And bingo, the user with this value in an extra profile property shows up in the search results.

Why use this over mapping the properties in the ‘ContentsHidden’ Managed Property? As you can see the ‘ContentsHidden’ property is included in the Ranking Model with a value of 0.1. If you want to give more weight to a custom property or you have more properties to which you want to assign a different weight, you will need to modify the Ranking Model.

SharePoint 2010 introduces the concept of Ranking Models. These models allow you to control the ranking of search results.

But how do you tell the search web parts to use your custom ranking model, once you specified it? As far as I can tell there’s 2 options:

  • Pass in the id of the model through the querystring by appending &rm=<guid of ranking model>
  • Use the Shared Query Manager

The Shared Query Manager is the option for modifying the query the search web parts are going to use.

To use the Query Manager to set an other Ranking Model I’ve created a custom web part. To retrieve the Query Manager all you have to do is call the static GetInstance method of the SharedQueryManager class.

After that, you can loop through the query manager and each location within the LocationList to set the ID of the Ranking Model:

public class SetRankingModel : WebPart
    {
        protected override void OnInit(EventArgs e)
        {
            try
            {
                QueryManager qm = SharedQueryManager.GetInstance(this.Page).QueryManager;
                foreach (LocationList ll in qm)
                {
                    foreach (Location l in ll)
                    {
                        try
                        {
                            l.RankingModelID = "5ea2750c-8165-4f65-bd12-6e6daad45fe1";
                        }
                        catch { }
                    }
                }
                base.OnInit(e);
            }
            catch { }
        }
    }

After you’ve placed this web part on a search results page, each web part will use your custom Ranking Model.

Today I was trying to fix an error in a custom webpart that performs a query on the SharePoint farm. The goal was to retreive the records within a site though the search system.

First I added a metadata property ItemDeclaredRecord that is mapped to ows__vti_ItemDeclaredRecord (DateTime). Then I tried the following query (using Steve Peschka’s Developer Search Tool):

SELECT Size, Rank, Path, Title, Description, Write FROM scope() WHERE  (ItemDeclaredRecord > '1900/01/01 00:00:00')  AND CONTAINS (Path, '"/projecten"')

This query works fine and retreives all records declared after January 1st, 1900 under the “projecten” site. To retreive all records within a specific subsite I then tried the following query:

SELECT Size, Rank, Path, Title, Description, Write FROM scope() WHERE  (ItemDeclaredRecord > '1900/01/01 00:00:00')  AND CONTAINS (Path, '"/projecten/subsite1"')

This threw a FaultException?! After fiddling around with the query for a few hours, I finally found a query that works:

SELECT Size, Rank, Path, Title, Description, Write FROM scope() WHERE  (ItemDeclaredRecord > '1900/01/01 00:00:00')  AND CONTAINS (Path, '"/projecten/subsite1"') ORDER BY Rank DESC

I’m not sure what the ORDER BY part does to the search system internally, but for now I’m guessing it’s SQL for ‘Pretty pretty please!’.

EDIT: The property you add in the ORDER BY part has to have the “Reduce storage requirements for text properties by using a hash for comparison” option checked.

One of the standard Ribbon buttons in SharePoint 2010 allows you to mail a link to a document. Unfortunately the dev team at Microsoft didn’t update the functionality of this button to use one of the enhanced UI features: multiple selection. When you select 2 or more items, the button is automatically disabled.

To bypass this inconvenience, we decided to implement our own version of the button to replace the standard button. Replacing a button is actually quite easy with the UI extensibility options in SP2010.

First of, I started with an empty SharePoint project in Visual Studio 2010. I chose to create a sandboxed solution. Because we need to deploy and inject a JavaScript file on every page, I decided to go for the solution Jan Tielens introduced in his blog post ‘Deploying and using jQuery with a SharePoint 2010 Sandboxed Solution’.

First of I added an empty element item to the project called Buttons. In this element I included the XML to hide the existing button and inject my own button:

<CustomAction Id="TTSendLinksEmailRemoveRibbonButton" Location="CommandUI.Ribbon">
  <CommandUIExtension>
    <CommandUIDefinitions> 
      <CommandUIDefinition Location="Ribbon.Documents.Share.EmailItemLink" />
    </CommandUIDefinitions>
  </CommandUIExtension>
</CustomAction>
<CustomAction Id="TTSendLinksEmailButton" Location="CommandUI.Ribbon" Sequence="15" Title="E-mail a link">
  <CommandUIExtension>
    <CommandUIDefinitions>
      <CommandUIDefinition Location="Ribbon.Documents.Share.Controls._children">
        <Button 
		Id="Ribbon.Documents.Share.TTSendLinksEmailButton"
		Alt="$Resources:core,cui_ButEmailLink;"
		LabelText="$Resources:core,cui_ButEmailLink;"
		ToolTipTitle="$Resources:core,cui_ButEmailLink;"
		ToolTipDescription="$Resources:core,cui_STT_ButEmailLinkDocument;"
		Sequence="15"
		Command="TT_SendLinksEmail_Button"
		Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png"
		Image16by16Top="-16"
		Image16by16Left="-88"
		Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png"
		Image32by32Top="-128"
		Image32by32Left="-448"
		TemplateAlias="o1" />
      </CommandUIDefinition>
    </CommandUIDefinitions>
	<CommandUIHandlers>
		<CommandUIHandler 
		Command="TT_SendLinksEmail_Button" 
		CommandAction="javascript:function sendLinksMail() {
			TamTam_SP2010_SendLinksMail_SendLinksMail();
		}
		sendLinksMail();"
		EnabledScript="javascript:function oneOrMoreEnable() {
			var items = SP.ListOperation.Selection.getSelectedItems();
			var ci = CountDictionary(items);
			return (ci > 0);
		}
		oneOrMoreEnable();" />
	</CommandUIHandlers>
  </CommandUIExtension>
</CustomAction>

For my own button, I copied the resourcestrings from the original XML that is in CMDUI.xml (14\TEMPLATE\GLOBAL\XML). This way the button gets the same look and feel and localized labels as the original button. When pressing the button the JavaScript function TamTam_SP2010_SendLinksMail_SendLinksMail is called. The EnabledScript enables the button when 1 or more items are selected.

To make sure the JavaScript is included, we’ll use a CustomAction element with ScriptLink as location (for more information see the previously mentioned blogpost by Jan Tielens):

<CustomAction
  ScriptSrc="~SiteCollection/SiteAssets/TamTam.SP2010.EmailALinkMultiple.js"
  Location="ScriptLink"
  Sequence="10">
</CustomAction>

Now all we have to do is create the necessary JavaScript. The out-of-the-box functionality opens the users email client with the link to the selected document in the body of the message.

To create the body of the message we’ll need to retrieve the link for every selected item by use of the Client Object Model. First we’ll get the ClientContext, the SPSite, the list were in and the id’s of the selected items:

TamTam_SP2010_SendLinksMail_SendLinksMail = function () {
  this.selectedItems = SP.ListOperation.Selection.getSelectedItems();
  this.selectedListGuid = SP.ListOperation.Selection.getSelectedList();
  this.context = SP.ClientContext.get_current();
  this.site = this.context.get_site();
  this.context.load(this.site);
  this.web = this.context.get_web()
  this.selectedList = this.web.get_lists().getById(this.selectedListGuid);
}

We then need to get the listitem object for every selected item. Because this is an asynchronous operation we’ll create an array, include every selected listitem in there and tell the context to load each item before calling executeQueryAsync:

this.selectedFiles = new Array();
var k;

for (k in this.selectedItems) {
  this.selectedFiles.push(this.selectedList.getItemById(this.selectedItems[k].id).get_file());
  this.context.load(this.selectedFiles[k]);
}

this.context.executeQueryAsync(
  Function.createDelegate(this, TamTam_SP2010_SendLinksMail_onQuerySucceeded), 
  Function.createDelegate(this, TamTam_SP2010_SendLinksMail_onQueryFailed)
);

Then in the onQuerySucceeded callback function we can get the url’s for the items and construct the email:

TamTam_SP2010_SendLinksMail_onQuerySucceeded = function () {
  var siteUrl = this.site.get_url();
  var k;
  var bodystring = "";
  
  for (k in this.selectedFiles) {
    var fileUrl = this.selectedFiles[k].get_serverRelativeUrl();
    bodystring += siteUrl + fileUrl + "%0d%0a%0d%0a";
  }
  
  window.open('mailto:?body=' + bodystring);
}