Connecting to Multiple Tenant Sites in a foreach Loop with PnP.PowerShell – Efficiently
I’ve been using PnP.PowerShell for years (once Todd Klindt showed me how awesome it is), and I thought I knew most of the tricks. Then Copilot showed me something so simple that I immediately wondered how I’d missed it all this time.
Yes, you’re hearing it from me: Copilot taught an experienced PnP.PowerShell user something valuable.

Maybe I’m the last person in the world to discover this pattern, but if not, it might save you some frustration.
It’s a very common pattern:
- Connect to the SharePoint Admin Center
- Loop through some or all of the sites in the tenant to either get or set some settings
Until now, I was treating every site connection as a brand-new authentication event. It worked, but it felt wasteful. After all, I’d already authenticated as a SharePoint Administrator when I connected to the Admin Center.
What I’ve been doing for years is connecting to the individual sites the same way I connected to the Admin Center, by either passing a credential object or just connecting interactively. If you have a lot of sites, the latter gets old really fast.
The little bit of sorcery Copilot showed me today is on lines 15-18 below. The magic is the -Connection $adminConnection parameter. After all, you’ve already proved you’re a SharePoint Admin, so why prove it again? Note that I don’t need to pass in the -ClientId again, either (even though I have it stored in a handy variable). The authentication context is already contained in the connection object.
# Connect to the SharePoint Admin Center
$adminConnection = Connect-PnPOnline -Tenant "$($tenant).onmicrosoft.com" -ClientId $clientId -Url $adminSiteUrl -Interactive -ReturnConnection
# Get all the tenant sites
$sites = Get-PnPTenantSite -Connection $adminConnection
# Filter if you don't want to run through all sites. Here, I'm only interested in modern Team Sites, with or without Microsoft 365 Groups
$filteredSites = $sites | Sort-Object -Property Title | Where-Object { $_.Template -eq "GROUP#0" -or $_.Template -eq "STS#3" }
foreach ($site in $filteredSites) {
# Connect to the site and change settings
$siteConnection = Connect-PnPOnline `
-Url $site.Url `
-Connection $adminConnection `
-ReturnConnection
# Do great stuff to make yourself a hero
}
# Do some post processing activity, like saving a log, csv, etc.
Such a simple thing. When Copilot suggested this, I was convinced it was hallucinating. The parameter was real, but surely it couldn’t be that easy.
It turns out it really is that simple: it works a charm.
Yes, the documentation for Connect-PnPOnline | PnP PowerShell sort of implies this is possible, but it never occurred to me.
You may ask “Can I just use -Connection $adminConnection on all my cmdlets instead of creating a new $siteConnection?” Well, it depends on what you are trying to do. Sometimes you have to connect to each site to accomplish your goals. Plus, I always create separate, named connection objects like $adminConnection or $siteConnection for clarity and to ensure subsequent cmdlets execute in the context of the target site. I always specify which connection I want to use, and it’s saved my bacon more times than I can count.
I suspect many experienced PnP.PowerShell users have known this trick for years. I didn’t.
Sometimes the biggest improvements aren’t new features. They’re the things that have been sitting in the documentation all along, waiting for someone to point them out.
That’s one of the reasons I still enjoy working with technology: every once in a while, you discover that something you’ve been doing the hard way for years can be replaced with a single parameter.
I haven’t tested this pattern with every authentication method, but it works beautifully with the interactive connection shown above.
Happy PowerShelling!
Love your work, Marc! And I also love PnP PowerShell, Copilot and Todd :) But I despise multiple authentication prompts while iterating sites.
A useful Connect-PnPOnline parameter I’ve started using since PnP.PowerShell 3.0 is -PersistLogin. Behaviour can be very sticky (e.g. across sessions and even reboots) so it’s awesome and powerful but also potentially risky if you’re not careful about checking what you’re connecting you. Once the delegated refresh token is persisted, MSAL can silently redeem it for new access tokens as you move from the admin URL to each site collection URL in that tenant… win! Disconnect-PnPOnline -ClearPersistedLogin to delete the stored token and force re-auth next time.
https://pnp.github.io/powershell/cmdlets/Connect-PnPOnline.html#-persistlogin