diff --git a/scripts/Copy-SharePoint-List-Columns-Between-Lists/README.md b/scripts/Copy-SharePoint-List-Columns-Between-Lists/README.md new file mode 100644 index 000000000..dae898184 --- /dev/null +++ b/scripts/Copy-SharePoint-List-Columns-Between-Lists/README.md @@ -0,0 +1,314 @@ +# How to Copy SharePoint List Columns Between Lists Using PowerShell + +## Professional Summary + +This PowerShell script copies selected SharePoint list fields from a source Microsoft 365 SharePoint site to a destination SharePoint site using PnP PowerShell authentication with an Azure AD application certificate. + +The script validates existing destination fields, identifies missing source fields, creates required fields using the original SharePoint schema XML, and produces an operational CSV report containing execution results, status, and error details. + +Designed for Microsoft 365 administrators, the script supports repeatable tenant administration tasks, controlled migrations, and SharePoint list standardisation activities. + +## Why it matters + +During Microsoft 365 migrations, tenant consolidations, SharePoint information architecture projects, or application deployments, administrators often need to replicate SharePoint list structures across sites. +Manually recreating fields introduces risks: + +- Incorrect field types +- Missing internal names +- Configuration inconsistencies +- Deployment delays +- Lack of audit visibility + +This script provides a controlled method to replicate required list columns while maintaining SharePoint schema integrity and generating an audit trail suitable for enterprise change management. + +## Benefits + +- Automates SharePoint field replication at scale +- Preserves SharePoint field schema definitions +- Avoids duplicate field creation +- Provides detailed operational reporting +- Supports unattended execution using certificate-based authentication +- Suitable for large Microsoft 365 environments +- Improves consistency across SharePoint site collections +- Provides migration validation evidence for governance and compliance requirements +- Helps teams adopt a more controlled Dev/Test/Production lifecycle for SharePoint solutions + +## Prerequisites + +- PowerShell 7+ +- PnP.PowerShell module +- Azure AD application registration +- Certificate-based authentication permissions: + - SharePoint Sites.Selected or appropriate SharePoint application permissions + +# [PnP PowerShell](#tab/pnpps) + +```powershell +#========================================== +# Configuration +#========================================== +$SourceSiteUrl = "https://contoso.sharepoint.com/sites/TEST-Finance" +$DestinationSiteUrl = "https://contoso.sharepoint.com/sites/PROD-Finance" + +$ClientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +$Thumbprint = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +$Tenant = "contoso.onmicrosoft.com" + +$SourceListName = "Finance" +$DestinationListName = "Finance" + +$FieldsToCopy = @( + "CostCentre" + "Department" + "BudgetCode" + "GLAccount" + "ExpenseCategory" + "BudgetAmount" + "ForecastAmount" + "ClaimAmount" + "Supplier" + "InvoiceNumber" + "PurchaseOrder" + "FinancialYear" + "ApprovalStatus" + "Approver" + "ReceiptReference" +) + +$OutputFolder = "C:\Temp\FieldCopy" + +#========================================== +# Initialisation +#========================================== + +$null = New-Item -Path $OutputFolder -ItemType Directory -Force + +$timeStamp = Get-Date -Format "yyyyMMdd_HHmmss" + +$reportPath = Join-Path $OutputFolder "FieldCopy_$timeStamp.csv" + +$startTime = Get-Date +$operator = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name +$computer = $env:COMPUTERNAME + +$results = [System.Collections.Generic.List[object]]::new() + +Write-Host "" +Write-Host "Field Copy Started" -ForegroundColor Cyan +Write-Host "Operator : $operator" +Write-Host "Computer : $computer" +Write-Host "Source Site : $SourceSiteUrl" +Write-Host "Destination Site : $DestinationSiteUrl" +Write-Host "" + +#========================================== +# Authentication +#========================================== + +try { + + $sourceConnection = Connect-PnPOnline ` + -Url $SourceSiteUrl ` + -ClientId $ClientId ` + -Thumbprint $Thumbprint ` + -Tenant $Tenant ` + -ReturnConnection ` + -ErrorAction Stop + + $destinationConnection = Connect-PnPOnline ` + -Url $DestinationSiteUrl ` + -ClientId $ClientId ` + -Thumbprint $Thumbprint ` + -Tenant $Tenant ` + -ReturnConnection ` + -ErrorAction Stop + + $sourceFields = Get-PnPField ` + -List $SourceListName ` + -Connection $sourceConnection ` + -ErrorAction Stop + + $destinationFields = Get-PnPField ` + -List $DestinationListName ` + -Connection $destinationConnection ` + -ErrorAction Stop +} +catch { + throw "Failed to connect to SharePoint or retrieve list fields. $($_.Exception.Message)" +} + +$destinationFieldNames = [System.Collections.Generic.HashSet[string]]::new( + [string[]]$destinationFields.InternalName +) + +$totalFields = $FieldsToCopy.Count +$currentField = 0 + +foreach ($fieldName in $FieldsToCopy) { + + $currentField++ + + Write-Progress ` + -Activity "Copying SharePoint Fields" ` + -Status "Processing $fieldName ($currentField of $totalFields)" ` + -PercentComplete (($currentField / $totalFields) * 100) + + $status = "" + $errorMessage = "" + + try { + + if ($destinationFieldNames.Contains($fieldName)) { + + $status = "Already Exists" + + Write-Host "[SKIP] $fieldName already exists." -ForegroundColor Yellow + } + else { + + $sourceField = $sourceFields | Where-Object InternalName -eq $fieldName + + if ($null -eq $sourceField) { + + $status = "Source Missing" + + Write-Host "[WARN] $fieldName not found in source list." -ForegroundColor Yellow + } + else { + + Add-PnPFieldFromXml ` + -List $DestinationListName ` + -FieldXml $sourceField.SchemaXml ` + -Connection $destinationConnection ` + -ErrorAction Stop | Out-Null + + $destinationFieldNames.Add($fieldName) | Out-Null + + $status = "Copied" + + Write-Host "[OK] $fieldName copied." -ForegroundColor Green + } + } + } + catch { + + $status = "Failed" + $errorMessage = $_.Exception.Message + + Write-Host "[FAIL] $fieldName" -ForegroundColor Red + Write-Host " $errorMessage" -ForegroundColor DarkRed + } + + $results.Add([PSCustomObject]@{ + Date = Get-Date + Operator = $operator + Computer = $computer + SourceSite = $SourceSiteUrl + DestinationSite = $DestinationSiteUrl + SourceList = $SourceListName + DestinationList = $DestinationListName + Field = $fieldName + Status = $status + Error = $errorMessage + }) +} + +Write-Progress -Activity "Copying SharePoint Fields" -Completed + +$results | + Sort-Object Field | + Export-Csv $reportPath -NoTypeInformation -Encoding UTF8 + +$endTime = Get-Date +$duration = $endTime - $startTime + +$copied = ($results | Where-Object Status -eq "Copied").Count +$existing = ($results | Where-Object Status -eq "Already Exists").Count +$missing = ($results | Where-Object Status -eq "Source Missing").Count +$failed = ($results | Where-Object Status -eq "Failed").Count + +Write-Host "" +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Field Copy Complete" -ForegroundColor Green +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "" +Write-Host ("Fields Requested : {0}" -f $totalFields) +Write-Host ("Copied : {0}" -f $copied) -ForegroundColor Green +Write-Host ("Already Exists : {0}" -f $existing) -ForegroundColor Yellow +Write-Host ("Source Missing : {0}" -f $missing) -ForegroundColor Yellow +Write-Host ("Failed : {0}" -f $failed) -ForegroundColor Red +Write-Host "" +Write-Host ("Started : {0}" -f $startTime) +Write-Host ("Completed : {0}" -f $endTime) +Write-Host ("Duration : {0}" -f $duration) +Write-Host "" +Write-Host ("CSV Report : {0}" -f $reportPath) +Write-Host "" + +``` +[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)] + +# [Install module](#tab/module) + +```powershell + +Install-Module PnP.PowerShell -Scope CurrentUser + +``` + +# [Run](#tab/run) + +```powershell + +.\Copy-SharePointListColumnsBetweenLists.ps1 + +``` +[!INCLUDE [More about PnP PowerShell](../../docfx/includes/MORE-PNPPS.md)] +*** + +## Output + +### CSV Report Location + +C:\Temp\FieldCopy\ + +### Report Contents + +The script generates a CSV report containing: + +|Column|Description| +|------|-----------| +|Date|Execution timestamp| +|Operator|Account running the script| +|Computer|Execution host| +|SourceSite|Source SharePoint site| +|DestinationSite|Target SharePoint site| +|SourceList|Source list name| +|DestinationList|Destination list name| +|Field|Field processed| +|Status|Copy result| +|Error|Failure details if applicable| + +## Notes + +- Field creation uses the original SharePoint SchemaXml to preserve configuration. +- Existing destination fields are skipped automatically. +- The script is idempotent and safe to rerun. +- For enterprise deployments, store application IDs and certificates securely using Azure Key Vault, managed automation accounts, or enterprise credential management solutions. + +## Contributors + +|Author(s)| +|-----------| +|[Josiah Opiyo](https://github.com/ojopiyo)| + +*Built with a focus on automation, governance, least privilege, and clean Microsoft 365 tenants-helping M365 admins gain visibility and reduce operational risk.* + +## Version history + +|Version|Date|Comments| +|-------|----|--------| +|1.0|July 11, 2026|Initial release| + +[!INCLUDE [DISCLAIMER](../../docfx/includes/DISCLAIMER.md)] + diff --git a/scripts/Copy-SharePoint-List-Columns-Between-Lists/assets/preview.png b/scripts/Copy-SharePoint-List-Columns-Between-Lists/assets/preview.png new file mode 100644 index 000000000..72a9255df Binary files /dev/null and b/scripts/Copy-SharePoint-List-Columns-Between-Lists/assets/preview.png differ diff --git a/scripts/Copy-SharePoint-List-Columns-Between-Lists/assets/sample.json b/scripts/Copy-SharePoint-List-Columns-Between-Lists/assets/sample.json new file mode 100644 index 000000000..f3b80180b --- /dev/null +++ b/scripts/Copy-SharePoint-List-Columns-Between-Lists/assets/sample.json @@ -0,0 +1,56 @@ +[ + { + "name": "copy-sharepoint-list-columns-between-lists", + "source": "pnp", + "title": "How to Copy SharePoint List Columns Between Lists Using PowerShell", + "shortDescription": "This script copies selected SharePoint list fields from a source list to a destination list using PnP PowerShell and certificate-based authentication.", + "url": "https://pnp.github.io/script-samples/copy-sharepoint-list-columns-between-lists/README.html", + "longDescription": [ + "Replicates selected SharePoint list columns by comparing destination fields, copying missing source fields with SchemaXml, and exporting a CSV execution report." + ], + "creationDateTime": "2026-07-11", + "updateDateTime": "2026-07-11", + "products": [ + "SharePoint" + ], + "metadata": [ + { + "key": "PNP-POWERSHELL", + "value": "2.2.0" + } + ], + "categories": [ + "Provision", + "Configure" + ], + "tags": [ + "Connect-PnPOnline", + "Get-PnPField", + "Add-PnPFieldFromXml", + "Export-Csv" + ], + "thumbnails": [ + { + "type": "image", + "order": 100, + "url": "https://raw.githubusercontent.com/pnp/script-samples/main/scripts/copy-sharepoint-list-columns-between-lists/assets/preview.png", + "alt": "Preview of the sample How to Copy SharePoint List Columns Between Lists Using PowerShell" + } + ], + "authors": [ + { + "gitHubAccount": "ojopiyo", + "company": "", + "pictureUrl": "https://github.com/ojopiyo.png", + "name": "Josiah Opiyo" + } + ], + "references": [ + { + "name": "Want to learn more about PnP PowerShell and the cmdlets", + "description": "Check out the PnP PowerShell site to get started and for the reference to the cmdlets.", + "url": "https://aka.ms/pnp/powershell" + } + ] + } +]