Introduction

Hi guys, If you have already tried my previous script (available here) it's time to develop scripts on a deeper level. Here is one more cool script to run so that we can copy all the list items from a Source List to Target List within the same collection. This is a smart modern way of copying list items without losing data from any column values. The amazing part of this PS script is it can handle all types of List Columns like look-ups, Metadata, other data types + attachments. I have added/updated a few more blocks of code on my PS script and present a better but more refined PnP script.
Script
Here is the PnP Script,
  1. #Function to copy attachments between list items
  2. Function Copy - SPOAttachments() {
  3. param(
  4. [Parameter(Mandatory = $true)][Microsoft.SharePoint.Client.ListItem] $SourceItem,
  5. [Parameter(Mandatory = $true)][Microsoft.SharePoint.Client.ListItem] $DestinationItem)
  6. Try {
  7. #Get All Attachments from Source list items
  8. $Attachments = Get - PnPProperty - ClientObject $SourceItem - Property "AttachmentFiles"
  9. $Attachments | ForEach - Object {
  10. #Download the Attachment to Temp
  11. $File = Get - PnPFile - Connection $SourceConn - Url $_.ServerRelativeUrl - FileName $_.FileName - Path $Env: TEMP - AsFile - force
  12. #Add Attachment to Destination List Item
  13. $FileStream = New - Object IO.FileStream(($Env: TEMP + "\"+$_.FileName),[System.IO.FileMode]::Open)
  14. $AttachmentInfo = New - Object - TypeName Microsoft.SharePoint.Client.AttachmentCreationInformation $AttachmentInfo.FileName = $_.FileName $AttachmentInfo.ContentStream = $FileStream $AttachFile = $DestinationItem.AttachmentFiles.Add($AttachmentInfo) Invoke - PnPQuery - Connection $DestinationConn #Delete the Temporary File Remove - Item - Path $Env: TEMP\ $($_.FileName) - Force
  15. }
  16. }
  17. Catch {
  18. write - host - f Red "Error Copying Attachments:"
  19. $_.Exception.Message
  20. }
  21. }
  22. #Function to copy list items from one list to another
  23. Function Copy - SPOListItems() {
  24. param(
  25. [Parameter(Mandatory = $true)][Microsoft.SharePoint.Client.List] $SourceList,
  26. [Parameter(Mandatory = $true)][Microsoft.SharePoint.Client.List] $DestinationList)
  27. Try {
  28. #Get All Items from the Source List in batches
  29. Write - Progress - Activity "Reading Source..." - Status "Getting Items from Source List. Please wait..."
  30. $SourceListItems = Get - PnPListItem - List $SourceList - PageSize 500 - Connection $SourceConn
  31. $SourceListItemsCount = $SourceListItems.count
  32. Write - host "Total Number of Items Found:"
  33. $SourceListItemsCount
  34. #Get fields to Update from the Source List - Skip Read only, hidden fields, content type and attachments
  35. $SourceListFields = Get - PnPField - List $SourceListName | Where {
  36. (-Not($_.ReadOnlyField)) - and(-Not($_.Hidden)) - and($_.InternalName - ne "ContentType") - and($_.InternalName - ne "Attachments")
  37. }
  38. #Loop through each item in the source and Get column values, add them to Destination[int] $Counter = 1
  39. ForEach($SourceItem in $SourceListItems) {
  40. $ItemValue = @ {}
  41. #Map each field from source list to Destination list
  42. Foreach($SourceField in $SourceListFields) {
  43. #Check
  44. if the Field value is not Null
  45. If($SourceItem[$SourceField.InternalName] - ne $Null) {
  46. #Handle Special Fields
  47. $FieldType = $SourceField.TypeAsString
  48. If($FieldType - eq "User" - or $FieldType - eq "UserMulti" - or $FieldType - eq "Lookup" - or $FieldType - eq "LookupMulti") #People Picker or Lookup Field {
  49. $LookupIDs = $SourceItem[$SourceField.InternalName] | ForEach - Object {
  50. $_.LookupID.ToString()
  51. }
  52. $ItemValue.add($SourceField.InternalName, $LookupIDs)
  53. }
  54. ElseIf($FieldType - eq "URL") #Hyperlink {
  55. $URL = $SourceItem[$SourceField.InternalName].URL
  56. $Description = $SourceItem[$SourceField.InternalName].Description
  57. $ItemValue.add($SourceField.InternalName, "$URL, $Description")
  58. }
  59. ElseIf($FieldType - eq "TaxonomyFieldType" - or $FieldType - eq "TaxonomyFieldTypeMulti") #MMS {
  60. $TermGUIDs = $SourceItem[$SourceField.InternalName] | ForEach - Object {
  61. $_.TermGuid.ToString()
  62. }
  63. $ItemValue.add($SourceField.InternalName, $TermGUIDs)
  64. }
  65. Else {
  66. #Get Source Field Value and add to Hashtable
  67. $ItemValue.add($SourceField.InternalName, $SourceItem[$SourceField.InternalName])
  68. }
  69. }
  70. }
  71. Write - Progress - Activity "Copying List Items:" - Status "Copying Item ID '$($SourceItem.Id)' from Source List ($($Counter) of $($SourceListItemsCount))" - PercentComplete(($Counter / $SourceListItemsCount) * 100)
  72. #Copy column value from source to Destination
  73. $NewItem = Add - PnPListItem - List $DestinationList - Values $ItemValue
  74. #Copy Attachments
  75. Copy - SPOAttachments - SourceItem $SourceItem - DestinationItem $NewItem
  76. Write - Host "Copied Item ID from Source to Destination List:$($SourceItem.Id) ($($Counter) of $($SourceListItemsCount))"
  77. $Counter++
  78. }
  79. }
  80. Catch {
  81. Write - host - f Red "Error:"
  82. $_.Exception.Message
  83. }
  84. }
  85. #Set Parameters
  86. $SourceSiteURL = "https://abcd.sharepoint.com/sites/ArchivalDevelopmentTest"
  87. $SourceListName = "CountriesArchive"
  88. $DestinationSiteURL = "https://abcd.sharepoint.com/sites/ArchivalDevelopmentTest"
  89. $DestinationListName = "CountriesArchive2"
  90. #Connect to Source and destination sites
  91. $SourceConn = Connect - PnPOnline - Url $SourceSiteURL - UseWebLogin - ReturnConnection
  92. $SourceList = Get - PnPList - Identity $SourceListName - Connection $SourceConn
  93. $DestinationConn = Connect - PnPOnline - Url $DestinationSiteURL - UseWebLogin - ReturnConnection
  94. $DestinationList = Get - PnPList - Identity $DestinationListName - Connection $DestinationConn
  95. #Call the Function to Copy List Items between Lists
  96. Copy - SPOListItems - SourceList $SourceList - DestinationList $DestinationList
Just configure the above highlighted areas as per your requirement.
Steps
Open Windows ISE >> Open the above script file >> Run Script or F5
Advantages
You can use the above script for a cool, smooth archival process = Copy + Remove as you know how to remove List Items batchwise using PowerShell once above Copying is finished.
Applicable for both modern and classic experience.
Drawbacks
Copying Duplicate values if we run it multiple times
Can copy ListItems to another List within the same Site Collection only as SP Context can read one Site Coll at a time.
CrossSites not supported
Other Ideas:- Go with Migration Tools if you want to go with Site Collections from different Tenants on SP Online.
Cheers!