Monday, June 15, 2015

Create reminder workflow for Sharepoint Online which send emails when content is outdated

On the production sites it is often needed to monitor that content of appropriate pages is kept up to date. One of the way to do it is to create workflow which will check Modified date of the page and if it is older than specified time interval, sends email to responsible users. It can be done with custom workflow created in Sharepoint Designer 2013, but it is important to use “Sharepoint 2013 workflow” as template, because “Sharepoint 2010 workflow” don’t have very useful Go to action which will be needed in order to start workflow once and run it by the loop (by default in Sharepoint Online only 2010 workflows are enabled. See Enable Sharepoint 2013 workflows in Sharepoint Online post which shows how to enable 2013 workflows in Sharepoint Online).

In order to create such workflow we need to go to Sharepoint Designer 2013 and choose Workflows > List workflow. In the opened window specify workflow name and the platform:


In the workflow designer first of all we need to create workflow local variable called Past with Date/Time type:


Then create stages with actions like shown below:


Let’s go through stages one by one. First stage is called Initialization. Here we subtract 12 months from current date, store result to workflow local variable called Past and go to next stage called Check content and notify. In this stage we check whether Modified date of the current list item (page) is less than Past variable (current date minus 12 month). If yes, we send email with link on the item to the specific user:


Adding link on the current item to notification email in workflow is little bit tricky so I will also show how link is created:


I.e. we need to use Current Item URL from Workflow Context for link address.

On the last stage we wait for 30 days and repeat from stage 1. As result it will be enough to start workflow once for particular page (it can be done e.g. by selecting page in default doclib view and choose Advanced > Workflows in the context menu) and it will run by itself after that without need to restart it manually each month. This is one of the advantages of having Go to action.

Enable Sharepoint 2013 workflows in Sharepoint Online (Office 365)

In Sharepoint Online you may face with the problem when will try to create workflow in Sharepoint Designer 2013: only “Sharepoint 2010 workflow” will be available in Platform Type dropdown list, while new “Sharepoint 2013 workflow” won’t be there:


The problem will be more unclear after you will check in admin center’s settings page that “Block Sharepoint 2013 workflows” checkbox is unchecked:


In order to enable “Sharepoint 2013 workflows” in Office 365 go to Site settings > Workflow settings:


“Workflow Health” in parenthesis is actual a link (very hidden link which looks like text. This is for sure should be fixed by MS somehow). Click on it and you will see the following page:


On this page click Activate button. After that when you will reconnect to the site in Sharepoint Designer you should see “Sharepoint 2013 workflow” option in the list of available platforms:


Please note that this configuration should be made per SPWeb. I.e. if you activated it only on the root site, it won’t be available on the sub sites. You will need to activate it on sub sites where you need Sharepoint 2013 workflow separately. Hope that this information will help you.

Friday, June 12, 2015

Fix Operation is not valid due to the current state of an object when call ashx handler in Sharepoint context

Custom ashx handler may be useful in Sharepoint e.g. when we need to return some server data in json format in order to consume it on the client side. In this case we most probably copy ashx to subfolder of 14 or 15/Layouts folder and call it in context of Sharepoint site: If in codebehind of ashx handler we need to reopen site under elevated privileges like shown below:

   1: SPSecurity.RunWithElevatedPrivileges(
   2:     () =>
   3:         {
   4:             using (var site = new SPSite(SPContext.Current.Site.ID,
   5:                 SPContext.Current.Site.Zone))
   6:             {
   7:                 using (var web = site.OpenWeb(SPContext.Current.Web.ID))
   8:                 {
   9:                     ...
  10:                 }
  11:             }
  12:         });

we may get exception:

Operation is not valid due to the current state of an object

In order to avoid it we need to use slightly different code:

   1: var currentSite = SPContext.Current.Site;
   2: var currentWeb = SPContext.Current.Web;
   3: SPSecurity.RunWithElevatedPrivileges(
   4:     () =>
   5:         {
   6:             using (var site = new SPSite(currentSite.ID, currentSite.Zone))
   7:             {
   8:                 using (var web = site.OpenWeb(currentWeb.ID))
   9:                 {
  10:                     ...
  11:                 }
  12:             }
  13:         });

Hope that it will help someone.

Monday, June 8, 2015

Change date time fields format in Sharepoint from friendly to standard view

In Sharepoint 2013 when you create new list item or upload document to the doclib, OTB Created and Modified fields will show “A few seconds ago” text instead of actual date time (or e.g. “N minutes ago”):


In order to show date time in them we need to change format from Friendly to Standard mode. It is done in List settings > Field settings:


After that date time columns will show actual value:


Friday, May 22, 2015

Get Sharepoint current datetime in javascript for Sharepoint Online

First of all let’s clarify what I mean by Sharepoint current datetime in the title. This is the datetime which you can see in Created column when create new item in the list or upload new document to the doclib. As you probably know internally Sharepoint stores all datetimes in content database in UTC format. In the Site settings > Regional settings it is possible to specify timezone for the current site. Depending on selected timezone Sharepoint will show datetimes to end users (if you will change timezone and update list view, values in Created and Modified fields will be changed). I.e. Sharepoint current datetime is not always the same as server date time which is set in OS.

On practice we often need to get Sharepoint current datetime and server datetime, e.g. when want to display news which are not older than N days. In order to do that we will get server current datetime and compare it with Created/Modified fields of the news. The question is how to get this datetime in the javascript? We are talking about javascript because this is the basic way currently to create custom components for Sharepoint Online.

One of the ways is to use _spPageContextInfo.clientServerTimeDelta variable which is defined like that:

   1: clientServerTimeDelta: new Date("2015-05-21T16:54:00.0000000Z") - new Date()

If you will check my other article How to get URL of current site collection and other server side properties on client site in Sharepoint, you will find that date defined in string in example above is current UTC date time:

   1: sb.Append("\", clientServerTimeDelta: new Date(\"");
   2: sb.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
   3: sb.Append("\") - new Date()");

In javascript “new Date()” will create datetime object created for the local client’s timezone. I.e. _spPageContextInfo.clientServerTimeDelta contains bias between client’s current datetime and server’s UTC current datetime. When we need server’s current UTC datetime for comparing it with Created/Modified date, we may do it like this:

   1: var d = new Date();
   2: var serverDateTimeNow = _spPageContextInfo.clientServerTimeDelta + d;

There will be difference with actual server time depending on how late above code will be executed, but not crucial. The problem is that _spPageContextInfo.clientServerTimeDelta may not be always available on your page, so let’s consider other approach as well.

Interesting solution was posted by colleague Vadim Gremyachev here: Get current server datetime through javascript REST. However you may face with cross domain request problem when will try to use this approach in Sharepoint app (apps have own sub domains). Also when offset is calcuated:

   1: var offset = data.d.Information.Bias / 60.0;

it doesn’t takes into consideration daylight bias. I.e. more correct approach is the following:

   1: var offset = (data.d.Information.Bias + data.d.Information.DaylightBias) / 60.0;

(there is also StandardBias property but it is set to 0 in examples I saw. For safety you may add it as well). In order to be able to use it in Sharepoint app javascript object model should be used:

   1: var context = new SP.ClientContext(appWebUrl);
   2: var factory = new SP.ProxyWebRequestExecutorFactory(appWebUrl);
   3: context.set_webRequestExecutorFactory(factory);
   5: var hostContext = new SP.AppContextSite(context, postsArchiveURL);
   6: var web = hostContext.get_web();
   7: context.load(web);
   9: context.executeQueryAsync(function () {
  10: create new document or item)
  11:     var regionalSettings = web.get_regionalSettings();
  12:     context.load(regionalSettings);
  13:     context.executeQueryAsync(
  14:         function () {
  15:             var timeZone = regionalSettings.get_timeZone();
  16:             context.load(timeZone);
  17:             context.executeQueryAsync(
  18:                 function () {
  19:                     var info = timeZone.get_information();
  20:                     var offset = (info.get_bias() + info.get_daylightBias()) / 60.0;
  21:                     var serverDateTimeNow =
  22: new Date(new Date().getTime() - offset * 3600 * 1000).toISOString();
  23:                     console.log("serverDateTimeNow: " + serverDateTimeNow);
  24:                 },
  25:                 function (sender, args) {
  26:                     console.log(args.get_message());
  27:                 }
  28:             );
  29:         },
  30:         function (sender, args) {
  31:             console.log(args.get_message());
  32:         }
  33:     );
  34: }, function(sender, err) {
  35:     console.log(err.get_message());
  36: });

I intentially converted serverDateTime variable to string using toISOString() function, because in this format it may be added to the CAML query for comparing with Created or Modified date.

In order to get Sharepoint current datetime we need to combine 2 approaches: to the server’s current UTC datetime

   1: var serverDateTimeNow = _spPageContextInfo.clientServerTimeDelta + d;

add offset like shown in example above. As result we will get time which is almost the same that is shown in Created/Modified fields. Hope that this information will help you in your work.

Thursday, May 21, 2015

Enumerate all tenant’s site collections in Sharepoint Online via PowerShell

It is quite easy to enumerate all site collections via PowerShell for on-premise Sharepoint (see e.g. Set search settings in all site collections of Sharepoint web application via PowerShell), but for Sharepoint Online it is more tricky. C# solution which uses client object model was posted in the following article: Get list of site collections using CSOM in Office365. Let’s try to do the same in PowerShell. We will need Microsoft.Online.SharePoint.Client.Tenant.dll library which can be obtained from nuget.

Here is the script:

   1: param(
   2:     [string]$adminWebAppUrl,
   3:     [string]$login,
   4:     [string]$password
   5: )
   7: $currentDir = Convert-Path(Get-Location)
   8: $dllsDir = resolve-path($currentDir + "\dlls")
  10: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  11: "Microsoft.SharePoint.Client.dll"))
  12: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  13: "Microsoft.SharePoint.Client.Runtime.dll"))
  14: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  15: "Microsoft.SharePoint.Client.Taxonomy.dll"))
  16: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  17: "Microsoft.Online.SharePoint.Client.Tenant.dll"))
  19: if (-not $adminWebAppUrl)
  20: {
  21:     Write-Host "Specify admin web app url in adminWebAppUrl parameter"
  22: -foregroundcolor red
  23:     return
  24: }
  26: if (-not $login)
  27: {
  28:     Write-Host "Specify user name in login parameter" -foregroundcolor red
  29:     return
  30: }
  32: if (-not $password)
  33: {
  34:     Write-Host "Specify user password in password parameter" -foregroundcolor red
  35:     return
  36: }
  38: function Do-Something($url)
  39: {
  40:     Write-Host "Working with $url" -foregroundColor green
  41:     # ... add your logic here
  42: }
  44: # initialize client context
  45: $clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($siteURL)    
  46: $clientContext.RequestTimeOut = 1000 * 60 * 10;
  47: $clientContext.AuthenticationMode =
  48: [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
  49: $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
  50: $credentials =
  51: New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username,
  52: $securePassword)
  53: $clientContext.Credentials = $credentials
  54: $web = $clientContext.Web
  55: $site = $clientContext.Site
  56: $clientContext.Load($web)
  57: $clientContext.Load($site)
  58: $clientContext.ExecuteQuery()
  60: # enumerate all site collections
  61: $web = $clientContext.Web
  62: $tenant = New-Object "Microsoft.Online.SharePoint.TenantAdministration.Tenant"
  63: -ArgumentList $clientContext
  64: $props = $tenant.GetSiteProperties(0, $true)
  65: $clientContext.Load($props)
  66: $clientContext.ExecuteQuery()
  68: foreach($sp in $props)
  69: {
  70:     Do-Something $sp.Url
  71: }

The important moment is that we need to provide Sharepoint admin center URL as parameter for this script, not URL of any real site collection. In script we initialize client context (lines 45-58), enumerate site collections using Tenant class from Microsoft.Online.SharePoint.Client.Tenant.dll (lines 61-71) and for each site collection call our custom function. Hope that it will be helpful.

Sunday, April 19, 2015

Perform search requests in Sharepoint via javascript object model

In one of my previous posts I showed how to get Sharepoint user profile properties via javascript object model: here. In this post I will show how to do another common task via javascript: perform search requests. It will be useful e.g. if you develop search-driven solution for Sharepoint Online. Here is the javascript code:

   1: var newsItems = [];
   3: var NewsItem = function (title, url, ingress, newsDate) {
   4:     this.Title = title;
   5:     this.Url = url;
   6:     this.Ingress = ingress;
   7:     this.NewsDate = newsDate;
   8: }
   9: SP.SOD.executeFunc('',
  10: 'Microsoft.SharePoint.Client.Search.Query.KeywordQuery', function () {
  11:     var Search = Microsoft.SharePoint.Client.Search.Query;
  12:     var ctx = SP.ClientContext.get_current();
  13:     var site = ctx.get_site();
  14:     ctx.load(site);
  16:     var query = new Search.KeywordQuery(ctx);
  17:     query.set_queryText("..."); // search query
  18:     query.set_enableSorting(true);
  20:     var sortproperties = query.get_sortList();
  21:     sortproperties.add("NewsDate", 1);
  22:     query.set_rowLimit(100);
  23:     query.get_selectProperties().add("NewsIngress");
  24:     query.get_selectProperties().add("Path");
  25:     query.get_selectProperties().add("NewsDate");
  26:     query.set_trimDuplicates(false);
  28:     var executor = new Search.SearchExecutor(ctx);
  29:     var result = executor.executeQuery(query);
  31:     ctx.executeQueryAsync(function () {
  33:         var tableCollection = new Search.ResultTableCollection();
  34:         tableCollection.initPropertiesFromJson(result.get_value());
  35:         var rows = tableCollection.get_item(0).get_resultRows();
  36:         var enumItems = rows;
  37:         var currentRow = 0;
  38:         var rowCount = rows.length;
  40:         while (currentRow < rowCount) {
  41:             var row = rows[currentRow];
  42:             newsItems.push(new NewsItem(row["Title"], row["Path"], row["NewsIngress"],
  43:                 row["NewsDate"]));
  44:             currentRow++;
  45:         }
  46:     },
  47:     function (sender, args) {
  48:         console.log(args.get_message());
  49:     });
  50: });

At first we prepare query object (lines 16-26). Here we set actual query string (line 17) and various properties, including managed properties which should be retrieved from search index. After that we perform actual query to the search index asynchronously using SearchExecutor object (lines 28-44). Search results are saved to the array of news items which then may be used e.g. for binding to UI component. Having this example you will be able to easily adopt it for your scenario.