diff --git a/README.md b/README.md index 42e1d0a..b91c894 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ Das Skript kann: - Dateien inklusive Metadaten exportieren - normale SharePoint-Listen und deren Eintraege exportieren - exportierte Inhalte wieder in ein Ziel-Web importieren -- Feldwerte ueber eine Mapping-CSV von Quell- auf Ziel-InternalNames abbilden -- Listen- und Bibliotheksnamen ueber `manifest.csv` auf Zielnamen abbilden +- beim Export automatisch eine JSON-`MappingTable` fuer Feld-, Listen- und Bibliotheks-Mappings erzeugen +- beim Import dieselbe `MappingTable` fuer Container- und Metadaten-Mappings verwenden ## Voraussetzungen @@ -27,7 +27,7 @@ Import-Module SharePointServer ## Dateien im Projekt - `Start-SPMigration.ps1`: Hauptskript fuer Export und optionalen Import -- `FieldMapping.sample.csv`: Beispiel fuer das Feldmapping +- `FieldMapping.sample.csv`: altes CSV-Beispiel fuer Feldmapping, weiterhin als Fallback lesbar ## Exportierte Struktur @@ -42,6 +42,7 @@ OutputPath | | | |-- Dokument.pdf.properties.json |-- Lists | |-- ListeA.json +|-- MappingTable.json |-- manifest.csv ``` @@ -72,67 +73,85 @@ Fuer den Import sind vor allem diese Informationen relevant: Der Name des Inhaltstyps wird bewusst nicht als fuehrende Importinformation verwendet, da er sich im Zielsystem unterscheiden kann. -## Mapping-CSV +## MappingTable.json -Format: +Beim Export wird automatisch eine `MappingTable.json` erzeugt. Diese Datei enthaelt: -```csv -SourceInternalName;TargetInternalName -DHS2016Persdat;Persdat -DHS2016Dienstgrad;Dienstgrad -DHS2016SecurityClearance;SecurityClearance -``` - -Bedeutung: - -- `SourceInternalName`: interner Feldname aus dem Export -- `TargetInternalName`: interner Feldname im Zielsystem - -## manifest.csv fuer Listen- und Bibliotheksmapping - -Beim Export wird fuer jede Liste und jede Dokumentbibliothek eine Container-Zeile in `manifest.csv` geschrieben. - -Wichtige Spalten: - -- `RecordType` -- `ObjectType` -- `SourceTitle` -- `TargetTitle` -- `BaseTemplate` - -Fuer das Mapping ist insbesondere `TargetTitle` gedacht: - -- leer: das Skript sucht im Ziel mit dem Quellnamen -- gesetzt: das Skript sucht im Ziel mit dem Wert aus `TargetTitle` +- `LibraryMappings`: Mapping von Quellbibliothek auf Zielbibliothek +- `ListMappings`: Mapping von Quellliste auf Zielliste +- `MetadataColumnMappings`: Mapping der Metadaten-Spalten je Liste/Bibliothek Beispiel: -```csv -RecordType;ObjectType;SourceWebUrl;SourceTitle;TargetTitle;BaseType;BaseTemplate;RootFolderUrl;SourceServerRelativeUrl;FileName;FileUniqueId;FileLength;LocalFilePath;LocalMetadataPath;ExportedAtUtc -Container;DocumentLibrary;http://clshp001/;Documents;Dokumente;DocumentLibrary;101;/Documents;/Documents;;;;;; -Container;List;http://clshp001/;TestListe;ZielTestListe;GenericList;100;/Lists/TestListe;/Lists/TestListe;;;;;; +```json +{ + "SchemaVersion": 1, + "GeneratedAtUtc": "2026-04-15T08:00:00.0000000Z", + "SourceWebUrl": "http://sharepoint/sites/Quelle", + "LibraryMappings": [ + { + "ObjectType": "DocumentLibrary", + "SourceTitle": "Documents", + "TargetTitle": "Dokumente", + "BaseType": "DocumentLibrary", + "BaseTemplate": 101, + "RootFolderUrl": "/Documents", + "Hidden": false + } + ], + "ListMappings": [ + { + "ObjectType": "List", + "SourceTitle": "TestListe", + "TargetTitle": "ZielTestListe", + "BaseType": "GenericList", + "BaseTemplate": 100, + "RootFolderUrl": "/Lists/TestListe", + "Hidden": false + } + ], + "MetadataColumnMappings": [ + { + "ObjectType": "DocumentLibrary", + "ContainerSourceTitle": "Documents", + "SourceInternalName": "DHS2016Persdat", + "TargetInternalName": "Persdat", + "DisplayName": "Personaldaten", + "TypeAsString": "Text", + "Hidden": false, + "ReadOnly": false, + "Sealed": false + } + ] +} ``` +Fuer das Mapping ist vor allem relevant: + +- `TargetTitle`: Zielname von Liste oder Bibliothek +- `TargetInternalName`: Zielfeld fuer die exportierte Metadatenspalte + Wenn eine Ziel-Liste oder Ziel-Bibliothek nicht existiert, gibt das Skript eine Warnung aus, dass dieser Container manuell angelegt werden soll. ## Parameter ### Pflichtparameter -- `WebUrl`: Quell-Web fuer den Export -- `OutputPath`: Ausgabeordner fuer den Export +- `SourceUrl`: Quell-Web fuer den Export +- `TargetUrl`: Ziel-Web fuer den Import + +`OutputPath` ist optional. Standard ist `.\SPMigrationOutput` im aktuellen Verzeichnis. ### Exportparameter +- `Export`: fuehrt den Export aus - `IncludeHiddenLibraries`: exportiert auch versteckte Bibliotheken - `IncludeHiddenLists`: exportiert auch versteckte Listen -- `ExportOnly`: fuehrt nur den Export aus -- `ImportOnly`: fuehrt nur den Import aus einem vorhandenen Exportordner aus ### Importparameter -- `TargetWebUrl`: Ziel-Web fuer den Import -- `MappingCsvPath`: CSV fuer Feldmapping +- `Import`: fuehrt den Import aus einem vorhandenen Exportordner aus +- `MappingTable`: JSON-Datei mit Listen-, Bibliotheks- und Spalten-Mappings - `ImportFiles`: importiert nur Dateien/Bibliotheken - `ImportLists`: importiert nur Listen - `OverwriteFiles`: ueberschreibt vorhandene Dateien beim Import @@ -140,8 +159,8 @@ Wenn eine Ziel-Liste oder Ziel-Bibliothek nicht existiert, gibt das Skript eine Hinweis: - Wenn weder `ImportFiles` noch `ImportLists` gesetzt sind, werden beim Import beide Bereiche verarbeitet. -- Wenn `ExportOnly` gesetzt ist, wird kein Import gestartet. -- Wenn `ImportOnly` gesetzt ist, wird kein neuer Export gestartet. +- Wird `MappingTable` nicht angegeben, verwendet das Skript standardmaessig `OutputPath\MappingTable.json`. +- Die alte CSV-Variante wird fuer reines Feldmapping weiterhin als Fallback gelesen. ## Beispiele @@ -149,50 +168,49 @@ Hinweis: ```powershell .\Start-SPMigration.ps1 ` - -WebUrl "http://sharepoint/sites/Quelle" ` - -OutputPath "C:\Temp\SP-Export" ` - -ExportOnly + -SourceUrl "http://sharepoint/sites/Quelle" ` + -Export ` + -IncludeHiddenLists ` + -IncludeHiddenLibraries ``` -### Export und danach Import +### Nur Import ```powershell .\Start-SPMigration.ps1 ` - -WebUrl "http://sharepoint/sites/Quelle" ` - -OutputPath "C:\Temp\SP-Export" ` - -TargetWebUrl "http://sharepoint/sites/Ziel" ` - -MappingCsvPath ".\FieldMapping.sample.csv" + -TargetUrl "http://sharepoint/sites/Ziel" ` + -Import ` + -MappingTable "C:\Temp\SP-Export\MappingTable.json" ``` -### Export, manifest.csv anpassen, dann nur Import +### Export, MappingTable anpassen, dann Import ```powershell .\Start-SPMigration.ps1 ` - -WebUrl "http://sharepoint/sites/Quelle" ` + -SourceUrl "http://sharepoint/sites/Quelle" ` -OutputPath "C:\Temp\SP-Export" ` - -ExportOnly + -Export ``` -Danach `manifest.csv` bearbeiten und `TargetTitle` fuer Listen oder Bibliotheken setzen. +Danach `MappingTable.json` bearbeiten und dort `TargetTitle` sowie `TargetInternalName` anpassen. Anschliessend: ```powershell .\Start-SPMigration.ps1 ` -OutputPath "C:\Temp\SP-Export" ` - -TargetWebUrl "http://sharepoint/sites/Ziel" ` - -MappingCsvPath ".\FieldMapping.sample.csv" ` - -ImportOnly + -TargetUrl "http://sharepoint/sites/Ziel" ` + -Import ` + -MappingTable "C:\Temp\SP-Export\MappingTable.json" ``` ### Nur Dateien importrelevant ausfuehren ```powershell .\Start-SPMigration.ps1 ` - -WebUrl "http://sharepoint/sites/Quelle" ` - -OutputPath "C:\Temp\SP-Export" ` - -TargetWebUrl "http://sharepoint/sites/Ziel" ` - -MappingCsvPath ".\FieldMapping.sample.csv" ` + -TargetUrl "http://sharepoint/sites/Ziel" ` + -Import ` + -MappingTable "C:\Temp\SP-Export\MappingTable.json" ` -ImportFiles ` -OverwriteFiles ``` @@ -201,10 +219,9 @@ Anschliessend: ```powershell .\Start-SPMigration.ps1 ` - -WebUrl "http://sharepoint/sites/Quelle" ` - -OutputPath "C:\Temp\SP-Export" ` - -TargetWebUrl "http://sharepoint/sites/Ziel" ` - -MappingCsvPath ".\FieldMapping.sample.csv" ` + -TargetUrl "http://sharepoint/sites/Ziel" ` + -Import ` + -MappingTable "C:\Temp\SP-Export\MappingTable.json" ` -ImportLists ``` @@ -212,18 +229,18 @@ Anschliessend: ### Dokumentbibliotheken -- Dateien werden in die per `manifest.csv` aufgeloeste Zielbibliothek importiert. +- Dateien werden in die per `MappingTable.json` aufgeloeste Zielbibliothek importiert. - Unterordner werden bei Bedarf angelegt. -- Metadaten werden anschliessend ueber das CSV-Mapping gesetzt. +- Metadaten werden anschliessend ueber die `MetadataColumnMappings` gesetzt. ### Listen - Listeneintraege werden neu angelegt. -- Feldwerte werden ueber `FieldValues` und `FieldTextValues` gemappt. +- Feldwerte werden ueber `FieldValues`, `FieldTextValues` und `MetadataColumnMappings` gemappt. ## Bekannte Einschraenkungen -- Die Zielaufloesung kann ueber `manifest.csv` mit `TargetTitle` ueberschrieben werden. +- Die Zielaufloesung kann ueber `MappingTable.json` mit `TargetTitle` ueberschrieben werden. - Listenanhaenge werden derzeit noch nicht physisch mit importiert. - Ordner in normalen Listen werden beim Import derzeit uebersprungen. - Fehlende Ziellisten oder Zielbibliotheken werden derzeit nicht automatisch angelegt; das Skript gibt stattdessen eine Warnung zur manuellen Anlage aus. @@ -232,4 +249,4 @@ Anschliessend: ## Empfehlung -Zuerst immer mit `-ExportOnly` testen und die exportierten `*.properties.json` sowie die Listen-JSONs pruefen. Danach das Feldmapping vervollstaendigen und erst dann den kombinierten Export/Import gegen das Zielsystem laufen lassen. +Zuerst immer mit `-Export` testen und die exportierten `*.properties.json`, Listen-JSONs und die `MappingTable.json` pruefen. Danach die Zielnamen und Feldmappings vervollstaendigen und erst dann den Import gegen das Zielsystem laufen lassen. diff --git a/Start-SPMigration.ps1 b/Start-SPMigration.ps1 index a0fa95c..2727d64 100644 --- a/Start-SPMigration.ps1 +++ b/Start-SPMigration.ps1 @@ -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) {