DEV Community

Kinga
Kinga

Posted on • Edited on

Duplicate list using PnP Powershell

UPDATE 2024.08.18: This post has been archived. Please use Copy-PnPList, which allows an existing list to be copied to either the same site or to another site (same tenant).

Invoke-PnPSiteTemplate allows, between others, copying lists and libraries between sites.

Duplicating a list requires, however, a small modification of the exported template.
First, export the list to a variable:
$template = Get-PnPSiteTemplate -OutputInstance -ListsToExtract $listName -Handlers Lists

Since the duplicated list must have new url, the template must be updated to change the list, forms and views urls:

for ($i = 0; $i -lt $data.Value.Count; $i++) {
            $data.Value[$i].Title = $newName
            $data.Value[$i].Url = "Lists/$newName"
            $data.Value[$i].DefaultDisplayFormUrl = ($template.Lists[0].DefaultDisplayFormUrl -replace "/$listName/", "/$newName/")
            $data.Value[$i].DefaultEditFormUrl = ($template.Lists[0].DefaultEditFormUrl -replace "/$listName/", "/$newName/")
            $data.Value[$i].DefaultNewFormUrl = ($template.Lists[0].DefaultNewFormUrl -replace "/$listName/", "/$newName/")
            for ($j = 0; $j -lt $data.Value[$i].Views.Count; $j++) {
                $data.Value[$i].Views[$j].SchemaXml = ($data.Value[$i].Views[$j].SchemaXml -replace "/$listName/", "/$newName/")
            }
        }
Enter fullscreen mode Exit fullscreen mode

If the list contains calculated fields, these must be updated as well to avoid errors during provisioning. It's a knowns issue and the code below is inspired by the code sample, using regex for simplicity

for ($i = 0; $i -lt $data.Value.Count; $i++) {
            $list = $data.Value[$i]
            $lookupField = @{}
            $list.Fields | ForEach-Object { $schema = [xml]$_.SchemaXml; $lookupField[$schema.Field.Name] = $schema.Field.DisplayName}
            $list.FieldRefs | ForEach-Object { $lookupField[$_.Name] = $_.DisplayName}

            # Find all calculated fields. Because of a PnP bug, a template uses [{fieldtitle:<internalFieldName>}] in calculated field formulas and not the field's Display Name
            # The code below finds all instances of {fieldtitle:xxxx} in the field's formula, figures out all of the internal names used (the xxxx after the "fieldtitle:"),
            # looks up the field's Display Name, and replaces {fieldtitle:xxxx} in the calculation with the field's Display Name.
            for ($j = 0; $j -lt $list.Fields.Count; $j++) {
                $results = ([Regex]::Matches($list.Fields[$j].SchemaXml, '(?<={fieldtitle:)(.*?)(?=})') | Select-Object Value).Value | Get-Unique


                foreach ($field in $results) {
                    $displayName = $lookupField[$field]
                    "$results - $displayName"
                    $data.Value[$i].Fields[$j].SchemaXml = $data.Value[$i].Fields[$j].SchemaXml.Replace("{fieldtitle:$($field)}", $displayName)
                }
            }
        }
Enter fullscreen mode Exit fullscreen mode

Full code:

function Copy-List{
    param (
        [string]$listName, 
        [string]$newName
    )

    function Set-Formulas{
        param(
            [ref]$data
        )
        Write-Host "Set-Formulas"

        # We're using a for loop instead of a foreach so that we can have an index that we can use to update the original $template object
        for ($i = 0; $i -lt $data.Value.Count; $i++) {
            $list = $data.Value[$i]
            $lookupField = @{}
            $list.Fields | ForEach-Object { $schema = [xml]$_.SchemaXml; $lookupField[$schema.Field.Name] = $schema.Field.DisplayName}
            $list.FieldRefs | ForEach-Object { $lookupField[$_.Name] = $_.DisplayName}

            # Find all calculated fields. Because of a PnP bug, a template uses [{fieldtitle:<internalFieldName>}] in calculated field formulas and not the field's Display Name
            # The code below finds all instances of {fieldtitle:xxxx} in the field's formula, figures out all of the internal names used (the xxxx after the "fieldtitle:"),
            # looks up the field's Display Name, and replaces {fieldtitle:xxxx} in the calculation with the field's Display Name.
            for ($j = 0; $j -lt $list.Fields.Count; $j++) {
                $results = ([Regex]::Matches($list.Fields[$j].SchemaXml, '(?<={fieldtitle:)(.*?)(?=})') | Select-Object Value).Value | Get-Unique


                foreach ($field in $results) {
                    $displayName = $lookupField[$field]
                    "$results - $displayName"
                    $data.Value[$i].Fields[$j].SchemaXml = $data.Value[$i].Fields[$j].SchemaXml.Replace("{fieldtitle:$($field)}", $displayName)
                }
            }
        }
    }

    function Set-ListViews{
        param(
            [ref]$data,
            [string]$listName,
            [string]$newName
        )
        Write-Host "Set-ListViews"
        for ($i = 0; $i -lt $data.Value.Count; $i++) {
            $data.Value[$i].Title = $newName
            $data.Value[$i].Url = "Lists/$newName"
            $data.Value[$i].DefaultDisplayFormUrl = ($template.Lists[0].DefaultDisplayFormUrl -replace "/$listName/", "/$newName/")
            $data.Value[$i].DefaultEditFormUrl = ($template.Lists[0].DefaultEditFormUrl -replace "/$listName/", "/$newName/")
            $data.Value[$i].DefaultNewFormUrl = ($template.Lists[0].DefaultNewFormUrl -replace "/$listName/", "/$newName/")
            for ($j = 0; $j -lt $data.Value[$i].Views.Count; $j++) {
                $data.Value[$i].Views[$j].SchemaXml = ($data.Value[$i].Views[$j].SchemaXml -replace "/$listName/", "/$newName/")
            }
        }
    }

    Write-Host "Duplicating list $listName"
    $currentFolder = Split-Path -Parent $PSCommandPath

    $template = Get-PnPSiteTemplate -OutputInstance -ListsToExtract $listName -Handlers Lists 
    Set-ListViews -data ([ref]$template.Lists) -listName $listName -newName $newName
    Set-Formulas -data ([ref]$template.Lists) 
    Save-PnPSiteTemplate -Template $template -Out "$currentFolder/temp/$newName.xml" -Force
    Invoke-PnPSiteTemplate -Path "$currentFolder/temp/$newName.xml" 
}
Enter fullscreen mode Exit fullscreen mode

Using the function is as easy as:
Copy-List -listName "List" -newName "ListDuplicate"

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Retry later