PowerShell Script to Generate SharePoint PowerPoint Version History with Microsoft Graph
This PowerShell script connects to Microsoft Graph, resolves a SharePoint PowerPoint file from a sharing URL, reads the file's version history, and repeatedly restores a previous version until the target version count is reached. It is useful for learning how SharePoint versioning works through Microsoft Graph and for building a controlled test document with many version-history entries.
What It Does
- Installs and imports the Microsoft Graph authentication module if needed.
- Connects to Microsoft Graph with file and site read/write scopes.
- Converts a SharePoint sharing URL into a Microsoft Graph share ID.
- Resolves the target drive item and reads its current version history.
- Restores a previous version repeatedly until the configured target version count is met.
- Retries common transient Microsoft Graph and SharePoint lock errors.
Before running the script, update <tenant>, <site-name>, <document-guid>, and <presentation-file-name> so they point to the PowerPoint file you want to use in your own Microsoft 365 tenant. The SharePoint host should look like https://<tenant>.sharepoint.com.
How to Run the Script
Open PowerShell, change to the folder where you saved the script, and run it with the script path:
.\script_location\scriptname.ps1
For this example, if the script is saved in C:\Scripts, run:
Set-Location C:\Scripts .\increaseVersions.ps1
If PowerShell blocks the script from running, read Make Sure PowerShell Scripts Can Run on Windows for the execution policy commands that allow local scripts to run.
PowerShell Script
Clear-Host
Push-Location $PSScriptRoot
#region Settings
Write-Host "Configuring settings..."
$SharingUrl = "https://<tenant>.sharepoint.com/:p:/r/sites/<site-name>/_layouts/15/Doc.aspx?sourcedoc=%7B<document-guid>%7D&file=<presentation-file-name>.pptx&action=edit&mobileredirect=true"
$TargetVersionCount = 500
$SleepSeconds = 2
$GraphScopes = @(
"Files.ReadWrite.All",
"Sites.ReadWrite.All"
)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Write-Host "Target version count: $TargetVersionCount"
Write-Host "Sleep seconds between restores: $SleepSeconds"
#endregion Settings
#region Functions
function Install-RequiredGraphModule {
param(
[Parameter(Mandatory = $true)]
[string]$ModuleName
)
Write-Host "Checking module: $ModuleName"
$ExistingModule = Get-Module -ListAvailable -Name $ModuleName |
Sort-Object Version -Descending |
Select-Object -First 1
if ($ExistingModule) {
Write-Host "Module already installed: $ModuleName"
Write-Host "Version: $($ExistingModule.Version)"
Write-Host "Path: $($ExistingModule.ModuleBase)"
return
}
Write-Host "Module not found. Installing: $ModuleName"
$NuGetProvider = Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue
if (-not $NuGetProvider) {
Write-Host "NuGet provider not found. Installing NuGet provider..."
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser
Write-Host "NuGet provider installed."
}
else {
Write-Host "NuGet provider already installed."
}
$PowerShellGetModule = Get-Module -ListAvailable -Name PowerShellGet |
Sort-Object Version -Descending |
Select-Object -First 1
if (-not $PowerShellGetModule) {
throw "PowerShellGet is required but was not found."
}
Write-Host "PowerShellGet version: $($PowerShellGetModule.Version)"
$Gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
if (-not $Gallery) {
throw "PSGallery repository was not found."
}
if ($Gallery.InstallationPolicy -ne "Trusted") {
Write-Host "Setting PSGallery as trusted..."
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Write-Host "PSGallery is now trusted."
}
else {
Write-Host "PSGallery is already trusted."
}
Install-Module -Name $ModuleName -Scope CurrentUser -Force -AllowClobber
$InstalledModule = Get-Module -ListAvailable -Name $ModuleName |
Sort-Object Version -Descending |
Select-Object -First 1
if (-not $InstalledModule) {
throw "Failed to install module: $ModuleName"
}
Write-Host "Module installed successfully: $ModuleName"
Write-Host "Version: $($InstalledModule.Version)"
Write-Host "Path: $($InstalledModule.ModuleBase)"
}
function ConvertTo-GraphShareId {
param(
[Parameter(Mandatory = $true)]
[string]$Url
)
Write-Host "Encoding SharePoint URL for Microsoft Graph shares endpoint..."
$Bytes = [System.Text.Encoding]::UTF8.GetBytes($Url)
$Base64 = [System.Convert]::ToBase64String($Bytes)
$Encoded = "u!" + $Base64.TrimEnd("=").Replace("/", "_").Replace("+", "-")
if (-not $Encoded.StartsWith("u!")) {
throw "Failed to encode SharePoint URL."
}
Write-Host "SharePoint URL encoded successfully."
return $Encoded
}
function Invoke-GraphRequestWithRetry {
param(
[Parameter(Mandatory = $true)]
[string]$Method,
[Parameter(Mandatory = $true)]
[string]$Uri,
[Parameter(Mandatory = $false)]
[object]$Body = $null,
[Parameter(Mandatory = $false)]
[int]$MaxRetries = 12
)
$Attempt = 0
while ($Attempt -lt $MaxRetries) {
$Attempt++
try {
if ($null -ne $Body) {
return Invoke-MgGraphRequest -Method $Method -Uri $Uri -Body $Body
}
else {
return Invoke-MgGraphRequest -Method $Method -Uri $Uri
}
}
catch {
$StatusCode = $null
$ErrorText = $_.Exception.Message
if ($_.Exception.Response -and $_.Exception.Response.StatusCode) {
$StatusCode = [int]$_.Exception.Response.StatusCode
}
if ($StatusCode -in @(423, 429, 500, 502, 503, 504) -and $Attempt -lt $MaxRetries) {
if ($StatusCode -eq 423) {
$DelaySeconds = 30
Write-Host "File is locked by SharePoint or an Office editing session. Retry $Attempt of $MaxRetries in $DelaySeconds seconds..."
Write-Host "Close the file in PowerPoint Online, PowerPoint desktop, Teams, OneDrive sync, and any browser tabs."
}
else {
$DelaySeconds = [Math]::Min(60, [Math]::Pow(2, $Attempt))
Write-Host "Graph request failed with status $StatusCode. Retry $Attempt of $MaxRetries in $DelaySeconds seconds..."
}
Start-Sleep -Seconds $DelaySeconds
}
else {
Write-Host "Graph request failed."
Write-Host "Status code: $StatusCode"
Write-Host "Method: $Method"
Write-Host "Uri: $Uri"
Write-Host "Error: $ErrorText"
throw
}
}
}
throw "Graph request failed after $MaxRetries attempts."
}
function Get-GraphCollection {
param(
[Parameter(Mandatory = $true)]
[string]$Uri
)
$AllItems = New-Object System.Collections.Generic.List[object]
$NextUri = $Uri
while ($NextUri) {
$Response = Invoke-GraphRequestWithRetry -Method "GET" -Uri $NextUri
if ($Response.value) {
foreach ($Item in $Response.value) {
$AllItems.Add($Item)
}
}
if ($Response.'@odata.nextLink') {
$NextUri = $Response.'@odata.nextLink'
}
else {
$NextUri = $null
}
}
return $AllItems
}
function Get-CurrentVersionCount {
param(
[Parameter(Mandatory = $true)]
[string]$VersionsUri
)
Write-Host "Reading current version history..."
$Versions = Get-GraphCollection -Uri $VersionsUri
$VersionCount = $Versions.Count
Write-Host "Current version count: $VersionCount"
return @{
Count = $VersionCount
Versions = $Versions
}
}
function Get-RestoreVersion {
param(
[Parameter(Mandatory = $true)]
[object[]]$Versions
)
Write-Host "Selecting previous version to restore..."
$SortedVersions = $Versions |
Where-Object { $_.id -match '^\d+(\.\d+)?$' } |
Sort-Object { [version]$_.id } -Descending
if (-not $SortedVersions -or $SortedVersions.Count -lt 2) {
throw "At least two versions are required. Cannot restore the current version."
}
$RestoreVersion = $SortedVersions |
Select-Object -Skip 1 -First 1
if (-not $RestoreVersion -or -not $RestoreVersion.id) {
throw "Could not select a previous version to restore."
}
Write-Host "Selected previous version: $($RestoreVersion.id)"
return $RestoreVersion
}
#endregion Functions
#region Module Install And Import
Write-Host "Preparing Microsoft Graph module..."
Install-RequiredGraphModule -ModuleName "Microsoft.Graph.Authentication"
Write-Host "Importing Microsoft.Graph.Authentication..."
Import-Module Microsoft.Graph.Authentication -Force
$GraphModule = Get-Module -ListAvailable -Name Microsoft.Graph.Authentication |
Sort-Object Version -Descending |
Select-Object -First 1
if (-not $GraphModule) {
throw "Microsoft.Graph.Authentication was not found after install."
}
Write-Host "Microsoft.Graph.Authentication is ready."
Write-Host "Version: $($GraphModule.Version)"
#endregion Module Install And Import
#region Authentication
Write-Host "Connecting to Microsoft Graph..."
Connect-MgGraph -Scopes $GraphScopes
$Context = Get-MgContext
if (-not $Context) {
throw "Microsoft Graph connection failed."
}
Write-Host "Connected to Microsoft Graph."
Write-Host "Tenant/account details omitted from console output."
#endregion Authentication
#region Resolve SharePoint File
Write-Host "Resolving SharePoint file from URL..."
$ShareId = ConvertTo-GraphShareId -Url $SharingUrl
$DriveItemUri = "https://graph.microsoft.com/v1.0/shares/$ShareId/driveItem"
$DriveItem = Invoke-GraphRequestWithRetry -Method "GET" -Uri $DriveItemUri
if (-not $DriveItem.id) {
throw "Failed to resolve drive item ID."
}
if (-not $DriveItem.parentReference.driveId) {
throw "Failed to resolve drive ID."
}
$DriveId = $DriveItem.parentReference.driveId
$ItemId = $DriveItem.id
Write-Host "Resolved file: $($DriveItem.name)"
Write-Host "Drive ID: $DriveId"
Write-Host "Item ID: $ItemId"
#endregion Resolve SharePoint File
#region Get Version Information
$VersionsUri = "https://graph.microsoft.com/v1.0/drives/$DriveId/items/$ItemId/versions"
$VersionInfo = Get-CurrentVersionCount -VersionsUri $VersionsUri
$CurrentVersionCount = $VersionInfo.Count
$Versions = $VersionInfo.Versions
if ($CurrentVersionCount -ge $TargetVersionCount) {
Write-Host "Target already reached. Current version count: $CurrentVersionCount"
exit
}
if (-not $Versions -or $Versions.Count -eq 0) {
throw "No versions were returned for this file."
}
$RestoreVersion = Get-RestoreVersion -Versions $Versions
Write-Host "Version selected for repeated restore: $($RestoreVersion.id)"
#endregion Get Version Information
#region Increase Versions
Write-Host "Starting version increase process..."
while ($CurrentVersionCount -lt $TargetVersionCount) {
$RestoreUri = "https://graph.microsoft.com/v1.0/drives/$DriveId/items/$ItemId/versions/$($RestoreVersion.id)/restoreVersion"
Write-Host "Restoring version $($RestoreVersion.id). Current count: $CurrentVersionCount / $TargetVersionCount"
Invoke-GraphRequestWithRetry -Method "POST" -Uri $RestoreUri -Body "{}" | Out-Null
Write-Host "Restore request completed."
Start-Sleep -Seconds $SleepSeconds
$VersionInfo = Get-CurrentVersionCount -VersionsUri $VersionsUri
$NewVersionCount = $VersionInfo.Count
$Versions = $VersionInfo.Versions
if ($NewVersionCount -le $CurrentVersionCount) {
Write-Host "Version count did not increase. Previous count: $CurrentVersionCount. New count: $NewVersionCount."
Write-Host "Stopping to avoid endless loop."
break
}
$CurrentVersionCount = $NewVersionCount
$RestoreVersion = Get-RestoreVersion -Versions $Versions
Write-Host "Next restore version selected: $($RestoreVersion.id)"
if ($CurrentVersionCount -ge $TargetVersionCount) {
Write-Host "Target reached. Current version count: $CurrentVersionCount"
break
}
}
Write-Host "Version increase process completed."
Write-Host "Final version count: $CurrentVersionCount"
#endregion Increase Versions
.posts/2026.06.23/increaseVersions.ps1