Skip to content
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

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

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
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
}
}
}
}
}
}
}
}