Versioning in SharePoint Online is a powerful feature that allows teams to maintain historical copies of documents. However, over time, these versions can accumulate and consume significant storage space—especially in document libraries with frequent updates.
This article provides a step-by-step PowerShell script using the SharePointPnPPowerShellOnline module to clean up old versions of files in a specific folder within a document library—retaining only the latest 5 versions of each file.
๐ง Why This Is Useful
-
Storage Optimization: SharePoint libraries with thousands of old file versions can significantly inflate site storage.
-
Performance: Reducing version history helps improve performance in large libraries.
-
Targeted Cleanup: Instead of affecting the entire document library, you can limit cleanup to a specific folder.
๐ ️ Prerequisites
Install-Module -Name SharePointPnPPowerShellOnline -Force
๐ Script Overview
This script:
-
Connects to the SharePoint Online site.
-
Targets a specific folder in a document library.
-
Retrieves all files in that folder (recursively).
-
Keeps only the latest 5 versions of each file and deletes the rest.
๐ PowerShell Script
# Install and Import the Module (if not already done)
Install-Module -Name SharePointPnPPowerShellOnline -Force
Import-Module SharePointPnPPowerShellOnline
# Variables
$SiteURL = "https://gks.sharepoint.com/sites/yoursite"
$ListName = "TestVersionsDocLib"
$FolderServerRelativeUrl = "/sites/yoursite/TestVersionsDocLib/TargetFolder" # Change as needed
# Connect to SharePoint
Connect-PnPOnline -Url $SiteURL -UseWebLogin # Use -Interactive if using modern auth
# Get PnP Context
$Ctx = Get-PnPContext
# Get all files in the specified folder recursively
$ListItems = Get-PnPListItem -List $ListName -PageSize 2000 -Query "<View Scope='RecursiveAll'><Query><Where><BeginsWith><FieldRef Name='FileRef'/><Value Type='Text'>$FolderServerRelativeUrl</Value></BeginsWith></Where></Query></View>" | Where { $_.FileSystemObjectType -eq "File" }
foreach ($Item in $ListItems) {
$File = $Item.File
$Versions = $File.Versions
$Ctx.Load($File)
$Ctx.Load($Versions)
$Ctx.ExecuteQuery()
Write-Host "Scanning File: $($File.Name) with $($Versions.Count) versions"
if ($Versions.Count -gt 5) {
# Keep latest 5, delete the rest
$VersionsToDelete = $Versions | Sort-Object -Property Created -Descending | Select-Object -Skip 5
foreach ($version in $VersionsToDelete) {
$version.DeleteObject()
}
$Ctx.ExecuteQuery()
Write-Host "Deleted $($VersionsToDelete.Count) older versions of the file: $($File.Name)"
}
}
๐ Example Folder Path
If your document library is called TestVersionsDocLib
and the target folder is Invoices/2025
, the relative URL should be:
/sites/yoursite/TestVersionsDocLib/Invoices/2025
✅ Output
The script will:
-
Display each file being scanned.
-
Show how many versions were found.
-
Confirm deletion of versions beyond the latest 5.
⚠️ Important Considerations
-
This script only affects a specific folder—not the whole document library.
-
Always test in a development or QA site before using in production.
-
Deleting versions is irreversible—ensure you retain what’s necessary.
$SiteURL = "https://tc.sharepoint.com/teams/GK/ms"
$FolderSiteRelativeUrl = "Shared Documents/TargetTest"
Connect-PnPOnline -Url $SiteURL -UseWebLogin
# Test folder access
$Folder = Get-PnPFolder -Url $FolderSiteRelativeUrl
Write-Host "Folder found: $($Folder.Name)"
# Get files
$Files = Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl -ItemType File -Recursive
Write-Host "Found $($Files.Count) files in the folder"
You can test if the folder exists using this:
Get-PnPFolder -FolderSiteRelativeUrl "Shared Documents"
Get-PnPFolder -FolderSiteRelativeUrl "Shared Documents/4. Projects - WIP"
Get-PnPFolder -FolderSiteRelativeUrl "Shared Documents/4. Projects - WIP/FY'24"
Get-PnPFolder -FolderSiteRelativeUrl "Shared%20Documents%2F04%2E%20Projects%20%2D%20WIP"
Get-PnPFolder -FolderSiteRelativeUrl "Shared%20Documents%2F04%2E%20Projects%20%2D%20WIP%2FFY%2724%2FFY%2724%20%2D%20Cancellation%20Reason%20%26%20Subreason"
This helps isolate where the path is breaking.
#PS_Script_for Recursive Folder and Subfolders Tested
$SiteURL = "https://tc.sharepoint.com/teams/gks/SE"
$FolderSiteRelativeUrl = "Shared Documents/04. Projects - WIP/FY'24"
# Connect to SharePoint site
Connect-PnPOnline -Url $SiteURL -UseWebLogin
# Verify folder exists
try {
$Folder = Get-PnPFolder -Url $FolderSiteRelativeUrl
Write-Host "Folder found: $($Folder.Name)"
} catch {
Write-Host "Error: Folder not found at '$FolderSiteRelativeUrl'"
exit
}
# Get all files recursively
$Files = Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl -ItemType File -Recursive
Write-Host "Found $($Files.Count) files in the folder and subfolders."
foreach ($File in $Files) {
try {
$FileRef = $File.ServerRelativeUrl
$FileItem = Get-PnPFile -Url $FileRef -AsListItem
$Ctx = Get-PnPContext
$SPFile = $FileItem.File
$Versions = $SPFile.Versions
# Load file and versions
$Ctx.Load($SPFile)
$Ctx.Load($Versions)
$Ctx.ExecuteQuery()
Write-Host "`nScanning File: $($SPFile.Name) - Total Versions: $($Versions.Count)"
if ($Versions.Count -gt 2) {
# Sort by created date (descending), skip latest 2
$VersionsToDelete = $Versions | Sort-Object -Property Created -Descending | Select-Object -Skip 2
foreach ($Version in $VersionsToDelete) {
Write-Host "Deleting Version: ID=$($Version.ID), Created=$($Version.Created)"
$Version.DeleteObject()
}
# Commit deletion
$Ctx.ExecuteQuery()
Write-Host "Deleted $($VersionsToDelete.Count) older versions of: $($SPFile.Name)"
} else {
Write-Host "Skipping: Less than or equal to 3 versions present."
}
} catch {
Write-Host "Error processing file '$($File.ServerRelativeUrl)': $_"
}
}
PS_Script_ to delete the file versions on Entire Doc Library Tested
$SiteURL = "https://tc.sharepoint.com/sites/CSG_GKS_SPOsite"
$ListName="TestVersionsDocLib"
Connect-PnPOnline -Url $SiteURL -UseWebLogin
#Get the Context
$Ctx= Get-PnPContext
#Get All Items from the List - Exclude 'Folder' List Items
$ListItems = Get-PnPListItem -List $ListName | Where {$_.FileSystemObjectType -eq "File"}
ForEach ($Item in $ListItems)
{
#Get File Versions
$File = $Item.File
$Versions= $File.Versions
$Ctx.Load($File)
$Ctx.Load($Versions)
$Ctx.ExecuteQuery()
Write-host "Scanning File:"$File.Name
If($Versions.Count -gt0)
{
#Delete all versions
$Versions.DeleteAll()
$Ctx.ExecuteQuery()
Write-Host "Deleted All Previous Versions of the File:"$File.Name
}
}
PS_Script_ to delete file versions on Specific Folder in doc library Tested
$SiteURL = "https://{tenant}.sharepoint.com/teams/gks/SE"
$FolderSiteRelativeUrl = "Shared Documents/04. Projects - WIP/FY'24/FY'23 - Competitor Information"
Connect-PnPOnline -Url $SiteURL -UseWebLogin
# Test folder access
$Folder = Get-PnPFolder -Url $FolderSiteRelativeUrl
Write-Host "Folder found: $($Folder.Name)"
# Get files
$Files = Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl -ItemType File -Recursive
Write-Host "Found $($Files.Count) files in the folder"
foreach ($File in $Files) {
$FileRef = $File.ServerRelativeUrl
$FileItem = Get-PnPFile -Url $FileRef -AsListItem
$Ctx = Get-PnPContext
$SPFile = $FileItem.File
$Versions = $SPFile.Versions
$Ctx.Load($SPFile)
$Ctx.Load($Versions)
$Ctx.ExecuteQuery()
Write-Host "Scanning File: $($SPFile.Name) with $($Versions.Count) versions"
if ($Versions.Count -gt 2) {
# Sort by Created date descending and skip the latest 3
$VersionsToDelete = $Versions | Sort-Object -Property Created -Descending | Select-Object -Skip 2
foreach ($Version in $VersionsToDelete) {
$Version.DeleteObject()
}
$Ctx.ExecuteQuery()
Write-Host "Deleted $($VersionsToDelete.Count) older versions of the file: $($SPFile.Name)"
}
}
๐ Final Thoughts
Keeping version history under control is a best practice for maintaining a clean and efficient SharePoint environment. Automating this process with PowerShell ensures consistency and saves valuable administrator time.
If you need to scale this to multiple folders or automate it on a schedule, consider integrating it into an Azure Automation Runbook or a task scheduler.