๐ Recipe ยท SharePoint & OneDrive
SharePoint Site Hygiene Analysis: Inactive, Ownerless, and Duplicate Sites
Identify stale, unowned, and redundant SharePoint sites and produce an archive/delete plan
Complexity
Intermediate
Impact
governance + storage-optimization + compliance + reporting
Context
Why This Matters
SharePoint sprawl is one of the most common governance problems in Microsoft 365 tenants. Over time, project sites outlive their projects, owners leave the company, and teams create near-duplicate sites because they can't find the original. The result is storage bloat, confused end users, stale content surfacing in search, and compliance risk from orphaned data.
This recipe scans every non-personal SharePoint site in the tenant and flags three classes of hygiene issues:
- Inactive sites โ no modifications in the last 90 days (configurable).
- Ownerless sites โ no user currently holds the Owner permission, which blocks self-service governance and membership changes.
- Likely duplicates โ sites with identical or near-identical display names, a strong indicator of redundant workspaces.
The output is a CSV report with a recommended disposition (Archive, Review, or Reassign Owner) for each flagged site, giving you the raw material for a governance review cycle.
Run this quarterly, before storage-quota renewals, or ahead of a tenant-to-tenant migration.
Expected Outcomes
After completing this recipe you will have:
- A full inventory of SharePoint sites in the tenant (excluding OneDrive personal sites).
- A CSV report (
SharePoint_Hygiene_Report.csv) listing every site with one or more issues, including URL, current owners, last-modified date, detected issues, and a recommended action. - Counts of total sites analyzed and total sites flagged for reporting to stakeholders.
- A prioritized list of candidates to archive (inactive), reassign (ownerless), or consolidate (duplicates).
The report is designed to be handed directly to a governance steering committee or the site business owners for disposition sign-off.
Risks & Considerations
Important considerations
- Do not auto-delete based on this report.
lastModifiedDateTimeon a site can lag behind real user activity (e.g., file views without edits). Always require human sign-off before deletion. - Anonymized reporting. By default, M365 admin reports conceal user and site names. If your hygiene data comes back with obfuscated names, disable concealment in , or PATCH
/admin/reportSettingsto setdisplayConcealedNames=false. - Legal hold and retention. Sites under eDiscovery hold or a retention policy must not be archived or deleted without consulting Compliance, even if flagged as inactive.
- Duplicate detection is heuristic. Name-based matching will produce false positives (e.g., "HR" as a common prefix). Treat duplicate flags as candidates for review, not confirmed redundancy.
- Ownership via permissions vs. group owners. Microsoft 365 group-connected sites derive owners from the backing group. The site-permissions endpoint may show Owner roles differently than the group's owner list โ cross-reference both when a site is flagged ownerless.
- API throttling. Large tenants (>1,000 sites) will hit Graph throttling. Implement exponential backoff on 429 responses.
Required Permissions
| Permission | Why It's Needed |
|---|---|
| Sites.Read.All | Enumerate all SharePoint sites and read their metadata |
| Sites.FullControl.All | Read site permissions to determine ownership |
| Reports.Read.All | Access SharePoint site usage and activity reports |
| ReportSettings.ReadWrite.All | Disable anonymized reporting so site names are visible in reports |
| Group.Read.All | Resolve owners for Microsoft 365 group-connected sites |
The fastest way to get this done: just ask Dex. Copy the prompt below and paste it into your Dex conversation.
For IT Admins
Paste into Dex Pro
Analyze SharePoint site hygiene via the admin portals
The manual path combines the SharePoint admin center and the Microsoft 365 admin reports. It's suitable for tenants with fewer than ~200 sites or for spot-checking specific sites.
Step 1 โ Disable anonymized reporting (once)
- Go to .
- Uncheck Display concealed user, group, and site names in all reports.
- Click Save. It may take up to an hour for names to appear in new reports.
Step 2 โ Pull the SharePoint site usage report
- Navigate to .
- Set the date range to Last 90 days.
- Click Export to download the CSV.
- In the CSV, sort by
Last activity date. Rows older than 90 days (or with no activity) are inactivity candidates.
Step 3 โ Identify ownerless sites
- Go to the .
- Add the Primary admin column via the column picker if it isn't visible.
- Filter or sort on Primary admin โ sites with a blank value are ownerless.
- For group-connected sites, also verify in that the backing group has at least one owner.
Step 4 โ Detect likely duplicates
- In , export the site list using Export.
- Open the export in Excel, select the Site name column, and use Conditional Formatting โ Highlight Cells Rules โ Duplicate Values.
- Review highlighted rows โ same name across different URLs is a strong duplicate signal.
Step 5 โ Build the disposition plan
- Combine the three lists into one spreadsheet, adding a Recommendation column:
Archive(inactive),Reassign Owner(ownerless), orReview for consolidation(duplicate). - Circulate to site business owners for sign-off before acting.
Hygiene analysis via Microsoft Graph
The Graph-based approach is the most scalable and produces a repeatable, scriptable report. All endpoints below are against https://graph.microsoft.com/v1.0.
1. (Optional) Turn off concealed names
PATCH /admin/reportSettings
Content-Type: application/json
{
"displayConcealedNames": false
}Expected response: 204 No Content.
2. Enumerate all sites
GET /sites?search=*&$select=id,displayName,webUrl,lastModifiedDateTimeFollow @odata.nextLink until exhausted. Filter out personal sites by skipping any webUrl containing -my.sharepoint.com.
3. Check ownership per site
GET /sites/{site-id}/permissionsIn the response, collect entries where roles includes "owner". If the filtered list is empty, flag the site Ownerless.
4. Pull the SharePoint site usage report (bulk activity)
GET /reports/getSharePointSiteUsageDetail(period='D90')Returns CSV with Last Activity Date per site. Join on Site Id against the list from step 2. Any site whose last activity is more than 90 days ago (or null) is flagged Inactive.
5. Duplicate detection
Normalize displayName (lowercase, trim whitespace) and group sites by the normalized key. Groups with more than one member are flagged Likely Duplicate.
6. Build the report
For each site with one or more flags, emit a record:
{
"SiteName": "Marketing Team",
"Url": "https://contoso.sharepoint.com/sites/marketing",
"Owners": "",
"LastModified": "2024-02-11T09:14:00Z",
"Issues": "Ownerless, Inactive (>90 days)",
"Recommendation": "Archive"
}Throttling
On a 429 Too Many Requests response, honor the Retry-After header and back off exponentially. For tenants with >1,000 sites, batch the permission checks using the $batch endpoint (up to 20 requests per batch).
Run the hygiene analysis with PowerShell
The script below uses the Microsoft.Graph PowerShell SDK to enumerate sites, check ownership, pull the usage report, detect duplicates, and export a CSV with recommendations.
Prerequisites
- Install the Graph SDK if you haven't already:
Install-Module Microsoft.Graph -Scope CurrentUser - Connect with the required scopes:
Connect-MgGraph -Scopes "Sites.Read.All", "Sites.FullControl.All", "Reports.Read.All", "ReportSettings.ReadWrite.All" - (Optional) Disable concealed names so the report contains real site names:
Update-MgAdminReportSetting -DisplayConcealedNames:$false
How the script works
- Enumerate sites. Uses
Get-MgSite -Search '*' -Allto pull every site in the tenant, then filters out OneDrive personal sites. - Check ownership. For each site, calls
Get-MgSitePermissionand looks for entries with theownerrole. - Activity check. Compares each site's
LastModifiedDateTimeagainst a configurable threshold (default 90 days). - Duplicate detection. Groups sites by normalized (lowercased, trimmed) display name; any name appearing more than once is flagged.
- Recommendation logic. Inactive โ
Archive; Ownerless only โReassign Owner; Duplicate only โReview for consolidation; multiple flags โArchivewins. - Export. Writes
SharePoint_Hygiene_Report.csvto the output path.
Run it
.\Invoke-SharePointHygieneAnalysis.ps1 -InactiveDays 90 -OutputPath "C:\Reports\SharePoint_Hygiene_Report.csv"Expect roughly 2โ5 seconds per site for the ownership check. On a 500-site tenant the full run takes about 20 minutes.
Next steps