0

We are trying restore a lot of items from a SharePoint Online recycle bin (100K - 1M ). Is there a PowerShell script that can restore files from a recycle bin in bulk? Ideally, using SharePoint PnP PowerShell.

We are trying to use the Invoke-PnPSPRestMethod -Method Post -Url $apiCall -Content $BodyAsJson approach, but it throws an error:

Invoke-PnPSPRestMethod : {"odata.error":{"code":"-1, Microsoft.SharePoint.Client.InvalidClientQueryException", "message":{"lang":"en-us","value":"The parameter CliXml does not exist in method RestoreByIds."}}} at line: 1 char: 1

Current restore script that does not work due to the "CliXml does not exist" error

$SiteURL = "https://contoso.sharepoint.com/sites/Test-LargeRecycleBin"
Connect-PnPOnline -Url $SiteURL -UseWebLogin

# =================================================================== #
#   RESTORE RECYCE BIN IN BATCHES USING REST API (Does not work)
# =================================================================== #

$firstLevelItems = @(Get-PnPRecycleBinItem  -FirstStage -RowLimit 99999999 | ? DeletedByName -eq 'System Account')
$secondLevelItems = @(Get-PnPRecycleBinItem  -SecondStage -RowLimit 99999999 | ? DeletedByName -eq 'System Account')

Write-Host First Stage Recycle Bin Items: $firstLevelItems.Count -ForegroundColor Cyan
Write-Host Second Stage Recycle Bin Items: $secondLevelItems.Count -ForegroundColor Yellow

$restoreSet = $firstLevelItems + $secondLevelItems
Write-Host Both Stages Recycle Bin Items: $restoreSet.Count -ForegroundColor Cyan

$restoreFileSorted = $restoreSet | ?{$_.ItemType -eq "File"} | sort DirName, LeafName
Write-Host Both Stages Recycle Bin, Documents count: $restoreSet.Count -ForegroundColor Yellow
$ErrorActionPreference = "Stop"
$stopWatch = [system.diagnostics.stopwatch]::StartNew()

# TODO: Sample implementation using Azure App registration and OneDrive for Business https://gist.github.com/Smalls1652/048173273fde5d8f87b37700c987915c
# Batch restore up to 200 at a time
$restoreList = $restoreFileSorted | select Id, ItemType, LeafName, DirName
$apiCall = $siteUrl + "/_api/site/RecycleBin/RestoreByIds"
$restoreListCount = $restoreList.count
$start = 0
$leftToProcess = $restoreListCount - $start

while($leftToProcess -gt 0){
    If($leftToProcess -lt 200){$numToProcess = $leftToProcess} Else {$numToProcess = 200}
    Write-Host -ForegroundColor Yellow "Building statement to restore the following $numToProcess files"
    $Ids = @()
    for($i=0; $i -lt $numToProcess; $i++){
        $cur = $start + $i
        $curItem = $restoreList[$cur]
        Write-Host -ForegroundColor Green "Adding ", $curItem.ItemType, ": ", $curItem.DirName, "//", $curItem.LeafName
        $Ids+=$curItem.Id
    }
   
    $Body = @{
        "ids" = @($Ids)
    } 
    $BodyAsJson = $Body | ConvertTo-Json

    Write-Host -ForegroundColor Yellow $BodyAsJson
    Write-Host -ForegroundColor Yellow "Performing API Call to Restore items from RecycleBin..."
    try {
        Invoke-PnPSPRestMethod -Method Post -Url $apiCall -Content $BodyAsJson ContentType "application/json"  | Out-Null
    }
    catch {
        Write-Error "Unable to Restore"     
    }
    $start += 200
    $leftToProcess = $restoreListCount - $start
}


$stopWatch.Stop()
Write-Host Time it took to restore $restoreListCount documents from the $($SiteURL+$DestinationFolderUrl)  -ForegroundColor Cyan
$stopWatch

I would appreciate any help or pointers!

2
  • Denis - JamfoFL had the same problem as me. The code bombs out if some files have already been restored. Can you post an update that checks for the file being present int he original location before attempting to restore the file? Commented Dec 10, 2021 at 1:25
  • hi Donald, I've updated the code to include $ErrorActionPreference = 'SilentlyContinue' Commented Dec 10, 2021 at 22:28

1 Answer 1

1

OK, this method seems to be working. I hope it's useful for someone else as well.

# =================================================================== #
#         Load SharePoint PnP PowerShell Module
# =================================================================== #
Save-module  SharePointPnPPowerShellOnline -literalPath .
Import-Module (Get-ChildItem -Recurse -Filter "*.psd1").FullName

$SiteURL = "https://contoso.sharepoint.com/sites/Restore-Test"
Connect-PnPOnline -Url $SiteURL -UseWebLogin

$ErrorActionPreference = 'SilentlyContinue'

# =================================================================== #
#            RESTORE RECYCE BIN IN BATCHES USING REST API
# =================================================================== #
$firstLevelItems = @(Get-PnPRecycleBinItem  -FirstStage -RowLimit 99999999 )
$secondLevelItems = @(Get-PnPRecycleBinItem  -SecondStage -RowLimit 99999999 )

Write-Host First Stage Recycle Bin Items: $firstLevelItems.Count -ForegroundColor Cyan
Write-Host Second Stage Recycle Bin Items: $secondLevelItems.Count -ForegroundColor Yellow

$restoreSet = $firstLevelItems + $secondLevelItems
Write-Host Both Stages Recycle Bin Items: $restoreSet.Count -ForegroundColor Cyan

$restoreFileSorted = $restoreSet | ?{$_.ItemType -eq "File"} | sort DirName, LeafName
Write-Host Both Stages Recycle Bin, Documents count: $restoreSet.Count -ForegroundColor Yellow
$ErrorActionPreference = "Stop"

function Restore-RecycleBinItem {
    param(
        [Parameter(Mandatory)]
        [String]
        $Ids
    )
    
    $siteUrl = (Get-PnPSite).Url
    $apiCall = $siteUrl + "/_api/site/RecycleBin/RestoreByIds"
    $body = "{""ids"":[$($Ids)]}"   
    Invoke-PnPSPRestMethod -Method Post -Url $apiCall -Content $body 

}

$stopWatch = [system.diagnostics.stopwatch]::StartNew()

# Batch restore up to 200 at a time
$restoreList = $restoreFileSorted | select Id, ItemType, LeafName, DirName

$restoreListCount = $restoreList.count
$start = 0
$leftToProcess = $restoreListCount - $start


$stopWatch = [system.diagnostics.stopwatch]::StartNew()
while($leftToProcess -gt 0){
    If($leftToProcess -lt 200){$numToProcess = $leftToProcess} Else {$numToProcess = 200}
    Write-Host -ForegroundColor Yellow "Building statement to restore the following $numToProcess files"
    $Ids = @()
    for($i=0; $i -lt $numToProcess; $i++){
        $cur = $start + $i
        $curItem = $restoreList[$cur]
        
        $Ids+=$curItem.Id
    }
   
    Write-Host -ForegroundColor Yellow "Performing API Call to Restore items from RecycleBin..."
    $Ids_As_string = [System.String]::Join(",", $($Ids | % {'"'+ $_.tostring() + '"'}))
    Restore-RecycleBinItem -Ids $Ids_As_string
    
    $start += 200
    $leftToProcess = $restoreListCount - $start
}

$stopWatch.Stop()
Write-Host Time it took to restore $restoreListCount documents from the $($SiteURL+$DestinationFolderUrl)  -ForegroundColor Cyan
$stopWatch
 
3
  • 1
    Gold my friend, GOLD* Commented Jan 13, 2021 at 6:47
  • Denis, Your code almost did the trick for me, but I had an issue where it was trying to restore some files that had already been replaced, so the script would stop with a "file you were trying to restore already exists" error message. Is there any modification we can make to the script where it will ignore files with the same names and continue on? Thank you!
    – JamfoFL
    Commented Jun 1, 2021 at 17:58
  • @JamfoFL, I've updated the code to include $ErrorActionPreference = 'SilentlyContinue' Commented Dec 10, 2021 at 22:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.