Anything you do twice in the SharePoint admin UI, you should script. PowerShell is the difference between a consultant who spends an afternoon clicking through site settings and one who runs a tested script across fifty sites in two minutes. This article maps out the SharePoint PowerShell landscape and shows the patterns I reach for on almost every engagement.
Know your modules
There isn't one "SharePoint PowerShell" — there are several, and choosing the right one matters:
- SharePoint Online Management Shell (
Microsoft.Online.SharePoint.PowerShell) — tenant administration: creating site collections, setting tenant policies, managing sharing. Connects withConnect-SPOService. - PnP PowerShell (
SharePointPnPPowerShellOnline) — the developer/consultant workhorse: lists, fields, content types, pages, provisioning templates. Connects withConnect-PnPOnline. - SharePoint Management Shell (on-prem) — the
Microsoft.SharePoint.PowerShellsnap-in withGet-SPSiteet al., for farm administration only.
Connecting
# Tenant administration
Connect-SPOService -Url "https://contoso-admin.sharepoint.com"
# Site-level work with PnP
Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/ops" -Interactive
Prefer interactive or certificate-based auth over storing credentials in scripts.
Provisioning a list with fields
The bread-and-butter task: stand up a list with a defined schema, repeatably and idempotently. PnP makes it terse:
# Create the list only if it doesn't already exist
$list = Get-PnPList -Identity "Contracts" -ErrorAction SilentlyContinue
if (-not $list) {
$list = New-PnPList -Title "Contracts" -Template GenericList -OnQuickLaunch
}
# Add fields (Add-PnPField is safe to re-run with -ErrorAction)
Add-PnPField -List "Contracts" -DisplayName "Contract Value" `
-InternalName "ContractValue" -Type Currency -AddToDefaultView
Add-PnPField -List "Contracts" -DisplayName "Status" `
-InternalName "ContractStatus" -Type Choice `
-Choices "Draft","In Review","Approved","Archived" -AddToDefaultView
# Index a column you'll filter on (matters for large lists)
Set-PnPField -List "Contracts" -Identity "ContractStatus" `
-Values @{ Indexed = $true }
Bulk operations
Need to update hundreds of items? Pull them once, loop, and let PnP batch the writes:
$items = Get-PnPListItem -List "Contracts" -PageSize 500
foreach ($item in $items) {
if ($item["ContractStatus"] -eq "Approved" -and
$null -eq $item["ReviewedDate"]) {
Set-PnPListItem -List "Contracts" -Identity $item.Id `
-Values @{ "ReviewedDate" = (Get-Date) } | Out-Null
}
}
Content types the scripted way
Content types are tedious to build by hand and error-prone to keep consistent across sites. Script them:
$ct = New-PnPContentType -Name "Vendor Contract" `
-Group "Contoso Content Types" -ParentContentType (Get-PnPContentType -Identity "Document")
Add-PnPFieldToContentType -Field "ContractValue" -ContentType "Vendor Contract"
Add-PnPFieldToContentType -Field "ContractStatus" -ContentType "Vendor Contract"
# Attach it to a library
Add-PnPContentTypeToList -List "Contract Documents" -ContentType "Vendor Contract" -DefaultContentType
Automating the full site lifecycle
Tie it together: create the site collection with the SPO shell, then hand off to PnP to configure it:
# 1. Create the site (tenant admin context)
New-SPOSite -Url "https://contoso.sharepoint.com/sites/project-atlas" `
-Owner "admin@contoso.com" -StorageQuota 1024 `
-Title "Project Atlas" -Template "STS#3"
# 2. Configure it (site context)
Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/project-atlas" -Interactive
# ... New-PnPList / Add-PnPField / Set-PnPWebTheme etc.
New-SPOSite, poll with Get-SPOSite until status is Active before you try to connect and configure it, or your PnP commands may fail with a 404.
From scripts to templates
Once your provisioning logic grows past a few dozen lines, graduate from imperative scripts to the PnP provisioning engine: capture an entire configured site as an XML template with Get-PnPProvisioningTemplate, then re-apply it anywhere with Apply-PnPProvisioningTemplate. I cover that approach in depth in a later article — but the imperative cmdlets here remain the right tool for surgical, one-off changes and for the glue around the declarative templates.
Practical habits
- Parameterise site URLs and titles; never hard-code a tenant.
- Use
-ErrorActiondeliberately —SilentlyContinuefor "create if missing" checks,Stopwhere a failure must halt the run. - Log what you change with
Write-Host/ a transcript so a consultant after you can see what the script did. - Test against a dev site collection first — PowerShell will happily delete a thousand items without confirmation.
PowerShell is the consultant's force multiplier. The investment you make turning a manual checklist into a tested script pays back on the very next environment you have to stand up.