diff --git a/CHANGELOG.md b/CHANGELOG.md index e3b33318d..32f98209e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Properties of `Get-PnPAzureADServicePrincipal` are now all typed instead of some of them returning unparsed JSON fragments. [#2717](https://github.com/pnp/powershell/pull/2717) - Changed `Add-PnPTeamsChannel` to no longer require an `-OwnerUPN` to be provided when specifying `-ChannelType Standard` [#2786](https://github.com/pnp/powershell/pull/2786) - Changed `Add-PnPFile` by default to upload a file as a draft with a minor version now instead of publishing it as a major version. `-CheckinType MajorCheckIn` can be used to still upload the file as a major published version [#2806](https://github.com/pnp/powershell/pull/2806) +- Improved `Restore-PnPRecycleBinItem` and `Clear-PnPRecycleBinItem` cmdlets to better work with large items in recycle bin. [#2866] (https://github.com/pnp/powershell/pull/2866) - Changed `Get-PnPUserProfileProperty` to no longer return additional user profile properties under UserProfileProperties but instead directly on the returned instance. [#2840](https://github.com/pnp/powershell/pull/2840) ### Removed diff --git a/documentation/Add-PnPFile.md b/documentation/Add-PnPFile.md index c6af74948..a4f8031ae 100644 --- a/documentation/Add-PnPFile.md +++ b/documentation/Add-PnPFile.md @@ -360,7 +360,7 @@ Managed Metadata (multiple values with paths to terms): -Values @{"MetadataField Managed Metadata (multiple values with ids of terms): -Values @{"MetadataField" = "fe40a95b-2144-4fa2-b82a-0b3d0299d818","52d88107-c2a8-4bf0-adfa-04bc2305b593"} -Hyperlink or Picture: -Values @{"Hyperlink" = "https://github.com/OfficeDev/, OfficePnp"} +Hyperlink or Picture: -Values @{"Hyperlink" = "https://github.com/OfficeDev/, OfficePnP"} ```yaml Type: Hashtable diff --git a/documentation/Add-PnPListItem.md b/documentation/Add-PnPListItem.md index 979b9d252..49fbcd3d9 100644 --- a/documentation/Add-PnPListItem.md +++ b/documentation/Add-PnPListItem.md @@ -225,7 +225,7 @@ Managed Metadata (multiple values with paths to terms): -Values @{"MetadataField Managed Metadata (multiple values with ids of terms): -Values @{"MetadataField" = "fe40a95b-2144-4fa2-b82a-0b3d0299d818","52d88107-c2a8-4bf0-adfa-04bc2305b593"} -Hyperlink or Picture: -Values @{"Hyperlink" = "https://github.com/OfficeDev/, OfficePnp"} +Hyperlink or Picture: -Values @{"Hyperlink" = "https://github.com/OfficeDev/, OfficePnP"} ```yaml Type: Hashtable diff --git a/documentation/Add-PnPSiteTemplate.md b/documentation/Add-PnPSiteTemplate.md index aecff9661..6712e9316 100644 --- a/documentation/Add-PnPSiteTemplate.md +++ b/documentation/Add-PnPSiteTemplate.md @@ -26,7 +26,7 @@ Allows to add PnP Site Template object to a tenant template. ### EXAMPLE 1 ```powershell -Add-PnpSiteTemplate -TenantTemplate $tenanttemplate -SiteTemplate $sitetemplate +Add-PnPSiteTemplate -TenantTemplate $tenanttemplate -SiteTemplate $sitetemplate ``` Adds an existing site template to an existing tenant template object diff --git a/documentation/Clear-PnpRecycleBinItem.md b/documentation/Clear-PnpRecycleBinItem.md index 771ba78d0..f7fc22a13 100644 --- a/documentation/Clear-PnpRecycleBinItem.md +++ b/documentation/Clear-PnpRecycleBinItem.md @@ -34,7 +34,7 @@ Allows to permanently delete items from recycle bin. By default the command will ### EXAMPLE 1 ```powershell -Get-PnPRecycleBinItem | Where-Object LeafName -like "*.docx" | Clear-PnpRecycleBinItem +Get-PnPRecycleBinItem | Where-Object LeafName -like "*.docx" | Clear-PnPRecycleBinItem ``` Permanently deletes all the items in the first and second stage recycle bins of which the file names have the .docx extension diff --git a/src/Commands/RecycleBin/ClearRecycleBinItem.cs b/src/Commands/RecycleBin/ClearRecycleBinItem.cs index d328ded10..d9d3171ea 100644 --- a/src/Commands/RecycleBin/ClearRecycleBinItem.cs +++ b/src/Commands/RecycleBin/ClearRecycleBinItem.cs @@ -2,6 +2,7 @@ using Microsoft.SharePoint.Client; using PnP.PowerShell.Commands.Base.PipeBinds; +using PnP.PowerShell.Commands.Utilities; using Resources = PnP.PowerShell.Commands.Properties.Resources; namespace PnP.PowerShell.Commands.RecycleBin @@ -45,15 +46,16 @@ protected override void ExecuteCmdlet() if (ParameterSpecified(nameof(RowLimit))) { if (Force || ShouldContinue(SecondStageOnly ? Resources.ClearSecondStageRecycleBin : Resources.ClearBothRecycleBins, Resources.Confirm)) - { + { RecycleBinItemState recycleBinStage = SecondStageOnly ? RecycleBinItemState.SecondStageRecycleBin : RecycleBinItemState.None; - RecycleBinItemCollection items = ClientContext.Site.GetRecycleBinItems(null, RowLimit, false, RecycleBinOrderBy.DeletedDate, recycleBinStage); - ClientContext.Load(items); - ClientContext.ExecuteQueryRetry(); - - items.DeleteAll(); - ClientContext.ExecuteQueryRetry(); + var recycleBinItemCollection = RecycleBinUtility.GetRecycleBinItemCollection(ClientContext, RowLimit, recycleBinStage); + for (var i = 0; i < recycleBinItemCollection.Count; i++) + { + var recycleBinItems = recycleBinItemCollection[i]; + recycleBinItems.DeleteAll(); + ClientContext.ExecuteQueryRetry(); + } } } else diff --git a/src/Commands/RecycleBin/RestoreRecycleBinItem.cs b/src/Commands/RecycleBin/RestoreRecycleBinItem.cs index ec1d1971d..e51637575 100644 --- a/src/Commands/RecycleBin/RestoreRecycleBinItem.cs +++ b/src/Commands/RecycleBin/RestoreRecycleBinItem.cs @@ -3,6 +3,7 @@ using Microsoft.SharePoint.Client; using PnP.PowerShell.Commands.Base.PipeBinds; +using PnP.PowerShell.Commands.Utilities; using Resources = PnP.PowerShell.Commands.Properties.Resources; namespace PnP.PowerShell.Commands.RecycleBin @@ -24,7 +25,6 @@ protected override void ExecuteCmdlet() { if (ParameterSpecified(nameof(Identity))) { - var recycleBinItem = Identity.GetRecycleBinItem(ClientContext.Site); if (Force || ShouldContinue(string.Format(Resources.RestoreRecycleBinItem, recycleBinItem.LeafName), Resources.Confirm)) @@ -39,12 +39,13 @@ protected override void ExecuteCmdlet() { if (Force || ShouldContinue(string.Format(Resources.Restore0RecycleBinItems, RowLimit), Resources.Confirm)) { - RecycleBinItemCollection items = ClientContext.Site.GetRecycleBinItems(null, RowLimit, false, RecycleBinOrderBy.DeletedDate, RecycleBinItemState.None); - ClientContext.Load(items); - ClientContext.ExecuteQueryRetry(); - - items.RestoreAll(); - ClientContext.ExecuteQueryRetry(); + 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 diff --git a/src/Commands/Utilities/RecycleBinUtility.cs b/src/Commands/Utilities/RecycleBinUtility.cs index 0b5587ef0..464e39673 100644 --- a/src/Commands/Utilities/RecycleBinUtility.cs +++ b/src/Commands/Utilities/RecycleBinUtility.cs @@ -1,9 +1,7 @@ using Microsoft.SharePoint.Client; -using System; using System.Collections.Generic; using System.Linq; using System.Net; -using System.Text; namespace PnP.PowerShell.Commands.Utilities { @@ -39,11 +37,11 @@ internal static List GetRecycleBinItems(ClientContext ctx, int? { iterationRowLimit = 5000; } - + items = ctx.Site.GetRecycleBinItems(pagingInfo, iterationRowLimit, false, RecycleBinOrderBy.DefaultOrderBy, recycleBinStage); ctx.Load(items); ctx.ExecuteQueryRetry(); - recycleBinItems.AddRange(items.ToList()); + recycleBinItems.AddRange(items.ToList()); // Paging magic (if needed) // Based on this work our good friends at Portiva did ❤ @@ -61,5 +59,48 @@ internal static List GetRecycleBinItems(ClientContext ctx, int? return recycleBinItems; } + + internal static List GetRecycleBinItemCollection(ClientContext ctx, int? rowLimit = null, RecycleBinItemState recycleBinItemState = RecycleBinItemState.None) + { + string pagingInfo = null; + RecycleBinItemCollection items; + var recycleBinItems = new List(); + + do + { + // We don't actually know what the List View Threshold for the Recycle Bin is, so we'll use the safe number (5000) and implement paging. + int iterationRowLimit; + if (rowLimit.HasValue && rowLimit.Value >= 5000) + { + // Subtract this page's count from the rowLimit (we don't want duplicates or go out of bounds) + if (rowLimit.HasValue) rowLimit -= 5000; + + iterationRowLimit = 5000; + } + else if (rowLimit.HasValue && rowLimit.Value > 0 && rowLimit.Value < 5000) + { + iterationRowLimit = rowLimit.Value; + } + else + { + iterationRowLimit = 5000; + } + + items = ctx.Site.GetRecycleBinItems(pagingInfo, iterationRowLimit, false, RecycleBinOrderBy.DefaultOrderBy, recycleBinItemState); + ctx.Load(items); + ctx.ExecuteQueryRetry(); + recycleBinItems.Add(items); + + if (items.Count > 0) + { + var nextId = items.Last().Id; + var nextTitle = WebUtility.UrlEncode(items.Last().Title); + pagingInfo = $"id={nextId}&title={nextTitle}"; + } + } + while (items?.Count == 5000); + + return recycleBinItems; + } } }