SharePoint

Deploying Nintex Workflows via PowerShell

One of the more common tasks when working on a large project is to deploy Nintex Workflows via a PowerShell script. It’s not too difficult, because the SharePoint web front ends will have a copy of NWAdmin on them – installed in the 15 hive by Nintex during installation. If you’re not aware of it, NWAdmin is a command line tool that can – drum-roll – deploy workflows (among many other things).

The snippet of code below shows the general pattern I use to deploy many workflows in one go – essentially listing them all out in an arraylist, and then looping through it, calling NWAdmin via Powershell. If nothing else, this is a great example of the horrible syntax PowerShell forces upon you to call command line applications with parameters.

$web = Get-SPWeb "https://server/sites/site_collection/subsite"

$cmd = "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\BIN\NWAdmin.exe"

$workflows = New-Object System.Collections.ArrayList

# Fill an array with the worklow names, list names and filenames to process
$workflows.Add(("Process A","List A","process_a.nwf")) > $null
$workflows.Add(("Process B","List B","process_b.nwf")) > $null
$workflows.Add(("Process C","List C","process_c.nwf")) > $null

if (Test-Path($cmd)) {
	Write-Host " - NWAdmin Found" -foregroundcolor green
	$list_workflows_path = Resolve-Path $(".\Workflows\")
	if (Test-Path($list_workflows_path)) {
		foreach ($workflow in $workflows) {

			$workflow_name     = $workflow[0]
			$list_name         = $workflow[1]
			$workflow_filename = $workflow[2]

			$nwf_path = "$list_workflows_path$workflow_filename"

			if (Test-Path($nwf_path)) {
				if ($web.Lists[$list_name]){

					write-host $("Deploying '" + $workflow_name + "' to list '" + $list_name + "'") -foregroundcolor white
					$prm = "-o","DeployWorkflow","-workflowName",$("`"" + $workflow_name + "`""),"-nwfFile",$("`"" + $nwf_path + "`""),"-siteUrl",$("`"" + $web.Url + "`""),"-targetList",$("`"" + $list_name + "`""),"-overwrite"
					& $cmd $prm

				} else {
					Write-Host $("SharePoint List not found [" + $list_name + "]") -foregroundcolor red
				}
			} else {
				write-host $("Workflow File Not Found [" + $nwf_path + "]") -foregroundcolor red
			}
		}
	} else {
		write-host $("Workflows Directory Not Found [" + $list_workflows_path + "]") -foregroundcolor red
	}
	write-host "Complete!" -foregroundcolor green
} else {
	Write-Host " - NWAdmin Not Found" -foregroundcolor red
}

# release resources
$web.Close()
$web.Dispose()
Posted by Jonathan Beckett in Notes, 0 comments

Repairing Nintex Forms in Exported Nintex Workflows

A little while ago I was working on a SharePoint development project with Nintex Workflows and Nintex Forms. The project was being developed remotely – in a virtual machine – and then deployment scripts were given to the client to install on their development, test, and production farms as testing progressed. Along the way we found a pretty serious bug in Nintex Workflow and Forms, but thankfully found a workaround.

The Problem

When you design the forms for tasks within a Nintex Workflow (using Nintex Forms) the form designs are embedded in the Nintex Workflow when you export it. If you export a form directly from the form designer, you end up with an XML file describing the form. If you export a workflow containing a task form design from the workflow designer, you end up with an XML file describing the workflow, with the XML describing the task form escaped within it. The problem comes when you export from one farm, and import into another – you will discover the task forms may fail in the destination system. After a bit of digging, I figured out the the XML describing the forms contains hard-coded server relative paths to the origin system in Lookup fields, that are not dealt with during the import – or at least, that’s how I have seen this problem occur – there may be other field types that also have the source system URL baked into them.

It’s worth noting that I have contacted Nintex Support about this issue – I will update when they get back to me.

The Solution

The solution is pretty straightforward really – you can run some PowerShell to read the exported Workflows, replace the relative paths, and write them back. In the example below, we presume that all of the exported workflows exist within a folder called “Workflows”, and the modified versions will be stored in a subfolder called “Modified”.

# Connect to SharePoint
$web = Get-SPWeb "https://server/sites/site_collection/subsite"

$uri = [System.Uri]$url

# server relative path of site the workflows originally existed at
$source_localpath = "/sites/site_collection/subsite"

# server relative path of the site where the workflows are going to be imported
$destination_localpath = $uri.LocalPath

# escape the paths (because we will find both unescaped and escaped versions of the paths)
$source_localpath_escaped = $source_localpath -replace "/","\\/"
$destination_localpath_escaped = $destination_localpath -replace "/","`\/"

# Loop through files in Workflows subdirectory
foreach ( $source_file in $(Get-ChildItem './Workflows' -File | Sort-Object -Property Name) ) {
    
    # read the workflow file
    $file_content = Get-Content "./Workflows/List Workflows/$source_file"
    
    # replace the paths
    write-host " - Replace Paths"
    $file_content = $file_content -replace $source_localpath,$destination_localpath
    $file_content = $file_content -replace $source_localpath_escaped,$destination_localpath_escaped
            
    # Write the file into the modified subdirectory
    $file_content | out-file -encoding utf8 "../Workflows/Modified/$source_file"

}

# release resources
$web.Close()
$web.Dispose()

The script results in a modified set of exported workflows, which will import correctly into the destination farm. It’s worth noting that if you don’t do this, you end up in a world of trouble, because you can’t repair the forms the workflow import breaks – and you don’t know they are broken until you try to use them. I discovered the cause after digging through the SharePoint ULS logs.

I’m amazed that this particular bug got past quality control at Nintex, but then it’s a fantastically complex piece of kit, and so is SharePoint that it sits on top of. It’s also worth noting that this only effects the development of systems using multiple farms for development, testing, and production – if you do agile development in production, you would never see this problem.

Posted by Jonathan Beckett in Notes, 0 comments

Provisioning Large SharePoint Projects with Powershell

It makes sense when building a sizeable project in SharePoint on-premises to script the provisioning of all assets – the lists, content types, columns, views, and so on. This inevitably ends up with a colossal powershell script, so it makes sense to look for ways to break it up, and make it easier to work on (or for multiple people to work on individual parts of the whole). The following is a method I have come up with over the course of several projects – I figured it might be useful for others.

Approach

The basic approach is to run one powershell file that loads all the others, and executes them. Therefore we have one core file (e.g. “deploy.ps1”), and a folder (“lib”) full of scripts it runs. It occurred to me while building the core script that we could copy the way Unix and Linux often do things – and number the files in the folder – then if the core file sorts them, we can control the execution of the children purely by filename order (e.g. “lib/1000_provision_list.ps1”, “lib/2000_provision_content_type.ps1”, and so on).

The Core Deployment Script

So – the following snippet is an example of what the core deployment script looks like – expecting a couple of parameters – the name of the SharePoint site to deploy everything into, and a filter to optionally run a subset, or even a single script, based on it’s filename.

param( [string]$url = "https://server/sites/site_collection/subsite", [string]$filter = "" )

# record results to a text file in the same path as the deploy script
start-transcript ./deploy_log.txt -Append

# Add SharePoint Snap-In
if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) 
{
    Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}

if ($url -ne ""){

    Write-Host "Connecting to [$url]" -foregroundcolor green
    $web = Get-SPWeb $url

    # Loop through all files in the lib folder and execute them in order of their filename
    # and if their name matches the filter parameter passed to the script
    foreach ( $source_file in $(Get-ChildItem './lib' | Sort-Object -Property Name) ) {
        
        if ($source_file.Name -like "*$filter*") {
        
            Write-Host $("***** Start  [" + $source_file.Name + "] *****") -foregroundcolor green
            
            & $source_file.FullName
            
            Write-Host $("***** Finish [" + $source_file.Name + "] *****") -foregroundcolor green

        }
    }

    # Release Assets
    $web.Close() > $null
    $web.Dispose() > $null

} else {
    Write-Host "No URL supplied" -foregroundcolor red
}

Write-Host ""
Write-Host "Finished!" -foregroundcolor green

Stop-Transcript

It’s worth noting that the above example uses the “Start-Transcript” and “Stop Transcript” powershell functions, that very neatly record anything we write out through standard output to a text file. I only discovered this a few months ago – it’s very useful.

Writing the Worker Scripts

Apart from knowing we need to put the scripts in the “lib” folder, it’s useful to see what one of the child scripts might look like. The example below shows a good example – which might be saved as “lib/1000_provision_list.ps1”:

function provision_list($web){
    $list_internal_name = "mylist"
    $list_display_name = "My List"
    Write-Host $("Provisioning List [" + $list_display_name + "]")
    if ($web.Lists[$list_display_name]) {
        Write-Host " - $list_display_name Already Exists"
        $list = $web.Lists[$list_internal_name]
    } else {
        Write-Host " - $list_display_name ($list_internal_name)"
        $web.Lists.Add($list_internal_name, "", "GenericList") > $null
        $list = $web.Lists[$list_internal_name]
        $list.Title = $list_display_name
        $list.ContentTypesEnabled = $true
        $list.EnableVersioning = $true
        $list.NavigateForFormsPages = $false
        $list.Update() > $null
    }
}

provision_list($web)

The take-away from the library files is that they share any objects already instantiated by the core script – therefore we can presume $web already contains a valid SharePoint object. Therefore all each file contains is a function definition, and a call to run the function (using functions helps keep working variables scoped).

So there you have it – a working method of building large, flexible, and extendable provisioning scripts. Do comment if you’ve done something similar, or if you found this useful.

Posted by Jonathan Beckett in Notes, 0 comments

Bulk Uploading Files into Libraries in SharePoint with PowerShell

The following snippet shows a method of uploading a folder of files on the filesystem into a library in SharePoint. In this case the filesystem folder is presumed to exist in the same location as the PowerShell script, called “SiteAssets”, uploading to a library in the SharePoint subsite called “SiteAssets”.

# Connect to SharePoint Site
$web = get-spweb "https://server/sites/site_collection/subsite"

# loop through all files in the local siteassets subfolder
foreach ( $source_file in $(Get-ChildItem './SiteAssets' | Sort-Object -Property Name) ) {
    if ($source_file.Attributes -match 'Directory') {
        # its a folder - ignore it
    } else {
        
        $library = $web.GetFolder("SiteAssets")
        $library_files = $library.Files

        $file = Get-ChildItem $source_file.FullName

        $library_files.Add("SiteAssets/" + $source_file.Name,$file.OpenRead(),$true) > $null
    }
}

# Release resources
$web.Close()
$web.Dispose()
Posted by Jonathan Beckett in Notes, 0 comments

Provisioning Groups and Assigning Permissions within SharePoint Subsites with PowerShell

The following snippet shows how you might provision a new group within a subsite of SharePoint using PowerShell. Note that you not only need to create the group – you also need to associate it with the subsite, and assign permissions to the relationship between the group and the subsite. It’s not obvious at all.

# Connect to SharePoint Site
$web = get-spweb "https://server/sites/site_collection/subsite"

$group_name = "My Group"

# Remove group if it already exists
if ($web.SiteGroups[$group_name] -ne $null)
{
    $web.SiteGroups.Remove($group_name) > $null
    $web.Update() > $null
}

# Create the group
$web.SiteGroups.Add($group_name, $web.Site.Owner, $web.Site.Owner, $group_name) > $null
$web.Update() > $null

# Add Group to Associated Groups Collection
if ($web.AssociatedGroups[$group_name] -eq $null)
{
    $web.AssociatedGroups.Add($web.SiteGroups[$group_name])
    $web.Update()
}

# Assign Permissions to the Group
$group = $web.SiteGroups[$group_name]
$group_role_assignment = new-object Microsoft.SharePoint.SPRoleAssignment($group)
$full_control_role_definition = $web.RoleDefinitions["Full Control"]
$group_role_assignment.RoleDefinitionBindings.Add($full_control_role_definition) > $null
$web.RoleAssignments.Add($group_role_assignment) > $null
$web.Update()

# Release resources
$web.Close()
$web.Dispose()
Posted by Jonathan Beckett in Notes, 0 comments

Breaking Permissions Inheritance and Setting up new Permission Sets in SharePoint with PowerShell

The following snippet shows how you might create a new permission set in a subsite of SharePoint. One of the more common permission sets you might need in a production application is ‘restricted contribute’ – contribute without delete. This shows one method of doing that (there are many) – by duplicating the contribute permission set, minus the delete permission.

# Connect to SharePoint Site
$web = get-spweb "https://server/sites/site_collection/subsite"

# Break Inheritance for the subsite
$web.RoleDefinitions.BreakInheritance($false, $false) > $null
$web.Update() > $null

# Get the Contribute Definition
$contribute_role_definition = $web.RoleDefinitions["Contribute"]
$contribute_base_permissions = $contribute_role_definition.BasePermissions

# Delete the role definition if it exists
if ($web.RoleDefinitions["Restricted Contribute"]) {
    Write-Host " - Removing Existing Role Definition"
    $web.RoleDefinitions.Delete("Restricted Contribute")
    $web.Update()
}

# Create a new Role Definition, and copy the Base permissions minus Delete Items
$restricted_contribute_role_definition = New-Object Microsoft.SharePoint.SPRoleDefinition
$restricted_contribute_role_definition.BasePermissions = $contribute_base_permissions -bxor [Microsoft.SharePoint.SPBasePermissions]::DeleteListItems

# Set the name and description of the new role definition
$restricted_contribute_role_definition.Name = "Restricted Contribute"
$restricted_contribute_role_definition.Description = "Contribute without Delete Items"

# Apply the new role definition to the web
$web.RoleDefinitions.Add($restricted_contribute_role_definition);
$web.Update();

# Release Resources
$web.Close()
$web.Dispose()
Posted by Jonathan Beckett in Notes, 0 comments

Populating the QuickLaunch in SharePoint with PowerShell

The following snippet shows how you might re-write the contents of the Quick Launch within SharePoint on-prem. It’s pretty straightforward – just duplicate the parent and child lines for as many links as you need.

# Connect to Site
$web = get-spweb "https://server/sites/site_collection/subsite"

# Get the quicklaunch
$quicklaunch = $web.Navigation.QuickLaunch

# Remove all items in the quicklaunch
($quicklaunch | where {$_.Title -ne ""}).Delete() > $null

# Create a parent link
$navnode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode("Home", $web.Url, $false)
$parent_link = $quicklaunch.AddAsLast($navnode)

# Create a child link
$navnode = New-Object Microsoft.SharePoint.Navigation.SPNavigationNode("Link to a List", $($web.Url + "/lists/mylist"), $false)
$child_link = $parent_link.Children.AddAsLast($navnode)

# You can switch Quick Launch on and off entirely
$web.QuickLaunchEnabled = $true

# Write the changes to the web
$web.Update()

# Release resources
$web.Close()
$web.Update()
Posted by Jonathan Beckett in Notes, 0 comments

Creating List and Library Views in SharePoint with PowerShell

The code snippet below illustrates the creation of a view on a list. I generally leave the “All Items” views alone, and create new views to do anything specific for an application within SharePoint – it just makes life easier in the long run.

# Connect to SharePoint
$web = get-spweb "https://server/sites/site_collection/subsite"

# get the list to make a view
$list = $web.Lists["My List"]

$view_name = "My View"

# If the view already exists, remove it
if ($list.Views[$view_name] -ne $null){
    $view = $list.Views[$view_name]
    $list.Views.Delete($view.Id) > $null
    $list.Update() > $null
    $web.Update() > $null
}

# Create the view
$view_fields = New-Object System.Collections.Specialized.StringCollection
$view_fields.Add('LinkTitle') > $null
$view_fields.Add('Created') > $null
$view_fields.Add('Modified') > $null
$view_query = "<OrderBy><FieldRef Name='ID' Ascending='FALSE'/></OrderBy>"
$view_row_limit = 50
$view_paged = $true
$view_default = $true
$new_view = $list.Views.Add($view_name, $view_fields, $view_query, $view_row_limit, $view_paged, $view_default)

# Release Resources
$web.Close()
$web.Dispose()

Note that if you try to remove the only view existing on a list or library, it will fail – there must always be at least one view. Also note that fields must be identified by their internal names. Oh – finally – the view query is expressed in CAML – you can look that up elsewhere (it’s a special kind of hell).

Posted by Jonathan Beckett in Notes, 0 comments

Creating a Lookup Site Column on SharePoint with PowerShell

The following snippet creates a Lookup Site Column in SharePoint on-prem. Notice that the code checks for the existence of the site column before creating it – because typically you will be using this in a larger provisioning script, and it makes sense to let your code run more than once – so as you build a solution, you can just add new elements and run it again.

# Connect to SharePoint Site
$web = get-spweb "https://server/sites/sitecollection/site"

# Set Site Column name etc
$site_column_internal_name = "MySiteColumn"
$site_column_display_name  = "My Site Column"
$site_column_group         = "My Site Columns"
$site_column_description   = "Simple example of site column"

if ($web.Fields.ContainsField($site_column_internal_name)){

    # Get Existing Site Column
    $site_column = $web.Fields.GetField($site_column_internal_name)
 
} else {

    # Create new site column
    $lookup_list = $web.Lists["Title of Lookup List"]
    $web.Fields.AddLookup($site_column_internal_name,$lookup_list.ID,$false) > $null
    $field = $web.Fields.GetField($site_column_internal_name)
    $field.LookupField = $lookup_list.Fields["Title"]
    $field.Title = $site_column_display_name
    $field.Group = $site_column_group
    $field.Description = $site_column_description
    $field.Update() > $null
}

# instantiate field link object for field
$field_link = new-object Microsoft.SharePoint.SPFieldLink($field)

# add field link to existing content type
$content_type = $web.ContentTypes["My Content Type"]
$content_type.FieldLinks.Add($field_link) > $null
$content_type.Update() > $null

# release resources
$web.Close()
$web.Dispose()

Also notice that the site column will be provisioned in the web you connect to – but must be uniquely named across the entire site collection.

Posted by Jonathan Beckett in Notes, 0 comments

Creating a Multi Value Site Column in SharePoint with PowerShell

The following snippet creates a Multi Value Site Column in SharePoint on-prem. Notice that the code checks for the existence of the site column before creating it – because typically you will be using this in a larger provisioning script, and it makes sense to let your code run more than once – so as you build a solution, you can just add new elements and run it again.

# Connect to SharePoint Site
$web = get-spweb "https://server/sites/sitecollection/site"

# Set Site Column name etc
$site_column_internal_name = "MySiteColumn"
$site_column_display_name  = "My Site Column"
$site_column_group         = "My Site Columns"
$site_column_description   = "Simple example of site column"

if ($web.Fields.ContainsField($site_column_internal_name)){

    # Get Existing Site Column
    $site_column = $web.Fields.GetField($site_column_internal_name)
 
} else {

    # Create new site column
    $choices = New-Object System.Collections.Specialized.StringCollection
    $choices.Add("Yes") > $null
    $choices.Add("No") > $null
    $web.Fields.Add($site_column_internal_name,[Microsoft.SharePoint.SPFieldType]::Choice,$false,$false,$choices) > $null
    $field = $web.Fields.GetField($site_column_internal_name)
    $field.Title = $site_column_display_name
    $field.Group = $site_column_group
    $field.Description = $site_column_description
    $field.Update() > $null
}

# instantiate field link object for field
$field_link = new-object Microsoft.SharePoint.SPFieldLink($field)

# add field link to existing content type
$content_type = $web.ContentTypes["My Content Type"]
$content_type.FieldLinks.Add($field_link) > $null
$content_type.Update() > $null

# release resources
$web.Close()
$web.Dispose()

Also notice that the site column will be provisioned in the web you connect to – but must be uniquely named across the entire site collection.

Posted by Jonathan Beckett in Notes, 0 comments