|
| 1 | + |
| 2 | +# Restore multiple deleted items from the SharePoint Online Recycle Bin based on deletion date and user name |
| 3 | + |
| 4 | +## Summary |
| 5 | + |
| 6 | +This PowerShell script is designed to help SharePoint Online administrators to restore items from the recycle bin that were deleted by a specific account (such as "System Account" or a SharePoint App) within a user-defined number of days. |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +## Scenario |
| 11 | + |
| 12 | +Sometimes, there's a need to restore files that were accidentally deleted by users. One common scenario is when a user deletes a synced SharePoint folder without properly disconnecting it first. |
| 13 | + |
| 14 | +## Requirements |
| 15 | + |
| 16 | +To run this PowerShell script successfully, ensure the following: |
| 17 | + |
| 18 | +1. PowerShell Version: [PowerShell 7 or later](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5) |
| 19 | +2. PnP PowerShell Module: Installed and imported [(Install-Module PnP.PowerShell)](https://pnp.github.io/powershell/articles/installation.html) |
| 20 | +3. [App-Only Authentication](https://github.com/pnp/PnP-PowerShell/tree/master/Samples/SharePoint.ConnectUsingAppPermissions): You must have: |
| 21 | + + A registered Azure AD App with appropriate SharePoint permissions |
| 22 | + + A valid Client ID |
| 23 | + + A certificate installed locally with its thumbprint |
| 24 | +4. SharePoint Online Access: The app must have access to the target SharePoint site |
| 25 | +5. Log Directory: The specified log directory must exist on the local machine |
| 26 | + |
| 27 | +[More about Restore-PnPRecycleBinItem](https://pnp.github.io/powershell/cmdlets/Restore-PnPRecycleBinItem.html) |
| 28 | + |
| 29 | +# [PnP PowerShell](#tab/pnpps) |
| 30 | + |
| 31 | +```powershell |
| 32 | +
|
| 33 | +function Get-UserInput { |
| 34 | + [CmdletBinding()] |
| 35 | + param () |
| 36 | +
|
| 37 | + try { |
| 38 | + $inputData = [ordered]@{} |
| 39 | + $inputData["siteURL"] = Read-Host "Enter the SharePoint site URL (e.g., https://yourtenant.sharepoint.com/sites/demo)" |
| 40 | + $inputData["tenant"] = Read-Host "Enter your tenant domain (e.g., yourtenant.onmicrosoft.com)" |
| 41 | + $inputData["clientID"] = Read-Host "Enter the Azure AD App Client ID" |
| 42 | + $inputData["thumbprint"] = Read-Host "Enter the certificate thumbprint" |
| 43 | + $inputData["deletedByName"] = Read-Host "Enter the name of the account that deleted the items (e.g., System Account)" |
| 44 | + $inputData["logLocation"] = Read-Host "Enter the full path to the log directory (e.g., C:\Temp\Logs)" |
| 45 | + $inputData["numberOfDays"] = Read-Host "Enter the number of days to look back for deleted items" |
| 46 | +
|
| 47 | + return $inputData |
| 48 | + } catch { |
| 49 | + Write-Error "Error collecting user input: $_" |
| 50 | + exit 1 |
| 51 | + } |
| 52 | +} |
| 53 | +
|
| 54 | +function Test-LogDirectoryPath { |
| 55 | + param ([string]$Path) |
| 56 | + try { |
| 57 | + if (-not (Test-Path -Path $Path)) { |
| 58 | + throw "Log directory does not exist: $Path" |
| 59 | + } |
| 60 | + Write-Host "Log directory exists: $Path" |
| 61 | + } catch { |
| 62 | + Write-Error "Log directory validation failed: $_" |
| 63 | + exit 1 |
| 64 | + } |
| 65 | +} |
| 66 | +
|
| 67 | +function Connect-ToSharePoint { |
| 68 | + param ( |
| 69 | + [string]$Tenant, |
| 70 | + [string]$ClientID, |
| 71 | + [string]$Thumbprint, |
| 72 | + [string]$SiteURL |
| 73 | + ) |
| 74 | + try { |
| 75 | + Connect-PnPOnline -Tenant $Tenant -ClientId $ClientID -Thumbprint $Thumbprint -Url $SiteURL |
| 76 | + Write-Host "Connected to SharePoint site: $SiteURL" |
| 77 | + } catch { |
| 78 | + Write-Error "Failed to connect to SharePoint: $_" |
| 79 | + exit 1 |
| 80 | + } |
| 81 | +} |
| 82 | +
|
| 83 | +function Restore-RecycleBinItems { |
| 84 | + param ( |
| 85 | + [string]$DeletedByName, |
| 86 | + [datetime]$TargetDate, |
| 87 | + [int]$BatchSize, |
| 88 | + [string]$LogFile |
| 89 | + ) |
| 90 | +
|
| 91 | + try { |
| 92 | + $count = 0 |
| 93 | + Write-Host "Retrieving items deleted by '$DeletedByName' since $TargetDate..." |
| 94 | + $items = Get-PnPRecycleBinItem -RowLimit $BatchSize | Where-Object { |
| 95 | + $_.DeletedByName -eq $DeletedByName -and $_.DeletedDate -gt $TargetDate |
| 96 | + } |
| 97 | +
|
| 98 | + foreach ($item in $items) { |
| 99 | + $count++ |
| 100 | + Write-Host "$($item.Id) :::: $($item.Title) :::: $($item.ItemType) :::: $($item.DirName)" |
| 101 | + try { |
| 102 | + # Comment the next line if you want to skip the restoration and just log the items |
| 103 | + Restore-PnPRecycleBinItem -Identity $item.ID -Force |
| 104 | +
|
| 105 | + |
| 106 | + $logEntry = "$count. Deleted Date: $($item.DeletedDate) :: Restored item: $($item.Title) from $($item.DirName)" |
| 107 | + Write-Host $logEntry |
| 108 | + $logEntry | Out-File -FilePath $LogFile -Append |
| 109 | + } catch { |
| 110 | + $errorEntry = "$count. Deleted Date: $($item.DeletedDate) :: Failed to restore item: $($item.Title) - $_" |
| 111 | + Write-Warning $errorEntry |
| 112 | + $errorEntry | Out-File -FilePath $LogFile -Append |
| 113 | + } |
| 114 | + } |
| 115 | +
|
| 116 | + Write-Host "Restoration process completed" |
| 117 | + } catch { |
| 118 | + Write-Error "Error during recycle bin item restoration: $_" |
| 119 | + exit 1 |
| 120 | + } |
| 121 | +} |
| 122 | +
|
| 123 | +# === MAIN EXECUTION === |
| 124 | +
|
| 125 | +try { |
| 126 | + $userInput = Get-UserInput |
| 127 | +
|
| 128 | + $batchSize = 999999 |
| 129 | + $timeStamp = Get-Date -Format "yyyy-MM-dd-HHmmss" |
| 130 | + $targetDate = (Get-Date).AddDays(-[int]$userInput.numberOfDays).Date |
| 131 | + $logFileName = Join-Path -Path $userInput.logLocation -ChildPath "RestoreFile_$timeStamp.txt" |
| 132 | +
|
| 133 | + Test-LogDirectoryPath -Path $userInput.logLocation |
| 134 | + Connect-ToSharePoint -Tenant $userInput.tenant -ClientID $userInput.clientID -Thumbprint $userInput.thumbprint -SiteURL $userInput.siteURL |
| 135 | + Restore-RecycleBinItems -DeletedByName $userInput.deletedByName -TargetDate $targetDate -BatchSize $batchSize -LogFile $logFileName |
| 136 | +} catch { |
| 137 | + Write-Error "Unexpected error occurred: $_" |
| 138 | + exit 1 |
| 139 | +} finally { |
| 140 | + try { |
| 141 | + Disconnect-PnPOnline |
| 142 | + Write-Host "Disconnected from SharePoint." |
| 143 | + } catch { |
| 144 | + Write-Warning "Failed to disconnect from SharePoint: $_" |
| 145 | + } |
| 146 | +} |
| 147 | +# End of script |
| 148 | +``` |
| 149 | + |
| 150 | + |
| 151 | +## Contributors |
| 152 | + |
| 153 | +| Author(s) | |
| 154 | +|-----------| |
| 155 | +| Pankaj Badoni | |
| 156 | + |
| 157 | + |
| 158 | +[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)] |
| 159 | +<img src="https://m365-visitor-stats.azurewebsites.net/script-samples/scripts/spo-restore-multiple-items" aria-hidden="true" /> |
0 commit comments