● SCRIPTreporting
Get-InactiveSitesReport.ps1
Finds site collections nobody has touched in a long time - the shortlist for archival, ownership review or deletion.
<#
.SYNOPSIS
Finds site collections nobody has touched in a long time - the shortlist
for archival, ownership review or deletion.
.DESCRIPTION
Ranks every site collection by how long it has been since its content
last changed, and enriches each row with the numbers you need for the
"keep or kill" conversation: storage, owner, template, sharing setting
and whether the site is group-connected.
READ-ONLY: this script makes no changes to the tenant. Deleting sites is
deliberately out of scope - use this report as evidence, then act
through the admin center or a separate, reviewed script.
Caveat about LastContentModifiedDate: it moves for service-side changes
too (retention processing, ownership changes, Teams provisioning), so a
recent date is weak evidence that a human is using the site. Treat it as
an upper bound on inactivity and confirm with usage reports before
deleting anything.
.PARAMETER TenantAdminUrl
URL of the SharePoint admin center, e.g. https://contoso-admin.sharepoint.com
.PARAMETER InactiveDays
Sites untouched for at least this many days are reported. Default 365.
.PARAMETER MinStorageMB
Only report sites using at least this much storage. Default 0 (all).
.PARAMETER IncludeOneDrive
Include OneDrive personal sites. Off by default.
.PARAMETER OutputPath
CSV file to create. Defaults to a timestamped file in the current directory.
.EXAMPLE
.\Get-InactiveSitesReport.ps1 -TenantAdminUrl https://contoso-admin.sharepoint.com
110 site(s) in scope, cutoff 2026-06-29.
Done. 21 inactive site(s) written to .\InactiveSites_20260729-143912.csv
Storage held by inactive sites: 24 MB
Ten longest dormant:
Url DaysInactive StorageUsedMB Owner
--- ------------ ------------- -----
https://contoso.sharepoint.com/sites/quote-2026 72 1 megan@contoso.com
https://contoso.sharepoint.com/sites/AllCompany.0000000 72 1 megan@contoso.com
.EXAMPLE
.\Get-InactiveSitesReport.ps1 -TenantAdminUrl https://contoso-admin.sharepoint.com -InactiveDays 730 -MinStorageMB 1024
.NOTES
Requires : SharePoint Online Management Shell
(Install-Module Microsoft.Online.SharePoint.PowerShell)
Auth : Connect-SPOService, SharePoint Administrator role.
Samples : scripts/sample-outputs.md - what this prints, from a real run
Source : https://github.com/EasyPortal365/sharepoint-vault
#>
#Requires -Modules Microsoft.Online.SharePoint.PowerShell
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$TenantAdminUrl,
[ValidateRange(1, 100000)]
[int]$InactiveDays = 365,
[ValidateRange(0, [int]::MaxValue)]
[int]$MinStorageMB = 0,
[switch]$IncludeOneDrive,
[string]$OutputPath = ".\InactiveSites_$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
)
$ErrorActionPreference = 'Stop'
# The module ships for Windows PowerShell and does not auto-load in PowerShell 7,
# so #Requires alone leaves you with CommandNotFoundException. Import it explicitly.
Import-Module Microsoft.Online.SharePoint.PowerShell -WarningAction SilentlyContinue
$cutoff = (Get-Date).AddDays(-$InactiveDays)
Write-Host "Connecting to $TenantAdminUrl ..." -ForegroundColor Cyan
Connect-SPOService -Url $TenantAdminUrl
Write-Host 'Retrieving site collections ...' -ForegroundColor Cyan
$sites = @(Get-SPOSite -Limit All -IncludePersonalSite ([bool]$IncludeOneDrive) |
Where-Object { $_.Template -notlike 'REDIRECT*' })
Write-Host ("{0} site(s) in scope, cutoff {1:yyyy-MM-dd}." -f $sites.Count, $cutoff) -ForegroundColor Cyan
$stale = @($sites | Where-Object {
$_.LastContentModifiedDate -lt $cutoff -and $_.StorageUsageCurrent -ge $MinStorageMB
})
$report = $stale |
Sort-Object LastContentModifiedDate |
Select-Object -Property @(
'Url'
'Title'
'Template'
@{ Name = 'DaysInactive'; Expression = { [int]((Get-Date) - $_.LastContentModifiedDate).TotalDays } }
'LastContentModifiedDate'
@{ Name = 'StorageUsedMB'; Expression = { $_.StorageUsageCurrent } }
'Owner'
@{ Name = 'GroupConnected'; Expression = { $_.GroupId -and $_.GroupId -ne [guid]::Empty } }
'SharingCapability'
'LockState'
)
Write-Host ''
if ($report.Count -gt 0) {
$report | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
# Report the unit that carries information: rounding 21 MB to "0.0 GB"
# tells the reader there is nothing there, which is a different claim.
$reclaimMB = [math]::Round((($stale | Measure-Object StorageUsageCurrent -Sum).Sum), 0)
$reclaimText = if ($reclaimMB -ge 1024) { '{0} GB' -f [math]::Round($reclaimMB / 1024, 1) } else { '{0} MB' -f $reclaimMB }
Write-Host ("Done. {0} inactive site(s) written to {1}" -f $report.Count, (Resolve-Path $OutputPath)) -ForegroundColor Green
Write-Host ("Storage held by inactive sites: {0}" -f $reclaimText) -ForegroundColor Yellow
Write-Host ''
Write-Host 'Ten longest dormant:' -ForegroundColor Cyan
$report | Select-Object -First 10 | Format-Table Url, DaysInactive, StorageUsedMB, Owner -AutoSize
}
else {
Write-Host ("Done. No site has been inactive for {0}+ days." -f $InactiveDays) -ForegroundColor Green
}
Disconnect-SPOService
Was this helpful?ask or add your experience on GitHub →
Saved you an hour? A star on GitHub helps other SharePoint people find the vault.★ Star on GitHub