Skip to content

Instantly share code, notes, and snippets.

@Smalls1652
Last active July 7, 2020 23:39
Show Gist options
  • Save Smalls1652/048173273fde5d8f87b37700c987915c to your computer and use it in GitHub Desktop.
Save Smalls1652/048173273fde5d8f87b37700c987915c to your computer and use it in GitHub Desktop.
Restoring SPO Recycle Bin files

Please read this before running!

Warning!

I am not responsible for any data loss or any support issues! I had to make this myself to restore over 39,000 deleted files from a user's OneDrive, but needed to restore them in bulk. The GUI interface is painful to use and the SharePointPnPPowerShellOnline module fails to restore items (Due to the 5,000 item limit on SharePoint Online). I got the specific API calls on restoring items from the recycle bin by monitoring network traffic on a web browser while restoring an item from the GUI.

Custom Module Pre-requisite

This script requires a module I created that is nowhere near "production ready" and was only made to make it easier for me to create an authentication token to access Microsoft Graph API resources.

The repository for that module is here (with instructions on how to build it):

https://github.com/Smalls1652/pwsh-graph-connect

Azure AD application

You must create an Azure AD app registration for your tenant that has the necessary permissions for SharePoint (https://microsoft.sharepoint-df.com/). I've found it much easier to use delegated permissions and authenticating as yourself.

Running the script

In this example, we're recovering a bulk number of deleted items done by the 'OneDrive System Account' in a user's OneDrive. You must add yourself as a 'Site Collection Administrator' to the user's OneDrive SharePoint Online Site.

With both the pwsh-graph-connect and SharePointPnPPowerShellOnline modules:

Connect-PnPOnline -Url "https://contosocom-my.sharepoint.com/personal/john_contoso_com" -PnPO365ManagementShell
$token = Start-GraphApiConnection -ClientId "App/ClientId" -TenantId "TenantId" -Scopes "https://contosocom-my.sharepoint.com/.default"

$deletedItems = Get-PnPRecycleBinItem -FirstStage -RowLimit 150000
$deletedItemsBySystem = $deletedItems | Where-Object { $PSItem.DeletedByName -eq "System Account" }

.\Invoke-SharepointRecycleBinRestore.ps1 -SiteUri "https://contosocom-my.sharepoint.com/personal/john_contoso_com" -AuthToken $token -RecycleBinItems $deletedItemsBySystem

This can take a long time, depending on the number of files that need to be restored.

##Requires -Module @{ "ModuleName" = "pwsh-graph-connect" }
##Requires -Module @{ "ModuleName" = "SharePointPnPPowerShellOnline" }
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Position = 0, Mandatory)]
[System.Uri]$SiteUri,
[Parameter(Position = 1, Mandatory)]
[Microsoft.Identity.Client.AuthenticationResult]$AuthToken,
[Parameter(Position = 2, Mandatory)]
[System.Collections.Generic.List[Microsoft.SharePoint.Client.RecycleBinItem]]$RecycleBinItems
)
process {
$ProgressSplat = @{
"Activity" = "Restoring items from Recycle Bin";
"Status" = "Progress->";
"Id" = 0;
}
switch ($SiteUri.AbsoluteUri.EndsWith("/")) {
$false {
$SiteUri = [System.Uri]::new("$($SiteUri.AbsoluteUri)/")
break
}
Default {
break
}
}
$apiTarget = "_api/site/RecycleBin/RestoreByIds"
$apiUri = [System.Uri]::new($SiteUri, $apiTarget)
$apiHeader = @{
"Authorization" = ($AuthToken.CreateAuthorizationHeader());
"Content-Type" = "application/json";
}
$i = 1
$recycleBinItemCount = ($RecycleBinItems | Measure-Object).Count
Write-Progress @ProgressSplat -PercentComplete 0 -CurrentOperation "Starting loop"
foreach ($item in $RecycleBinItems) {
$progressPercent = [System.Math]::Round((($i / $recycleBinItemCount) * 100))
Write-Progress @ProgressSplat -PercentComplete $progressPercent -CurrentOperation "Restoring '$($item.LeafName)' [Item - $($i) / $($recycleBinItemCount) ]"
$bodyJson = @{
"ids" = @($item.Id);
} | ConvertTo-Json
if ($PSCmdlet.ShouldProcess($item.LeafName, "Restore from recycle bin")) {
try {
$null = Invoke-RestMethod -Method "Post" -Uri $apiUri -Headers $apiHeader -Body $bodyJson -ErrorAction Stop
}
catch [Microsoft.PowerShell.Commands.HttpResponseException] {
$errorDetails = $PSItem
switch ($errorDetails.Exception.Response.StatusCode) {
"Unauthorized" {
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
$errorDetails.Exception,
"TokenExpired",
[System.Management.Automation.ErrorCategory]::AuthenticationError,
$AuthToken
)
)
break
}
Default {
Write-Warning "A non-terminating error occurred: $($errorDetails.ErrorDetails.Message)"
break
}
}
}
catch [System.IO.IOException] {
$errorDetails = $PSItem
throw $errorDetails
}
catch {
$errorDetails = $PSItem
$PSCmdlet.ThrowTerminatingError($errorDetails)
}
}
$i++
}
Write-Progress @ProgressSplat -Completed
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment