From 88b073fc01fadc07e9ed4a226197e936696f3ffd Mon Sep 17 00:00:00 2001
From: Michal Romiszewski <23618894+mromiszewski@users.noreply.github.com>
Date: Wed, 3 May 2023 20:42:24 +0200
Subject: [PATCH 1/2] Add list item version history cmdlets
---
.../Base/PipeBinds/ListItemVersionPipeBind.cs | 25 ++++++
src/Commands/Lists/GetListItemVersion.cs | 40 +++++++++
src/Commands/Lists/RemoveListItemVersion.cs | 66 ++++++++++++++
src/Commands/Lists/RestoreListItemVersion.cs | 85 +++++++++++++++++++
src/Commands/Properties/Resources.Designer.cs | 17 +++-
src/Commands/Properties/Resources.resx | 3 +
6 files changed, 232 insertions(+), 4 deletions(-)
create mode 100644 src/Commands/Base/PipeBinds/ListItemVersionPipeBind.cs
create mode 100644 src/Commands/Lists/GetListItemVersion.cs
create mode 100644 src/Commands/Lists/RemoveListItemVersion.cs
create mode 100644 src/Commands/Lists/RestoreListItemVersion.cs
diff --git a/src/Commands/Base/PipeBinds/ListItemVersionPipeBind.cs b/src/Commands/Base/PipeBinds/ListItemVersionPipeBind.cs
new file mode 100644
index 000000000..317147d03
--- /dev/null
+++ b/src/Commands/Base/PipeBinds/ListItemVersionPipeBind.cs
@@ -0,0 +1,25 @@
+namespace PnP.PowerShell.Commands.Base.PipeBinds;
+
+public class ListItemVersionPipeBind
+{
+ public ListItemVersionPipeBind(string versionLabel)
+ {
+ if (int.TryParse(versionLabel, out var id))
+ {
+ Id = id;
+ }
+ else
+ {
+ VersionLabel = versionLabel;
+ }
+ }
+
+ public ListItemVersionPipeBind(int id)
+ {
+ Id = id;
+ }
+
+ public int Id { get; } = -1;
+
+ public string VersionLabel { get; }
+}
\ No newline at end of file
diff --git a/src/Commands/Lists/GetListItemVersion.cs b/src/Commands/Lists/GetListItemVersion.cs
new file mode 100644
index 000000000..afb4e1315
--- /dev/null
+++ b/src/Commands/Lists/GetListItemVersion.cs
@@ -0,0 +1,40 @@
+using System.Linq;
+using System.Management.Automation;
+using PnP.PowerShell.Commands.Base.PipeBinds;
+using PnP.PowerShell.Commands.Extensions;
+
+namespace PnP.PowerShell.Commands.Lists
+{
+ [Cmdlet(VerbsCommon.Get, "PnPListItemVersion")]
+ public class GetListItemVersion : PnPWebCmdlet
+ {
+ [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
+ public ListPipeBind List;
+ [Parameter(Mandatory = true, ValueFromPipeline = true)]
+ public ListItemPipeBind Identity;
+
+ protected override void ExecuteCmdlet()
+ {
+ var list = List.GetList(CurrentWeb);
+
+ if (list is null)
+ {
+ throw new PSArgumentException($"Cannot find the list provided through -{nameof(List)}", nameof(List));
+ }
+
+ var item = Identity.GetListItem(list);
+
+ if (item is null)
+ {
+ throw new PSArgumentException($"Cannot find the list item provided through -{nameof(Identity)}", nameof(Identity));
+ }
+
+ item.LoadProperties(i => i.Versions);
+
+ if (item.Versions is not null)
+ {
+ WriteObject(item.Versions.ToList(), true);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Commands/Lists/RemoveListItemVersion.cs b/src/Commands/Lists/RemoveListItemVersion.cs
new file mode 100644
index 000000000..5822e649a
--- /dev/null
+++ b/src/Commands/Lists/RemoveListItemVersion.cs
@@ -0,0 +1,66 @@
+using System.Linq;
+using System.Management.Automation;
+using Microsoft.SharePoint.Client;
+using PnP.PowerShell.Commands.Base.PipeBinds;
+using PnP.PowerShell.Commands.Extensions;
+using Resources = PnP.PowerShell.Commands.Properties.Resources;
+
+namespace PnP.PowerShell.Commands.Lists
+{
+ [Cmdlet(VerbsCommon.Remove, "PnPListItemVersion")]
+ public class RemoveListItemVersion : PnPWebCmdlet
+ {
+ [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
+ public ListPipeBind List;
+ [Parameter(Mandatory = true, ValueFromPipeline = true)]
+ public ListItemPipeBind Identity;
+ [Parameter(Mandatory = true, ValueFromPipeline = true)]
+ public ListItemVersionPipeBind Version;
+ [Parameter(Mandatory = false)]
+ public SwitchParameter Force;
+
+ protected override void ExecuteCmdlet()
+ {
+ var list = List.GetList(CurrentWeb);
+
+ if (list is null)
+ {
+ throw new PSArgumentException($"Cannot find the list provided through -{nameof(List)}", nameof(List));
+ }
+
+ var item = Identity.GetListItem(list);
+
+ if (item is null)
+ {
+ throw new PSArgumentException($"Cannot find the list item provided through -{nameof(Identity)}", nameof(Identity));
+ }
+
+ item.LoadProperties(i => i.Versions);
+
+ ListItemVersion version = null;
+ if (!string.IsNullOrEmpty(Version.VersionLabel))
+ {
+ version = item.Versions.FirstOrDefault(v => v.VersionLabel == Version.VersionLabel);
+ }
+ else if (Version.Id != -1)
+ {
+ version = item.Versions.FirstOrDefault(v => v.VersionId == Version.Id);
+ }
+
+ if (version is null)
+ {
+ throw new PSArgumentException($"Cannot find the list item version provided through -{nameof(Version)}", nameof(Version));
+ }
+
+ if(Force || ShouldContinue(string.Format(Resources.Delete0, version.VersionLabel), Resources.Confirm))
+ {
+ WriteVerbose($"Trying to remove version {Version.VersionLabel}");
+
+ version.DeleteObject();
+ ClientContext.ExecuteQueryRetry();
+
+ WriteVerbose($"Removed version {Version.VersionLabel} of list item {item.Id} in list {list.Title}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Commands/Lists/RestoreListItemVersion.cs b/src/Commands/Lists/RestoreListItemVersion.cs
new file mode 100644
index 000000000..8806b3a34
--- /dev/null
+++ b/src/Commands/Lists/RestoreListItemVersion.cs
@@ -0,0 +1,85 @@
+using System.Linq;
+using System.Management.Automation;
+using Microsoft.SharePoint.Client;
+using PnP.PowerShell.Commands.Base.PipeBinds;
+using PnP.PowerShell.Commands.Extensions;
+using Resources = PnP.PowerShell.Commands.Properties.Resources;
+
+namespace PnP.PowerShell.Commands.Lists
+{
+ [Cmdlet(VerbsData.Restore, "PnPListItemVersion")]
+ public class RestoreListItemVersion : PnPWebCmdlet
+ {
+ [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
+ public ListPipeBind List;
+ [Parameter(Mandatory = true, ValueFromPipeline = true)]
+ public ListItemPipeBind Identity;
+ [Parameter(Mandatory = true, ValueFromPipeline = true)]
+ public ListItemVersionPipeBind Version;
+ [Parameter(Mandatory = false)]
+ public SwitchParameter Force;
+
+ private static readonly FieldType[] UnsupportedFieldTypes =
+ {
+ FieldType.Attachments,
+ FieldType.Computed
+ };
+
+ protected override void ExecuteCmdlet()
+ {
+ var list = List.GetList(CurrentWeb);
+
+ if (list is null)
+ {
+ throw new PSArgumentException($"Cannot find the list provided through -{nameof(List)}", nameof(List));
+ }
+
+ var item = Identity.GetListItem(list);
+
+ if (item is null)
+ {
+ throw new PSArgumentException($"Cannot find the list item provided through -{nameof(Identity)}", nameof(Identity));
+ }
+
+ item.LoadProperties(i => i.Versions);
+
+ ListItemVersion version = null;
+ if (!string.IsNullOrEmpty(Version.VersionLabel))
+ {
+ version = item.Versions.FirstOrDefault(v => v.VersionLabel == Version.VersionLabel);
+ }
+ else if (Version.Id != -1)
+ {
+ version = item.Versions.FirstOrDefault(v => v.VersionId == Version.Id);
+ }
+
+ if (version is null)
+ {
+ throw new PSArgumentException($"Cannot find the list item version provided through -{nameof(Version)}", nameof(Version));
+ }
+
+ if (Force || ShouldContinue(string.Format(Resources.Restore, version.VersionLabel), Resources.Confirm))
+ {
+ WriteVerbose($"Trying to restore to version with label '{version.VersionLabel}'");
+
+ var fields = ClientContext.LoadQuery(list.Fields.Include(f => f.InternalName,
+ f => f.Title, f => f.Hidden, f => f.ReadOnlyField, f => f.FieldTypeKind));
+ ClientContext.ExecuteQueryRetry();
+
+ foreach (var fieldValue in version.FieldValues)
+ {
+ var field = fields.FirstOrDefault(f => f.InternalName == fieldValue.Key || f.Title == fieldValue.Key);
+ if (field is { ReadOnlyField: false, Hidden: false } && !UnsupportedFieldTypes.Contains(field.FieldTypeKind))
+ {
+ item[field.InternalName] = fieldValue.Value;
+ }
+ }
+
+ item.Update();
+ ClientContext.ExecuteQueryRetry();
+
+ WriteVerbose($"Restored version {version.VersionLabel} of list item {item.Id} in list {list.Title}");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Commands/Properties/Resources.Designer.cs b/src/Commands/Properties/Resources.Designer.cs
index e1e3af75c..a75ce14e8 100644
--- a/src/Commands/Properties/Resources.Designer.cs
+++ b/src/Commands/Properties/Resources.Designer.cs
@@ -357,7 +357,7 @@ internal static string NoApiAccessToken {
return ResourceManager.GetString("NoApiAccessToken", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to There is currently no connection yet. Use Connect-PnPOnline to connect..
///
@@ -384,7 +384,7 @@ internal static string NoContextPresent {
return ResourceManager.GetString("NoContextPresent", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to The current connection holds no SharePoint context. Please use one of the Connect-PnPOnline commands which uses the -Url argument to connect..
///
@@ -393,13 +393,13 @@ internal static string NoDefaultSharePointConnection {
return ResourceManager.GetString("NoDefaultSharePointConnection", resourceCulture);
}
}
-
+
internal static string NoSharePointConnectionInProvidedConnection {
get {
return ResourceManager.GetString("NoSharePointConnectionInProvidedConnection", resourceCulture);
}
}
-
+
///
/// Looks up a localized string similar to No Tenant Administration Url specified. Connect with Connect-PnPOnline and specify the TenantAdminUrl parameter..
///
@@ -724,6 +724,15 @@ internal static string ResetTenantRecycleBinItem {
}
}
+ ///
+ /// Looks up a localized string similar to Restore '{0}'?.
+ ///
+ internal static string Restore {
+ get {
+ return ResourceManager.GetString("Restore", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Restore {0} items from recycle bin to their original locations?.
///
diff --git a/src/Commands/Properties/Resources.resx b/src/Commands/Properties/Resources.resx
index 84fd486e3..6f08f2f15 100644
--- a/src/Commands/Properties/Resources.resx
+++ b/src/Commands/Properties/Resources.resx
@@ -366,4 +366,7 @@
Restore {0} items from recycle bin to their original locations?
+
+ Restore '{0}'?
+
\ No newline at end of file
From f3509e4682f1ddd96fcefca54fb7f7ac544c44ce Mon Sep 17 00:00:00 2001
From: Michal Romiszewski <23618894+mromiszewski@users.noreply.github.com>
Date: Wed, 3 May 2023 20:42:42 +0200
Subject: [PATCH 2/2] Add list item version history cmdlets documentation
---
documentation/Get-PnPListItemVersion.md | 93 ++++++++++++++
documentation/Remove-PnPListItemVersion.md | 128 ++++++++++++++++++++
documentation/Restore-PnPListItemVersion.md | 128 ++++++++++++++++++++
3 files changed, 349 insertions(+)
create mode 100644 documentation/Get-PnPListItemVersion.md
create mode 100644 documentation/Remove-PnPListItemVersion.md
create mode 100644 documentation/Restore-PnPListItemVersion.md
diff --git a/documentation/Get-PnPListItemVersion.md b/documentation/Get-PnPListItemVersion.md
new file mode 100644
index 000000000..35ff71b1c
--- /dev/null
+++ b/documentation/Get-PnPListItemVersion.md
@@ -0,0 +1,93 @@
+---
+Module Name: PnP.PowerShell
+title: Get-PnPListItemVersion
+schema: 2.0.0
+applicable: SharePoint Online
+external help file: PnP.PowerShell.dll-Help.xml
+online version: https://pnp.github.io/powershell/cmdlets/Get-PnPListItemVersion.html
+---
+
+# Get-PnPListItemVersion
+
+## SYNOPSIS
+Retrieves the previous versions of a list item.
+
+## SYNTAX
+
+```powershell
+Get-PnPListItemVersion -List -Identity [-Connection ]
+```
+
+## DESCRIPTION
+This cmdlet retrieves the version history of a list item.
+
+## EXAMPLES
+
+### EXAMPLE 1
+```powershell
+Get-PnPListItemVersion -List "Demo List" -Identity 1
+```
+
+Retrieves the list item version history.
+
+## PARAMETERS
+
+### -List
+The ID, Title or Url of the list.
+
+```yaml
+Type: ListPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Identity
+The ID of the listitem, or actual ListItem object.
+
+```yaml
+Type: ListItemPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Version
+The ID or label of the version.
+
+```yaml
+Type: ListItemPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Connection
+Optional connection to be used by the cmdlet. Retrieve the value for this parameter by either specifying -ReturnConnection on Connect-PnPOnline or by executing Get-PnPConnection.
+
+```yaml
+Type: PnPConnection
+Parameter Sets: (All)
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+## RELATED LINKS
+
+[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp)
\ No newline at end of file
diff --git a/documentation/Remove-PnPListItemVersion.md b/documentation/Remove-PnPListItemVersion.md
new file mode 100644
index 000000000..62922f05d
--- /dev/null
+++ b/documentation/Remove-PnPListItemVersion.md
@@ -0,0 +1,128 @@
+---
+Module Name: PnP.PowerShell
+title: Remove-PnPListItemVersion
+schema: 2.0.0
+applicable: SharePoint Online
+external help file: PnP.PowerShell.dll-Help.xml
+online version: https://pnp.github.io/powershell/cmdlets/Remove-PnPListItemVersion.html
+---
+
+# Remove-PnPListItemVersion
+
+## SYNOPSIS
+Removes a specific list item version.
+
+## SYNTAX
+
+```powershell
+Remove-PnPListItemVersion -List -Identity -Version [-Force] [-Verbose] [-Connection ]
+```
+
+## DESCRIPTION
+This cmdlet removes a specific list item version.
+
+## EXAMPLES
+
+### EXAMPLE 1
+```powershell
+Remove-PnPListItemVersion -List "Demo List" -Identity 1 -Version 512
+```
+
+Removes the list item version with Id 512.
+
+### EXAMPLE 2
+```powershell
+Remove-PnPListItemVersion -List "Demo List" -Identity 1 -Version "1.0"
+```
+
+Removes the list item version with version label "1.0".
+
+## PARAMETERS
+
+### -List
+The ID, Title or Url of the list.
+
+```yaml
+Type: ListPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Identity
+The ID of the listitem, or actual ListItem object.
+
+```yaml
+Type: ListItemPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Version
+The ID or label of the version.
+
+```yaml
+Type: ListItemPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Force
+If provided, no confirmation will be requested and the action will be performed.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Verbose
+When provided, additional debug statements will be shown while executing the cmdlet.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Connection
+Optional connection to be used by the cmdlet. Retrieve the value for this parameter by either specifying -ReturnConnection on Connect-PnPOnline or by executing Get-PnPConnection.
+
+```yaml
+Type: PnPConnection
+Parameter Sets: (All)
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+## RELATED LINKS
+
+[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp)
\ No newline at end of file
diff --git a/documentation/Restore-PnPListItemVersion.md b/documentation/Restore-PnPListItemVersion.md
new file mode 100644
index 000000000..b8d60c5d7
--- /dev/null
+++ b/documentation/Restore-PnPListItemVersion.md
@@ -0,0 +1,128 @@
+---
+Module Name: PnP.PowerShell
+title: Restore-PnPListItemVersion
+schema: 2.0.0
+applicable: SharePoint Online
+external help file: PnP.PowerShell.dll-Help.xml
+online version: https://pnp.github.io/powershell/cmdlets/Restore-PnPListItemVersion.html
+---
+
+# Restore-PnPListItemVersion
+
+## SYNOPSIS
+Restores a specific list item version.
+
+## SYNTAX
+
+```powershell
+Restore-PnPListItemVersion -List -Identity -Version [-Force] [-Verbose] [-Connection ]
+```
+
+## DESCRIPTION
+This cmdlet restores a specific list item version.
+
+## EXAMPLES
+
+### EXAMPLE 1
+```powershell
+Restore-PnPListItemVersion -List "Demo List" -Identity 1 -Version 512
+```
+
+Restores the list item version with Id 512.
+
+### EXAMPLE 2
+```powershell
+Restore-PnPListItemVersion -List "Demo List" -Identity 1 -Version "1.0"
+```
+
+Restores the list item version with version label "1.0".
+
+## PARAMETERS
+
+### -List
+The ID, Title or Url of the list.
+
+```yaml
+Type: ListPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Identity
+The ID of the listitem, or actual ListItem object.
+
+```yaml
+Type: ListItemPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Version
+The ID or label of the version.
+
+```yaml
+Type: ListItemPipeBind
+Parameter Sets: (All)
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Force
+If provided, no confirmation will be requested and the action will be performed.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Verbose
+When provided, additional debug statements will be shown while executing the cmdlet.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Connection
+Optional connection to be used by the cmdlet. Retrieve the value for this parameter by either specifying -ReturnConnection on Connect-PnPOnline or by executing Get-PnPConnection.
+
+```yaml
+Type: PnPConnection
+Parameter Sets: (All)
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+## RELATED LINKS
+
+[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp)
\ No newline at end of file