04 February 2019

Add background colour to your Planner Cards - now available as a Chrome extension!

UPDATE: Microsoft have changed the render model for Planner so unfortunately this approach doesn't work any more. This is no great surprise and my post was always disclaimed that this could happen at any time!
In the meantime, I've created another Chrome extension to make Planner render in Dark Mode which is really cool!

One thing that bugs me about Planner is the way the Labels are presented on the Task Board. They are so small and don't really catch the eye - wouldn't it be better if the Task Card itself was given the colour of the label?

Just like this:
Planner - with coloured tasks!
I think this makes a big Task Board much easier to visually scan, and I'm clearly not alone in wanting this based on this User Voice request, and this one, and this one, and this one!

Disclaimer time! Unfortunately Microsoft hasn't yet provided methods to customise Planner in an enterprise-ready, supportable manner. So this is a bit of a hack using client-initiated JavaScript injection. The approach is (by necessity) rather crude - it uses jQuery DOM examination to scan Task Cards to see if they any labels applied, and to force background colours accordingly. Cards can have multiple labels, but I chose in this implementation to simply use the first label applied - this will be the 'hottest' colour of the selected labels. I also chose to add a grey colour to unlabelled Cards and to give all cards a highlight colour down the left to match the Planner visual theming of labels.

The approach relies heavily on specific mark-up structures and CSS class names which are liable to change at any time, so be aware that this will stop working at some point when Microsoft update Planner. I used an ugly approach to run the code every second - this ensures that the colours can be updated quickly as you change Cards labels, without requiring a page reload.

So - without further ado, here's the code:

// Script to colour code Planner tasks based on labels!

function colourisePlannerTaskCards() {

    // Colourise task cards
    $(".taskBoardCard").each( function(i) {
        
        // Light theme works better for individual cards (otherwise we get contrast issues)
        $(this).addClass("theme-light");

        // Choose colour
        if ($(this).find(".categoryLabelColor-0").length > 0)
        {
            $(this).find(".container").css("background", 
                "linear-gradient(to right,#e000f1 0,#e000f1 2%,#f777ff 2%,#f777ff 100%)");
        }
        else if($(this).find(".categoryLabelColor-1").length > 0)
        {
            $(this).find(".container").css("background", 
                "linear-gradient(to right,#e04e27 0,#e04e27 2%,#f9a286 2%,#f9a286 100%)");
        }
        else if($(this).find(".categoryLabelColor-2").length > 0)
        {
            $(this).find(".container").css("background", 
                "linear-gradient(to right,#e39e27 0,#e39e27 2%,#fbd18e 2%,#fbd18e 100%)");
        }
        else if($(this).find(".categoryLabelColor-3").length > 0)
        {
            $(this).find(".container").css("background", 
                "linear-gradient(to right,#aee01e 0,#aee01e 2%,#ddf48a 2%,#ddf48a 100%)");
        }
        else if($(this).find(".categoryLabelColor-4").length > 0)
        {
            $(this).find(".container").css("background", 
                "linear-gradient(to right,#46a08e 0,#46a08e 2%,#9cd5c7 2%,#9cd5c7 100%)");
        }
        else if($(this).find(".categoryLabelColor-5").length > 0)
        {
            $(this).find(".container").css("background", 
                "linear-gradient(to right,#62cef0 0,#62cef0 2%,#a7eeff 2%,#a7eeff 100%)");
        }
        else
        {
            $(this).find(".container").css("background",
                "linear-gradient(to right,#a5a7a9 0,#a5a7a9 2%,#d5d7d9 2%,#d5d7d9 100%)");
        }
        $(this).find(".textContent").css("background", "none");
        $(this).find(".bottomBar").css("background", "none").css("border-top", "none");

    });

    setTimeout(colourisePlannerTaskCards, 1000);
}

window.addEventListener('load', function(){
    colourisePlannerTaskCards();
});

Try it out and let me know what you think in the comments! And of course upvote the User Voice suggestions that I linked to at the top of the post.

And as a final note, in creating this hack I noticed an interesting CSS class "theme-light" which is applied on the <body> element and heavily influences the CSS. If this is class changed (manually or through injected JavaScript) to "theme-dark" then suddenly we can see that Microsoft has a partially implemented native dark theme for Planner! It's ultra-high-contrast with bright green cards and it's clearly a work in development but I'm keen to see it fully implemented.

04 March 2018

Practical Nintex Governance

For those who asked for the slides, and for those who weren't fortunate enough to make it to San Diego for the amazing 2018 Nintex "xchange" conference, you can download the deck for my session "Practical Nintex Governance" from this page: http://nintex2018sessioncatalog.azurewebsites.net/SessionDetail.aspx?id=133432

Hope to see you next year!

10 May 2017

Office 365 Groups in the Real World

A massive shout out to all the organisers, sponsors, speakers and attendees for another very successful Office 365 Saturday Perth event over the weekend!

As promised, here are the slides I presented on the day, I hope they are useful. Thanks to everyone for the positive feedback, it's clear that some of the key messages regarding Groups implementation really hit home for a few people!


Cheers,
Marty

26 October 2015

SharePoint Capacity Planning with Nintex (Part 1)

Using Nintex Workflow to automatically manage thousands of Nintex Forms or other SharePoint List Items

SharePoint Capacity Planning

One of the great myths of SharePoint surrounds the List View Threshold. The common story goes like this: someone creates a list or library with the default configuration, then suddenly it starts getting some serious use, and once it hits 5,001 items, people start seeing strange behaviour. People then conclude that SharePoint can’t handle any more than 5,000 items, and negative impressions of SharePoint are the result.
Of course, SharePoint supports 30,000,000 items in a list or library!
Effective use of large lists generally requires just a little bit of capacity planning around your views and indexes. A simple approach is to use folders and subfolders to ensure that there is a maximum of 5,000 child nodes (items and subfolders) at any given level. The best way to ensure such a folder structure is maintained, is to completely automate it and remove any need for human intervention – Nintex Workflow to the rescue!

The Business Problem

A client came to me recently and asked about implementation strategies for a Nintex Form that was going to receive several hundred submissions every day! Clearly some capacity planning would be required, otherwise the list would become unmanageable very quickly.

So how can we construct a simple but clear Information Architecture? Ideally one that is reasonably generic so that it could be reused across multiple lists if required?

The Solution

The approach that I suggested was to build a subfolder structure that separates items based on a date. The date can be separated into year, month and day components, and the approach can use one, two or three levels of subfolders, depending on the expected capacity. Items would be stored at the leaf nodes in the structure.

The following table gives a sense of the capacities that would require second and/or third levels to be used, ensuring that we never exceed the 5,000 list view threshold.

Number of Subfolder Levels
Subfolder Levels
Expected items created per day
1
Year
Up to 12
2
Year and Month
Up to 150
3
Year, Month and Day
Up to 5000

So for my client and their hundreds of forms every day, we would need to use all three levels.

Here’s a diagram of how this folder structure would be constructed, and also a naming convention which aligns chronological ordering with alphanumeric ordering:

Subfolder structure for automated filing and capacity management

For each item to be filed into this structure, we need to nominate a specific date for the item. This date value could be the date of item creation, or in a Nintex Workflow context, it could be the date when the Workflow meets a milestone point such as process completion.

Managing Item URLs

Nintex Forms are managed within SharePoint as List Items rather than as Documents, and List Items have a very neat property whereby you can move them around the folder structure within a library, without changing their URLs! So we can move items into their subfolder structure at any time within their lifecycle, without impacting on end users or creating dead hyperlinks. Note that if you try this with documents, then you’ll see that the URL of each document will change to reflect its position within the folder structure, and this will lead to broken hyperlinks and frustrated end users!
This approach will work with any List Items, including Nintex Forms, but is not as neat for Documents because of URL changes

Implementation Considerations

This solution concept is very generic; it only requires the list items to be associated with a date. It is not coupled to the schema of a specific list or a Nintex Form schema, so we can implement the process within a User Defined Action (UDA). The UDA can then be quickly and reliably dragged and dropped into any Workflow across the SharePoint system, gaining huge reuse and management benefits. If you’re not familiar with UDAs, check out this Nintex Community article.

There is always a trade-off in software development, and in this case, the selection to use a UDA increase reusability but introduces some build complexity because list item context is not available in the Nintex Workflow designer.

Build

Stay tuned for part 2 where we build the UDA which automatically files list items into the described subfolder structure, building out folders as required.

24 May 2015

Slide Deck from Office 365 Satuday Perth #o365per

Massive thanks to Haylee for organising another very successful Office 365 Saturday event in Perth, and thanks to the sponsors, presenters and attendees for contributing to the day!

As promised, here's the slide deck from my presentation: "The evolving Office 365 landscape - Build and Ignite".

I hope that everyone picked up something useful from the presentation and from the day.

02 May 2015

SharePoint Explorer updated with the latest Office 365 CSOM APIs

For those that may have missed it, Microsoft recently released a significant update to the CSOM capabilities of Office 365.

So that makes it a great time to update the SharePoint Explorer tool that I have blogged about previously (here and here).

The updated download is available here. Please read through the previous blog posts to understand the tool and its usage.

Enjoy!

19 February 2015

Visual Studio intellisense for NWF$ - jQuery inside Nintex Forms

Those who have written custom JavaScript to act within Nintex Forms are probably familiar with the NWF$ construct which is the name that Nintex has given to the jQuery object - which by convention is usually called just $.

This allows us to harness the power of jQuery but necessitates using a different naming convention that Visual Studio intellisense just doesn't understand. So, here's a quick tip on how you can get everything working nicely in your IDE.

Firstly, work out the version of jQuery that is in use (I can see from tracing my browser session that Nintex Forms 365 is currently using version 1.10.2, from https://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.10.2.min.js).

In your Visual Studio project, open up the Package Manager Console (you can find it from the "View" menu under "Other Windows"), and type in the following command (note the version number is used to make sure intellisense knows exactly which commands should be available):
Install-Package jQuery-vsdoc -Version 1.10.2
This will bring in the appropriate files for jQuery and intellisense, but intellisense will only work against the $ or jQuery names, not against the NWF$ name as used by Nintex.

To allow intellisense to understand the NWF$ name, we need to amke a small modification to one of the files added by the Package Installation process. In the Solution Explorer, find the file whose name ends with vsdoc.js - in my case, this is jquery-1.10.2-vsdoc.js. Open up this file and scroll right to the bottom, where you'll see this fragment:
jQuery.fn = jQuery.prototype;
jQuery.fn.init.prototype = jQuery.fn;
window.jQuery = window.$ = jQuery;
})(window);
Now add the following line:
NWF$ = jQuery;
so that the end of the file now looks like this:
jQuery.fn = jQuery.prototype;
jQuery.fn.init.prototype = jQuery.fn;
window.jQuery = window.$ = jQuery;
NWF$ = jQuery;
})(window);
Save the file, and that's it!

Now, when you start using NWF$, Visual Studio will understand what you're doing and will help suggest jQuery functions for you:

Visual Studio intellisense against the NWF$ object

30 August 2014

PowerShell cmdlet to Import Nintex User Defined Actions (UDAs)

Anyone who is doing non-trivial Workflow development in Nintex should be aware of UDAs; Nintex has a great summary of them here, and I've already blogged about important considerations for automated importing of these here.

The next logical step from my last blog post then is to create a PowerShell cmdlet to make this process nice and neat - so here it is! As with my PowerShell cmdlet to provision Nintex Workflow Constants, this needs to be run on a SharePoint server within the Farm - sorry to those who don't have server access! I'm always interested to hear about remote methods that can achieve these results, let me know in the comments in you find out how this can be done.

You can view and download the cmdlet here. Note that the file has been renamed with a .ps1.txt file extension as some data transfer mechanisms block .ps1 files as a security concern - so you'll need to rename the file to have a .ps1 extension after you download it.
<#
.SYNOPSIS
   Publishes a Nintex Workflow UDA (User Defined Action), keeping it's original GUID intact!
.DESCRIPTION
   Because this approach retains GUIDs, you should avoid deploying the same UDA multiple times
   in an environment. If you want to reuse your UDA in a broader scope that where it is defined,
   you should promote it to a higher level rather than duplicating it.
   This script is designed for moving UDAs between farms.
.PARAMETER <Scope>
   Mandatory - where the UDA is defined. Must be one of: Farm, SiteCollection, Web
.PARAMETER <Url>
   Mandatory - the URL of the Site. Note that even for a Farm UDA you need to specify the URL of a valid Web for the publishing process to work.  
.PARAMETER <UdaFilePath>
   Mandatory - the full Path to the .uda file.
.PARAMETER <ChangeComments>
   Optional - but allows comments to be added to the publish process.
.PARAMETER <Publish>
   Optional - deafult value is $true. Using $false will allow it to be imported without publishing.
.EXAMPLE
   #Here's an example that publishes a Farm level UDA:
   .\Publish-NintexUda.ps1 `
  -Scope "Farm" `
  -Url "http://myfarm/sites/myweb" `
  -UdaFilePath "C:\ExampleUda.uda" `
#>

Param(
 [Parameter(Mandatory = $true, Position = 1)]
 [string] $Scope,
 [Parameter(Mandatory = $true, Position = 2)]
 [string] $Url,
 [Parameter(Mandatory = $true, Position = 3)]
 [string] $UdaFilePath,
 [Parameter(Mandatory = $false, Position = 4)]
 [string] $ChangeComments = "",
 [Parameter(Mandatory = $false, Position = 5)]
 [bool] $Publish = $true
 )

[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SharePoint') | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName('Nintex.Workflow') | Out-Null

function global:ImportNintexWorkflowUDA($filePath, $web, $publish, $configScope, $publishScope, $comments)
{ 
 $fs = New-Object System.IO.FileStream($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
 $ms = New-Object System.IO.MemoryStream
 # This only works for .NET 4 and above, so it won't work in SP 2010
 # $fs.CopyTo($ms)
 
 # Here's the more compatible (and ugly) alternative
 $buffer = New-Object Byte[] 4096
    $read = $fs.Read($buffer, 0, $buffer.Length)
 while ($read -gt 0)
 {
  $ms.Write($buffer, 0, $read)
  $read = $fs.Read($buffer, 0, $buffer.Length)
 }

 
 $fs.Close()
 $fs.Dispose() 
 $uda = [Nintex.Workflow.UserDefinedActions.UserDefinedAction]::Import($web, $ms, $configScope)
 $uda.Update($web, $publish, $publishScope, $comments)
 $ms.Close()
 $ms.Dispose()
}

$ArgsValid = $true

if ($Scope -ne "Farm" -and $Scope -ne "SiteCollection" -and $Scope -ne "Web")
{
 Write-Host "Error - Scope parameter must be one of: Farm, SiteCollection, Web" -ForegroundColor Red
 $ArgsValid = $false
}

$configScope = $null
$publishScope = $null

if ($Scope -eq "Farm")
{
 $configScope = [Nintex.Workflow.ConfigurationScope]::Farm
 $publishScope = [Nintex.Workflow.Publishing.Scope]::Farm
}
if ($Scope -eq "SiteCollection")
{
 $configScope = [Nintex.Workflow.ConfigurationScope]::Site
 $publishScope = [Nintex.Workflow.Publishing.Scope]::SiteCollection
}
if ($Scope -eq "Web")
{
 $configScope = [Nintex.Workflow.ConfigurationScope]::Web
 $publishScope = [Nintex.Workflow.Publishing.Scope]::Web
}

if ($ArgsValid)
{  
 $web = $null
 $web = Get-SPWeb $Url
 
 if ($web -ne $null)
 {
  Write-Host ("Attempting to publish UDA... ") -NoNewline 
  ImportNintexWorkflowUDA $UdaFilePath $web $Publish $configScope $publishScope $ChangeComments
  Write-Host "done"
 } 
}

26 July 2014

PowerShell cmdlet to provision Nintex Workflow Constants

If you are building Nintex Workflows, and especially if you are deploying them across multiple environments, then you should be considering using Nintex Workflow constants. These constants are remarkably useful to make your Nintex Workflows portable, so that they can easily be deployed into multiple environments without confusion or wasted effort. Any workflow parameters which are specific to the environment in which the workflow is running, or which are likely to be changed by the business at some stage, should be built into your workflows using these constants.

Many of our clients are looking to improve their processes for deploying and managing SharePoint and Nintex based functionality, and the critical aspects tend to fall around governance and repeatability. So in the interests of repeatability, here's a PowerShell cmdlet to create a new Nintex Workflow constant.

With this script you can specify any type of constant, including credentials which are useful for service accounts. You can also define the constant at a scope of Farm, Site Collection or Site.

The cmdlet needs to be run on a server within the SharePoint farm where the constant is to be created. If anyone is aware of alternate methods that can be run remotely, please let me know in the comments!

You can view and download the cmdlet here. Note that the file has been renamed with a .ps1.txt file extension as some data transfer mechanisms block .ps1 files as a security concern - so you'll need to rename the file to have a .ps1 extension after you download it.
<#
.SYNOPSIS
   Creates a Nintex Workflow Constant
.DESCRIPTION
   
.PARAMETER <Name>
   Mandatory - the Name of the Constant. Keep these unique!
.PARAMETER <Description>
   Mandatory - the Description of the Constant. Use this to help other developers understand what it's used for.
.PARAMETER <Type>
   Mandatory - the Type of the Constant. Must be one of: Number, String, Date, SecureString, Credential
.PARAMETER <Scope>
   Mandatory - where the Constant is defined. Must be one of: Farm, SiteCollection, Web
.PARAMETER <Value>
   Optional - the value of the Constant. This isn't used for Credential type constants, but it's Mandatory for every other type.
.PARAMETER <Url>
   Optional - but Mandatory is the Scope is SiteCollection or Web. Defines where the Constant should live.
.PARAMETER <Sensitive>
   Optional - default value is $false.
.PARAMETER <AdminOnly>
   Optional - default value is $false.
.PARAMETER <Username>
   Optional - but Mandatory if Type is Credential.
.PARAMETER <Password>
   Optional - but Mandatory if Type is Credential.
.EXAMPLE
   #Here's an example that generates a Sensitive, Credential type constant at Web level:
   .\Create-NintexWFConstant.ps1 `
  -Name "test" `
  -Description "Example only" `
  -Scope "Web" `
  -Type "Credential" `
  -Sensitive $true `
  -Username "DOMAIN\user" `
  -Password "password123" `
  -Url "http://myfarm/sites/myweb"

 #Here's a second example that creates a Farm string type Constant:
 .\Create-NintexWFConstant.ps1 `
  -Name "test2" `
  -Description "Example only" `
  -Scope "Farm" `
  -Type "String" `
  -Value "example constant value"
#>

Param(
 [Parameter(Mandatory = $true, Position = 1)]
 [string] $Name,
 [Parameter(Mandatory = $true, Position = 2)]
 [string] $Description,
 [Parameter(Mandatory = $true, Position = 3)]
 [string] $Type,
 [Parameter(Mandatory = $true, Position = 4)]
 [string] $Scope,
 [Parameter(Mandatory = $true, Position = 5)]
 [string] $Value,
 [Parameter(Mandatory = $false, Position = 6)]
 [string] $Url,
 [Parameter(Mandatory = $false, Position = 7)]
 [bool] $Sensitive = $false,
 [Parameter(Mandatory = $false, Position = 8)]
 [bool] $AdminOnly = $false,
 [Parameter(Mandatory = $false, Position = 9)]
 [string] $Username = "",
 [Parameter(Mandatory = $false, Position = 10)]
 [string] $Password = ""
)

[System.Reflection.Assembly]::LoadWithPartialName('Nintex.Workflow') | Out-Null

$ArgsValid = $true

if ($Type -ne "Number" -and $Type -ne "String" -and $Type -ne "Date" -and $Type -ne "SecureString" -and $Type-ne "Credential")
{
 Write-Host "Error - Type parameter must be one of: Number, String, Date, SecureString, Credential." -ForegroundColor Red
 $ArgsValid = $false
}

if ($Type -eq "Credential")
{
 if ($Username -eq $null -or $Username -eq "" -or $Password -eq $null -or $Password -eq "")
 {
  Write-Host "Credential Error - Username and Password combination not supplied." -ForegroundColor Red
  $ArgsValid = $false
 }

 # Generate string for Credential
 $cred = New-Object Nintex.Workflow.CredentialValue($Username, $Password)
 $serialiser = New-Object System.Xml.Serialization.XmlSerializer($cred.GetType())
 $sb = New-Object System.Text.StringBuilder
 $sw = New-Object System.IO.StringWriter($sb)
 $serialiser.Serialize($sw, $cred)
 $Value = $sb.ToString()

}
else
{
 if ($Value -eq $null -or $Value -eq "")
 {
  Write-Host "Error - Value must be supplied for Constants of Type other than Credential." -ForegroundColor Red
  $ArgsValid = $false
 }
}

$SiteId = [Guid]::Empty
$WebId = [Guid]::Empty

if ($Scope -eq "Farm")
{
 #Use default SiteId and WebId values
}
else
{
 if ($Scope -eq "SiteCollection")
 {
  $SiteId = (Get-SPSite $Url).Id
 }
 else
 {
  if ($Scope -eq "Web")
  {
   $Web = Get-SPWeb $Url
   $WebId = $Web.Id
   $SiteId = $Web.Site.Id
  }
  else
  {
   Write-Host "Error - Scope parameter not valid: must be one of Farm, SiteCollection, Web." -ForegroundColor Red
   $ArgsValid = $false
  }
 }
}


if ($ArgsValid)
{
 Write-Host ("Attempting to create Workflow Constant " + $Name + " ... ") -NoNewline 

 $constant = New-Object Nintex.Workflow.WorkflowConstant(`
  $Name, $Description, $Value, $Sensitive, $SiteId, $WebId, $Type, $AdminOnly)

 $constant.Update()

 Write-Host "done"
}

22 June 2014

Building a SharePoint Forms Platform: SharePoint Saturday Slide Deck

SharePoint Saturday Perth 2014 was a huge success - we had more attendees than any other SharePoint Saturday in Australian history! Massive thanks to everyone who came along, to all the presenters, and of course to the sponsors and organisers.

As promised, here's the slide deck that I presented on the day - Building a SharePoint Forms Platform: Real World Considerations and Lessons. Enjoy!

http://1drv.ms/1pzksEZ

04 April 2014

Automated publishing of Nintex Workflow User Defined Actions, with workflow references

One of my absolute favourite features in Nintex Workflow is the User Defined Action (UDA). It lets you wrap up a commonly used piece of functionality with defined input and output parameters, into a neat, centrally managed object, which can be easily reused in workflows across the entire farm. A UDA can also easily be updated whereby Nintex Workflow automatically updates all workflows that reference the UDA.

For a workflow project we are currently building, I was a little disappointed to find that there is no supported method to automate the import of a UDA. This slows down our planned deployment process which is nearly entirely automated through PowerShell.

What was even more troubling was that importing a UDA through the user interface did not work correctly for imported workflows that referenced the UDA. Each UDA referenced within a workflow appeared like this:
This is how a UDA appears in a workflow if the reference is broken, for example through an import process
Attempting to configure the UDA only threw exceptions. The only way to resolve this was to manually delete the UDA references, replace them with new references, and update all the parameters in the references. This is very slow, error-prone and frustrating!

With some investigation, it turns out that each UDA has an internal GUID identifier which is used by workflows to reference the UDA. This GUID can been seen in both the exported .uda and .nwf files. It seems that when importing a UDA through the user interface, a new GUID is assigned to the UDA. This is what was breaking the references.

Warning: the solution I am presenting here is not technically part of the supported Nintex Workflow API, so it is not guaranteed to work for all future releases, but it works for my initial testing against Nintex Workflow 2013 version 3.0.6.0. This process is based on the suggestion from user tburdin on the excellent Nintex Connect Forums, in this thread. It uses the publicly exposed members of the Nintex Workflow assemblies to automate the import of UDAs. Here is the PowerShell snippet:

[System.Reflection.Assembly]::LoadWithPartialName('Nintex.Workflow') | Out-Null
 
$fs = New-Object System.IO.FileStream($filePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
$ms = New-Object System.IO.MemoryStream
$fs.CopyTo($ms)
$fs.Close()
$fs.Dispose() 
$uda = [Nintex.Workflow.UserDefinedActions.UserDefinedAction]::Import($web, $ms, [Nintex.Workflow.ConfigurationScope]::Site)
$uda.Update($web, $publish, [Nintex.Workflow.Publishing.Scope]::SiteCollection, "")
$ms.Close()
$ms.Dispose()

Note that the code assumes you've populated the following variables:
  • $name: the name of your UDA.
  • $filePath: the full path to your exported .uda file.
  • $web: the SPWeb object defining the context of where the UDA is going to be imported
  • $publish: a Boolean value indicating whether to publish the UDA or simply import it
The above code publishes to the Site Collection level, but you can also publish to a single site or even to the entire Farm, by altering the values of the Nintex enumerations referenced in the code. To publish to a single site, use [Nintex.Workflow.ConfigurationScope]::Web and [Nintex.Workflow.Publishing.Scope]::Web, and to publish to a farm, use [Nintex.Workflow.ConfigurationScope]::Farm and [Nintex.Workflow.Publishing.Scope]::Farm.

The best aspect of this process is that it retains the original GUID reference of each UDA. So, when you import UDA using this method and then import workflows that reference the original UDAs, no re-wiring is required! You can fully automate the process and have a portable workflow deployment strategy.

It's worth noting that Nintex probably updates those GUIDs during the import process to ensure that you can never get in a situation where you have multiple UDAs in a farm that use the same GUID. For example, you could import the same UDA into multiple sites (you wouldn't want to do this, but it would certainly be possible). In this scenario, having two or more UDAs with the same GUID would almost certainly cause conflicts. Use with caution!

31 October 2013

Help! I deleted my root site!

Don't laugh! This happened to a colleague of mine quite recently, and in the past I have seen consultants lose contracts for making this mistake. The last thing you want to be doing is an unscheduled test of the restore process and explaining the finer details of RTO and RPO to your stakeholders... that's a scary thought for Halloween!

Since SharePoint 2010 Service Pack 1, we have had the Site Recycle Bin available to restore accidentally deleted sites. However, to access this you need to go to the root site of the site collection containing the site you deleted. If you deleted the root site itself, this is not possible anymore.

Although it may not be immediately obvious (especially to content owners who don't wade into Central Administration), deleting a root site is essentially the same operation as deleting the entire site collection. The Site Collection Recycle Bin works in both cases.

Using the Site Collection Recycle Bin is pretty easy but it does require PowerShell, and you'll need to either be on SharePoint 2013 or have 2010 with Service Pack 1 installed.

Run Get-SPDeletedSite to get a list of all deleted site collections in the farm.

Then, use the ID of your specific site collection with the Restore-SPDeletedSite command.

Simple!

Note to Microsoft: perhaps the "Delete this site" shouldn't be shown for a root web and we can leave the deletion of site collections to those who can access Central Admin or PowerShell. This would provide a symmetry with the creation of site collections.

Note to SharePoint users: always be careful that you're actually in the site you think you're in before you delete a site!

31 July 2013

Why can't I create Community Sites in a Community Portal?

The Communities workload is a fantastic addition to SharePoint 2013; check out here for an overview of the feature set.

When creating a new Community Portal site collection, it's natural to immediately want to create Community Sites. If your planned Communities Information Architecture is very small – or you’re doing testing or development – then you might want to create Community Sites as sub-sites of the Community Portal.

Unfortunately I've seen several SharePoint 2013 environments now where this is simply not possible out-of-the-box. Attempting to create a sub-site of a brand new Community Portal only offers a limited set of site templates which does not include Community Site:
There's no Community Site option...
This is a little frustrating, but it is easily fixed!

Go into Site Settings for your Community Portal, and select Site Collection Features. Make sure that SharePoint Server Standard Site Collection Features is activated - by default this may not be active:

Once this is done, you can create Community sites:
That's better!
From here, you may find that you receive the following error when attempting to create a Community Site:

Dependency feature 'Ratings' (id: 915c240e-a6cc-49b8-8b2c-0bff8b553ed3) for feature 'CommunitySite' (id: 961d6a9c-4388-4cf2-9733-38ee8c89afd4) is not activated at this scope.


This one is also pretty easy to solve; just run the following PowerShell command will activate the dependent hidden Feature referenced in the above message:

Enable-SPFeature –Identity Ratings –url [site collection URL]

Now you're good to go!

28 May 2013

SharePoint 2013 and Visio 2013: Visualise Success

Thanks to those who attended today's presentation on SharePoint 2013 and Visio 2013!

As promised, here are the slides from today's presentation. Thanks to William Cornwill from Microsoft for the material and the opportunity to present, it's always good to get people thinking about how they can use the broad Microsoft tool set to achieve business goals.

20 May 2013

Synchronising Active Directory User Attributes into SharePoint Online User Profile Properties: Part 2

I have received quite a bit of interest in the Active Directory / Office 365 User Profile synchronisation implementation that has been mentioned previously in this blog, as well as in some local SharePoint community presentations, so I thought it would be a good time to write it up in a bit more detail.

Goal:
Customise the Office 365 User Profile Syncronisation Service to synchronise additional attributes (e.g. Mobile phone number) from Active Directory into Office 365.

Constraint:
SharePoint Online does not allow us to change mappings for the built-in User Profile Syncronisation service.

Solution:
Build a custom automated connector that synchronises Active Directory and SharePoint Online User Profiles.

Approach:
Out of the various APIs offered by SharePoint, the only method that we found we could update User Profile information programmatically was by using the UserProfileService.asmx web service. Note that our investigations were conducted against a SharePoint 2010 based tenancy; new 2013 based tenancies offer REST web services which will probably be able to achieve the same goal.

The primary difficulty we found with using the web service was that many operations (including any updates to a SharePoint Online User Profile) would only succeed if the call to the web service was made whilst authenticated as the user who owns the profile being queried or updated! Not even when authenticated as a global tenant administrator and site collection administrator can you programmatically update someone else's User Profile, which is a bit strange given that SharePoint allows you to do exactly this through the tenancy administration interface.

The way we got around this authentication problem was by performing the User Profile update operation from within JavaScript code which is embedded into the Master Page of the My Site Host site collection. This custom JavaScript code runs every time a user loads any page within the site collection, and because it runs in the user's browser, the Client OM operations within the code run in the context of the current user, who is authorised to update their own User Profile. But how does the JavaScript code know what values to write into the User Profile?

This is where things get complicated! We set up a temporary data holding area (just a large custom SharePoint list) within the My Site Host. We built a custom on-premises Windows Service to query AD and push user data into the custom list on a periodic schedule. The data is then already sitting inside the My Site Host site collection and can be queried from our custom JavaScript code using the Client OM.

The following diagram helps to illustrate the process (click to expand):
Data flow of the custom AD / Office 365 connector
To follow this diagram, let's start with just the red arrows. On the left hand side of the brick wall (the corporate firewall) we have the on-premises elements of the solution: Active Directory (AD) and our Custom Windows Service. The Custom Windows Service initiates the transaction and uses the .NET Client OM to write data into the Custom List up in Office 365. This is as far as the data can travel whilst our automated processing is authenticating into Office using a shared service account.

From here, everything happens in the cloud; let's look at the yellow lines. Whenever any user loads a SharePoint Content Page in the My Site Host site collection, the browser executes some custom JavaScript code in the context of the current user. This code first uses the JavaScript Client OM to query the data from the Custom List that was populated earlier in the process. The JavaScript code then calls the SharePoint Web Service (UserProfleService.asmx) to push the data into the SharePoint User Profile.

The process of moving data from Office 365 User Profiles into Active Directory is basically the same, but  in reverse. For those User Profile Properties that are marked has being able to be read by Everyone, the Custom Windows Service can read the data directly via the UserProfileService.asmx web service and we can short circuit a lot of the process.

Summary:
Using the above approach, we built an automated process that is completely configurable regarding property mappings and synchronisation direction, that works for various data types, across both 2010 and 2013 versions of SharePoint Online. It's admittedly an awkward approach, but it appears to be the only way to achieve the desired outcome with the current limitations in SharePoint Online.

As a final note, this is a great example of the type of solution that could be achieved using the new SharePoint 2013 Provider Hosted Application model - provided of course that 2010 tenancy support is not required.

03 March 2013

Maximum of 10 fields in a Secure Store Target Application

The blog post title pretty much says it all - in all variants of the Secure Store Service Application: both SharePoint 2010 and 2013; on-premises or Office 365; you can't have more than 10 fields in a Target Application.

This seems a rather arbitrary and constrictive limitation - in the app I was working on we had a total of 11 fields that we wanted to secure so now we will have to create two Target Applications which is an annoying additional overhead.

Trying to do this through the UI gives an error with "The maximum field number was reached.", here's a screenshot of it in the newly release SharePoint 2013 version of Office 365:

This error dialog popus once when you hit "Add Field" if you already have 10 fields listed.
If you try via PowerShell, then you get this response:


New-SPSecureStoreApplication : Specified argument was out of the range of valid values.
Parameter name: applicationFields


I couldn't find this limitation documented anywhere other than in this Office 365 article - but of course, the limitation is broader than just SharePoint Online.

It's not a blocking issue because you can always create more Target Applications, but hopefully being aware of this limitation will allow you to plan out your Target Applications without having to figure out why your PowerShell deployment scripts don't seem to behave as expected.


28 February 2013

Perth SharePoint User Group Presentation Deck

I'm thrilled to be presenting later today at the Perth SharePoint User Group on Cloud and Hybrid SharePoint Deployments: Enterprise User Profile Management. Here's a download link to the slide deck in case anyone wants to follow up on any of the resources or information presented:

http://sdrv.ms/YBbnfE

Thanks to the Perth SPUG team for the opportunity to present and to all the attendees!

31 January 2013

Synchronising Active Directory User Attributes into SharePoint Online User Profile Properties

Picture an organisation that uses Active Directory for Identity Management, and their AD database contains a range of user properties. That shouldn't require too much imagination...

Now imagine that, after previously having no real trouble synchronising these properties into SharePoint on-premises User Profiles in the past, they now want to synchronise the properties into Office 365 SharePoint Online User Profiles. It would be natural to assume that once ADFS and DirSync were correctly configured, it would be pretty basic to set up property mappings as needed. Unfortunately, things aren't quite so simple...

The short story is that only the following properties get synchronised (here I'm ignoring internal reference properties that end users won't care about):

AD Attribute User Profile Property
givenName First Name
sn Last Name
displayName Name
telephoneNumber Work Phone
department Department
title Title
title Job Title
manager Manager
wWWHomePage Public Site Redirect
proxyAddresses SIP Address
mail Work e-mail
physicalDeliveryOfficeName Office

This is a good start but many organisations will want more (Mobile Number is the first one that comes to mind). Unfortunately, it just isn't possible in the current version of SharePoint Online. You can't add any more properties to be synchronised or even change the mappings for the properties that are already being synchronised. If you try to modify a User Profile Property through the Office 365 Admin UI, the relevant options are presented but not usable:

Here's a property which is already being synchronised through ADFS that I'd like to modify but there are no Source Data Connections or Attributes to select.

Here's the not so helpful dropdown when I click on Source Data Connection. It looks like this UI has been left in SharePoint Online but has no function at this point in time.
What is really frustrating is all that text on the left relating to BCS which would be a viable option in SharePoint on-premises (and was suggested to me by multiple people). Unfortunately, this process requires configuring the BCS service application which is not possible in SharePoint Online. This Community forum post reiterates that the approach is not available for Office 365.

I've discussed this with a few people within Microsoft and the limitation has been acknowledged although it's not directly documented anywhere.

One key fact to remember in understanding the cause of this limitation is that the synchronisation is a two step process. First, the properties are synchronised from your on-premises domain up into the Office 365 domain using ADFS and DirSync (I'm no expert on this process and I've probably oversimplified it). This process allows the data to become available to the Exchange and Lync components of Office 365. The properties that are synchronised in this process are listed by Microsoft here, as far as I am aware this cannot be customised (but I'd welcome it if anyone can correct me here).

Secondly, the properties are synchronised from the cloud domain into SharePoint Online User Profiles using a synchronisation process which is completely internal and hidden from Office 365 tenancy administrators. The documentation here shows the default property mappings for this process for SharePoint 2010 which as far as I can tell applies to both on-premises and Office 365 instances of SharePoint. The key difference is that on-premises SharePoint provides a friendly interface for customising these mappings whereas SharePoint Online offers no UI or administrative capacity at all.

By combining the above documentation it's fairly simple to work out which properties make it all the way from start to finish. If you want more properties then you'll have to put together a completely custom process, but that's a topic for another post...

Note that all of this discussion is based on the 2010 (Wave 14) version of SharePoint Online, I haven't seen ADFS hooked up to a Wave 15 version yet so I'm not sure whether the situation will improve in the upcoming release of Office 365. I'd love to hear if anyone can shed some light on this!

29 July 2012

SharePoint 2013 Explorer

UPDATE: Newer version of the tool now available here.



Here is one of the first developer utilities specifically built for SharePoint 2013. It will let you peek and poke at the internals of your SharePoint 2013 site collections, either on-premises or in the cloud.

The tool is based on SharePoint Explorer 365, read this post for the full details of its origin, capabilities and usage instructions. The changes in this new version are as follows:

  • Updated passive authentication code for Office 365 Preview as detailed in this post.
  • Compiled against SharePoint 2013 versions of Microsoft.SharePoint.Client.dll and Microsoft.SharePoint.Client.Runtime.dll, which provide a range of additional objects, properties and methods compared to the 2010 versions (see below screenshots).
    Because of this, this version of the tool will not work against SharePoint 2010 site collections.
  • Compiled against the .NET Framework 4.0, so this needs to be installed if you want to run the tool.

Here is the download, read my previous post on SharePoint Explorer 365 regarding disclaimers and such.

You can see the expanded capacity of the 2013 version of the Client Object Model by comparing screenshots of the tool compiled with 2013 assemblies with screenshots of the same tool compiled with the 2010 assemblies.

Site Collection objects and properties in 2010 Client OM assemblies

Site Collection objects and properties in 2013 Client OM assemblies

Methods available against the Site Collection in 2010 assemblies

Methods available against the Site Collection in 2013 assemblies
This expansion of Client OM capabilities in SharePoint 2013 is great news, especially for Office 365 developers. The screenshots above relate to site collections, but there are new capabilities for webs, lists, and other SharePoint objects as well. This tool, or a .NET reflection tool, provides great insights into these new capabilities.

Note that SharePoint 2013 is in Preview and is liable to change as it matures (this is especially true of the Online version), so the tool may stop working at any time. In the meantime, have fun with it!

Thanks again to Stefan Stanev for building such an easily extendible SharePoint utility and providing the source code to the SharePoint community!