Archive

Archive for the ‘Development’ Category

Create lookup field using PowerShell and CSOM

May 19, 2014 3 comments

For our projects we always try to avoid manual configurations. This is because it is a tedious and error prone process if you work with a DTAP environment. To avoid this, we also try to script as much as possible for SharePoint Online projects. Lately we worked with creating lookup fields in SharePoint online, using PowerShell and CSOM. Creating fields this way is pretty easy, but connecting lookup fields forced us to think about casting the Microsoft.SharePoint.Client.Field object to a Microsoft.SharePoint.Client.FieldLookup object.

Within CSOM this can be done by leveraging the ClientRuntimeContext.CastTo method, but… This is a generic method (object of type T). This is something which is not easily supported by PowerShell. To use this method, you can use reflection using the MakeGenericMethod method.

The full PowerShell script is provided below

#————————————————————-
# LOAD CLIENT ASSEMBLIES
#————————————————————-
$clientAssembliesFolder = “D:\ClientAssemblies”
Add-Type -Path (Join-Path -Path $clientAssembliesFolder -ChildPath “Microsoft.SharePoint.Client.dll”)
Add-Type -Path (Join-Path -Path $clientAssembliesFolder -ChildPath “Microsoft.SharePoint.Client.Runtime.dll”)

#————————————————————-
# INITIALIZE CONTEXT
#————————————————————-
[string]$siteUrl = "https://[UseYourOwn].sharepoint.com/sites/Dev"
[string]$username = “admin@[UseYourOwn].onmicrosoft.com”
[string]$password = “[UseYourOwn]”
$pwd = $password | ConvertTo-SecureString -AsPlainText -Force
$context = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $pwd)
$context.Credentials = $credentials

#————————————————————-
# LOAD CASTTO FOR LOOKUPS
#————————————————————-
$castToMethodGeneric = [Microsoft.SharePoint.Client.ClientContext].GetMethod(“CastTo”)
$castToMethodLookup = $castToMethodGeneric.MakeGenericMethod([Microsoft.SharePoint.Client.FieldLookup])

#————————————————————-
# LOAD LISTS
#————————————————————-
[string] $originaListTitle = “List1”
[string] $destinationListTitle = “List2”
$listOriginal = $context.Web.Lists.GetByTitle($originaListTitle)
$context.Load($listOriginal)
$listDestination = $context.Web.Lists.GetByTitle($destinationListTitle)
$context.Load($listDestination)
$context.ExecuteQuery() # This loads the necessary list ID

#————————————————————-
# CREATE LOOKUP
#————————————————————-
[string] $internalName = “LookupWithStaticName”
[string] $displayName = “LookupTest”
[string] $displayFieldForLookup = “Title”
[string] $lookupFieldXML = “<Field DisplayName=`”$internalName`” Type=`”Lookup`” />”
$option = [Microsoft.SharePoint.Client.AddFieldOptions]::AddFieldToDefaultView

$newLookupField
= $listDestination.Fields.AddFieldAsXml($lookupFieldXML, $true, $option)
$context.Load($newLookupField)
$lookupField = $castToMethodLookup.Invoke($context, $newLookupField)
$lookupField.Title = $displayName
$lookupField.LookupList = $listOriginal.Id
$lookupField.LookupField = $displayFieldForLookup
$lookupField.Update()
$context.ExecuteQuery()

Advertisements

Display templates not reflecting ALL changes

April 10, 2014 4 comments

Lately I’ve been working a lot with custom display templates and custom refiners. This worked all pretty well, I could update the look and feel by changing the body without any problem. However, when I made changes in the head section of the display template, this didn’t seem to reflect my changes… And there you update your Managed Properties and in my case I also added some logic to retrieve some variables.

Using the debugger of Internet Explorer I saw the old version of the Javascript files were still used for the display templates. Somehow these where cached… The URL of my display template ended with ?ctag=122$$15.0.4481.1005. Well this number of course can change, but somewhere the old version was still in the SharePoint cache. This led me to look at my Result Types, since my display templates are used based on those…

On the top of the page I found the following message:

PropertySyncNeeded

Just click the Update link and you will get the following message:

PropertySyncDone

Refresh your search result page and the latest version of your display template will be used!

Generate overview of active features

For now the last section about generating documentation for your custom SharePoint solution. The following PowerShell scripts will provide you with an overview of activated features on your Farm, WebApplication and Site Collections. Since this script is meant for custom solutions, I assume you have used a prefix (Visual Studio adds the solution name by default as a prefix to all features).

Activated farm features

#———————————————————————————————————————-
# Add SharePoint PowerShell Snapin 
#———————————————————————————————————————-
if ( (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null ) {
    Add-PSSnapin Microsoft.SharePoint.Powershell


#———————————————————————————————————————-

# Get variables
#———————————————————————————————————————-
$featurePrefix = “Macaw”

#———————————————————————————————————————-
# Get information
#———————————————————————————————————————-
Get-SPFeature | where {$_.Scope -eq “Farm” -and $_.DisplayName.StartsWith($featurePrefix ,“CurrentCultureIgnoreCase”)} | Select DisplayName

Activated web application features

#———————————————————————————————————————-
# Add SharePoint PowerShell Snapin 
#———————————————————————————————————————-
if ( (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null ) {
    Add-PSSnapin Microsoft.SharePoint.Powershell

#———————————————————————————————————————-
# Get variables
#———————————————————————————————————————-
$featurePrefix = “Macaw”

#———————————————————————————————————————-
# Get information
#———————————————————————————————————————-
Get-SPWebApplication | foreach {
    Write-Host ([string]::Format(“Web Application: {0} ({1})”, $_.DisplayName, $_.Url))
   $_.Features | where {$_.Definition.DisplayName.StartsWith($featurePrefix ,“CurrentCultureIgnoreCase”)} | foreach {
        Write-Host ([string]::Format(“- {0} ({1})”, $_.Definition.DisplayName, $_.Definition.Id))
    }

    Write-Host “”
}

Activated site collection features

This script skips all personal sites, which you can easily add to your overview by removing the where clause.

#———————————————————————————————————————-
# Add SharePoint PowerShell Snapin 
#———————————————————————————————————————-
if ( (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null ) {
    Add-PSSnapin Microsoft.SharePoint.Powershell

#———————————————————————————————————————-
# Get variables
#———————————————————————————————————————-
$featurePrefix = “Macaw”

#———————————————————————————————————————-
# Get information
#———————————————————————————————————————-
Get-SPWebApplication | Get-SPSite | Where { $_.Url -notmatch “/personal/”} | foreach {
    Write-Host ([string]::Format(“Site Collection: {0} ({1})”, $_.RootWeb.Title, $_.Url))
   $_.Features | where {$_.Definition.DisplayName.StartsWith($featurePrefix ,“CurrentCultureIgnoreCase”)} | foreach {
        Write-Host ([string]::Format(“- {0} ({1})”, $_.Definition.DisplayName, $_.Definition.Id))
    }

    Write-Host “”
}

Previous posts about automatically generate documentation of your custom solution:

Generate solution documentation

April 3, 2014 1 comment

Sometimes you want an overview of your custom farm solutions which are deployed on your farm. Normally I prefix all my solutions to make it clear who created them. Using this prefix you can easily run a PowerShell script which provides you with a list of installed solutions and the web applications to which these solutions are deployed:

#———————————————————————————————————————-
# Add SharePoint PowerShell Snapin 
#———————————————————————————————————————-
if ( (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null ) {
    Add-PSSnapin Microsoft.SharePoint.Powershell

#———————————————————————————————————————-
# Get variables
#———————————————————————————————————————-
$solutionPrefix = “Macaw”

#———————————————————————————————————————-
# Get information
#———————————————————————————————————————-
foreach($solution in Get-SPSolution | Where {$_.Name.StartsWith($solutionPrefix ,“CurrentCultureIgnoreCase”)}) {
    Write-Host “Name: “ $solution.Name
    Write-Host “Identity: “ $solution.Id

    if($solution.get_ContainsWebApplicationResource() -eq $false) {
        Write-Host “Applications: Globally deployed”
    }
    else {
        Write-Host “Deployed to application(s):”
        $solution.DeployedWebApplications | foreach-object {
            Write-Host ([string]::Format(“- {0} ({1})”, $_.DisplayName, $_.Url))
        }
    }

    Write-Host “”
}

For a PowerShell script which documents all features, look at this post.

Replace SharePoint special characters

Sometimes during SharePoint development item are programmatically added to SharePoint based on for example the title of the site, which can contain special characters. This can occur when creating documents, SharePoint groups etc. You will receive a message stating that the following characters are not allowed: / \ [ ] : | < > + = ; , ? * ‘ @

In my case I wanted to create custom SharePoint groups based on the title of the web. To avoid these errors when characters like that are used in the Web title, I’ve used a regular expression to remove these characters. This can be accomplished by the following lines of code:

// Ensure SharePoint safe title (characters [“/\[]:|<>+=;,?*’@] are not allowed)
Regex pattern = new Regex(“[:\\[\\]|<>+=;,\\?\\*’@\\\”\\\\/]?”);
string title = pattern.Replace(web.Title,“”);

Categories: Development, SharePoint

Generate feature documentation

March 31, 2014 2 comments

For my projects I always provide documentation about the solutions our team has developed. Part of the development is an overview of the features which are added to the different solutions. This because most of the features tend to be hidden and the customer and administrators will not use Visual Studio to check which features are added.

As most developers I don’t like to document everything twice and on multiple locations. All features in my project are already documented in Visual Studio. I’ve added titles, images, descriptions etc. This information can easily be reused to generate documentation about the features. For that, I use the following PowerShell script:

 

# Set the feature folder based on the SharePoint version
$featureFolder = “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\FEATURES”

# Provide a prefix to filter the set of features
$featureFolderPrefix = “Macaw.”

# Initialise a counter
$counter = 0

# Loop through all features
foreach($folder in Get-ChildItem -LiteralPath $featureFolder | where { $_.PSIsContainer -and $_.Name.StartsWith($featureFolderPrefix,“CurrentCultureIgnoreCase”) }) {

   #Increment the counter
   $counter++

   # Retrieve the feature XML
   $featureXML = Get-ChildItem $folder.FullName -Filter feature.xml -Recurse
   [xml] $featureContent = Get-Content $featureXML.FullName
   $feature = $featureContent.Feature

   # Write some of the properties to screen
   $feature | select Title, Description, Id, Scope,
     @{Label=“Hidden”;Expression={if($featureContent.Feature.Hidden) { “Yes”} else { “No”}}},
     @{Label=“Full name”;Expression={$featureXML.FullName}},
     
@{Label=“Number”;Expression={$counter}}

   # Split the results with an empty row
   Write-Host “”
}

Getting custom ranking model for People Search working

June 3, 2013 2 comments

Today I’ve created a custom ranking model for the SharePoint 2010 People Search. There are a lot of examples online on how to create a custom ranking model and how to use it (for example http://blog.petergerritsen.nl/2010/10/12/people-search-use-a-custom-ranking-model-to-search-in-added-profile-properties/).

When you’ve added the custom ranking model to SharePoint, you can use it using a query string parameter called rm1. But that doesn’t really look nice, and when people don’t use that query string variable, they will end up using the default raking model again.

It does look like you can set it as the default raking model by tweaking the properties of the Core Result WebPart. It has a property to set the default ranking model, but…. It doesn’t seem to work for the People Search. The People Search will keep setting the ranking model back to the default people search ranking model MainPeopleModel.

So where does this leave us… We can still use the query string option and we can off course always use a JavaScript on the page to redirect using the query string variable if it is not set. But, that does mean we can get useless hits on the page and useless search queries (since we redirect the user right after their request). This is something I really don’t like… It causes load on the servers which should not be there.

To get around this issue, I’ve created a small WebPart. The WebPart allows you to provide the ID (GUID) of a custom ranking model and during the initialization of the page, it sets the custom ranking model for the query manager of the page. This ensures that the search query will use your custom ranking model, without performing useless queries or redirecting the user. The code to get this working you can find below. Please note, I’ve added my own logging module to this code and you probably want to add your own logging logic J.

using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using PeterHeibrink.SP2010.Intranet.Core.Logging;
using Microsoft.Office.Server.Search.Query;
using Microsoft.Office.Server.Search.WebControls;
using Microsoft.SharePoint.WebPartPages;
using WebPart = System.Web.UI.WebControls.WebParts.WebPart;

namespace PeterHeibrink.SP2010.Intranet.WebParts.WebParts.SetSearchRankingModel
{
/// <summary>
/// This class implements a <see cref=”WebPart”/> which allows for
/// overriding the ranking model ID.
/// </summary>
/// <remarks>
/// This WebPart is needed for applying a custom ranking profile for people search.
/// For people search, the default ranking model setting in the WebPart properties is
/// not persisted and cannot be set by updating the WebPart XML.
/// </remarks>

[ToolboxItemAttribute(false)]
public class SetSearchRankingModel : WebPart
{

#region WebPart Properties

/// <summary>
/// This property allows for setting the Ranking Model ID
/// </summary>
/// <remarks>
/// This is used to force the search webparts on the page to use
/// an alternative ranking model. This is done because the people search 
/// webpart will otherwise not use a custom ranking model.
/// </remarks>
[WebBrowsable(true), WebDisplayName(“Ranking model ID”),
WebDescription(“This should be the ID of the ranking model”)]
[Personalizable(PersonalizationScope.Shared),
SPWebCategoryName(“Ranking model”)]
public string RankingModelId 

{
   get { return _rankingModelId; }
set { _rankingModelId = value; }

}

private string _rankingModelId = string.Empty;

 #endregion

#region Overriden events

/// <summary>
/// This event will ensure the <see cref=”QueryManager”/> will use a custom
/// ranking profile.
/// </summary>
/// <param name=”e”>Event arguments</param>
protected override void OnInit(EventArgs e)
{
// Makse sure the property is set
if (string.IsNullOrEmpty(RankingModelId))
{

LogService.LogWarning(
    LogCategory.WebPart,
“An WebPart has been added to override the Ranking Model of a Search Query, but no Ranking Model ID has been provided.”);
return;

}

// Get the current query manager and make sure it is available
QueryManager queryManager = SharedQueryManager.GetInstance(this.Page).QueryManager;
if (queryManager == null)
{
LogService.LogError(
LogCategory.WebPart,
“An WebPart has been added to override the Ranking Model of a Search Query, but there is no Query Manager available.”);

    return;
}

// Set the custom ranking model for all locations
foreach (LocationList locationList in queryManager)
foreach (Location location in locationList)
location.RankingModelID = RankingModelId;

LogService.LogMedium(
        LogCategory.WebPart,
“An WebPart has been added to override the Ranking Model of a Search Query, the locations have been updated with the new ranking model.”);

base.OnInit(e);

}

/// <summary>
/// This event will add a control to the page displaying the ranking model setting.
/// </summary>
/// <remarks>
/// The Ranking model is displayed to allow for easy checking if the webpart is added on the page
/// by accounts without permissions.
///
</remarks>
 

protected override void CreateChildControls()
{
Controls.Add(new  LiteralControl(string.Format(“<!– Ranking model set to: ‘{0}’–>”, RankingModelId)));

}

#endregion

}

}

Categories: Development, SharePoint
Ben Prins

What I want to remember about SharePoint

blog.frederique.harmsze.nl

my world of work and user experiences

Bram de Jager - Coder, Speaker, Author

Office 365, SharePoint and Azure