Blog about anything related to my learnings
  • About
  • posts
bluesky
Blog about anything related to my learnings
POSTS

I have been doing some testing: major versions are trimmed if exceeding the 500 versions, minor versions gets incremented up to 511 and beyond end user needs to publish as major. Minor version exceeded Auto trimming works with files without retention labels trimming the minor/major version bypassing the recycle bin.

Versions trimmed However with files with retention labels are behaving similarly to retention policy and none of the major or minor versions get deleted, Versions not trimmed version are expired More versions marked as expired

only to be extended further for more days after 1-2 days instead being deleted.

Expired Date extended

The call with Ms is go through the behaviour as it’s very different from the documentation they have.

It seems SharePoint Intelligent Versioning and the 500 Version Limit we may end up with major version of more than 500 as well according to this blog post. Minor versions have been enabled on all SMP libraries and hence minor versions will creep quicker unless users intentionally update to major which does not happen regularly. At the point of initial migration, the retention requirement for the file does not get kicked in probably explaining loss of migrated version if version count is more than 500. https://office365itpros.com/2025/01/06/intelligent-versioning-500/

https://office365itpros.com/2024/11/13/intelligent-versioning-spo/ - Another one Intelligent Versioning & Retention: Where do Versions Go? explaining the expiry date getting adjusted to a later date for sites with retention policies. Retention labels are behaving similar to retention policies in BoE environment in respect to keeping all versions and why?

minor versions: minor versions have a different limit with a maximum threshold of 511

# ===== Settings =====
$clientId  = "xxxxxx-xxxxx"
$dateTime  = Get-Date -Format "yyyy-MM-dd-HH-mm-ss"
$CertPath  = Read-Host "Please enter Certificate (.PFX) Path"
$CertPassword = Read-Host "Please enter Certificate Password" -AsSecureString
$Org = "contoso.onmicrosoft.com"

$fileUrl = "https://contoso.sharepoint.com/sites/testsite/testlibrary/test_resh_retentionlabel.docx"

$invocation    = Get-Variable -Name MyInvocation -ValueOnly
$directoryPath = Split-Path $invocation.MyCommand.Path

# Create log header
"Timestamp,Message,Status" | Out-File -FilePath $logFile -Encoding utf8

function Get-LibraryNameFromFileUrl {
    param([string]$fileUrl, [string]$siteUrl)

    # Remove base site URL
    $relative = $fileUrl.Replace($siteUrl, "")

    # Trim leading "/"
    if ($relative.StartsWith("/")) {
        $relative = $relative.Substring(1)
    }

    # First path segment = library name
    $libraryName = $relative.Split("/")[0]

    return $libraryName
}


# ===== Helper: Extract the Web (site) URL from a full file URL =====
function Get-WebUrlFromFileUrl {
    param([string]$fileUrl)

    $u = [System.Uri]$fileUrl
    $segments = $u.AbsolutePath.Trim('/').Split('/')

    if ($segments.Length -ge 2 -and ($segments[0] -ieq 'sites' -or $segments[0] -ieq 'teams')) {
        return "$($u.Scheme)://$($u.Host)/$($segments[0])/$($segments[1])"
    }
    else {
        return "$($u.Scheme)://$($u.Host)"
    }
}

# ===== Helper: Run an operation with exponential backoff retries =====
function Invoke-WithRetry {
    param(
        [Parameter(Mandatory=$true)][ScriptBlock]$Operation,
        [int]$MaxRetries = 8,
        [int]$BaseDelaySeconds = 2,
        [string]$Context = ""
    )

    for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) {
        try {
            return & $Operation
        }
        catch {
            $msg = $_.Exception.Message
            $transient = ($msg -match '429' -or $msg -match 'throttl' -or $msg -match '5\d\d' -or $msg -match 'temporar' -or $msg -match 'timeout')

            if ($attempt -lt $MaxRetries) {
                $delay = [Math]::Pow(2, $attempt - 1) * ($transient ? $BaseDelaySeconds : 1)
                Write-Host "Attempt $attempt failed ($Context): $msg. Retrying in $delay sec..." "Info"
                Start-Sleep -Seconds $delay
            }
            else {
                Write-Host "Operation failed after $MaxRetries attempts ($Context): $msg" "Error"
                throw
            }
        }
    }
}

# ===== Connect to SharePoint using PnP (App-only with certificate) =====
try {
    $webUrl = Get-WebUrlFromFileUrl -fileUrl $fileUrl
    Write-Host "Connecting to site: $webUrl" "Info"

    Connect-PnPOnline -Url $webUrl `
                      -ClientId $clientId `
                      -Tenant $Org `
                      -CertificatePath $CertPath `
                      -CertificatePassword $CertPassword
    # Cmdlet syntax reference: Connect-PnPOnline (App-only with certificate)  [Docs]  [1](https://github.com/PowerShell/PowerShell/issues/24892)

    $web = Invoke-WithRetry -Context "Get-PnPWeb" -Operation { Get-PnPWeb }
    Write-host "Connected to web: $($web.Title)" -ForegroundColor Green
}
catch {
    Write-Host "PnP connection failed: $_" -ForegroundColor Red
    exit 1
}

# ===== Resolve server-relative path and parent folder =====
$serverRelativeUrl = ([System.Uri]$fileUrl).AbsolutePath
if (-not $serverRelativeUrl) {
    Write-Host "Could not resolve server-relative URL from fileUrl: $fileUrl" -ForegroundColor Red
    exit 1
}

# ===== Pre-flight: Get the list item backing the file =====
try {
    $listItem = Invoke-WithRetry -Context "Get-PnPFile -AsListItem" -Operation {
        Get-PnPFile -Url $serverRelativeUrl -AsListItem -ErrorAction Stop
    }
    $file = Invoke-WithRetry -Context "Get-PnPFile -As file" -Operation {
        Get-PnPFile -Url $serverRelativeUrl -ErrorAction Stop
    }
    if (-not $listItem -or -not $listItem.Id) {
        throw "List item not found for $serverRelativeUrl"
    }
    Write-Host "Located file item Id=$($listItem.Id)" -ForegroundColor Green
}
catch {
    Write-Host "The file/list item was not found: $serverRelativeUrl. $_" -ForegroundColor Red
    exit 1
}

$list = Get-LibraryNameFromFileUrl -fileUrl $fileUrl -siteUrl $webUrl
# ===== Loop: update Title and publish (major) each time =====
for ($j = 1; $j -le 3; $j++) {
try{
for ($i = 1; $i -le 511; $i++) {

    $newTitle = "test $i"

        Write-Host "[$i/550] Setting Title='$newTitle'" -ForegroundColor Green

        # Update Title on the list item
        Invoke-WithRetry -Context "Set-PnPListItem (Title=$newTitle)" -Operation {
            Set-PnPListItem -List $list `
                            -Identity $listItem.Id `
                            -Values @{ "Title" = $newTitle } `
                            -ErrorAction Stop
        }

    }

    Invoke-WithRetry -Context "Set-PnPFileCheckedIn (Major)" -Operation {
        $file.Publish("Major version published by script")
        $file.Update()
        Invoke-PnPQuery
    }
    # Major check-in publishes a major version  [Docs]  [4](https://pnp.github.io/powershell/cmdlets/Set-PnPFileCheckedIn.html)

        Write-Host "Updated Title and published major version (Title='$newTitle')" -ForegroundColor Green
    }
    catch {
        Write-Host "Error at iteration $i (Title='$newTitle'): $_" -ForegroundColor Red
        continue
    }
}

Write-Host "Completed Title updates from 'test 1' to 'test 550' with major versions." -ForegroundColor Green

Outcome of script

Minor versions up to 511 Minor Versions

Major versions up to 500 Major Versions

Error creating more than 511 error creating more than 511

Version history error thrown error deleting history

we’re not able to control that number - The ‘Version Count limit’ allows for which major version you store minor versions for, but not the number of minor versions.

So if you choose “3” for ‘keep drafts for…’ then you’re potentially keeping 1533 minor versions on top of the major version count.

would it not be ’every file that has a label’ ? we might be able to script getting some numbers on that?

it was a little tricky to find, because you have to locate a file that has a label has multiple versions which are older than 6 months

https://support.microsoft.com/en-us/office/how-versioning-works-in-lists-and-libraries-0f6cd105-974f-44a4-aadb-43ac5bdfd247#:~:text=The%20maximum%20number%20of%20minor,a%20version%20of%20a%20file.

References

https://support.microsoft.com/en-us/office/how-versioning-works-in-lists-and-libraries-0f6cd105-974f-44a4-aadb-43ac5bdfd247#:~:text=The%20maximum%20number%20of%20minor,a%20version%20of%20a%20file.

https://office365itpros.com/2025/01/06/intelligent-versioning-500/ https://office365itpros.com/2024/11/13/intelligent-versioning-spo/

    © Blog about anything related to my learnings 2026
    bluesky