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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.