Search code examples
powershellsharepointversion-controlsharepoint-online

SharePoint | Delete Version History using PnP PowerShell


I'm trying to execute the script below from SharePoint Diary.

And it works for me... partially. I do have version deletions, but as soon as the script scans a large file, I get errors in the terminal. The most common being :

        Scanning File: file.psd
Get-PnPProperty: C:\Users\Thibault\SharePoint_ClearLibraryVersionHistory.ps1:19
Line |
  19 |  …    $Versions = Get-PnPProperty -ClientObject $File -Property Versions
     |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Operation is not valid due to the current state of the object.

Indeed, it has many psd and psb files in the document library, they are heavy (several GB) and these are precisely the ones we want to delete and that the script ignores.

I spend day and night reading all possible documentation but without success. I'm a bit desperate to say the least^^

Thank you very much in advance for your help ;)

#Parameters
$SiteURL = "https://crescent.sharepoint.com/sites/IC"
$LibraryName = "Energy"
  
Try {
    #Connect to PnP Online
    Connect-PnPOnline -Url $SiteURL -Interactive
 
    #Get the Library
    $Library = Get-PnPList -Identity $LibraryName
    $global:counter=0
 
    #Get All Items from the List - Get 'Files
    $ListItems = Get-PnPListItem -List $Library -Fields FileLeafRef,FileRef -PageSize 2000 -ScriptBlock { Param($items) $global:counter += $items.Count; Write-Progress `
                    -PercentComplete ($global:Counter / ($Library.ItemCount) * 100) -Activity "Getting Files of '$($Library.Title)'" `
                       -Status "Processing Files $global:Counter of $($Library.ItemCount)";} | Where {($_.FileSystemObjectType -eq "File")} 
    Write-Progress -Activity "Completed Retrieving Files!" -Completed
 
    #Loop through each file
    $global:counter=1
    ForEach ($Item in $ListItems)
    {
        #Get File and File Versions
        $Versions = Get-PnPFileVersion -Url $Item.FieldValues.FileRef
        Write-host -f Yellow "Scanning File ($global:counter of $($Library.ItemCount)) : $($Item.FieldValues.FileRef)"
       
        If($Versions.Count -gt 0)
        {
            #Delete all versions
            $Versions.DeleteAll()
            Invoke-PnPQuery
            Write-Host -f Green "`tDeleted All Previous Versions of the File!" #"$Item.FieldValues.FileRef
        }
        $global:counter++
    }
}
Catch {
    write-host -f Red "Error Cleaning up Version History!" $_.Exception.Message
}

I've tried many scripts found on the web and with ChatGPT but the error returned is always the same.

I tried the trial version of the DMS Shuttle tool and it worked like a charm. But it's not free and you have to pay to clean up TB of data^^.

UPDATE

My research (thanks @mclayton) suggests that files larger than 2 GB are ignored.

So I tried this approach for a specific file:

# Define Parameters
$site1 = "https://contoso.sharepoint.com/sites/MySite"
$relativePath = "/sites/MySite/Documents partages/Livraisons/subfolder/FRONT/_subfolder 2023/img.psb"

# Connect to SharePoint Online site
Connect-PnPOnline -Url $site1 -Interactive

$context = Get-PnPContext
$file = $context.Web.GetFileByServerRelativeUrl($relativePath)

# load file object without versions property
$context.Load($file)
$context.ExecuteQuery()

#delete all versions
$file.Versions.DeleteAll()
$context.ExecuteQuery()

And it worked instantly! No more :

Operation is not valid due to the current state of the object.

But how can we now reproduce this action on all the files in a site's library?


Solution

  • I'm answering my own question because I've finally found the solution!

    The problem was thornier than expected because :

    1. Get-PnPProperty doesn't handle large files (probably over 2GB, but perhaps even as little as 250MB). But this also applies to other commands such as Get-PnPFileVersion.
    2. The Get-PnPContext approach is the correct one, but it had to be applied to a whole library of documents, and commands such as Get-PnPList or Get-PnPListItem didn't retrieve all the files (again, probably because they were too heavy or there were too many versions of a given file).

    So, the trick was to use Get-PnPFileInFolder recursively and, most importantly, not to load version properties (or any other properties) until the file had been processed.

    In this way, we get what we want: all versions of all files in a SharePoint library are deleted, even for large files or files with many versions. The script does take a little time to run, as all files will be scanned (except those in system folders), but it works like a charm (tested on several libraries).

    A little bonus: at the end of execution, the script generates a csv file summarizing all operations performed.

    Modify the parameters $siteUrl, $libraryName, $csvFilePath according to your environment et voilà!

    For my part, I was able to recover over 550GB of storage space available in SharePoint without spending any money on a software solution ;)

    Oh, and one last point: if you don't want to do this again, remember to disable version control (if you're not using it, of course). The SharePoint Diary scripts, especially the one called PnP PowerShell to Disable Versioning on a List, work perfectly.

    The script (hope it helps):

    # Define Parameters
    $siteUrl = "https://mycompagny.sharepoint.com/sites/Marketing"
    $libraryName = "Documents"
    $csvFilePath = "C:\Users\USERNAME\VersionCleanupReport.csv"
    
    # Initialize array to store operation details
    $operationDetails = @()
    
    # Connect to SharePoint Online site
    Connect-PnPOnline -Url $siteUrl -Interactive
    
    # Get all files in the document library
    $files = Get-PnPFolder -ListRootFolder $libraryName | Get-PnPFileInFolder -Recurse -ExcludeSystemFolders
    
    # Iterate through each file
    foreach ($file in $files) {
        # Attempt to delete all versions of the file
        try {
            Write-Host -ForegroundColor Yellow "Scanning file: $($file.ServerRelativeUrl)"
    
            # Get the file object without loading version properties
            $context = Get-PnPContext
            $fileObject = $context.Web.GetFileByServerRelativeUrl($file.ServerRelativeUrl)
            $context.Load($fileObject)
            $context.ExecuteQuery()
    
            # Check if the file has versions
            $versions = $fileObject.Versions
            $context.Load($versions)
            $context.ExecuteQuery()
    
            $versionsCount = $versions.Count
    
            if ($versionsCount -gt 0) {
                # Delete all versions of the file
                $fileObject.Versions.DeleteAll()
                $context.ExecuteQuery()
    
                Write-Host -ForegroundColor Green "Deleted $versionsCount versions of file: $($file.ServerRelativeUrl)"
    
                # Add operation details to the array
                $operationDetails += [PSCustomObject]@{
                    "File" = $file.ServerRelativeUrl
                    "Operation" = "Deleted all versions"
                    "Timestamp" = Get-Date
                }
            } else {
                Write-Host -ForegroundColor DarkGray "No versions to delete for file: $($file.ServerRelativeUrl)"
            }
        } catch {
            Write-Host -ForegroundColor Red "Error processing file: $($file.ServerRelativeUrl) - $_"
    
            # Add error details to the array
            $operationDetails += [PSCustomObject]@{
                "File" = $file.ServerRelativeUrl
                "Operation" = "Error"
                "ErrorMessage" = $_.Exception.Message
                "Timestamp" = Get-Date
            }
        }
    }
    
    # Export operation details to CSV file
    $operationDetails | Export-Csv -Path $csvFilePath -NoTypeInformation -Encoding UTF8
    
    # Disconnect from SharePoint Online site
    Disconnect-PnPOnline