Quite often as a consultant, you get to work on truly fun and funny situations. The customer email that prompted this blog post is one of them.
My contact at the customer site emailed with a problem. A few weeks earlier one of their users deleted some pictures from one of their document libraries. Okay, not a few pictures, nearly 100,000 of them. And not only had that user deleted nearly 100,000 items, they hadn’t told anyone for 3 weeks, while they tried to upload the pictures they still had locally. I know what you’re saying, this is a job for the Recycle Bin! And you’re right, it is, but the other factors made it complicated. They couldn’t just restore all of the files in the Recycle Bin as there were also 3 weeks’ worth of legitimately deleted documents in there, including documents from that user. Also, since the user had re-uploaded a bunch of the deleted documents there would be “overwrite” prompts all over that would slow down the process. And let’s not forget that even if it all went smoothly, someone would have to manually restore nearly 100,000 files. No easy feat.
The customer talked to me because they wanted to share the story (it was a big funny after the fact) but also because they knew I always said that PowerShell could do anything. They were hoping PowerShell could bale them out of this mess too. I don’t mean to spoil the end of this story, but they were right, it could.
My tool of choice when it comes to making magic in Office 365 is the PnP PowerShell. I had never done this before, so I had to do a bit of spelunking. I ran Get-Command *recycle* -Module SharePointPnPPowerShellOnline to see what PowerShell cmdlets availed themselves to me. Looky, looky, there it is, Restore-PnPRecycleBinItem. Did you just hear angels sing? I know I did.
Now we needed to weave a little PowerShell magic. We couldn’t restore all of the deleted files, since other folks had legitimately deleted files in the meantime. We also only wanted to restore .JPG files that this user had deleted. Here’s command that got the files we needed:
Get-PnPRecycleBinItem | Where-Object -Property Leafname -Like -Value "*.jpg" | Where-Object -Property Dirname -Like -Value "Shared Documents/*" | Where-Object -Property DeletedByEmail -EQ –Value shane@tkdemo.com
That returned all the files, now what to do with them? Restore them, of course. This bit of code grabs all the files, counts them as it restores them, then spits out the time and date when it’s done.
$bin = Get-PnPRecycleBinItem | Where-Object -Property Leafname -Like -Value "*.jpg" | Where-Object -Property Dirname -Like -Value "Shared Documents/*" | Where-Object -Property DeletedByEmail -EQ –Value shane@tkdemo.com
$bin | foreach -begin { $a = 0} -Process {Write-Host "$a - $($_.LeafName)" ; $_ | Restore-PnPRecycleBinItem -Force ; $a++ } -End { Get-Date }
Normally, that would have worked, but it was going to take some time to restore the files. A loooong time. Several days in fact. With some testing I surmised that the client PowerShell was the bottleneck. I used a little PowerShell trick to break the entire 100,000 item collection into chunks of 10,000 and run it multiple PowerShell windows and on multiple machines. I changed the second line to look something like this:
($bin[20001..30000]) | foreach -begin { $a = 0} -Process {Write-Host "$a - $($_.LeafName)" ; $_ | Restore-PnPRecycleBinItem -Force ; $a++ } -End { Get-Date }
The $bin[20001..30000] only sends items 20001 to 30000 of the collection down the pipeline. I changed that on each client. $bin[0..10000], $bin[10001..20000], and so on. After they finished I ran through it all one more time to make sure I didn’t miss any. The entire script looked like this:
# Make sure necessary modules are installed
# PnP PowerShell to get access to Office 365
Install-Module SharePointPnPPowerShellOnline
# Module to securely store passwords
Install-Module CredentialManager
# Saved credentials
New-StoredCredential -Target "ImportantSite" -UserName madowner@tkdemo.com -Password 'Password goes here' -Persist LocalMachine
# Now the actual meat
# Connect to the site collection where the files were deleted
Connect-PnPOnline -Url https://tkdemo.sharepoint.com/ -Credentials ImportantSite
# Filter the recycle bin for only the files we want to restore, JPGs, from the Shared document library, deleted by Shane
$bin = Get-PnPRecycleBinItem | Where-Object -Property Leafname -Like -Value "*.jpg" | Where-Object -Property Dirname -Like -Value "Shared Documents/*" | Where-Object -Property DeletedByEmail -EQ -Value shane@tkdemo.com
# See how many pictures Shane deleted.
$bin.count
# Walk through the collection and restore each document. Spit out the time at the end so you know how long it took. Also keep a counter to see how it’s going as it churns through the collection
$bin | foreach -begin { $a = 0} -Process {Write-Host "$a - $($_.LeafName)" ; $_ | Restore-PnPRecycleBinItem -Force ; $a++ } -End { Get-Date }
# Since the OM is the bottleneck you can run this on multiple machines to speed things up. Here’s how to only restore a subset of the collection
($bin[20001..30000]) | foreach -begin { $a = 0} -Process {Write-Host "$a - $($_.LeafName)" ; $_ | Restore-PnPRecycleBinItem -Force ; $a++ } -End { Get-Date }
I hope you never need to use this.
tk
ShortURL: https://www.toddklindt.com/PoshRestoreSPOFiles