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"
}