Limits version history for PowerPoint documents across all SharePoint sites in your tenant. This script helps free up storage by trimming excessive version history while keeping a configurable number of recent versions.
- Reducing SharePoint storage costs by cleaning up old PowerPoint versions
- Enforcing version retention policies across the tenant
- One-time cleanup before implementing library-level version limits
- Auditing how much storage is consumed by PowerPoint version history
Install-Module -Name PnP.PowerShell -Force -Scope CurrentUser| Permission | Purpose |
|---|---|
| SharePoint Administrator | Required to enumerate all sites via Get-PnPTenantSite |
| Site Collection Administrator | Required for each site to access document libraries and delete versions |
For app-only authentication, register an Azure AD app with:
Sites.FullControl.Allapplication permission- Certificate-based authentication
- Connects to SharePoint tenant admin center
- Enumerates all SharePoint sites (excluding system sites, OneDrive, etc.)
- Filters sites based on include/exclude lists if specified
- For each site, finds all document libraries
- Uses server-side CAML queries to efficiently find PowerPoint files (.pptx, .ppt)
- Sorts versions by Created date (most reliable method)
- For files with more versions than the limit, deletes oldest versions
- By default, deleted versions go to the site recycle bin (recoverable)
Generate a CSV report without making any changes:
.\Limit-PowerPointVersions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -MajorVersionsToKeep 5 -ReportOnlyThis creates a CSV with:
- Every file that would be affected
- Number of versions to delete per file
- Estimated storage savings per file
- No changes are made
See what would happen in the console:
.\Limit-PowerPointVersions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -MajorVersionsToKeep 5 -WhatIfBefore running tenant-wide, test on one site:
.\Limit-PowerPointVersions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -MajorVersionsToKeep 5 -IncludeSites "https://contoso.sharepoint.com/sites/TestSite".\Limit-PowerPointVersions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -MajorVersionsToKeep 5You'll be prompted for confirmation due to ConfirmImpact = "High".
Exclude HR, Legal, and other sensitive sites:
.\Limit-PowerPointVersions.ps1 -TenantAdminUrl "https://contoso-admin.sharepoint.com" -MajorVersionsToKeep 5 -ExcludeSites "*HR*", "*Legal*", "*Compliance*", "*Executive*"| Parameter | Required | Default | Description |
|---|---|---|---|
-TenantAdminUrl |
Yes | - | SharePoint admin URL (e.g., https://contoso-admin.sharepoint.com) |
-MajorVersionsToKeep |
No | 5 | Number of versions to retain (1-500) |
-ClientId |
No | - | Azure AD app client ID for app-only auth |
-CertificatePath |
No | - | Path to .pfx certificate for app-only auth |
-CertificatePassword |
No | - | Password for the certificate (SecureString) |
-SkipRecycleBin |
No | $false | Permanently delete versions (bypasses recycle bin) |
-LogDirectory |
No | Script dir | Directory for log files |
-ThrottleDelayMs |
No | 500 | Delay between site connections to avoid throttling |
-IncludeSites |
No | - | Array of specific site URLs to process (only these) |
-ExcludeSites |
No | - | Array of URL patterns to exclude (wildcards supported) |
-ReportOnly |
No | $false | Generate report without making changes |
-WhatIf |
No | - | Preview mode - shows what would happen |
-Confirm |
No | $true | Prompts before making changes |
| Aspect | Impact |
|---|---|
| File Availability | Users can continue working while script runs. No file locks. |
| Modified Date | Deleting versions does NOT change "Modified Date" or "Modified By" of the live file. |
| Data Loss Risk | If a user needed a specific old version, it will be gone (or in recycle bin). |
| Recycle Bin Noise | Site recycle bins may be flooded with thousands of version entries, making it harder to find accidentally deleted files. |
| Aspect | Impact |
|---|---|
| Audit Logs | Generates massive "Delete" events in M365 Unified Audit Log. Plan for this. |
| Storage Quota | You won't see savings immediately. SharePoint metrics update every 24-48 hours. |
| Recycle Bin Storage | Items in recycle bin still count against site quota for up to 93 days. |
| Throttling | Microsoft may throttle if processing too fast. Use -ThrottleDelayMs to control. |
If you need immediate storage relief:
- Run the cleanup script
- Wait for it to complete
- Empty site collection recycle bins (this is a separate operation)
# Example: Empty recycle bin for a site
Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/MySite" -Interactive
Clear-PnPRecycleBinItem -All -ForceThe script uses CAML queries to filter PowerPoint files on the SharePoint server:
<View Scope='RecursiveAll'>
<Query>
<Where>
<Or>
<Eq><FieldRef Name='File_x0020_Type'/><Value Type='Text'>pptx</Value></Eq>
<Eq><FieldRef Name='File_x0020_Type'/><Value Type='Text'>ppt</Value></Eq>
</Or>
</Where>
</Query>
</View>This reduces network traffic by ~90% compared to downloading all items and filtering client-side.
Versions are sorted by Created date (descending), not by parsing version labels. This is more reliable because:
- Version labels can have unexpected formats (1.0, 1.1, 2.0, etc.)
- Draft versions may have minor numbers
- Created date is always accurate regardless of label format
- Uses
-PageSize 2000for pagination - Falls back to client-side filtering if CAML query fails (e.g., Large List Resource Quota issues)
- Each site connection includes configurable throttle delay
The script generates:
| File | Description |
|---|---|
PowerPoint-VersionCleanup-[timestamp].log |
Detailed execution log |
PowerPoint-VersionCleanup-[timestamp]-Report.csv |
CSV with all affected files (always generated) |
| Column | Description |
|---|---|
| SiteUrl | SharePoint site URL |
| Library | Document library name |
| FileName | PowerPoint file name |
| FileUrl | Full path to file |
| TotalVersions | Current number of versions |
| VersionsToDelete | How many would be/were deleted |
| VersionsToKeep | Configured retention count |
| EstimatedSizeBytes | Bytes that would be/were freed |
| EstimatedSize | Human-readable size |
For unattended/scheduled execution, use certificate-based authentication:
$cert = New-SelfSignedCertificate -Subject "CN=PowerPointVersionCleanup" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyExportPolicy Exportable `
-KeySpec Signature `
-KeyLength 2048 `
-KeyAlgorithm RSA `
-HashAlgorithm SHA256 `
-NotAfter (Get-Date).AddYears(2)
# Export PFX (for script)
$pwd = ConvertTo-SecureString -String "YourPassword" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath ".\PowerPointCleanup.pfx" -Password $pwd
# Export CER (for Azure AD)
Export-Certificate -Cert $cert -FilePath ".\PowerPointCleanup.cer"- Go to Azure Portal > Azure Active Directory > App registrations
- New registration: "PowerPoint Version Cleanup"
- API permissions > Add: SharePoint > Application >
Sites.FullControl.All - Grant admin consent
- Certificates & secrets > Upload the .cer file
$certPassword = Read-Host -AsSecureString "Enter certificate password"
.\Limit-PowerPointVersions.ps1 `
-TenantAdminUrl "https://contoso-admin.sharepoint.com" `
-MajorVersionsToKeep 5 `
-ClientId "your-app-client-id" `
-CertificatePath ".\PowerPointCleanup.pfx" `
-CertificatePassword $certPassword `
-Confirm:$false[2026-01-21 14:30:22] [INFO] === PowerPoint Version History Cleanup Script ===
[2026-01-21 14:30:22] [INFO] Configuration: Keep 5 versions per file
[2026-01-21 14:30:22] [INFO] Recycle Bin: Enabled - Versions sent to recycle bin
[2026-01-21 14:30:22] [INFO] Throttle Delay: 500 ms between sites
[2026-01-21 14:30:22] [INFO] Exclude Patterns: *HR*, *Legal*
[2026-01-21 14:30:25] [SUCCESS] Successfully connected to tenant admin
[2026-01-21 14:30:28] [INFO] Found 47 sites (before include/exclude filtering)
[2026-01-21 14:30:30] [INFO] === [1/47] Processing Site: https://contoso.sharepoint.com/sites/Marketing ===
[2026-01-21 14:30:32] [INFO] Site has 3 document libraries
[2026-01-21 14:30:33] [INFO] Library 'Documents': Found 12 PowerPoint files
[2026-01-21 14:30:35] [INFO] File 'Q4 Presentation.pptx': recycle 8 of 13 versions (kept newest 5) - 24.5 MB
...
========================================
EXECUTION SUMMARY
========================================
[2026-01-21 15:45:12] [SUMMARY] Sites Processed: 42
[2026-01-21 15:45:12] [SUMMARY] Sites Skipped (excluded): 5
[2026-01-21 15:45:12] [SUMMARY] Libraries Processed: 89
[2026-01-21 15:45:12] [SUMMARY] PowerPoint Files Scanned: 234
[2026-01-21 15:45:12] [SUMMARY] Files with Excess Versions: 67
[2026-01-21 15:45:12] [SUMMARY] Versions Deleted: 412
[2026-01-21 15:45:12] [SUMMARY] Storage Freed: 1.24 GB
[2026-01-21 15:45:12] [SUMMARY] Errors Encountered: 2
========================================
[2026-01-21 15:45:12] [INFO] Log file: .\PowerPoint-VersionCleanup-20260121-143022.log
[2026-01-21 15:45:12] [INFO] Report exported: .\PowerPoint-VersionCleanup-20260121-143022-Report.csv
========================================
[2026-01-21 15:45:12] [WARNING] IMPORTANT NOTES:
[2026-01-21 15:45:12] [WARNING] - Deleted versions are in site recycle bins (93 day retention)
[2026-01-21 15:45:12] [WARNING] - Recycle bin items still count against storage quota
[2026-01-21 15:45:12] [WARNING] - Consider emptying recycle bins to reclaim storage immediately
[2026-01-21 15:45:12] [WARNING] - Storage metrics update every 24-48 hours
[2026-01-21 15:45:12] [WARNING] - Check M365 Unified Audit Log for 'Delete' events
Your account needs at least Site Collection Administrator on each site. For app-only auth, ensure Sites.FullControl.All is granted.
Increase -ThrottleDelayMs to 2000 or higher, or run during off-peak hours.
If you see "CAML query failed, falling back to client-side filtering", this usually means:
- The library has more than 5,000 items (Large List Threshold)
- The File_x0020_Type column isn't indexed
The script will still work but will be slower for that library.
Large libraries with thousands of files take time to enumerate. The script uses pagination (2000 items/page) to handle this efficiently.
Ensure:
- Both
-ClientIdand-CertificatePathare provided - Certificate file exists and password is correct
- Certificate is uploaded to the Azure AD app registration
- Parallel Processing: PowerShell 7+
ForEach-Object -Parallelfor processing multiple sites simultaneously - Checkpoint/Resume: Save progress to resume after failures
- PnP Batch Operations: Use batching for version deletions
For large tenants, two helper scripts are provided:
Launch a simple Windows Forms GUI to select and process sites interactively:
.\Start-CleanupGUI.ps1Features:
- Connect to tenant and load all sites
- Checkbox selection for which sites to process
- Real-time progress and status per site
- "View Log" button to see results for completed sites
- Export site list to file
- Stop button to halt processing
Process sites from a file (useful for splitting work across machines/windows):
# Create a file with site URLs (one per line)
# sites.txt:
# https://contoso.sharepoint.com/sites/Marketing
# https://contoso.sharepoint.com/sites/Sales
# https://contoso.sharepoint.com/sites/Engineering
# Run batch processing
.\Invoke-BatchCleanup.ps1 -SiteListPath ".\sites.txt" -TenantAdminUrl "https://contoso-admin.sharepoint.com" -ReportOnlyFeatures:
- Reads sites from .txt (one URL per line) or .csv (with "Url" column)
- Processes each site individually
- Optional batch size with pause between batches
- Creates summary CSV with results
- Individual logs per site in timestamped folder
Parameters:
| Parameter | Description |
|---|---|
-SiteListPath |
Path to .txt or .csv with site URLs |
-TenantAdminUrl |
Tenant admin URL |
-BatchSize |
Sites per batch before pausing (0 = no batching) |
-ReportOnly |
Only generate reports |
-SkipRecycleBin |
Permanent deletion |
Example: Split work across 3 terminal windows
# Terminal 1: Sites 1-100
.\Invoke-BatchCleanup.ps1 -SiteListPath ".\sites-batch1.txt" -TenantAdminUrl "https://contoso-admin.sharepoint.com"
# Terminal 2: Sites 101-200
.\Invoke-BatchCleanup.ps1 -SiteListPath ".\sites-batch2.txt" -TenantAdminUrl "https://contoso-admin.sharepoint.com"
# Terminal 3: Sites 201-300
.\Invoke-BatchCleanup.ps1 -SiteListPath ".\sites-batch3.txt" -TenantAdminUrl "https://contoso-admin.sharepoint.com"To run on a schedule (e.g., monthly cleanup):
# Create scheduled task
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-ExecutionPolicy Bypass -File `"C:\Scripts\Limit-PowerPointVersions.ps1`" -TenantAdminUrl `"https://contoso-admin.sharepoint.com`" -MajorVersionsToKeep 5 -ClientId `"app-id`" -CertificatePath `"C:\Scripts\cert.pfx`" -ExcludeSites `"*HR*`",`"*Legal*`" -Confirm:`$false"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 2am
Register-ScheduledTask -TaskName "PowerPoint Version Cleanup" -Action $action -Trigger $triggerMIT License
