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

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

PowerShell to Copy Attachment to Another List in SharePoint Online:
Had a requirement to copy attachments between SharePoint Online lists. (only attachment - Not list items!). This script gets the attachments from source list, searches the matching list item based on "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 list attachment to document library, use: SharePoint Online: Copy Attachment from List to Document Library

7 comments:

  1. 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!

    ReplyDelete
  2. Copied attachment is blank always. IS there a workaround?

    ReplyDelete
    Replies
    1. Script is updated with a different approach to copy attachments. Try now!

      Delete
  3. Only One character of the file content is being copied. Any alternatives?

    ReplyDelete
  4. 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\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"

    #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.

    ReplyDelete
    Replies
    1. Script has been updated with a different approach to fix the issue. Try now!

      Delete

Please Login and comment to get your questions answered!

Powered by Blogger.