Every SharePoint cleanup I’ve run starts with the same question: how many sites do we actually have? The admin center shows them, but you can’t easily filter by template, join the list with storage numbers, or hand a CSV to the person who has to chase site owners.
PnP PowerShell answers that in a few lines. I tested everything below on my own Microsoft 365 tenant in PowerShell 7 with PnP.PowerShell 3.3: listing every site collection, filtering by template and URL, adding OneDrive sites, finding subsites and exporting the result.
Quick answer: Connect to the SharePoint admin center, not a site: Connect-PnPOnline -Url https://contoso-admin.sharepoint.com -ClientId <your-app-id> -Interactive. Then run Get-PnPTenantSite to return every site collection (OneDrive excluded). Add -Template, -Filter "Url -like 'sales'" or -IncludeOneDriveSites to narrow or widen the list, and pipe it to Export-Csv for a report.
What you need before you start
Get-PnPTenantSite reads tenant-level data, so a regular site member can’t run it. Check these three things first. If PnP isn’t on your machine yet, follow my guide on installing PnP PowerShell for SharePoint Online.
- PowerShell 7.4 or later with the PnP.PowerShell module. Current PnP versions don’t load in Windows PowerShell 5.1.
- The SharePoint Administrator or Global Administrator role. The cmdlet runs against the tenant administration site.
- Your own Entra ID app registration for the
-ClientIdparameter. The shared PnP app stopped working on September 9, 2024, which is why older scripts without a client ID fail.
If you landed here from an older tutorial and saw an error about Register-PnPManagementShellAccess, that’s the same retired app; my post on the Register-PnPManagementShellAccess error explains the switch.
Get all site collections with Get-PnPTenantSite
Connect to the admin center URL, which is your tenant name followed by -admin, then call the cmdlet with no parameters. I store the result in a variable so I can count, group and filter it without asking SharePoint again:
$adminUrl = "https://contoso-admin.sharepoint.com"
$clientId = "00000000-0000-0000-0000-000000000000" # your Entra ID app's client ID
Connect-PnPOnline -Url $adminUrl -ClientId $clientId -Interactive
$sites = Get-PnPTenantSite
"Site collections found: $($sites.Count)"
Here’s the real run on my tenant. Grouping by template is the fastest way to see what kind of sites you have before you filter anything:

Each object includes Title, Url, Template, Owner, StorageUsageCurrent (in MB) and LastContentModifiedDate. Some properties come back with default values unless you add -Detailed, which fills in every attribute but runs noticeably slower on a large tenant.
For one site, pass its URL: Get-PnPTenantSite -Identity https://contoso.sharepoint.com/sites/Marketing -Detailed. Modern sites can also be looked up by their site ID.
Filter sites by template: team sites, communication sites and more
The Template value tells you what kind of site each one is. These are the codes I saw in my tenant, and they’re the ones you’ll meet most often. If the difference between the first two is fuzzy, read my comparison of team sites and communication sites.
| Template | What it is |
|---|---|
| GROUP#0 | Team site connected to a Microsoft 365 group (including sites created by Microsoft Teams) |
| SITEPAGEPUBLISHING#0 | Communication site |
| STS#3 | Modern team site without a Microsoft 365 group |
| STS#0 | Classic team site (often the tenant root site on older tenants) |
| TEAMCHANNEL#0 / #1 | Site for a Teams private or shared channel |
| RedirectSite#0 | Redirect left behind when a site URL was changed |
| SRCHCEN#0, APPCATALOG#0, SPSMSITEHOST#0 | System sites: search center, app catalog, OneDrive host |
Pass a template to -Template and SharePoint filters on the server, which is faster than pulling everything and filtering locally:
# Communication sites
Get-PnPTenantSite -Template SITEPAGEPUBLISHING#0
# Team sites connected to a Microsoft 365 group (includes Teams team sites)
Get-PnPTenantSite -Template GROUP#0
# Any site with a Microsoft 365 group behind it
Get-PnPTenantSite -GroupIdDefined $true
# Classic-style team sites without a group
Get-PnPTenantSite -Template STS#3
In my tests the template value wasn’t case-sensitive: SITEPAGEPUBLISHING#0 returned the root site even though it reports SitePagePublishing#0.
Filter sites by URL or other properties
-Filter takes a server-side expression. With -like, the value matches anywhere in the URL, so