PnP PowerShell

With my recent posts about the Graph PowerShell module I would be remiss to not mention the PnP PowerShell module. This is a cross-platform module that helps you manage SharePoint Online (so yes, works on macOS whoo hoo!).

Prior to this one had to use the SharePoint Online Management Shell module and while that works it is limited to Windows only. I use that too occassionally as a lot of blog posts etc. still refer to those cmdlets and sometimes it’s just easier to RDP to Windows and use those rather than figure out the newer PnP variants. :) But as a rule I try and figure out/ stick with the Microsoft.Graph (instead of AzureAD) or PnP.PowerShell (insted of SPO Management Shell) nowadays.

Here’s how I connect to either of these using an app registration and certificate.

Something that I wasn’t aware of when I started working with PnP PowerShell. Behind the scenes these do not use the Graph API; they use the SharePoint API.

What I mean is that say you go to the screen in your app registration to give it API permissions. Typically you have the Graph permissions highlighted.

The Graph API can manage SharePoint too, and that’s where all the new work is happening. Thus, for instance, under the Sites section you’ll see a bunch of SharePoint permissions that can be granted… including a new and useful one to grant access to Selected Sites. If an app registration has this permission assigned to it, it is possible to then restrict it to specific SharePoint Online sites rather than give it control to All sites.

On paper this is a Good Thing. But where it comes to bite you is that not everything SharePoint related currently uses the Graph API. :) Coz if you scroll down that initial page on API permissions you’ll find a SharePoint API section:

Here are the permissions it grants:

Notice, it’s quite similar to the ones from Graph API (there’s no Sites.Selected as that’s the newer one and exclusive to Graph API only).

(Update 23 Feb 2022: Was looking into this again for an issue at work and noticed that there’s a new Sites.Selected permission under SharePoint APIs now. Looks like is was silently introduced in Jan of this year. This makes the rest of my blog post obsolete. 😊)

Now, the thing is that something like the PnP PowerShell module still uses the older SharePoint API permissions rather than the Graph API permissions. Thus the app registration that PnP PowerShell creates (or if you create one yourself with limited rights – say Sites.ReadAll) are all using the SharePoint API permissions and not the Graph API permissions. Not a big deal in itself as it still works, but the catch is that some newer features might be missing in PnP PowerShell because of this.

The way this bit me, for instance, was that I created an app registration using the newer Sites.Selected permission. Then I tried using the PnP PowerShell modules with this app registration, hoping they’d work fine and my script would have limited permissions. But it did not work, and my script had access to nothing at all… which is when I realized PnP PowerShell requires the SharePoint API and there’s no way to thus give it a limited app registration.

“Luckily”, all is not lost because one can use good old Invoke-MgGraphRequest and query SharePoint via the Graph API. In my case, for instance, I wanted a script to read a specific SharePoint Online list and search for a specific item. So I read it using something along these lines:

This is not all fun and joy though, as the Invoke-MgGraphRequest does not like to take SharePoint lists/ sites as the argument. It needs the Graph specific reference!

Say you have a site with a Url like this:

I can’t pass this to Invoke-GraphRequest. From the API docs I can get the URL format that Graph expects.

So the Graph specific URL would be If do a request against this URL I will get some details:

Notice the id in there? This is to be interepreted as {hostname},{},{} (thanks to StackOverflow for pointing me to this, I couldn’t find this in any of the Graph docs). Anyways, that second field in the id is your Site Id. You need that for any subsequent Invoke-MgGraphRequest queries to (say) read a list in that site.

For instance, I could do the following to get the lists:

If I expand the value property of the result, I’ll see all my lists with their ids! Armed with that info I can make a final request to the full URL that includes the site Id from above and the list Id.

This is what I was doing in the snippet above. To search the list for a particular $Item in the Title field I can thus do:

Not much fun to be honest, but good to know it is possible to do something like this if you want to stick with the Graph API and make use of its newer features.

I tend to use PnP PowerShell for my management work and also where I can use it with full admin rights; but if I need to talk to SharePoint in a script and want limited access I resort to Graph.

Update: Haven’t used this, but I came across the Get-MgSite cmdlet later in the day. Looks like I might be able to use that to query a SPO site. As usual there’s not much help on it and my initial attempt at searching for a site fails…

Something to dig into later…