Taming SharePoint's Version Monster: A PowerShell Adventure

Taming SharePoint's Version Monster: A PowerShell Adventure
The 3AM Nightmare itself on a Sunday - The Sharepoint Storage Quote exceeded

Introduction

Let’s face it—SharePoint Online is a fantastic platform when it behaves. But when you start dealing with libraries bloated with 50 versions of every file, that initial sparkle of joy turns into a slow-loading nightmare. Microsoft’s default versioning policy, while helpful in theory, often leaves us with libraries teetering on the brink of chaos.

If your SharePoint library is creeping toward the abyss of bloated storage or hitting performance roadblocks, this blog post (and accompanying PowerShell script) is your rescue operation. We’ll trim file versions across an entire library, reclaim storage, and give your SharePoint site a much-needed cleanse—complete with logging and storage stats to prove you did something heroic today.


Why Is This Necessary?

Microsoft, in its infinite wisdom, assumes you need every single version of every file since the dawn of time. While that might work for someone who writes a document once a decade, for most of us, it’s a recipe for disaster.

Here are the top reasons why trimming file versions is a must:

  1. Storage Costs: Extra versions mean extra storage, which you’re paying for.
  2. Performance Hits: Libraries bloated with versions slow down search, queries, and user access.
  3. Human Error: Nobody’s using the 48th version of a file from 2020. Be honest.

Let’s take back control, and while we’re at it, poke some fun at SharePoint quirks.


The Script

Here’s the PowerShell script that trims file versions across a SharePoint Online library, logs every action, and calculates the library size before and after.

powershellCopy code# Import the PnP PowerShell Module
Import-Module PnP.PowerShell

# Define variables
$siteUrl = "https://yourtenant.sharepoint.com/sites/YourSite" # Your site URL
$libraryName = "YourLibraryName"  # Name of the library
$maxVersionsToKeep = 30  # Maximum versions to keep
$pageSize = 1000  # Number of items per batch
$logFilePath = ".\VersionTrimmingLog.txt"  # Path to log file

# Initialize the log file
if (-Not (Test-Path (Split-Path -Path $logFilePath -Parent))) {
    New-Item -ItemType Directory -Path (Split-Path -Path $logFilePath -Parent) | Out-Null
}
New-Item -ItemType File -Path $logFilePath -Force | Out-Null
Write-Output "Version Trimming Log - $(Get-Date)" | Out-File -FilePath $logFilePath -Append

# Authenticate to SharePoint Online
Connect-PnPOnline -Url $siteUrl -UseWebLogin

# Function to calculate total size of versions in a library
function GetTotalLibrarySize {
    param (
        [string]$libraryName
    )

    $totalSize = 0
    $listItems = Get-PnPListItem -List $libraryName `
                                 -PageSize $pageSize `
                                 -Fields FileRef, FileLeafRef, FileSystemObjectType `
                                 -ErrorAction SilentlyContinue

    foreach ($item in $listItems) {
        if ($item.FileSystemObjectType -eq "File") {
            $fileUrl = $item["FileRef"]
            $fileItem = Get-PnPFile -Url $fileUrl -AsListItem -ErrorAction SilentlyContinue
            if ($fileItem -ne $null) {
                $versions = Get-PnPProperty -ClientObject $fileItem -Property "Versions"
                foreach ($version in $versions) {
                    $totalSize += $version.Size
                }
            }
        }
    }

    return $totalSize
}

# Function to trim versions for files in a library
function TrimVersionsInLibrary {
    param (
        [string]$libraryName
    )

    Write-Host "Processing library: $libraryName"
    Write-Output "Processing library: $libraryName" | Out-File -FilePath $logFilePath -Append

    $listItems = Get-PnPListItem -List $libraryName `
                                 -PageSize $pageSize `
                                 -Fields FileRef, FileLeafRef, FileSystemObjectType `
                                 -ErrorAction SilentlyContinue

    foreach ($item in $listItems) {
        if ($item.FileSystemObjectType -eq "File") {
            $fileUrl = $item["FileRef"]
            Write-Host "Processing file: $fileUrl"
            Write-Output "Processing file: $fileUrl" | Out-File -FilePath $logFilePath -Append

            $fileItem = Get-PnPFile -Url $fileUrl -AsListItem -ErrorAction SilentlyContinue

            if ($fileItem -ne $null) {
                $versions = Get-PnPProperty -ClientObject $fileItem -Property "Versions"
                if ($versions.Count -gt $maxVersionsToKeep) {
                    Write-Output "Trimming versions for file: $fileUrl. Total versions: $($versions.Count)" | Out-File -FilePath $logFilePath -Append
                    $versionsToDelete = $versions | Select-Object -Skip $maxVersionsToKeep
                    foreach ($version in $versionsToDelete) {
                        Write-Output "Deleting version: $($version.VersionLabel) (Size: $($version.Size))" | Out-File -FilePath $logFilePath -Append
                        $version.DeleteObject()
                    }
                    Invoke-PnPQuery
                    Write-Host "Version trimming completed for file: $fileUrl"
                    Write-Output "Version trimming completed for file: $fileUrl" | Out-File -FilePath $logFilePath -Append
                } else {
                    Write-Host "No trimming needed for file: $fileUrl"
                    Write-Output "No trimming needed for file: $fileUrl" | Out-File -FilePath $logFilePath -Append
                }
            } else {
                Write-Output "Could not retrieve file item for $fileUrl" | Out-File -FilePath $logFilePath -Append
            }
        }
    }
}

# Calculate total size before trimming
Write-Host "Calculating total library size before trimming..."
$totalSizeBefore = GetTotalLibrarySize -libraryName $libraryName
Write-Output "Total size before trimming: $totalSizeBefore bytes" | Out-File -FilePath $logFilePath -Append

# Start processing the entire library
TrimVersionsInLibrary -libraryName $libraryName

# Calculate total size after trimming
Write-Host "Calculating total library size after trimming..."
$totalSizeAfter = GetTotalLibrarySize -libraryName $libraryName
Write-Output "Total size after trimming: $totalSizeAfter bytes" | Out-File -FilePath $logFilePath -Append

# Log the size difference
$sizeDifference = $totalSizeBefore - $totalSizeAfter
Write-Output "Size reduced by: $sizeDifference bytes" | Out-File -FilePath $logFilePath -Append

# Disconnect from SharePoint Online
Disconnect-PnPOnline

Write-Host "Version trimming completed for the entire library."
Write-Output "Version trimming completed for the entire library." | Out-File -FilePath $logFilePath -Append

How It Works

  1. Authentication: Log into your SharePoint Online site.
  2. Calculate Size: Get the total size of file versions in the library before trimming.
  3. Version Trimming: Process all files, keeping only the last $maxVersionsToKeep versions.
  4. Logging: Create a detailed log of all actions.
  5. Compare Sizes: Calculate the storage saved and log the difference.

Why Microsoft Deserves a Side-Eye Here

  • Version Default Madness: Why, Microsoft, do we need 500 versions of a file by default? Who’s asking for this?!
  • No Easy Tools: The SharePoint UI doesn’t offer an out-of-the-box way to bulk trim versions. Cue PowerShell.
  • Performance Cost: If your libraries slow to a crawl, check the versioning mess. It’s probably the culprit.

End of Story - hurray!

With this script, you’ve now got the power to clean up SharePoint libraries, reduce storage costs, and finally see some order in the chaos. Plus, you can proudly show off the size reduction stats to management. Take that, SharePoint bloat!

This will probably avoid quoting and purchasing 2 additional TB of Sharepoint Storage for your tenant - and selling a kidney to pay for it.

Feel free to tweak the script, run it on test environments first, and share your thoughts in the comments. What’s your take on Microsoft’s versioning choices? Let the ranting begin! 🚀

Read more