How To : Use Powershell Scripts in Office 365 through the SharePoint CSOM

When we first started to work with Office 365, I remember being quite concerned at the lack of PowerShell cmdlets – basically all the commands we’re used to using do not exist there. Here’s a gratuitous graph to illustrate the point:

image

So yes, nearly 800 PowerShell commands in SP2013 (up from around 530 in SP2010) down to a measly 30 in SharePoint Online. And those 30 mainly cover basic operations with sites, users and permissions – no scripting of, say, Managed Metadata, user profiles, search and so on. It’s true to say that some of these things are now available down at site-collection scope (needed, of course, when you don’t have a true “Central Admin” site but there are still “tenant-level” settings that you want to use script for rather than make manual changes through the UI.

So what’s a poor developer/administrator to do?

The answer is to write PowerShell as you always did, but embed CSOM code in there. More examples later, but here’s a small illustration:

# get the site collection scoped Features collections (e.g. to activate one) – not showing how to obtain $clientContext here..
$siteFeatures = $clientContext.Site.Features
$clientContext.Load($siteFeatures)
$clientContext.ExecuteQuery()

So we’re using the .NET CSOM, but instead of C# we are using PowerShell’s ability to call any .NET object (indeed, nearly every script will use PowerShell’s New-Objectcommand). All the things we came to love about PowerShell are back on the table:

  • Scripts can be easily amended, no need to recompile (or open Visual Studio)
  • We can debug with PowerGui or PowerShell ISE
  • We can leverage other things PowerShell is good at e.g. easily reading from XML files, using other PowerShell modules and other APIs (including .NET) etc.

Of course, we can only perform operations where the method exists in the .NET CSOM – that’s the boundary of what we can do.

Getting started

Step 1 – understand the landscape

The first thing to understand is that there are actually 3 different approaches for scripting against Office 365/SharePoint Online, depending on what you need to do. It might just be me, but I think that when you start it’s easy to get confused between them, or not fully appreciate that they all exist. The 3 approaches I’m thinking of are:

  • SharePoint Online cmdlets
  • MSOL cmdlets
  • PowerShell + CSOM

This post focuses on the last flavor. I also wrote a short companion post about the overall landscape and with some details/examples on the other flavors, at Using SharePoint Online and MSOL cmdlets in PowerShell with Office 365

Step 2 – prepare the machine you will run scripts against SharePoint Online

Option 1 – if you will NOT run scripts from a SP2013 box (e.g. a SP2013 VM):

You need to obtain the SharePoint DLLs which comprise the .NET CSOM, and copy them to a folder on your machine – your scripts will reference these DLLs.

  1. Go to any SharePoint 2013 server, and copy any DLL
  2. which starts with Microsoft.SharePoint.Client*.dll from the C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI folder.
  3. Store them in a folder on your machine e.g. C:\Lib – make a note of this location.

CSOM DLLs

Option 2 – if you WILL run scripts from a SP2013 box (e.g. a SP2013 VM):

In this case, there is no need to copy the DLLs – your scripts will reference them in the original SharePoint install location (C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI).

The top of your script – referencing DLLs and authentication

Each .ps1 file which calls the SharePoint CSOM needs to deal with two things before you can use the API – loading the CSOM types, and authenticating/obtaining a ClientContext object. So, you’ll need this at the top of your script:

** N.B. My newer code samples do not show in RSS Readers – click here for full article **
# replace these details (also consider using Get-Credential to enter password securely as script runs)..
$username = “SomeUser@SomeOrg.onmicrosoft.com”
$password = “SomePassword”
$securePassword = ConvertTo-SecureString $Password -AsPlainText -Force
# the path here may need to change if you used e.g. C:\Lib..
Add-Type -Path “c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll”
Add-Type -Path “c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll”
# note that you might need some other references (depending on what your script does) for example:
Add-Type -Path “c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Taxonomy.dll”
# connect/authenticate to SharePoint Online and get ClientContext object..
$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword)
$clientContext.Credentials = $credentials
if (!$clientContext.ServerObjectIsNull.Value)
{
Write-Host “Connected to SharePoint Online site: ‘$Url'” -ForegroundColor Green
}
view rawTopOfScript_PSCSOM.ps1 hosted with ❤ by GitHub

In the scripts which follow, we’ll include this “top of script” stuff by dot-sourcing TopOfScript.ps1 in every script below – you could follow a similar approach (perhaps with a different name!) or simply paste that stuff into every script you create. If you enter a valid set of credentials and URL, running the script above should see you ready to rumble:

PS CSOM got context

Script examples

Activating a Feature in SPO

Something you might want to do at some point is enable or disable a Feature using script. The script below, like the others that follow it, all reference my TopOfScript.ps1 script above:

** N.B. My newer code samples do not show in RSS Readers – click here for full article **
. .\TopOfScript.ps1
[bool]$enable = $true
[bool]$force = $false
# using the Minimal Download Strategy Feature here..
$FeatureId = [GUID](“87294C72-F260-42f3-A41B-981A2FFCE37A”)
# ..and working with the web-scoped Features – use $clientContext.Site.Features for site-scoped Features
$webFeatures = $clientContext.Web.Features
$clientContext.Load($webFeatures)
$clientContext.ExecuteQuery()
if ($enable)
{
$webfeatures.Add($featureId, $force, [Microsoft.SharePoint.Client.FeatureDefinitionScope]::None)
}
else
{
$webfeatures.Remove($featureId, $force)
}
try
{
$clientContext.ExecuteQuery()
if ($enable)
{
Write-Host “Feature ‘$FeatureId’ successfully activated..”
}
else
{
Write-Host “Feature ‘$FeatureId’ successfully deactivated..”
}
}
catch
{
Write-Error “An error occurred whilst activating/deactivating the Feature. Error detail: $($_)
}
view rawActivateOrDeactivateFeature_PSCSOM.ps1 hosted with ❤ by GitHub

PS CSOM activate feature

Enable side-loading (for app deployment)

Along very similar lines (because it also involves activating a Feature), is the idea of enabling “side-loading” on a site. By default, if you’re developing a SharePoint app it can only be F5 deployed from Visual Studio to a site created from the Developer Site template, but by enabling “side-loading” you can do it on (say) a team site too. Since the Feature isn’t visible (in the UI), you’ll need a script like this:

** N.B. My newer code samples do not show in RSS Readers – click here for full article **
. .\TopOfScript.ps1
[bool]$enable = $true
[bool]$force = $false
# this is the side-loading Feature ID..
$FeatureId = [GUID](“AE3A1339-61F5-4f8f-81A7-ABD2DA956A7D”)
# ..and this one is site-scoped, so using $clientContext.Site.Features..
$siteFeatures = $clientContext.Site.Features
$clientContext.Load($siteFeatures)
$clientContext.ExecuteQuery()
if ($enable)
{
$siteFeatures.Add($featureId, $force, [Microsoft.SharePoint.Client.FeatureDefinitionScope]::None)
}
else
{
$siteFeatures.Remove($featureId, $force)
}
try
{
$clientContext.ExecuteQuery()
if ($enable)
{
Write-Host “Feature ‘$FeatureId’ successfully activated..”
}
else
{
Write-Host “Feature ‘$FeatureId’ successfully deactivated..”
}
}
catch
{
Write-Error “An error occurred whilst activating/deactivating the Feature. Error detail: $($_)
}
view rawEnableSideLoading_PSCSOM.ps1 hosted with ❤ by GitHub

PS CSOM enable side loading

Iterating webs

Sometimes you might want to loop through all the webs in a site collection, or underneath a particular web:

** N.B. My newer code samples do not show in RSS Readers – click here for full article **
1234567891011121314151617181920
. .\TopOfScript.ps1
$rootWeb = $clientContext.Web
$childWebs = $rootWeb.Webs
$clientContext.Load($rootWeb)
$clientContext.Load($childWebs)
$clientContext.ExecuteQuery()
function processWeb($web)
{
$lists = $web.Lists
$clientContext.Load($web)
$clientContext.ExecuteQuery()
Write-Host “Web URL is” $web.Url
}
foreach ($childWeb in $childWebs)
{
processWeb($childWeb)
}
view rawIterateWebs.ps1 hosted with ❤ by GitHub

PS CSOM iterate webs

(Worth noting that you also see SharePoint-hosted app webs also in the image above, since these are just subwebs (albeit ones which get accessed on the app domain URL rather than the actual host site’s web application URL).

Iterating webs, then lists, and updating a property on each list

Or how about extending the sample above to not only iterate webs, but also the lists in each – the property I’m updating on each list is the EnableVersioning property, but you easily use any other property or method in the same way:

** N.B. My newer code samples do not show in RSS Readers – click here for full article **
. .\TopOfScript.ps1
$enableVersioning = $true
$rootWeb = $clientContext.Web
$childWebs = $rootWeb.Webs
$clientContext.Load($rootWeb)
$clientContext.Load($childWebs)
$clientContext.ExecuteQuery()
function processWeb($web)
{
$lists = $web.Lists
$clientContext.Load($web)
$clientContext.Load($lists)
$clientContext.ExecuteQuery()
Write-Host “Processing web with URL “ $web.Url
foreach ($list in $web.Lists)
{
Write-Host “– “ $list.Title
# leave the “Master Page Gallery” and “Site Pages” lists alone, since these have versioning enabled by default..
if ($list.Title -ne “Master Page Gallery” -and $list.Title -ne “Site Pages”)
{
Write-Host “—- Versioning enabled: “ $list.EnableVersioning
$list.EnableVersioning = $enableVersioning
$list.Update()
$clientContext.Load($list)
$clientContext.ExecuteQuery()
Write-Host “—- Versioning now enabled: “ $list.EnableVersioning
}
}
}
foreach ($childWeb in $childWebs)
{
processWeb($childWeb)
}
view rawIterateWebsAndListsEnableVersioning.ps1 hosted with ❤ by GitHub

PS CSOM iterate lists enable versioning

Import search schema XML

In SharePoint 2013 and Office 365, many aspects of search configuration (such as Managed Properties and Crawled Properties, Query Rules, Result Sources and Result Types) can be exported and importing between environments as an XML file. The sample below shows the import operation handled with PS + CSOM:

** N.B. My newer code samples do not show in RSS Readers – click here for full article **
. .\TopOfScript.ps1
# need some extra types bringing in for this script..
Add-Type -Path “c:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Search.dll”
# TODO: replace this path with yours..
$pathToSearchSchemaXmlFile = “C:\COB\Cloud\PS_CSOM\XML\COB_TenantSearchConfiguration.xml”
# we can work with search config at the tenancy or site collection level:
#$configScope = “SPSiteSubscription”
$configScope = “SPSite”
$searchConfigurationPortability = New-Object Microsoft.SharePoint.Client.Search.Portability.SearchConfigurationPortability($clientContext)
$owner = New-Object Microsoft.SharePoint.Client.Search.Administration.SearchObjectOwner($clientContext, $configScope)
[xml]$searchConfigXml = Get-Content $pathToSearchSchemaXmlFile
$searchConfigurationPortability.ImportSearchConfiguration($owner, $searchConfigXml.OuterXml)
$clientContext.ExecuteQuery()
Write-Host “Search configuration imported” -ForegroundColor Green
view rawImportSearchSchema.ps1 hosted with ❤ by GitHub

PS CSOM import search schema

Summary

As you can hopefully see, there’s lots you can accomplish with the PowerShell and CSOM combination. Anything that can be done with CSOM API can be wrapped into a script, and you can build up a library of useful PowerShell snippets just like the old days. There are some interesting things that you CANNOT do with CSOM (such as automating the process of uploading/deploying a sandboxed WSP to Office 365), but there ARE approaches for solving even these problems, and I’ll most likely cover this (and our experiences) in future posts.

A final idea on the PowerShell + CSOM front is the idea that you can have “hybrid” scripts which can deal with both SharePoint Online and on-premises SharePoint. For example, on my current project everything we build must be deployable to both SPO and on-premises, and our scripts take a “DeploymentTarget” parameter where the values can be “Online” or “OnPremises”. There are some differences (i.e. branching) in the scripts, but for many operations the same commands can be run.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s