Refactor parameter names and enhance mapping functionality in migration script

This commit is contained in:
Torsten Brendgen
2026-04-15 18:14:51 +02:00
parent 25428ccce2
commit 0525206707
2 changed files with 432 additions and 129 deletions

View File

@@ -1,17 +1,23 @@
[CmdletBinding()]
param(
[string]$WebUrl,
[Alias("WebUrl")]
[string]$SourceUrl,
[Parameter(Mandatory = $true)]
[string]$OutputPath,
[string]$OutputPath = (Join-Path -Path (Get-Location).Path -ChildPath "SPMigrationOutput"),
[switch]$IncludeHiddenLibraries,
[switch]$IncludeHiddenLists,
[string]$TargetWebUrl,
[Alias("TargetWebUrl")]
[string]$TargetUrl,
[string]$MappingCsvPath,
[Alias("MappingCsvPath")]
[string]$MappingTable,
[switch]$Export,
[switch]$Import,
[switch]$ImportFiles,
@@ -19,8 +25,10 @@ param(
[switch]$OverwriteFiles,
[Alias("ExportOnly")]
[switch]$ExportOnly,
[Alias("ImportOnly")]
[switch]$ImportOnly
)
@@ -51,7 +59,7 @@ function Invoke-MigrationImport {
[string]$TargetWebUrl,
[Parameter(Mandatory = $true)]
[string]$MappingCsvPath,
[string]$MappingTablePath,
[switch]$ImportFiles,
@@ -63,13 +71,8 @@ function Invoke-MigrationImport {
$resolvedInputPath = [System.IO.Path]::GetFullPath($InputPath)
$filesRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Files")
$listsRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Lists")
$manifestPath = [System.IO.Path]::Combine($resolvedInputPath, "manifest.csv")
$fieldMapping = Get-FieldMapping -Path $MappingCsvPath
$containerManifestMap = Get-ContainerManifestMap -ManifestRows (Read-ManifestRows -ManifestPath $manifestPath)
if ($fieldMapping.Count -eq 0) {
throw "Die Mapping-CSV enthaelt keine gueltigen Zuordnungen."
}
$resolvedMappingTablePath = [System.IO.Path]::GetFullPath($MappingTablePath)
$migrationMappingTable = Get-MigrationMappingTable -Path $resolvedMappingTablePath
$shouldImportFiles = $ImportFiles.IsPresent
$shouldImportLists = $ImportLists.IsPresent
@@ -85,11 +88,11 @@ function Invoke-MigrationImport {
$targetWeb = Get-SPWeb -Identity $TargetWebUrl -ErrorAction Stop
if ($shouldImportFiles) {
Import-SPDocumentLibraries -Web $targetWeb -FilesRootPath $filesRootPath -FieldMapping $fieldMapping -ContainerManifestMap $containerManifestMap -OverwriteFiles:$OverwriteFiles
Import-SPDocumentLibraries -Web $targetWeb -FilesRootPath $filesRootPath -MigrationMappingTable $migrationMappingTable -OverwriteFiles:$OverwriteFiles
}
if ($shouldImportLists) {
Import-SPLists -Web $targetWeb -ListsRootPath $listsRootPath -FieldMapping $fieldMapping -ContainerManifestMap $containerManifestMap
Import-SPLists -Web $targetWeb -ListsRootPath $listsRootPath -MigrationMappingTable $migrationMappingTable
}
Write-Host ("Import abgeschlossen. Quelle: {0}" -f $resolvedInputPath)
@@ -825,6 +828,101 @@ function Read-ManifestRows {
return @(Import-Csv -Path $ManifestPath -Delimiter ";")
}
function New-MigrationContainerMappingEntry {
param(
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List
)
return [PSCustomObject]@{
ObjectType = $ObjectType
SourceTitle = $List.Title
TargetTitle = $List.Title
BaseType = $List.BaseType.ToString()
BaseTemplate = [int]$List.BaseTemplate
RootFolderUrl = $List.RootFolder.ServerRelativeUrl
Hidden = [bool]$List.Hidden
}
}
function New-MigrationFieldMappingEntries {
param(
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[Microsoft.SharePoint.SPList]$List
)
$rows = @()
foreach ($field in @($List.Fields | Sort-Object -Property InternalName)) {
$rows += [PSCustomObject]@{
ObjectType = $ObjectType
ContainerSourceTitle = $List.Title
SourceInternalName = $field.InternalName
TargetInternalName = $field.InternalName
DisplayName = $field.Title
TypeAsString = $field.TypeAsString
Hidden = [bool]$field.Hidden
ReadOnly = [bool]$field.ReadOnlyField
Sealed = [bool]$field.Sealed
}
}
return $rows
}
function New-MigrationMappingTable {
param(
[Parameter(Mandatory = $true)]
[string]$SourceWebUrl,
[Microsoft.SharePoint.SPList[]]$DocumentLibraries = @(),
[Microsoft.SharePoint.SPList[]]$Lists = @()
)
$libraryMappings = @()
$listMappings = @()
$metadataColumnMappings = @()
foreach ($library in @($DocumentLibraries)) {
$libraryMappings += New-MigrationContainerMappingEntry -ObjectType "DocumentLibrary" -List $library
$metadataColumnMappings += New-MigrationFieldMappingEntries -ObjectType "DocumentLibrary" -List $library
}
foreach ($list in @($Lists)) {
$listMappings += New-MigrationContainerMappingEntry -ObjectType "List" -List $list
$metadataColumnMappings += New-MigrationFieldMappingEntries -ObjectType "List" -List $list
}
return [PSCustomObject]@{
SchemaVersion = 1
GeneratedAtUtc = [System.DateTime]::UtcNow.ToString("o")
SourceWebUrl = $SourceWebUrl
LibraryMappings = $libraryMappings
ListMappings = $listMappings
MetadataColumnMappings = $metadataColumnMappings
}
}
function Write-MigrationMappingTable {
param(
[Parameter(Mandatory = $true)]
$MappingTable,
[Parameter(Mandatory = $true)]
[string]$Path
)
$json = $MappingTable | ConvertTo-Json -Depth 10
[System.IO.File]::WriteAllText($Path, $json, [System.Text.Encoding]::UTF8)
}
function Get-ContainerManifestEntryKey {
param(
[Parameter(Mandatory = $true)]
@@ -935,6 +1033,185 @@ function Read-JsonFile {
return Get-Content -Path $Path -Raw -Encoding UTF8 | ConvertFrom-Json
}
function Get-MigrationMappingTable {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not [System.IO.File]::Exists($Path)) {
throw ("MappingTable nicht gefunden: {0}" -f $Path)
}
$extension = [System.IO.Path]::GetExtension($Path)
if ($extension -ieq ".json") {
$mappingTable = Read-JsonFile -Path $Path
}
elseif ($extension -ieq ".csv") {
$fieldMapping = Get-FieldMapping -Path $Path
$metadataColumnMappings = @()
foreach ($sourceInternalName in $fieldMapping.Keys) {
$metadataColumnMappings += [PSCustomObject]@{
ObjectType = "*"
ContainerSourceTitle = "*"
SourceInternalName = [string]$sourceInternalName
TargetInternalName = [string]$fieldMapping[$sourceInternalName]
DisplayName = ""
TypeAsString = ""
Hidden = $false
ReadOnly = $false
Sealed = $false
}
}
$mappingTable = [PSCustomObject]@{
SchemaVersion = 1
GeneratedAtUtc = [System.DateTime]::UtcNow.ToString("o")
SourceWebUrl = ""
LibraryMappings = @()
ListMappings = @()
MetadataColumnMappings = $metadataColumnMappings
}
}
else {
throw "MappingTable muss eine JSON- oder CSV-Datei sein."
}
if ($null -eq $mappingTable) {
throw "MappingTable konnte nicht gelesen werden."
}
return $mappingTable
}
function Get-ContainerMappingTableEntryKey {
param(
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
return ("{0}|{1}" -f $ObjectType.Trim(), $SourceTitle.Trim()).ToLowerInvariant()
}
function Get-ContainerMappingMap {
param(
[Parameter(Mandatory = $true)]
$MigrationMappingTable
)
$containerMap = @{}
$rows = @(
@(Get-ObjectPropertyValue -Object $MigrationMappingTable -PropertyName "LibraryMappings" -DefaultValue @()) +
@(Get-ObjectPropertyValue -Object $MigrationMappingTable -PropertyName "ListMappings" -DefaultValue @())
)
foreach ($row in $rows) {
$objectType = [string](Get-ObjectPropertyValue -Object $row -PropertyName "ObjectType")
$sourceTitle = [string](Get-ObjectPropertyValue -Object $row -PropertyName "SourceTitle")
if ([string]::IsNullOrWhiteSpace($objectType) -or [string]::IsNullOrWhiteSpace($sourceTitle)) {
continue
}
$key = Get-ContainerMappingTableEntryKey -ObjectType $objectType -SourceTitle $sourceTitle
$containerMap[$key] = $row
}
return $containerMap
}
function Get-TargetContainerTitleFromMappingTable {
param(
[Parameter(Mandatory = $true)]
[hashtable]$ContainerMappingMap,
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
$key = Get-ContainerMappingTableEntryKey -ObjectType $ObjectType -SourceTitle $SourceTitle
$entry = $ContainerMappingMap[$key]
if ($null -eq $entry) {
return $SourceTitle
}
$targetTitle = [string](Get-ObjectPropertyValue -Object $entry -PropertyName "TargetTitle")
if ([string]::IsNullOrWhiteSpace($targetTitle)) {
return $SourceTitle
}
return $targetTitle.Trim()
}
function Test-ContainerHasExplicitTargetMappingInTable {
param(
[Parameter(Mandatory = $true)]
[hashtable]$ContainerMappingMap,
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
$key = Get-ContainerMappingTableEntryKey -ObjectType $ObjectType -SourceTitle $SourceTitle
$entry = $ContainerMappingMap[$key]
if ($null -eq $entry) {
return $false
}
$targetTitle = [string](Get-ObjectPropertyValue -Object $entry -PropertyName "TargetTitle")
return -not [string]::IsNullOrWhiteSpace($targetTitle)
}
function Get-FieldMappingForContainer {
param(
[Parameter(Mandatory = $true)]
$MigrationMappingTable,
[Parameter(Mandatory = $true)]
[string]$ObjectType,
[Parameter(Mandatory = $true)]
[string]$SourceTitle
)
$rows = @(Get-ObjectPropertyValue -Object $MigrationMappingTable -PropertyName "MetadataColumnMappings" -DefaultValue @())
$mapping = [ordered]@{}
foreach ($row in $rows) {
$rowObjectType = [string](Get-ObjectPropertyValue -Object $row -PropertyName "ObjectType")
$rowContainerSourceTitle = [string](Get-ObjectPropertyValue -Object $row -PropertyName "ContainerSourceTitle")
$sourceInternalName = [string](Get-ObjectPropertyValue -Object $row -PropertyName "SourceInternalName")
$targetInternalName = [string](Get-ObjectPropertyValue -Object $row -PropertyName "TargetInternalName")
if ([string]::IsNullOrWhiteSpace($sourceInternalName) -or [string]::IsNullOrWhiteSpace($targetInternalName)) {
continue
}
$objectTypeMatches = ($rowObjectType -eq "*") -or ($rowObjectType -eq $ObjectType)
$containerMatches = [string]::IsNullOrWhiteSpace($rowContainerSourceTitle) -or ($rowContainerSourceTitle -eq "*") -or ($rowContainerSourceTitle -eq $SourceTitle)
if (-not $objectTypeMatches -or -not $containerMatches) {
continue
}
$mapping[$sourceInternalName.Trim()] = $targetInternalName.Trim()
}
return $mapping
}
function Resolve-SPField {
param(
[Parameter(Mandatory = $true)]
@@ -1388,10 +1665,7 @@ function Import-SPDocumentLibraries {
[string]$FilesRootPath,
[Parameter(Mandatory = $true)]
[System.Collections.IDictionary]$FieldMapping,
[Parameter(Mandatory = $true)]
[hashtable]$ContainerManifestMap,
$MigrationMappingTable,
[switch]$OverwriteFiles
)
@@ -1401,6 +1675,7 @@ function Import-SPDocumentLibraries {
return
}
$containerMappingMap = Get-ContainerMappingMap -MigrationMappingTable $MigrationMappingTable
$metadataFiles = @(Get-ChildItem -Path $FilesRootPath -Recurse -File -Filter *.properties.json)
Write-Host ("Gefundene Datei-Metadaten: {0}" -f $metadataFiles.Length)
@@ -1421,14 +1696,14 @@ function Import-SPDocumentLibraries {
continue
}
$targetLibraryTitle = Get-TargetContainerTitle -ContainerManifestMap $ContainerManifestMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
$targetLibraryTitle = Get-TargetContainerTitleFromMappingTable -ContainerMappingMap $containerMappingMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
$targetLibrary = $Web.Lists.TryGetList($targetLibraryTitle)
if ($null -eq $targetLibrary) {
if (Test-ContainerHasExplicitTargetMapping -ContainerManifestMap $ContainerManifestMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle) {
if (Test-ContainerHasExplicitTargetMappingInTable -ContainerMappingMap $containerMappingMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle) {
Write-Warning ("Zielbibliothek '{0}' fuer Quellbibliothek '{1}' nicht gefunden. Bitte diese Bibliothek im Ziel manuell anlegen." -f $targetLibraryTitle, $sourceLibraryTitle)
}
else {
Write-Warning ("Zielbibliothek '{0}' nicht gefunden und kein TargetTitle im manifest.csv gepflegt. Bitte Bibliothek manuell anlegen oder TargetTitle im Manifest setzen." -f $sourceLibraryTitle)
Write-Warning ("Zielbibliothek '{0}' nicht gefunden und kein TargetTitle in der MappingTable gepflegt. Bitte Bibliothek manuell anlegen oder TargetTitle in der MappingTable setzen." -f $sourceLibraryTitle)
}
continue
}
@@ -1459,6 +1734,7 @@ function Import-SPDocumentLibraries {
$spItem = $spFile.Item
if ($null -ne $itemMetadata) {
$fieldMapping = Get-FieldMappingForContainer -MigrationMappingTable $MigrationMappingTable -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
Apply-FieldMappingToItem -Item $spItem -FieldMapping $FieldMapping -SourceFieldValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldValues") -SourceFieldTextValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldTextValues")
Save-SPListItem -Item $spItem
}
@@ -1474,10 +1750,7 @@ function Import-SPLists {
[string]$ListsRootPath,
[Parameter(Mandatory = $true)]
[System.Collections.IDictionary]$FieldMapping,
[Parameter(Mandatory = $true)]
[hashtable]$ContainerManifestMap
$MigrationMappingTable
)
if (-not [System.IO.Directory]::Exists($ListsRootPath)) {
@@ -1485,6 +1758,7 @@ function Import-SPLists {
return
}
$containerMappingMap = Get-ContainerMappingMap -MigrationMappingTable $MigrationMappingTable
$listFiles = @(Get-ChildItem -Path $ListsRootPath -File -Filter *.json)
Write-Host ("Gefundene Listen-JSONs: {0}" -f $listFiles.Length)
@@ -1499,14 +1773,14 @@ function Import-SPLists {
continue
}
$targetListTitle = Get-TargetContainerTitle -ContainerManifestMap $ContainerManifestMap -ObjectType "List" -SourceTitle $sourceListTitle
$targetListTitle = Get-TargetContainerTitleFromMappingTable -ContainerMappingMap $containerMappingMap -ObjectType "List" -SourceTitle $sourceListTitle
$targetList = $Web.Lists.TryGetList($targetListTitle)
if ($null -eq $targetList) {
if (Test-ContainerHasExplicitTargetMapping -ContainerManifestMap $ContainerManifestMap -ObjectType "List" -SourceTitle $sourceListTitle) {
if (Test-ContainerHasExplicitTargetMappingInTable -ContainerMappingMap $containerMappingMap -ObjectType "List" -SourceTitle $sourceListTitle) {
Write-Warning ("Zielliste '{0}' fuer Quellliste '{1}' nicht gefunden. Bitte diese Liste im Ziel manuell anlegen." -f $targetListTitle, $sourceListTitle)
}
else {
Write-Warning ("Zielliste '{0}' nicht gefunden und kein TargetTitle im manifest.csv gepflegt. Bitte Liste manuell anlegen oder TargetTitle im Manifest setzen." -f $sourceListTitle)
Write-Warning ("Zielliste '{0}' nicht gefunden und kein TargetTitle in der MappingTable gepflegt. Bitte Liste manuell anlegen oder TargetTitle in der MappingTable setzen." -f $sourceListTitle)
}
continue
}
@@ -1517,6 +1791,7 @@ function Import-SPLists {
}
Write-Host ("Importiere Liste: {0}" -f $targetListTitle)
$fieldMapping = Get-FieldMappingForContainer -MigrationMappingTable $MigrationMappingTable -ObjectType "List" -SourceTitle $sourceListTitle
foreach ($sourceItem in $items) {
$fileSystemObjectType = [string](Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FileSystemObjectType")
@@ -1542,29 +1817,44 @@ $resolvedOutputPath = [System.IO.Path]::GetFullPath($OutputPath)
$filesRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Files")
$listsRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Lists")
$manifestPath = [System.IO.Path]::Combine($resolvedOutputPath, "manifest.csv")
$mappingTablePath = if ([string]::IsNullOrWhiteSpace($MappingTable)) {
[System.IO.Path]::Combine($resolvedOutputPath, "MappingTable.json")
}
else {
[System.IO.Path]::GetFullPath($MappingTable)
}
$importConfigurationProvided =
(-not [string]::IsNullOrWhiteSpace($TargetWebUrl)) -or
(-not [string]::IsNullOrWhiteSpace($MappingCsvPath)) -or
$ImportFiles -or
$ImportLists -or
$OverwriteFiles
$shouldExport = $Export.IsPresent -or $ExportOnly.IsPresent
$shouldImport = $Import.IsPresent -or $ImportOnly.IsPresent
if ($ExportOnly -and $ImportOnly) {
if (-not $shouldExport -and -not $shouldImport) {
$shouldExport = -not [string]::IsNullOrWhiteSpace($SourceUrl)
$shouldImport = -not [string]::IsNullOrWhiteSpace($TargetUrl)
}
if ($ExportOnly.IsPresent -and $ImportOnly.IsPresent) {
throw "ExportOnly und ImportOnly koennen nicht gleichzeitig gesetzt werden."
}
if ($ImportOnly) {
if ([string]::IsNullOrWhiteSpace($TargetWebUrl) -or [string]::IsNullOrWhiteSpace($MappingCsvPath)) {
throw "Fuer ImportOnly muessen TargetWebUrl und MappingCsvPath angegeben werden."
if (-not $shouldExport -and -not $shouldImport) {
throw "Bitte mindestens -Export oder -Import angeben."
}
if ($shouldImport -and [string]::IsNullOrWhiteSpace($TargetUrl)) {
throw "Fuer den Import muss TargetUrl angegeben werden."
}
if ($shouldImport -and -not $shouldExport) {
if (-not [System.IO.File]::Exists($mappingTablePath)) {
throw ("MappingTable nicht gefunden: {0}" -f $mappingTablePath)
}
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetWebUrl -MappingCsvPath $MappingCsvPath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetUrl -MappingTablePath $mappingTablePath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles
return
}
if ([string]::IsNullOrWhiteSpace($WebUrl)) {
throw "Fuer den Export muss WebUrl angegeben werden."
if ([string]::IsNullOrWhiteSpace($SourceUrl)) {
throw "Fuer den Export muss SourceUrl angegeben werden."
}
Ensure-Directory -Path $resolvedOutputPath
@@ -1578,7 +1868,7 @@ if ([System.IO.File]::Exists($manifestPath)) {
$web = $null
try {
$web = Get-SPWeb -Identity $WebUrl -ErrorAction Stop
$web = Get-SPWeb -Identity $SourceUrl -ErrorAction Stop
$documentLibraries = @(
$web.Lists | Where-Object {
@@ -1599,6 +1889,10 @@ try {
Write-Host ("Gefundene Bibliotheken: {0}" -f $documentLibraries.Length)
Write-Host ("Gefundene Listen: {0}" -f $lists.Length)
$migrationMappingTable = New-MigrationMappingTable -SourceWebUrl $web.Url -DocumentLibraries $documentLibraries -Lists $lists
Write-MigrationMappingTable -MappingTable $migrationMappingTable -Path $mappingTablePath
Write-Host ("MappingTable geschrieben: {0}" -f $mappingTablePath)
foreach ($library in $documentLibraries) {
Export-SPDocumentLibrary -Web $web -List $library -FilesRootPath $filesRootPath -ManifestPath $manifestPath
}
@@ -1608,27 +1902,19 @@ try {
}
if ($documentLibraries.Length -eq 0 -and $lists.Length -eq 0) {
Write-Warning ("Keine exportierbaren Bibliotheken oder Listen im Web gefunden: {0}" -f $WebUrl)
Write-Warning ("Keine exportierbaren Bibliotheken oder Listen im Web gefunden: {0}" -f $SourceUrl)
return
}
Write-Host ("Export abgeschlossen. Ausgabe: {0}" -f $resolvedOutputPath)
if ($ExportOnly) {
Write-Host "ExportOnly ist gesetzt. Import wird uebersprungen."
if (-not $shouldImport) {
Write-Host "Import wurde nicht angefordert. Export ist abgeschlossen."
return
}
if (-not $importConfigurationProvided) {
return
}
if ([string]::IsNullOrWhiteSpace($TargetWebUrl) -or [string]::IsNullOrWhiteSpace($MappingCsvPath)) {
throw "Fuer den automatischen Import muessen TargetWebUrl und MappingCsvPath angegeben werden."
}
Write-Host ("Starte Import nach Export. Zielweb: {0}" -f $TargetWebUrl)
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetWebUrl -MappingCsvPath $MappingCsvPath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles
Write-Host ("Starte Import nach Export. Zielweb: {0}" -f $TargetUrl)
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetUrl -MappingTablePath $mappingTablePath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles
}
finally {
if ($null -ne $web) {