Skip to content

Extended Restore-PnPRecycleBinItem functionality to efficiently restore selected set of items in bulk #4705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions documentation/Restore-PnPRecycleBinItem.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ Restores the provided recycle bin item to its original location.
Restore-PnPRecycleBinItem -Identity <RecycleBinItemPipeBind> [-Force] [-RowLimit <Int32>]
[-Connection <PnPConnection>]
```
```powershell
Restore-PnPRecycleBinItem -IdList <string[]> [-Connection <PnPConnection>]
```

## DESCRIPTION
This cmdlet restores the specified item from the recycle bin to its original location.
This cmdlet restores the specified item or set of items from the recycle bin to its original location.

## EXAMPLES

Expand All @@ -45,6 +48,13 @@ Get-PnPRecycleBinItem -RowLimit 10000 | Restore-PnPRecycleBinItem -Force

Permanently restores up to 10,000 items in the recycle bin without asking for confirmation.

### EXAMPLE 4
```powershell
Restore-PnPRecycleBinItem -IdList @("31897b05-fd3b-4c49-9898-2e7f10e59cac","b16f0733-9b07-4ef3-a4b6-896edca4babd", "367ef9d2-6080-45ea-9a03-e8c9029f59dd")
```

Restores the recycle bin items with Id 31897b05-fd3b-4c49-9898-2e7f10e59cac, b16f0733-9b07-4ef3-a4b6-896edca4babd, 367ef9d2-6080-45ea-9a03-e8c9029f59dd to their original location.

## PARAMETERS

### -Connection
Expand All @@ -66,7 +76,7 @@ If provided, no confirmation will be asked to restore the recycle bin item.

```yaml
Type: SwitchParameter
Parameter Sets: (All)
Parameter Sets: (Restore Single Item By Id)

Required: False
Position: Named
Expand All @@ -80,7 +90,7 @@ Id of the recycle bin item or the recycle bin item object itself to restore.

```yaml
Type: RecycleBinItemPipeBind
Parameter Sets: (All)
Parameter Sets: (Restore Single Item By Id)

Required: False
Position: Named
Expand All @@ -90,18 +100,31 @@ Accept wildcard characters: False
```

### -RowLimit
Limits restoration to specified number of items.
Limits restoration to a specified number of items.

```yaml
Type: Int32
Parameter Sets: (All)
Parameter Sets: (Restore Single Item By Id)

Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```
### -IdList
String array of Recycle Bin Item Ids

```yaml
Type: String array
Parameter Sets: (Restore Multiple Items By Id)

Required: True
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```

## RELATED LINKS

Expand Down
2 changes: 1 addition & 1 deletion src/Commands/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"profiles": {
"profiles": {
"PnP.PowerShell-Module": {
"commandName": "Executable",
"executablePath": "pwsh",
Expand Down
79 changes: 49 additions & 30 deletions src/Commands/RecycleBin/RestoreRecycleBinItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,72 @@ namespace PnP.PowerShell.Commands.RecycleBin
[OutputType(typeof(void))]
public class RestoreRecycleBinItem : PnPSharePointCmdlet
{
[Parameter(Mandatory = false, ValueFromPipeline = true)]
private const string ParameterSetName_RESTORE_MULTIPLE_ITEMS_BY_ID = "Restore Multiple Items By Id";
private const string ParameterSetName_RESTORE_SINGLE_ITEM_BY_ID = "Restore Single Items By Id";

[Parameter(Mandatory = false, ParameterSetName = ParameterSetName_RESTORE_SINGLE_ITEM_BY_ID, Position = 0, ValueFromPipeline = true)]
public RecycleBinItemPipeBind Identity;

[Parameter(Mandatory = false)]

[Parameter(Mandatory = false, ParameterSetName = ParameterSetName_RESTORE_SINGLE_ITEM_BY_ID)]
public SwitchParameter Force;

[Parameter(Mandatory = false)]
[Parameter(Mandatory = false, ParameterSetName = ParameterSetName_RESTORE_SINGLE_ITEM_BY_ID)]
public int RowLimit;

[Parameter(Mandatory = true, ParameterSetName = ParameterSetName_RESTORE_MULTIPLE_ITEMS_BY_ID)]
public string[] IdList;

protected override void ExecuteCmdlet()
{
if (ParameterSpecified(nameof(Identity)))
switch (ParameterSetName)
{
var recycleBinItem = Identity.GetRecycleBinItem(Connection.PnPContext);
case ParameterSetName_RESTORE_SINGLE_ITEM_BY_ID:

if (recycleBinItem == null)
{
throw new PSArgumentException("Recycle bin item not found with the ID specified", nameof(Identity));
}

if (Force || ShouldContinue(string.Format(Resources.RestoreRecycleBinItem, recycleBinItem.LeafName), Resources.Confirm))
{
recycleBinItem.Restore();
}
}
else
{
if (ParameterSpecified(nameof(RowLimit)))
{
if (Force || ShouldContinue(string.Format(Resources.Restore0RecycleBinItems, RowLimit), Resources.Confirm))
if (ParameterSpecified(nameof(Identity)))
{
var recycleBinItemCollection = RecycleBinUtility.GetRecycleBinItemCollection(ClientContext, RowLimit, RecycleBinItemState.None);
for (var i = 0; i < recycleBinItemCollection.Count; i++)
var recycleBinItem = Identity.GetRecycleBinItem(Connection.PnPContext);

if (recycleBinItem == null)
{
throw new PSArgumentException("Recycle bin item not found with the ID specified", nameof(Identity));
}

if (Force || ShouldContinue(string.Format(Resources.RestoreRecycleBinItem, recycleBinItem.LeafName), Resources.Confirm))
{
var recycleBinItems = recycleBinItemCollection[i];
recycleBinItems.RestoreAll();
ClientContext.ExecuteQueryRetry();
recycleBinItem.Restore();
}
}
}
else
{
if (Force || ShouldContinue(Resources.RestoreRecycleBinItems, Resources.Confirm))
else
{
Connection.PnPContext.Site.RecycleBin.RestoreAll();
if (ParameterSpecified(nameof(RowLimit)))
{
if (Force || ShouldContinue(string.Format(Resources.Restore0RecycleBinItems, RowLimit), Resources.Confirm))
{
var recycleBinItemCollection = RecycleBinUtility.GetRecycleBinItemCollection(ClientContext, RowLimit, RecycleBinItemState.None);
for (var i = 0; i < recycleBinItemCollection.Count; i++)
{
var recycleBinItems = recycleBinItemCollection[i];
recycleBinItems.RestoreAll();
ClientContext.ExecuteQueryRetry();
}
}
}
else
{
if (Force || ShouldContinue(Resources.RestoreRecycleBinItems, Resources.Confirm))
{
Connection.PnPContext.Site.RecycleBin.RestoreAll();
}
}
}
}
break;

case ParameterSetName_RESTORE_MULTIPLE_ITEMS_BY_ID:
RecycleBinUtility.RestoreRecycleBinItemInBulk(HttpClient, ClientContext, IdList, this);
break;

}
}
}
Expand Down
63 changes: 61 additions & 2 deletions src/Commands/Utilities/RecycleBinUtility.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
using Microsoft.SharePoint.Client;
using Newtonsoft.Json;
using PnP.PowerShell.Commands.Model.Mail;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Net;
using System.Net.Http;
using System.Xml.Linq;

namespace PnP.PowerShell.Commands.Utilities
{
Expand Down Expand Up @@ -99,8 +105,61 @@ internal static List<RecycleBinItemCollection> GetRecycleBinItemCollection(Clien
}
}
while (items?.Count == 5000);

return recycleBinItems;
}

internal static void RestoreRecycleBinItemInBulk(HttpClient httpClient, ClientContext ctx, string[] idsList, RecycleBin.RestoreRecycleBinItem restoreRecycleBinItem)
{
//restoreRecycleBinItem provides us the reference to the instance of RestoreRecycleBinItem object. We use this object to log key information as verbose
Uri currentContextUri = new Uri(ctx.Url);
string apiCall = $"{currentContextUri}/_api/site/RecycleBin/RestoreByIds";

string idsString = string.Join("','", idsList); // Convert array to a comma-separated string

try
{
string requestBody = $"{{'ids':['{idsString}']}}";
REST.RestHelper.Post(httpClient, apiCall, ctx, requestBody, "application/json", "application/json");
restoreRecycleBinItem.WriteVerbose("Whole batch restored successfuly.");
}
catch (Exception ex)
{
{
//fall back logic
//Unable to process as batch because of an error in restoring one of the ids in batch, processing individually
restoreRecycleBinItem.WriteVerbose($"Unable to process as batch because of an error in restoring one of the ids in batch. Error:{ex.Message}");
restoreRecycleBinItem.WriteVerbose($"Switching to individul restore of items ...");

foreach (string id in idsList)
{
try
{
string requestBody = $"{{'ids':['{id}']}}";
REST.RestHelper.Post(httpClient, apiCall, ctx, requestBody, "application/json", "application/json");
restoreRecycleBinItem.WriteVerbose($"Item - {id} restored successfuly.");

}
catch (Exception e)
{
var odataError = e.Message;
if (odataError != null)
{
if (odataError.Contains("Value does not fall within the expected range."))
{
restoreRecycleBinItem.WriteVerbose($"Item - {id} already restored.");
}
else
{
//Most common reason is that an item with the same name already exists. To restore the item, rename the existing item and try again
restoreRecycleBinItem.WriteVerbose($"Item - {id} restore failed. Error:{odataError}");
}
}
//Digest errors because we cannot do anything
}
}
}
}
}
}
}
}