Skip to content
All articlesSharePoint

How Much SharePoint Data Hasn't Been Touched in Years? Report It in GB

Three ways to total the GB of SharePoint files not modified in 2, 3 or 5 years: Purview content search, a PnP PowerShell script and SharePoint Storage Explorer.

3 Sept 202612 min read
How Much SharePoint Data Hasn't Been Touched in Years? Report It in GB

Start With What SharePoint Can and Cannot Tell You

The question is usually put to a SharePoint administrator in one sentence: how many gigabytes of our SharePoint content has nobody touched in two, three or five years? It is the number that turns "our storage is almost full" into a decision about what to archive or delete, and it is the number a storage business case is built on. SharePoint Online does not have a report for it. A question on Microsoft Q&A asking for the total size in GB of data not modified in the last five years across all sites got a straight answer: there is no built-in report, but every file exposes its last-modified date and its size, so the total can be assembled.

One honest caveat before any method. SharePoint does not expose a last-accessed date for files. Opening a document does not change anything on the item, and the audit log records file access only for as long as its retention window keeps events, which is nowhere near five years. Every method below, and every third-party tool, uses the Modified date. That is the date of the last content or metadata change, which means a bulk metadata edit, a migration that did not preserve timestamps, or a retention label applied by policy can make old files look recent. Treat "not modified since" as the practical definition of cold data, and read the results with that in mind.

There are three routes: a Purview content search for a quick estimate, a PnP PowerShell script for a per-site, per-library CSV, and SharePoint Storage Explorer's deep scan for the same breakdown without writing code.

Route 1: Purview Content Search With a Last-Modified Query

If you hold an eDiscovery role, Microsoft Purview content search can estimate cold content across the tenant in a few clicks. Create a search, choose SharePoint sites as the location (all sites, or a list), and put a date query in the keyword box. The searchable property is LastModifiedTime, and the syntax accepts a single comparison, or two comparisons joined with AND for a range:

lastmodifiedtime<=2021-09-03
lastmodifiedtime>=2016-09-03 AND lastmodifiedtime<=2021-09-03

The condition builder offers the same thing as a Last modified condition with Before, so you do not have to type the query. Run the search and open its statistics: the summary reports an estimated item count and an estimated size per location, which is the closest SharePoint offers to a one-click answer.

What it cannot do matters as much. The size is the current version of each matching file only; version history is not counted. The index includes copies held in the Preservation Hold Library, which can inflate both counts and sizes on sites under retention. The estimate covers everything the index knows about, so lists, pages and site assets may be mixed in with documents unless you add IsDocument:true. And you get a total per site, not per library or folder, so it tells you the scale of the problem and not where in the site it lives. Use it to size the question before spending hours on a script.

Route 2: PnP PowerShell, Bucketed by Age and Summed in GB

For the number most people actually want, a CSV of every site and library with the bytes older than one, two, three, five and seven years, the script below walks the tenant with PnP.PowerShell. It lists every site, connects to each, enumerates the visible document libraries, pages through the files in batches of 2,000, buckets each file by its Modified date and sums File_x0020_Size per bucket. One row per library, sizes in GB, sorted however you like in Excel afterwards.

You need the PnP.PowerShell module, an Entra ID app registration for the sign-in (Register-PnPEntraIDAppForInteractiveLogin creates one and gives you the client ID), SharePoint Administrator rights to list the sites, and read access to each site. For an unattended run, use a certificate-based app registration with Sites.Read.All and replace -Interactive -ClientId with -ClientId -Tenant -CertificatePath.

# Requires PnP.PowerShell, an Entra app registration for sign-in
# (Register-PnPEntraIDAppForInteractiveLogin) and SharePoint Administrator rights.
$clientId = "<your-app-client-id>"
$adminUrl = "https://contoso-admin.sharepoint.com"
$out      = "C:\Reports\SharePointFileAge.csv"
$now      = Get-Date
$buckets  = 1, 2, 3, 5, 7                 # years since last modified

Connect-PnPOnline -Url $adminUrl -Interactive -ClientId $clientId
$sites = Get-PnPTenantSite                # add -IncludeOneDriveSites to include OneDrive

$rows = foreach ($site in $sites) {
    try {
        Connect-PnPOnline -Url $site.Url -Interactive -ClientId $clientId
        $libs = Get-PnPList -Includes BaseType, Hidden, ItemCount |
                Where-Object { $_.BaseType -eq "DocumentLibrary" -and -not $_.Hidden -and $_.ItemCount -gt 0 }

        foreach ($lib in $libs) {
            $total  = [long]0
            $aged   = @{}
            foreach ($y in $buckets) { $aged[$y] = [long]0 }

            Get-PnPListItem -List $lib -PageSize 2000 -Fields "Modified","File_x0020_Size","FSObjType" |
              ForEach-Object {
                if ($_["FSObjType"] -ne 0) { return }          # skip folders
                $size  = [long]$_["File_x0020_Size"]           # current version, bytes
                $years = ($now - [datetime]$_["Modified"]).TotalDays / 365.25
                $total += $size
                foreach ($y in $buckets) { if ($years -ge $y) { $aged[$y] += $size } }
              }

            $row = [ordered]@{ Site = $site.Url; Library = $lib.Title; Items = $lib.ItemCount
                               TotalGB = [math]::Round($total / 1GB, 2) }
            foreach ($y in $buckets) { $row["Over${y}yGB"] = [math]::Round($aged[$y] / 1GB, 2) }
            [pscustomobject]$row
        }
    }
    catch { Write-Warning "$($site.Url): $($_.Exception.Message)" }
}

$rows | Export-Csv -Path $out -NoTypeInformation

Read the caveats before you trust the CSV. File_x0020_Size is the size of the current version only; version history, which on heavily edited libraries can be several times the live content, is not in these figures, and neither is the hidden Preservation Hold Library. The -PageSize 2000 keeps each request under the 5,000-item list view threshold, but a library with a million files still means five hundred requests, and SharePoint Online throttles clients that push too hard. PnP.PowerShell honours the retry-after header and backs off on a 429 response, so the script survives, but a large tenant takes hours and a very large one takes days; run it from a machine that stays awake, outside business hours, and consider scoping $sites to the largest sites first with a Where-Object on StorageUsageCurrent. OneDrive accounts are excluded unless you add -IncludeOneDriveSites, which multiplies the run time by the number of users.

Route 3: SharePoint Storage Explorer's Deep Scan

If a script is not something your team wants to own, SharePoint Storage Explorer produces the same breakdown from a Windows desktop. It is free, read-only, registers an Entra ID app in your tenant for the scan and never writes to SharePoint, and it scans every site, library, folder and file. A light scan gives site and library totals; a deep scan enumerates every file with its size and modified date, which is what an age report needs.

From a deep scan you get the per-site and per-library totals, an inactive content report that brackets files at 30, 90, 180 and 365-plus days of age, a cleanup-targets report that lists files older than two years and larger than 100 MB alongside sites that have not been modified in twelve months, and a CSV export from every view. For year-by-year buckets, open the file browser for a library and export it, which writes the size and modified date of every file under the selected folder to CSV, then pivot by year in Excel; the site overview's own export gives each site's total and last-modified date for the site-level version of the same question. The scan runs in the background and can be scoped to the sites the light scan flagged, so you do not have to scan a 50 TB tenant to answer a question about its twenty largest sites. Version counts and version bytes per file need a deep scan with file versions, which takes longer, so leave that option off for a first age report.

Reading the Result

There is no reliable rule of thumb for what share of a tenant is older than two years; it depends entirely on how long the tenant has existed, whether it was migrated from file servers with timestamps preserved, and how the organisation works. A ten-year-old tenant that absorbed a file server migration will look very different from one created three years ago. So rather than compare against a benchmark, look for four things in the CSV.

Which sites carry the cold bytes. The distribution is almost always skewed: a handful of project, department or archive sites hold most of the content over two years old, and those are the sites where a policy would have the largest effect. Whether the five-year bucket is dominated by a few libraries, which points at a migration dump or a records library rather than organic ageing. Whether the two-year bucket is close to the total on sites that are still busy, which usually means the site's day-to-day work happens in a small subset of libraries while the rest is finished work nobody has cleared. And which file types make up the cold bytes, because media, exports and archive files behave differently from documents when it comes to what users will want back.

Then sanity-check the timestamps. If an entire site shows nothing older than a migration date, the migration reset the modified dates and the site is older than it looks; if an old site shows a burst of recent modifications on every file, look for a bulk metadata change or a retention label applied by policy before treating that content as active.

What to Do With the Number

The cold-data total is the input to a storage decision. Paste it into the SharePoint storage calculator to compare what that content costs above the tenant allocation of 1 TB plus 10 GB per licence, at Microsoft's $0.20 per GB per month, against what it would cost in your own Azure Blob Storage. That comparison is what a business case for archiving is built on.

Deleting is the cheapest option only for content nobody will ever ask for again, and the age report cannot tell you that; the file that has not been opened in six years is often the one legal or finance asks for in year seven. Archiving keeps it findable. Squirrel applies the same rule you used in the report, last modified more than N years ago, filtered by site, library, folder path or file type if you want, and moves matching document library files into Azure Blob Storage in your own Azure subscription, leaving a stub in place so users, SharePoint search and Microsoft 365 Copilot still find the file, and restore is self-service. Retention labels and holds survive the archive and the restore. The policy keeps running, so the report you produced today does not have to be produced again next year.

One thing archiving does not change: item count. Every archived file is replaced by a stub, so the 5,000-item list view threshold and the 300,000-item sync guidance are unchanged. What leaves the library is the bytes, the version chains and the weight in the search index, which is exactly what the age report measured.

Turn the cold-data total into a policy

Squirrel archives files by last-modified age, site, library, folder or file type into your own Azure Blob Storage, leaves a stub so users can still open them, and keeps running as the tenant ages.

See how Squirrel archives by age

Frequently Asked Questions

Can I report last-accessed instead of last-modified?

Not for files in SharePoint Online. There is no last-accessed property on a file; opening a document does not update anything on the item. The unified audit log records file access events, but only for its retention window, so it can tell you what was opened recently and cannot tell you what has not been opened in five years. Last modified is the only date every file carries, and it is what every reporting method uses.

How do I get a CSV of files older than N years for all sites?

Use the PnP PowerShell script above and change the $buckets values to the years you want, or export the file browser from a SharePoint Storage Explorer deep scan and filter the modified column in Excel. Both give one line per file or per library with the size, so the total for any cut-off is a sum. A Purview content search with lastmodifiedtime gives an estimate per site without the per-library detail.

How long does this take on a large tenant?

It depends on the number of files rather than the number of gigabytes, because every file has to be read once. The script pages through libraries 2,000 items at a time and is throttled by SharePoint when it pushes too hard, so a tenant with tens of millions of files takes days. Scope the run to the largest sites first, run it outside business hours, and use certificate-based sign-in so it does not stop for a prompt.

Does the report include version history?

No. File_x0020_Size, the content search size estimate and a standard deep scan all report the current version of each file. Version history sits on top of those figures and can be larger than the live content on heavily edited libraries. To measure it, run the version usage report from the SharePoint Online Management Shell or a SharePoint Storage Explorer deep scan with file versions, and see SharePoint version history storage.

What should I do with old files: delete or archive?

Delete only what you are certain nobody, including legal, audit and finance, will ask for again, and only where no retention policy applies, because under retention a deletion becomes a copy in the Preservation Hold Library and storage goes up rather than down. Archive the rest. A policy that moves files by last-modified age into your own Azure Blob Storage keeps them findable and restorable, keeps labels and holds intact, and costs a fraction of holding them in SharePoint.

About the author
Mark Smith - Co-Founder, SmiKar Software

Mark Smith co-founded SmiKar Software in 2015 and has spent the past decade helping organisations solve Microsoft 365 data management challenges. He works with the SmiKar team to build solutions for SharePoint archiving, storage optimisation, governance and compliance, supporting customers from growing businesses through to Fortune 500 enterprises.

More about SmiKar

Ready when you are

Cut your Microsoft 365 storage bill - keep your data in your tenant.