Wednesday, November 15, 2017

Fix problem with “The formula refers to a column that does not exist” error in calculated columns for Sharepoint lists which use owssvr.dll

Sharepoint contains many hidden features which are really useful. One of them is possibility to generate iCal files (.ics) from calendar event. In order to do it you need to add calculated field to the calendar with the following formula:

   1: =”http://<SITE_URL>/_vti_bin/owssvr.dll?CS=109&Cmd=Display&List=<LIST_GUID>
   2: &CacheControl=1&ID=”&ID&”&Using=event.ics”

where instead of <SITE_URL> you should use url of web site where calendar list is located and instead of <LIST_GUID> – guid of the calendar list. You may get list guid if will go to List settings page – guid will be in browser address bar in List query string parameter. Also note that list guid should be added without curly braces because otherwise link won’t be clickable in list item view form.

This formula will work properly on English sites (and for several other languages – see below) but if you will try to use it on some other language site (e.g. on Finnish) you may get the following error:

The formula refers to a column that does not exist. Check the formula for spelling mistakes or change the non-existing column to an existing column

(error message will be translated on the local language as well). The problem is related with internal representation of ID field and with fact that in formulas fields’ display names should be used instead of internal names. If we will check it e.g. using Sharepoint Manager tool there will be property called SchemaXmlWithResourceTokens which contains xml definition of ID field. It will look similar to this:

   1: <Field ID="..."
   2:        ColName="tp_ID"
   3:        RowOrdinal="0"
   4:        ReadOnly="TRUE"
   5:        Type="Counter"
   6:        Name="ID"
   7:        PrimaryKey="TRUE"
   8:        DisplayName="$Resources:core,ID;"
   9:        SourceID=""
  10:        StaticName="ID"
  11:        FromBaseType="TRUE" />

Pay attention of DisplayName attribute of the field: for ID (which is internal field which exists for all list items) it is retrieved from core.resx file, i.e. it is localized as well as other fields. So e.g. for Finnish language (DisplayName=Tunnus) formula will look like this:

   1: =”http://<SITE_URL>/_vti_bin/owssvr.dll?CS=109&Cmd=Display&List=<LIST_GUID>
   2: &CacheControl=1&ID=”&Tunnus&”&Using=event.ics”

I checked core.resx for all languages available for Sharepoint 2013 and found the following languages which will have the same problem: Catalan (ca-ES), Bulgarian (bg-BG), Greek (el-GR), Basque (eu-ES), Hebrew (he-IL), Hungarian (hu-HU), Dutch (nl-nl) uses “Id” instead of “ID”, Polish (pl-PL), Russian (ru-RU), Turkish (tr-TR), Ukranian (uk-UA), Chinese Taiwan (zh-TW). Other languages use “ID” display name for ID field.

Saturday, November 4, 2017

Problem with reusing taxonomy terms with navigation settings in different term sets in Sharepoint

Suppose that we have the following sub sites in the same Sharepoint Online site collection:

  • /en – publishing site
  • /content/en –authoring site

And we need to use the same managed metadata navigation on these publishing and authoring sub sites. In order to achieve that we need to create 2 navigation term sets (e.g. Navigation.en-US and NavigationContent.en-US) and configure navigation settings for the terms:

We can’t use same term set for 2 sub sites because Sharepoint allows to use navigation term set for single site only. As we need to create 2 term sets anyway we would like at least to reuse terms from 1st term set Navigation.en-US in 2nd term set NavigationContent.en-US like it is shown on the picture above (in order to have less maintenance work). And here we face with the problem: it seems like that during reusing navigation settings of the terms are not reused. I.e. if we create terms in 1st term set Navigation.en-US, then reuse them in 2nd term set NavigationContent.en-US and then configure navigation settings in Navigation.en-US – reused terms in NavigationContent.en-US won’t inherit changed navigation settings automatically as we would expect.

Workaround for this problem is quite simple: at first configure navigation settings in source term set (Navigation.en-US) and only after that reuse terms in 2nd term set (NavigationContent.en-US). In this case navigation settings will be inherited properly. But if you will change navigation settings after that in original terms from the source term set – they won’t be changed automatically in 2nd term set. I.e. you will need to change them in 2nd term set explicitly.

Saturday, October 28, 2017

Configure several SSL sites on single IP in IIS with SNI

As you probably know the classic way to configure https site in IIS is to add site binding for 443 port on specific IP address with SSL certificate. This approach works but the problem is that you may have only 1 https site per IP address. If you would try to create second site on the same IP the following message is shown:

At least one other site is using the same HTTPS binding and the binding is configured with a different certificate. Are you sure that you want to reuse this HTTPS binding and reassign the other site or sites to use the new certificate?

In order to create more https sites additional IP addresses are required. In turn it adds more maintenance efforts, firewalls configurations, etc.

Currently in IIS there is more convenient way which allows to create multiple https sites on the same IP address: Server Name Indication or SNI. SNI support was added to IIS from version 8 and all major browsers support it several years already (see Server Name Indication).

With SNI when you create SSL binding on IP address which is already used for other https site you have to check checkbox “Server Name Indication” and specify domain name which will be used for identifying the site:

There won’t be warnings and binding will be created successfully.

Tuesday, October 17, 2017

One way to avoid “Term update failed because of save conflict” error when create managed metadata terms in Sharepoint

In one of the project we used the following PowerShell CSOM code for creating managed metadata navigation terms in Sharepoint Online:

   1: $newTerm = $navTermSet.CreateTerm($web.Title,
   2:     [Microsoft.SharePoint.Client.Publishing.Navigation.NavigationLinkType]::SimpleLink,
   3:     [System.Guid]::NewGuid())
   4: $newTerm.SimpleLinkUrl = $web.ServerRelativeUrl
   5: $termStore.CommitAll()
   6: $ctx.ExecuteQuery()

It worked successfully for many tenants but for one tenant it gave the following error:

Exception calling "ExecuteQuery" with "0" argument(s): "Term update failed because of save conflict."

Error appeared randomly, i.e. for the same sub site some time term has been created successfully and some time it failed with above error. There were no other changes so the reason was not in pending changes.

In order to avoid this error we applied the following workaround:

   1: do
   2: {
   3:     Try
   4:     {
   5:         $newTerm = $navTermSet.CreateTerm($web.Title,
   6:             [Microsoft.SharePoint.Client.Publishing.Navigation.NavigationLinkType]::SimpleLink,
   7:             [System.Guid]::NewGuid())
   8:         $newTerm.SimpleLinkUrl = $web.ServerRelativeUrl
   9:         $termStore.CommitAll()
  10:         $ctx.ExecuteQuery()
  11:         break
  12:     }
  13:     Catch
  14:     {
  15:         Write-Host "Error occured, try one more time" -foregroundcolor yellow
  16:     }
  17: } while ($true)

I.e. instead of single call to CreateTerm and ExecuteQuery we call it in the loop until call will be successful. Log showed that with this approach all navigation terms have been created properly at the end although for some sub sites it failed 1 time, for other 2 and for some even 3 times until it created term successfully, while for most of sub sites there were no errors at all. Hope that this engineering approach will help some one:).

Wednesday, September 27, 2017

Camlex has been moved to Github

Some time ago MS announced Codeplex shutdown in 2017. I personally liked Codeplex – all these years which it was used for hosting Camlex project I was quite satisfied with its services and functionality. But time goes on and MS made this decision which we have to live with. Regardless of Codeplex shutdown Camlex development will be continued and I’m glad to announce that it was migrated to Github. So the new project home is Earlier when project was hosted on Codeplex there were 3 choices how to get ready for use .Net assembly:

  • download it from Codeplex
  • install it directly in VS from Nuget (Camlex.NET.dll package for basic server object model and Camlex.Client.dll for client object model)
  • get latest source code and compile project in VS

After migration to Github Nuget will be primary way of getting binaries and of course it will be still possible to get source code and compile it in VS (project will be remain open source with the same Ms-Pl license). Issues and discussions were migrated together with source code and documentation (discussions were migrated as closed issues to Github with “Discussion: ” prefix: Migrate issues and discussions from Codeplex to Github). So let’s continue Camlex journey with the new home and will add more value to it already on Github.

Tuesday, September 26, 2017

Migrate issues and discussions from Codeplex to Github

As you probably know Codeplex will be shut down soon. I used Codeplex many years for hosting Camlex project – open source library for creating dynamic CAML queries for Sharepoint by C# lambda expressions. Migration guide available on Codeplex says how to move source code to the Github, but unfortunately it doesn’t mention how to move issues and discussions. In this post I will share my experience of how to migrate issues and discussions from Codeplex to Github.

For migrating issues I used Codeplex-Issues-Importer Python script which worked quite well: it added issues with Codeplex label and closed those issues which were closed on Codeplex. But for discussions it was not so straightforward. First of all in Github there are no such thing as “discussion” as in Codeplex, so I decided to move them to Github issues with “Discussion: [Title]” prefix. In order to perform migration itself I made fork of Codeplex-Issues-Importer and modified it so it started to parse Codeplex discussions instead of issues and then save them into Github as issue. Fork can be found here: Script is not perfect but probably will be enough just for keeping old discussions in migrated project. Result of migration can be checked here:

Monday, September 4, 2017

Sliding session for Sharepoint 2013 with FBA and persistent cookies

Sliding session allows user to use site without being reauthenticated if last action was done less than configured session lifetime. In Sharepoint 2013 FBA the following parameters of security token service config are used for setting session lifetime:

  • CookieLifetime
  • FormsTokenLifeTime
  • LogonTokenCacheExpirationWindow

They are well described in the following article SharePoint 2013 authentication lifetime settings and I won’t repeat it here. The problem is that when you use persistent cookies (i.e. those which are stored on client’s side) only CookieLifetime are actually used (to be more precise, FormsTokenLifeTime is used for setting initial ValidTo value for session security token). In addition to that sliding sessions doesn’t work by default, i.e. regardless of whether user made actions on the site or not he will be logged out after cookies will be expired. Persistent cookies can be set e.g. if user checked “Remember Me” checkbox on the login page:

   1: private bool AuthenticateFormsUser(Uri context, string username, string pwd,
   2:     bool rememberMe)
   3: {
   4:     if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(pwd))
   5:     {
   6:         return false;
   7:     }
   9:     try
  10:     {
  11:         var formsAuthOption = SPFormsAuthenticationOption.None;
  12:         var tokenType = SPSessionTokenWriteType.WriteSessionCookie;
  13:         if (rememberMe)
  14:         {
  15:             formsAuthOption = SPFormsAuthenticationOption.PersistentSignInRequest;
  16:             tokenType = SPSessionTokenWriteType.WritePersistentCookie;
  17:         }
  19:         var authProvider = GetAuthProvider(SPContext.Current.Site);
  20:         var securityToken = SPSecurityContext.SecurityTokenForFormsAuthentication(
  21:             context,
  22:             authProvider.MembershipProvider,
  23:             authProvider.RoleProvider,
  24:             username,
  25:             pwd,
  26:             formsAuthOption);
  28:         var fam = SPFederationAuthenticationModule.Current;
  29:         fam.SetPrincipalAndWriteSessionToken(securityToken, tokenType);
  30:         return true;
  31:     }
  32:     catch (Exception)
  33:     {
  34:         return false;
  35:     }
  36: }

Here on lines 12-16 code checks whether rememberMe parameter is true and if yes uses persistent cookies.

So is it possible to have sliding expiration sessions when persistent cookies are used? The answer is yes, but in order to do that we will need custom HTTP module which will renew token on each request:

   1: public class SlidingSessionModule : IHttpModule
   2: {
   3:     public void Init(HttpApplication context)
   4:     {
   5:         FederatedAuthentication.SessionAuthenticationModule.SessionSecurityTokenReceived +=
   6:             SessionAuthenticationModule_SessionSecurityTokenReceived;
   7:     }
   9:     private void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender,
  10:         SessionSecurityTokenReceivedEventArgs e)
  11:     {
  12:         try
  13:         {
  14:             if (e == null)
  15:             {
  16:                 return;
  17:             }
  18:             var sessionToken = e.SessionToken;
  19:             if (sessionToken == null)
  20:             {
  21:                 return;
  22:             }
  23:             if (claimsPrincipal == null)
  24:             {
  25:                 return;
  26:             }
  28:             TimeSpan cookieLifetime = TimeSpan.FromSeconds(0);
  29:             SPSecurity.RunWithElevatedPrivileges(
  30:                 () =>
  31:                     {
  32:                         cookieLifetime = Microsoft.SharePoint.Administration.Claims.
  33:                             SPSecurityTokenServiceManager.Local.CookieLifetime;
  34:                     });
  36:             DateTime utcNow = DateTime.UtcNow;
  37:             DateTime validFrom = utcNow;
  38:             DateTime validTo = utcNow + cookieLifetime;
  39:             var sam = FederatedAuthentication.SessionAuthenticationModule;
  40:             e.SessionToken = sam.CreateSessionSecurityToken(claimsPrincipal,
  41:                 sessionToken.Context, validFrom, validTo, sessionToken.IsPersistent);
  42:             e.ReissueCookie = true;
  43:         }
  44:         catch (Exception x)
  45:         {
  46:             // log
  47:         }
  48:     }
  50:     public void Dispose()
  51:     {
  52:     }
  53: }

In the module we subscribe on SessionAuthenticationModule.SessionSecurityTokenReceived event (lines 5-6) and in event handler we renew token with extended ValidFrom and ValidTo properties (lines 36-42) which are set from CookieLifetime property of security token service config (lines 29-34) so you may continue configure it from PowerShell.

Then we need to install this module by adding dll to the GAC and the following line to the web.config <modules> section:

   1: <modules>
   2:   ..
   3:   <add name="SlidingSessionModule"
   4:     type="SlidingSessionModule.SlidingSessionModule, SlidingSessionModule, Version=, Culture=neutral, PublicKeyToken=..." />
   5: </modules>

After that you will have sliding sessions with persistent cookies for Sharepoint FBA.