SharePoint Online: Copy Attachments from One List to Another using PowerShell

Requirement: Copy Attachments from one list to another in SharePoint Online.

PowerShell to Copy Attachment to Another List in SharePoint Online:

I had a requirement to copy attachments between SharePoint Online lists. (only attachments – Not list items!). This script gets the attachments from the source list, searches for the matching list item based on the “Mapping Column” value, and attaches list attachments to the destination list items.

#Load SharePoint CSOM Assemblies
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"

Function Copy-Attachments()
{
    param
    (
        [Parameter(Mandatory=$true)] [string] $SiteURL,
        [Parameter(Mandatory=$true)] [string] $SourceListName,
        [Parameter(Mandatory=$true)] [string] $TargetListName,
        [Parameter(Mandatory=$false)] [string] $MappingColumn="Title"
    )    
    Try {
    #Setup Credentials to connect
    $Cred = Get-Credential
    $Cred = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Cred.UserName,$Cred.Password)
    
    #Setup the context
    $Ctx = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)
    $Ctx.Credentials = $Cred

    #Get the Source List & Target Lists
    $SourceList = $Ctx.Web.Lists.GetByTitle($SourceListName)
    $TargetList = $Ctx.Web.Lists.GetByTitle($TargetListName)
    
    #Get All Items
    $SourceListItems = $SourceList.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
    $TargetListItems = $TargetList.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
    $Ctx.Load($SourceListItems)
    $Ctx.Load($TargetListItems)
    $Ctx.ExecuteQuery()

    #Iterate through each list item from source
    ForEach($SourceItem in $SourceListItems)
    {
        #Get All attachments from the List Item
        $AttachmentsColl = $SourceItem.AttachmentFiles
        $Ctx.Load($AttachmentsColl)
        $Ctx.ExecuteQuery()

        #Get Matching List item in the target list
        $ListItem = $TargetListItems | Where { $_[$MappingColumn] -eq $SourceItem[$MappingColumn]}
        if($ListItem -ne $null)
        {
            #Get attachment for each list item
            ForEach($Attachment in $AttachmentsColl)
            {
                Write-host "Copying attachment '$($Attachment.FileName)' from Item ID '$($SourceItem.ID)'"
                #Get attachment File from Source List Item
                $File = $Ctx.Web.GetFileByServerRelativeUrl($Attachment.ServerRelativeUrl)
                $Ctx.Load($File)
                $Ctx.ExecuteQuery()

                #Get the Source File Content
                $FileContent = $File.OpenBinaryStream()
                $Ctx.ExecuteQuery()
                $Buffer = New-Object Byte[]($File.length)
                $BytesRead = $FileContent.Value.Read($Buffer, 0, $Buffer.Length)
                $MemoryStream = New-Object -TypeName System.IO.MemoryStream(,$Buffer)

                #Add Attachment to Target List Item
                $AttachmentCreation = New-Object Microsoft.SharePoint.Client.AttachmentCreationInformation
                $AttachmentCreation.ContentStream = $MemoryStream
                $AttachmentCreation.FileName = $Attachment.FileName 
                [void]$ListItem.AttachmentFiles.Add($AttachmentCreation)
                $Ctx.ExecuteQuery()
                $MemoryStream.Close()                
            }
       }
    }

    write-host  -f Green "List Attachments Copied from '$SourceListName' to '$TargetListName' !"
    }
    Catch {
        write-host -f Red "Error Copying List Attachments!" $_.Exception.Message
    } 
}

#Set Parameters
$SiteURL= "https://crescent.sharepoint.com/"
$SourceListName="Projects"
$TargetListName="Project Temp"

#Call the function to copy list items
Copy-Attachments -siteURL $SiteURL -SourceListName $SourceListName -TargetListName $TargetListName 

PnP PowerShell to Copy Attachments between List Items

Here is the PnP PowerShell way to copy attachments between list items in SharePoint Online:

#Parameters
$SiteURL= "https://crescent.sharepoint.com/sites/marketing"
$ListName = "Config"

#Function to copy attachments between list items
Function Copy-SPOAttachments($SourceItem, $TargetItem)
{
    Try {  
        #Get All Attachments from Source
        $Attachments = Get-PnPProperty -ClientObject $SourceItem -Property "AttachmentFiles"
        $Attachments | ForEach-Object {
        #Download the Attachment to Temp
        $File  = Get-PnPFile -Url $_.ServerRelativeUrl -FileName $_.FileName -Path $env:TEMP -AsFile -force

        #Add Attachment to Target List Item
        $FileStream = New-Object IO.FileStream(($env:TEMP+"\"+$_.FileName),[System.IO.FileMode]::Open)  
        $AttachmentInfo = New-Object -TypeName Microsoft.SharePoint.Client.AttachmentCreationInformation
        $AttachmentInfo.FileName = $_.FileName
        $AttachmentInfo.ContentStream = $FileStream
        $AttachFile = $TargetItem.AttachmentFiles.add($AttachmentInfo)
        $Ctx.ExecuteQuery()    
    
        #Delete the Temporary File
        Remove-Item -Path $env:TEMP\$($_.FileName) -Force
        }
    }
    Catch {
        write-host -f Red "Error Copying Attachments:" $_.Exception.Message
    }
}

#Connect to PnP Online
Connect-PnPOnline -Url $SiteURL -Credentials (Get-credential)
$Ctx=Get-PnPContext

#Get Source and Target List Items
$SourceItem = Get-PnPListItem -List $ListName -Id 1
$TargetItem = Get-PnPListItem -List $ListName -Id 4

#Call the function to copy attachments between list items
Copy-SPOAttachments $SourceItem $TargetItem

If you want to copy the list attachment to a document library, use: SharePoint Online: Copy Attachment from List to Document Library

Salaudeen Rajack

Salaudeen Rajack - Information Technology Expert with Two-decades of hands-on experience, specializing in SharePoint, PowerShell, Microsoft 365, and related products. He has held various positions including SharePoint Architect, Administrator, Developer and consultant, has helped many organizations to implement and optimize SharePoint solutions. Known for his deep technical expertise, He's passionate about sharing the knowledge and insights to help others, through the real-world articles!

8 thoughts on “SharePoint Online: Copy Attachments from One List to Another using PowerShell

  • Hi.. I get this Error Copying Attachments: Cannot convert argument “parameters”, with value: “Microsoft.SharePoint.Client.AttachmentCreationInformation”, for “Add” to type “Microsoft.SharePoint.Client.AttachmentCreationInformation” perhaps PNP and SPO cmdlets are conflicting?

    Reply
  • Thank you for sharing your knowledge. This is one of the best sites on Sharepoint solutions.
    I am having an issue with file attachment copying from one list to other. I could copy the attachment to other list but the file is not being opened as the file size appearing as 1KB. Looks like Buffer is not getting the data.

    Here is my code. please assist.
    #Load SharePoint CSOM Assemblies
    Add-Type –Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\ISAPI\Microsoft.SharePoint.Client.dll”
    Add-Type –Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\ISAPI\Microsoft.SharePoint.Client.Runtime.dll”

    #Get attachment for each list item
    ForEach($Attachment in $AttachmentsColl)
    {

    $AttachmentCreation = New-Object Microsoft.SharePoint.Client.AttachmentCreationInformation
    #Get the Source attachment
    $FileContent = [Microsoft.SharePoint.Client.File]::OpenBinaryDirect($Ctx, $Attachment.ServerRelativeUrl)
    $Buffer = New-Object byte[]($FileContent.length)
    $BytesRead = $FileContent.stream.Read($Buffer, 0, $Buffer.Length)
    $ContentStream = New-Object -TypeName System.IO.MemoryStream ($Buffer)
    $AttachmentCreation.ContentStream = $ContentStream
    $AttachmentCreation.FileName = $Attachment.FileName
    [void]$TargetItem.AttachmentFiles.Add($AttachmentCreation)
    $FileContent.Stream.Close()
    $Archivectx.ExecuteQuery()
    }
    }

    I could download and upload the attachment but thats taking much time and cant be used for our requirement.

    Reply
  • Only One character of the file content is being copied. Any alternatives?

    Reply
  • Copied attachment is blank always. IS there a workaround?

    Reply
  • I think you you put more comments in the code so that the people who are no that expert in PS can understand a bit better the code. Thanks for sharing the wisdom though!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *