diff --git a/documentation/Restore-PnPRecycleBinItem.md b/documentation/Restore-PnPRecycleBinItem.md index 8131c6507..42a7c5c18 100644 --- a/documentation/Restore-PnPRecycleBinItem.md +++ b/documentation/Restore-PnPRecycleBinItem.md @@ -18,9 +18,12 @@ Restores the provided recycle bin item to its original location. Restore-PnPRecycleBinItem -Identity [-Force] [-RowLimit ] [-Connection ] ``` +```powershell +Restore-PnPRecycleBinItem -IdList [-Connection ] +``` ## 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 @@ -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 @@ -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 @@ -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 @@ -90,11 +100,11 @@ 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 @@ -102,6 +112,19 @@ 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 diff --git a/src/Commands/Properties/launchSettings.json b/src/Commands/Properties/launchSettings.json index 6f38dd905..ac107e695 100644 --- a/src/Commands/Properties/launchSettings.json +++ b/src/Commands/Properties/launchSettings.json @@ -1,5 +1,5 @@ { - "profiles": { + "profiles": { "PnP.PowerShell-Module": { "commandName": "Executable", "executablePath": "pwsh", diff --git a/src/Commands/RecycleBin/RestoreRecycleBinItem.cs b/src/Commands/RecycleBin/RestoreRecycleBinItem.cs index 228ae4cab..aefa3f949 100644 --- a/src/Commands/RecycleBin/RestoreRecycleBinItem.cs +++ b/src/Commands/RecycleBin/RestoreRecycleBinItem.cs @@ -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; + } } } diff --git a/src/Commands/Utilities/RecycleBinUtility.cs b/src/Commands/Utilities/RecycleBinUtility.cs index 464e39673..fd8061479 100644 --- a/src/Commands/Utilities/RecycleBinUtility.cs +++ b/src/Commands/Utilities/RecycleBinUtility.cs @@ -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 { @@ -99,8 +105,61 @@ internal static List 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 + } + } + } + } + } } -} +} \ No newline at end of file