PnP PowerShell for SharePoint Admins

Everything I have written about administering SharePoint Online with PnP PowerShell, in the order it makes sense to read it rather than the order I happened to publish it.

Every script in these posts was run against a real tenant before the post was written, and the output you see is what came back. Where something in the documentation turned out to be wrong, the post says so.

Written against PnP.PowerShell 2.12.0 on PowerShell 7.

Start here

If you have never used PnP PowerShell, or you are coming back to a script that stopped working, read these three in order.

Install PnP PowerShell and Connect to SharePoint Online
Installing the module, registering the app registration it now requires, connecting, and confirming the connection actually worked. Includes the two things that surprise everyone first – a site with four lists reporting nineteen, and the Forms folder that appears in every library.

Connect-PnPOnline: Every Way to Authenticate, and When to Use Each
Interactive, device login, certificate, managed identity and the rest – what each one is for, and which ones still work.

Connect-PnPOnline: Specified Method Is Not Supported
The error every old script hits now. The shared PnP Management Shell app was deleted in September 2024, and this is how to register your own.

Working with data

Read SharePoint Lists, Items and Files with PnP PowerShell
Getting things out without breaking anything. Filtering the noise, handling person and lookup fields, and reading file version history.

Find a SharePoint List’s Internal Column Names
Why asking for a column by the name on screen returns an empty string. Every column has two names, renaming never changes the one that matters, and Created By is called Author.

Update SharePoint List Items from a CSV
Three ways this goes wrong, two of them silent. An invalid Choice value is written without complaint, and an empty cell deletes data.

Bulk Updates: Batching, Throttling and Retry
A write costs 1.2 seconds. Batching makes inserts nineteen times faster and updates only three times faster, and just ten cmdlets support it. Read this before you point a loop at anything large.

Reporting and governance

The posts admins tend to bookmark. Each ends with a complete script you can run.

Find Broken Permission Inheritance
Every site, list, folder and item that does not inherit its permissions, and who can reach it. Includes the folder that breaks the obvious approach and a PnP switch that reports success while doing nothing at all.

Report Who Has Access to a Site
Access arrives by five routes and most scripts check one. Both obvious approaches are wrong in opposite directions – one cries wolf on every site, the other stays quiet while the whole company has edit access.

List Every Site with Owner, Storage and Last Activity
A tenant-wide inventory, and why the owner column is harder than it looks. Three of the four owner properties are empty on every site, and a naive ownerless-site report was wrong 68% of the time on my tenant.

PowerShell itself

Not SharePoint-specific, but they come up constantly when running these scripts.

Running Scripts Is Disabled on This System
Five things produce this error and only one is fixed by Set-ExecutionPolicy. Which scope is actually winning, the mark of the web, and why fixing it in PowerShell 5.1 does nothing for PowerShell 7.

try/catch Is Not Catching Your Error
Most cmdlet errors are non-terminating, so try/catch never sees them. Why -ErrorAction Stop is the whole trick, and why try/catch does nothing at all for robocopy or git.

How these are written

There is a demo site I use for all of this – a list with a deliberately awkward mix of column types, a library with nested folders and version history, and permission inheritance broken in two places on purpose. A permission report that comes back empty proves nothing, so the test data has something real to find.

Scripts are run there first. If a cmdlet behaves differently from the documentation, that goes in the post, because that is the part you cannot find anywhere else.