|
| 1 | + |
| 2 | + |
| 3 | +# Site Collection Ownership Validation |
| 4 | + |
| 5 | +## Summary |
| 6 | + |
| 7 | +This script audits all SharePoint Online site collections in a Microsoft 365 tenant to validate site ownership. It identifies site collections that have **no owners**, **disabled owners**, or **guest-only owners**, helping administrators detect governance and operational risks across the tenant. |
| 8 | + |
| 9 | +The output provides a clear, actionable dataset that can be used for remediation, reporting, or ongoing governance monitoring in large Microsoft 365 environments. |
| 10 | + |
| 11 | +## Why It Matters / Real-World Scenario |
| 12 | + |
| 13 | +In large tenants, SharePoint sites are frequently created through Teams, automation, or self-service processes. Over time, owners leave the organisation, accounts are disabled, or sites become orphaned. |
| 14 | + |
| 15 | +Without a valid internal owner: |
| 16 | +- Access requests and security incidents cannot be actioned |
| 17 | +- Data retention and compliance requirements may be violated |
| 18 | +- External users may retain unmanaged access |
| 19 | +- IT teams become the default escalation point with no clear decision authority |
| 20 | + |
| 21 | +This script enables proactive identification of these risks before they result in security, compliance, or operational issues. |
| 22 | + |
| 23 | +## Benefits |
| 24 | +- Improves governance by enforcing clear ownership accountability |
| 25 | +- Reduces security and compliance risk across SharePoint and Teams-connected sites |
| 26 | +- Supports audits and compliance reviews with evidence-based reporting |
| 27 | +- Enables targeted remediation rather than blanket administrative ownership |
| 28 | +- Scales efficiently for large Microsoft 365 tenants |
| 29 | + |
| 30 | + |
| 31 | +# [PnP PowerShell](#tab/pnpps) |
| 32 | + |
| 33 | +```powershell |
| 34 | +
|
| 35 | +# Prerequisites: |
| 36 | +# - PnP.PowerShell module |
| 37 | +# - Microsoft Graph permissions: User.Read.All, Directory.Read.All |
| 38 | +
|
| 39 | +Connect-PnPOnline -Url "https://<tenant>-admin.sharepoint.com" -Interactive |
| 40 | +
|
| 41 | +$tenantSites = Get-PnPTenantSite -IncludeOneDriveSites:$false |
| 42 | +$results = @() |
| 43 | +
|
| 44 | +foreach ($site in $tenantSites) { |
| 45 | + try { |
| 46 | + Connect-PnPOnline -Url $site.Url -Interactive -ErrorAction Stop |
| 47 | + $owners = Get-PnPSiteCollectionAdmin |
| 48 | +
|
| 49 | + $ownerDetails = foreach ($owner in $owners) { |
| 50 | + $user = Get-MgUser -UserId $owner.LoginName -ErrorAction SilentlyContinue |
| 51 | +
|
| 52 | + [PSCustomObject]@{ |
| 53 | + DisplayName = $owner.Title |
| 54 | + UserPrincipalName = $owner.LoginName |
| 55 | + IsGuest = ($user.UserType -eq "Guest") |
| 56 | + IsDisabled = (-not $user.AccountEnabled) |
| 57 | + } |
| 58 | + } |
| 59 | +
|
| 60 | + $hasInternalActiveOwner = $ownerDetails | Where-Object { |
| 61 | + $_.IsGuest -eq $false -and $_.IsDisabled -eq $false |
| 62 | + } |
| 63 | +
|
| 64 | + if (-not $hasInternalActiveOwner) { |
| 65 | + $results += [PSCustomObject]@{ |
| 66 | + SiteUrl = $site.Url |
| 67 | + SiteTitle = $site.Title |
| 68 | + OwnerCount = $ownerDetails.Count |
| 69 | + Owners = ($ownerDetails | Select-Object -ExpandProperty UserPrincipalName) -join "; " |
| 70 | + RiskReason = if ($ownerDetails.Count -eq 0) { |
| 71 | + "No owners assigned" |
| 72 | + } elseif ($ownerDetails.IsGuest -notcontains $false) { |
| 73 | + "Guest-only ownership" |
| 74 | + } else { |
| 75 | + "All owners disabled" |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + } |
| 80 | + catch { |
| 81 | + Write-Warning "Failed to process site: $($site.Url)" |
| 82 | + } |
| 83 | +} |
| 84 | +
|
| 85 | +$results |
| 86 | +
|
| 87 | +
|
| 88 | +
|
| 89 | +``` |
| 90 | + |
| 91 | + |
| 92 | +# [Usage](#tab/usage) |
| 93 | + |
| 94 | +1. Install required modules: |
| 95 | + |
| 96 | +```powershell |
| 97 | +
|
| 98 | +Install-Module PnP.PowerShell |
| 99 | +Install-Module Microsoft.Graph |
| 100 | +
|
| 101 | +
|
| 102 | +``` |
| 103 | + |
| 104 | +2. Install required modules: |
| 105 | +- SharePoint Administrator role |
| 106 | +- Microsoft Graph permissions: **User.Read.All**, **Directory.Read.All** |
| 107 | + |
| 108 | +3. Run the script from a secure admin workstation. |
| 109 | + |
| 110 | +[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)] |
| 111 | +*** |
| 112 | + |
| 113 | + |
| 114 | +## Output |
| 115 | + |
| 116 | +The script returns a collection of objects containing: |
| 117 | +- Site URL |
| 118 | +- Site title |
| 119 | +- Owner count |
| 120 | +- Owner UPNs |
| 121 | +- Governance risk reason |
| 122 | + |
| 123 | +This output can be: |
| 124 | +- Exported to CSV |
| 125 | +- Ingested into Power BI |
| 126 | +- Used as input for remediation automation |
| 127 | + |
| 128 | +## Notes |
| 129 | +- Script excludes OneDrive sites by default |
| 130 | +- Designed to be read-only (no changes applied) |
| 131 | +- Suitable for scheduled execution (e.g. quarterly governance reviews) |
| 132 | +- Can be extended to auto-assign fallback owners if required |
| 133 | + |
| 134 | +## Contributors |
| 135 | + |
| 136 | +| Author(s) | |
| 137 | +|-----------| |
| 138 | +| [Josiah Opiyo](https://github.com/ojopiyo) | |
| 139 | + |
| 140 | +*Built with a focus on automation, governance, least privilege, and clean Microsoft 365 tenants—helping M365 admins gain visibility and reduce operational risk.* |
| 141 | + |
| 142 | + |
| 143 | +## Version history |
| 144 | + |
| 145 | +Version|Date|Comments |
| 146 | +-------|----|-------- |
| 147 | +1.0|Dec 22, 2025|Initial release |
| 148 | + |
| 149 | + |
| 150 | +[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)] |
| 151 | +<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-site-collection-ownership-validation" aria-hidden="true" /> |
0 commit comments