Set-GlobalSiteAdmin
Adds a site collection administrator to all SharePoint sites.
Syntax
.\Set-GlobalSiteAdmin.ps1 [-AdminUrl <String>] [-AppId <String>] [-Tenant <String>] [-AddAdmin <String>] [-Cloud <String>] [-Site <String>] [-Out <String>]
Description
Adds a specified user as a site collection administrator to all SharePoint sites in the tenant (excluding OneDrive, admin, and system sites). Results are exported to an Excel file showing success/failure for each site.
Examples
Example 1
.\Set-GlobalSiteAdmin.ps1 -AdminUrl 'https://contoso-admin.sharepoint.com' -AppId 'xxx' -Tenant 'contoso.onmicrosoft.com' -AddAdmin 'admin@contoso.com'
# Add user as admin to all sites
Example 2
.\Set-GlobalSiteAdmin.ps1 -AdminUrl 'https://contoso-admin.sharepoint.com' -AppId 'xxx' -Tenant 'contoso.onmicrosoft.com' -AddAdmin 'admin@contoso.com' -Site 'https://contoso.sharepoint.com/sites/test'
# Test on a single site first
Parameters
-AdminUrl
The SharePoint Admin Center URL (e.g., https://contoso-admin.sharepoint.com).
- Type:
String - Required: No
-AppId
The Entra ID application (client) ID for authentication.
- Type:
String - Required: No
-Tenant
Tenant ID or domain for the connection.
- Type:
String - Required: No
-AddAdmin
The UPN of the user to add as site collection administrator.
- Type:
String - Required: No
-Cloud
Cloud environment to connect to. Defaults to 'Global'. Valid values: Global, USGov, USGovDoD, Germany, China
- Type:
String - Required: No
- Valid values:
Global,GCC,GCCH,DoD,Germany,China
-Site
Optional single site URL to test on before running against all sites.
- Type:
String - Required: No
-Out
Output file path for the Excel export. If not specified, generates a filename based on tenant name and current timestamp.
- Type:
String - Required: No
Notes
Requires PnP.PowerShell and ImportExcel modules.
Full Script
Copy this complete standalone script (includes all helper functions):
#Requires -Version 7.0
#Requires -Modules ImportExcel, PnP.PowerShell
<#
.SYNOPSIS
Adds a site collection administrator to all SharePoint sites.
.DESCRIPTION
Adds a specified user as a site collection administrator to all SharePoint
sites in the tenant (excluding OneDrive, admin, and system sites).
Results are exported to an Excel file showing success/failure for each site.
.PARAMETER AdminUrl
The SharePoint Admin Center URL (e.g., https://contoso-admin.sharepoint.com).
.PARAMETER AppId
The Entra ID application (client) ID for authentication.
.PARAMETER Tenant
Tenant ID or domain for the connection.
.PARAMETER AddAdmin
The UPN of the user to add as site collection administrator.
.PARAMETER Cloud
Cloud environment to connect to. Defaults to 'Global'.
Valid values: Global, USGov, USGovDoD, Germany, China
.PARAMETER Site
Optional single site URL to test on before running against all sites.
.PARAMETER Out
Output file path for the Excel export. If not specified, generates
a filename based on tenant name and current timestamp.
.EXAMPLE
.\Set-GlobalSiteAdmin.ps1 -AdminUrl 'https://contoso-admin.sharepoint.com' -AppId 'xxx' -Tenant 'contoso.onmicrosoft.com' -AddAdmin 'admin@contoso.com'
# Add user as admin to all sites
.EXAMPLE
.\Set-GlobalSiteAdmin.ps1 -AdminUrl 'https://contoso-admin.sharepoint.com' -AppId 'xxx' -Tenant 'contoso.onmicrosoft.com' -AddAdmin 'admin@contoso.com' -Site 'https://contoso.sharepoint.com/sites/test'
# Test on a single site first
.NOTES
Requires PnP.PowerShell and ImportExcel modules.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)
#region Helper Functions
function Write-TimeLog {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position = 0)]
[string]$Message,
[Parameter()]
[ConsoleColor]$Color = 'White'
)
Write-Host "[$(Get-Date -Format 'HH:mm')] $Message" -ForegroundColor $Color
}
function Connect-PnPIfNeeded {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$SiteUrl,
[Parameter(Mandatory)]
[string]$AppId,
[Parameter(Mandatory)]
[string]$Tenant,
[Parameter()]
[ValidateSet('Global', 'GCC', 'GCCH', 'DoD', 'Germany', 'China')]
[string]$Cloud = 'Global'
)
try {
$currentConnection = Get-PnPConnection -ErrorAction Stop
if ($currentConnection.Url -eq $SiteUrl) {
Write-TimeLog "Using existing PnP connection to $SiteUrl" -Color Cyan
return
}
Write-TimeLog "Switching PnP connection from $($currentConnection.Url) to $SiteUrl" -Color Yellow
}
catch {
# Not connected - will connect below
}
$envMap = @{
'Global' = 'Production'
'GCC' = 'USGovernment'
'GCCH' = 'USGovernmentHigh'
'DoD' = 'USGovernmentDoD'
'Germany' = 'Germany'
'China' = 'China'
}
$connectParams = @{
Url = $SiteUrl
ClientId = $AppId
Tenant = $Tenant
Interactive = $true
ErrorAction = 'Stop'
}
if ($Cloud -ne 'Global') {
$connectParams['AzureEnvironment'] = $envMap[$Cloud]
}
try {
Connect-PnPOnline @connectParams
Write-TimeLog "Connected to PnP: $SiteUrl" -Color Green
}
catch {
Write-TimeLog "Failed to connect to PnP: $($_.Exception.Message)" -Color Red
throw
}
}
function Get-DefaultExcelParams {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path
)
@{
Path = $Path
TableStyle = 'Medium2'
AutoSize = $true
FreezeTopRow = $true
}
}
function New-ReportPath {
[CmdletBinding()]
param(
[Parameter()]
[string]$Out,
[Parameter(Mandatory)]
[string]$FriendlyName,
[Parameter(Mandatory)]
[string]$OrgDisplayName,
[Parameter()]
[string]$Extension = 'xlsx'
)
if (!$Out) {
"$OrgDisplayName-$FriendlyName-$(Get-Date -Format 'yyyy-MM-dd_HH-mm').$Extension"
}
elseif ($Out -notlike "*.$Extension") {
"$Out.$Extension"
}
else {
$Out
}
}
#endregion Helper Functions
]
[ValidatePattern('^https?://')]
[string]$AdminUrl,
[Parameter(Mandatory)]
[string]$AppId,
[Parameter(Mandatory)]
[string]$Tenant,
[Parameter(Mandatory)]
[string]$AddAdmin,
[ValidateSet('Global', 'GCC', 'GCCH', 'DoD', 'Germany', 'China')]
[string]$Cloud = 'Global',
[string]$Site,
[string]$Out
)
Connect-PnPIfNeeded -SiteUrl $AdminUrl -AppId $AppId -Tenant $Tenant -Cloud $Cloud
# Generate output path
$tenantName = ($AdminUrl -replace 'https?://', '' -replace '-admin\.sharepoint\..+$', '')
$Out = New-ReportPath -Out $Out -FriendlyName 'GlobalSiteAdmin' -OrgDisplayName $tenantName
$results = [System.Collections.Generic.List[object]]::new()
if ($Site) {
Write-TimeLog "Single site mode: $Site"
$sites = @([PSCustomObject]@{ Url = $Site; Title = $Site })
}
else {
Write-TimeLog "Fetching all SharePoint sites..."
$sites = Get-PnPTenantSite | Where-Object {
$_.Url -notlike '*-admin.sharepoint.*' -and
$_.Url -notlike '*-my.sharepoint.*' -and
$_.Url -notlike '*/portals/hub' -and
$_.Url -notlike '*/search'
}
Write-TimeLog "Found $($sites.Count) sites to process"
}
$i = 0
foreach ($s in $sites) {
$i++
$siteUrl = $s.Url
$siteTitle = $s.Title
Write-TimeLog "[$i/$($sites.Count)] Processing: $siteTitle ($siteUrl)"
try {
Set-PnPTenantSite -Identity $siteUrl -Owners $AddAdmin -ErrorAction Stop
$results.Add([PSCustomObject]@{
SiteUrl = $siteUrl
SiteTitle = $siteTitle
Status = 'Success'
Error = $null
})
Write-TimeLog " Added $AddAdmin as admin" -Color Green
}
catch {
$results.Add([PSCustomObject]@{
SiteUrl = $siteUrl
SiteTitle = $siteTitle
Status = 'Failed'
Error = $_.Exception.Message
})
Write-TimeLog " Failed: $($_.Exception.Message)" -Color Red
}
}
# Export results
$excelParams = Get-DefaultExcelParams -Path $Out
$results | Export-Excel @excelParams -WorksheetName 'Results'
Write-TimeLog "Exported results to: $Out" -Color Green
Write-TimeLog "Summary: $($results.Where({$_.Status -eq 'Success'}).Count) succeeded, $($results.Where({$_.Status -eq 'Failed'}).Count) failed"