There are a multitude of ways to manipulate SharePoint, I will take the opportunity to give you a primer in connecting to your tenant using SPO and PnP PowerShell, and extending the default functionality with CSOM.

Prep work

First up, you’ll need to update your local machines Execution Policy to allow for these cmdlets to to be executed.

Open your PowerShell console as an administrator and run the following commands:

Check and set your machines’ execution policy

> Get-ExecutionPolicy -List

# expected default results:
#           Scope ExecutionPolicy
#           ----- ---------------
#   MachinePolicy       Undefined
#      UserPolicy       Undefined
#         Process       Undefined
#     CurrentUser       Undefined
#    LocalMachine       Undefined

> Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope LocalMachine -Force # update your local machine scope to be unrestricted
> Get-ExecutionPolicy -List

# expected results:
#           Scope ExecutionPolicy
#           ----- ---------------
#   MachinePolicy       Undefined
#      UserPolicy       Undefined
#         Process       Undefined
#     CurrentUser       Undefined
#    LocalMachine    unrestricted

If you haven’t done so already, you’ll need to install the the SharePoint PowerShell cmdlets from PowerShell Gallery: Microsoft.Online.SharePoint.PowerShell.

We will do this via the PowerShell console. Run the following commands:

> Install-Module -Name Microsoft.Online.SharePoint.PowerShell -AllowClobber -Force;

> Get-Module -Name *SharePoint.PowerShell -ListAvailable | Select-Object Name,Version;

# expected results:
# Name                                   Version    
# ----                                   -------    
# Microsoft.Online.SharePoint.PowerShell [latestVersionNumber]

Now you’ll need to do the same for PNP PowerShell, run the following commands:

> Install-Module SharePointPnPPowerShellOnline -AllowClobber -Force

> Get-Module -Name SharePointPnPPowerShell* -ListAvailable | Select-Object Name,Version

# expected results:
# Name                          Version    
# ----                          -------    
# SharePointPnPPowerShellOnline [latestVersionNumber]

A note on the -AllowClobber switch

If you import a command with the same name as a command in the current session, the imported command hides or replaces the original commands when -AllowClobber is specified. This goes a long way towards avoiding ambiguity. We’ll be using this somewhat regulrarly.

You’re now good to continue.

SPO PowerShell

Notes about the SPO cmdlets

Now, we’re going to use the SPO cmdlets to connect to the admin admin centre, this is what we use these for, but please be aware that you can do a lot of harm there, tread carefully. PNP PowerShell is used for, and is far better at manipulating individual site collections, we’ll come onto that soon in the next section.

Please take a moment to familiarise yourself with the documentation for Connect-SPOService, specifically:

Connects a SharePoint Online administrator or Global Administrator to a SharePoint Online connection (the SharePoint Online Administration Center). This cmdlet must be run before any other SharePoint Online cmdlets can run.

Connect to the SharePoint Online Administration Center

> Connect-SPOService -Url https://[tenantName] # you'll be prompted for credentials

# you should now be connected to the admin centre

> $sites = Get-SPOSite # get all sites within this tenant

> $sites # display the sites you just retrieved

# example results:
# Url                                                Owner                Storage Quota
# ---                                                -----                -------------
# https://[tenantName]                              26214400
# https://[tenantName]    [email protected]    26214400
# https://[tenantName]    [email protected]    26214400
# https://[tenantName]                                    26214400
# https://[tenantName]                         26214400
# https://[tenantName]                         26214400
# https://[tenantName]                         26214400
#   remainder elided for brevity

You can now continue and work on your tenant using the SPO cmdlets, though remember, be careful! One last thing…

> Disconnect-SPOService # always disconnect when you're finished!

PNP PowerShell

Next up, we’re going to do something very similar using PNP PowerShell, though this time we will be connecting directly to an existing Site Collection.

> Connect-PnPOnline -Url https://[tenantName][siteName] # enter your credentials

# if the previous method fails, you've likely got MFA enabled on your tenant, use this instead

> Connect-PnPOnline -Url https://[tenantName][siteName] -UseWebLogin # this will provide a web login interface

# you should now be connected to your chosen site

> $site = Get-PNPSite # retrieve a reference to the current site and persist it to a variable

> $site

# example results:
# Url                                                     CompatibilityLevel
# ---                                                     ------------------
# https://[tenantName][siteName]    15

# now you can do some cool stuff, let's retrieve this site collections' recycle bin items

> $rec = Get-PnPRecycleBinItem

# example results:
# Title                  Id       ItemType   LeafName
# -----                  --       --------   --------
# Test Document 1.docx   [guid]   File       Test Document 1.docx
# Test Doc 3.docx        [guid]   File       Test Doc 3.docx
# Test Doc 3.docx        [guid]   File       Test Doc 3.docx
# Test Doc 1.docx        [guid]   File       Test Doc 1.docx
# Test Doc 2.docx        [guid]   File       Test Doc 2.docx

You could now continue removing those recycle bin items if you felt like it.

SharePoint Client-Side Object Model in PnP PowerShell (CSOM)

I’m now going to demonstrate using CSOM while we’re still connected to the previous site collection with PNP.

One important part to note is that all of these cmdlets are built using CSOM behind the scenes (both SPO and PnP). CSOM is a derivative of C#, so if you know that language you’ll do well here - if not, read along and use some Google-Fu to catch up where necessary.

# we should still be connected to a site collection, if not, reconnect using Connect-PnPOnline

# retrieve the context of the connected site
> $ctx = Get-PnPContext

> $ctx

# expected results:
# RetryCount                   : 10
# Delay                        : 1000
# PropertyBag                  : {}
# Web                          : Microsoft.SharePoint.Client.Web
# Site                         : Microsoft.SharePoint.Client.Site
# RequestResources             : Microsoft.SharePoint.Client.RequestResources
# FormDigestHandlingEnabled    : True
# ServerVersion                : 16.0.20308.12005
# Url                          : https://[tenantName][siteName]
# ApplicationName              : SharePoint PnP PowerShell Library
# ClientTag                    : PnPPS:2006:Get-PnPRecycleBinItem
# DisableReturnValueCache      : True
# ValidateOnClient             : True
# AuthenticationMode           : Default
# FormsAuthenticationLoginInfo : 
# Credentials                  : Microsoft.SharePoint.Client.SharePointOnlineCredentials
# WebRequestExecutorFactory    : Microsoft.SharePoint.Client.DefaultWebRequestExecutorFactory
# PendingRequest               : Microsoft.SharePoint.Client.ClientRequest
# HasPendingRequest            : True
# Tag                          : 
# RequestTimeout               : 1800000
# StaticObjects                : {[Microsoft$SharePoint$SPContext$Current, Microsoft.SharePoint.Client.RequestContext]}
# ServerSchemaVersion          :
# ServerLibraryVersion         : 16.0.20308.12005
# RequestSchemaVersion         :
# TraceCorrelationId           : [guid]

Great, we’ve got far more information than we had before. What can we do with it?

Let’s try to retrieve all of the Lists that have been provisioned on this Site Collection, this is still just PnP, we’ll get back to CSOM soon, I promise:

> $lists = Get-PnPList # retrieve all lists on this Site Collection

> $lists

# example results:
# Title                               Id       Url                                         
# -----                               --       ---                                         
# _catalogs/hubsite                   [guid]   /sites/[siteName]/_catalogs/hubsite
# appdata                             [guid]   /sites/[siteName]/_catalogs/appdata
# appfiles                            [guid]   /sites/[siteName]/_catalogs/appfiles
# -------- elided
# Documents                           [guid]   /sites/[siteName]/Shared Documents
#   remainder elided for brevity

Let’s isolate our Shared Documents Library from within our $lists object.

> $docLib = $lists | Where-Object {$_.Title -eq "Documents"}

> $docLib

# expected results:
# Title       Id       Url
# -----       --       ---
# Documents   [guid]   /sites/OrgDemo/Shared Documents

We’re getting there, I promise.

Let’s say that we now need to retrieve all of the Site Content Types that are attached to this Library. Surely we can just do this right?

> $docLib.ContentTypes

# An error occurred while enumerating through a collection: The collection has not been initialized. It has not been requested or the request 
has not been executed. It may need to be explicitly requested..


What’s happened here? The explanation is in the exception output.

The collection has not been initialized


It may need to be explicitly requested

So what do we do?

This is going to get a little heavy, bare with me.

# we've already got our context, but just to be sure...
> $ctx # retrieve it again if it's null with $ctx = Get-PnPContext

# retrieve Content Types from $docLib
> $contentTypes = $docLib.ContentTypes # <- CSOM
> $ctx.Load($contentTypes) # <- CSOM
> $ctx.ExecuteQuery() # <- CSOM
> $contentTypes

# example results:
Name                   Id       Group           Description
----                   --       -----           -----------
Document               [guid]   Content Types   Generic Document
Folder                 [guid]   Content Types   Create a new folder

CSOM to the rescue! You can take this further by slowly building up the objects you’re working with by retrieving all Fields on each Content Type, or whatever else you need to do. Pretty cool huh?

Don’t forget to disconnect from your PnP Session!

> Disconnect-PnPOnline

Let me know if you’ve got any questions.

Job’s another goodun!