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

163
README.md
View File

@@ -8,8 +8,8 @@ Das Skript kann:
- Dateien inklusive Metadaten exportieren - Dateien inklusive Metadaten exportieren
- normale SharePoint-Listen und deren Eintraege exportieren - normale SharePoint-Listen und deren Eintraege exportieren
- exportierte Inhalte wieder in ein Ziel-Web importieren - exportierte Inhalte wieder in ein Ziel-Web importieren
- Feldwerte ueber eine Mapping-CSV von Quell- auf Ziel-InternalNames abbilden - beim Export automatisch eine JSON-`MappingTable` fuer Feld-, Listen- und Bibliotheks-Mappings erzeugen
- Listen- und Bibliotheksnamen ueber `manifest.csv` auf Zielnamen abbilden - beim Import dieselbe `MappingTable` fuer Container- und Metadaten-Mappings verwenden
## Voraussetzungen ## Voraussetzungen
@@ -27,7 +27,7 @@ Import-Module SharePointServer
## Dateien im Projekt ## Dateien im Projekt
- `Start-SPMigration.ps1`: Hauptskript fuer Export und optionalen Import - `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 ## Exportierte Struktur
@@ -42,6 +42,7 @@ OutputPath
| | | |-- Dokument.pdf.properties.json | | | |-- Dokument.pdf.properties.json
|-- Lists |-- Lists
| |-- ListeA.json | |-- ListeA.json
|-- MappingTable.json
|-- manifest.csv |-- 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. 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 - `LibraryMappings`: Mapping von Quellbibliothek auf Zielbibliothek
SourceInternalName;TargetInternalName - `ListMappings`: Mapping von Quellliste auf Zielliste
DHS2016Persdat;Persdat - `MetadataColumnMappings`: Mapping der Metadaten-Spalten je Liste/Bibliothek
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`
Beispiel: Beispiel:
```csv ```json
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;;;;;; "SchemaVersion": 1,
Container;List;http://clshp001/;TestListe;ZielTestListe;GenericList;100;/Lists/TestListe;/Lists/TestListe;;;;;; "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. Wenn eine Ziel-Liste oder Ziel-Bibliothek nicht existiert, gibt das Skript eine Warnung aus, dass dieser Container manuell angelegt werden soll.
## Parameter ## Parameter
### Pflichtparameter ### Pflichtparameter
- `WebUrl`: Quell-Web fuer den Export - `SourceUrl`: Quell-Web fuer den Export
- `OutputPath`: Ausgabeordner fuer den Export - `TargetUrl`: Ziel-Web fuer den Import
`OutputPath` ist optional. Standard ist `.\SPMigrationOutput` im aktuellen Verzeichnis.
### Exportparameter ### Exportparameter
- `Export`: fuehrt den Export aus
- `IncludeHiddenLibraries`: exportiert auch versteckte Bibliotheken - `IncludeHiddenLibraries`: exportiert auch versteckte Bibliotheken
- `IncludeHiddenLists`: exportiert auch versteckte Listen - `IncludeHiddenLists`: exportiert auch versteckte Listen
- `ExportOnly`: fuehrt nur den Export aus
- `ImportOnly`: fuehrt nur den Import aus einem vorhandenen Exportordner aus
### Importparameter ### Importparameter
- `TargetWebUrl`: Ziel-Web fuer den Import - `Import`: fuehrt den Import aus einem vorhandenen Exportordner aus
- `MappingCsvPath`: CSV fuer Feldmapping - `MappingTable`: JSON-Datei mit Listen-, Bibliotheks- und Spalten-Mappings
- `ImportFiles`: importiert nur Dateien/Bibliotheken - `ImportFiles`: importiert nur Dateien/Bibliotheken
- `ImportLists`: importiert nur Listen - `ImportLists`: importiert nur Listen
- `OverwriteFiles`: ueberschreibt vorhandene Dateien beim Import - `OverwriteFiles`: ueberschreibt vorhandene Dateien beim Import
@@ -140,8 +159,8 @@ Wenn eine Ziel-Liste oder Ziel-Bibliothek nicht existiert, gibt das Skript eine
Hinweis: Hinweis:
- Wenn weder `ImportFiles` noch `ImportLists` gesetzt sind, werden beim Import beide Bereiche verarbeitet. - Wenn weder `ImportFiles` noch `ImportLists` gesetzt sind, werden beim Import beide Bereiche verarbeitet.
- Wenn `ExportOnly` gesetzt ist, wird kein Import gestartet. - Wird `MappingTable` nicht angegeben, verwendet das Skript standardmaessig `OutputPath\MappingTable.json`.
- Wenn `ImportOnly` gesetzt ist, wird kein neuer Export gestartet. - Die alte CSV-Variante wird fuer reines Feldmapping weiterhin als Fallback gelesen.
## Beispiele ## Beispiele
@@ -149,50 +168,49 @@ Hinweis:
```powershell ```powershell
.\Start-SPMigration.ps1 ` .\Start-SPMigration.ps1 `
-WebUrl "http://sharepoint/sites/Quelle" ` -SourceUrl "http://sharepoint/sites/Quelle" `
-OutputPath "C:\Temp\SP-Export" ` -Export `
-ExportOnly -IncludeHiddenLists `
-IncludeHiddenLibraries
``` ```
### Export und danach Import ### Nur Import
```powershell ```powershell
.\Start-SPMigration.ps1 ` .\Start-SPMigration.ps1 `
-WebUrl "http://sharepoint/sites/Quelle" ` -TargetUrl "http://sharepoint/sites/Ziel" `
-OutputPath "C:\Temp\SP-Export" ` -Import `
-TargetWebUrl "http://sharepoint/sites/Ziel" ` -MappingTable "C:\Temp\SP-Export\MappingTable.json"
-MappingCsvPath ".\FieldMapping.sample.csv"
``` ```
### Export, manifest.csv anpassen, dann nur Import ### Export, MappingTable anpassen, dann Import
```powershell ```powershell
.\Start-SPMigration.ps1 ` .\Start-SPMigration.ps1 `
-WebUrl "http://sharepoint/sites/Quelle" ` -SourceUrl "http://sharepoint/sites/Quelle" `
-OutputPath "C:\Temp\SP-Export" ` -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: Anschliessend:
```powershell ```powershell
.\Start-SPMigration.ps1 ` .\Start-SPMigration.ps1 `
-OutputPath "C:\Temp\SP-Export" ` -OutputPath "C:\Temp\SP-Export" `
-TargetWebUrl "http://sharepoint/sites/Ziel" ` -TargetUrl "http://sharepoint/sites/Ziel" `
-MappingCsvPath ".\FieldMapping.sample.csv" ` -Import `
-ImportOnly -MappingTable "C:\Temp\SP-Export\MappingTable.json"
``` ```
### Nur Dateien importrelevant ausfuehren ### Nur Dateien importrelevant ausfuehren
```powershell ```powershell
.\Start-SPMigration.ps1 ` .\Start-SPMigration.ps1 `
-WebUrl "http://sharepoint/sites/Quelle" ` -TargetUrl "http://sharepoint/sites/Ziel" `
-OutputPath "C:\Temp\SP-Export" ` -Import `
-TargetWebUrl "http://sharepoint/sites/Ziel" ` -MappingTable "C:\Temp\SP-Export\MappingTable.json" `
-MappingCsvPath ".\FieldMapping.sample.csv" `
-ImportFiles ` -ImportFiles `
-OverwriteFiles -OverwriteFiles
``` ```
@@ -201,10 +219,9 @@ Anschliessend:
```powershell ```powershell
.\Start-SPMigration.ps1 ` .\Start-SPMigration.ps1 `
-WebUrl "http://sharepoint/sites/Quelle" ` -TargetUrl "http://sharepoint/sites/Ziel" `
-OutputPath "C:\Temp\SP-Export" ` -Import `
-TargetWebUrl "http://sharepoint/sites/Ziel" ` -MappingTable "C:\Temp\SP-Export\MappingTable.json" `
-MappingCsvPath ".\FieldMapping.sample.csv" `
-ImportLists -ImportLists
``` ```
@@ -212,18 +229,18 @@ Anschliessend:
### Dokumentbibliotheken ### 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. - Unterordner werden bei Bedarf angelegt.
- Metadaten werden anschliessend ueber das CSV-Mapping gesetzt. - Metadaten werden anschliessend ueber die `MetadataColumnMappings` gesetzt.
### Listen ### Listen
- Listeneintraege werden neu angelegt. - Listeneintraege werden neu angelegt.
- Feldwerte werden ueber `FieldValues` und `FieldTextValues` gemappt. - Feldwerte werden ueber `FieldValues`, `FieldTextValues` und `MetadataColumnMappings` gemappt.
## Bekannte Einschraenkungen ## 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. - Listenanhaenge werden derzeit noch nicht physisch mit importiert.
- Ordner in normalen Listen werden beim Import derzeit uebersprungen. - 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. - 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 ## 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.

View File

@@ -1,17 +1,23 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$WebUrl, [Alias("WebUrl")]
[string]$SourceUrl,
[Parameter(Mandatory = $true)] [string]$OutputPath = (Join-Path -Path (Get-Location).Path -ChildPath "SPMigrationOutput"),
[string]$OutputPath,
[switch]$IncludeHiddenLibraries, [switch]$IncludeHiddenLibraries,
[switch]$IncludeHiddenLists, [switch]$IncludeHiddenLists,
[string]$TargetWebUrl, [Alias("TargetWebUrl")]
[string]$TargetUrl,
[string]$MappingCsvPath, [Alias("MappingCsvPath")]
[string]$MappingTable,
[switch]$Export,
[switch]$Import,
[switch]$ImportFiles, [switch]$ImportFiles,
@@ -19,8 +25,10 @@ param(
[switch]$OverwriteFiles, [switch]$OverwriteFiles,
[Alias("ExportOnly")]
[switch]$ExportOnly, [switch]$ExportOnly,
[Alias("ImportOnly")]
[switch]$ImportOnly [switch]$ImportOnly
) )
@@ -51,7 +59,7 @@ function Invoke-MigrationImport {
[string]$TargetWebUrl, [string]$TargetWebUrl,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[string]$MappingCsvPath, [string]$MappingTablePath,
[switch]$ImportFiles, [switch]$ImportFiles,
@@ -63,13 +71,8 @@ function Invoke-MigrationImport {
$resolvedInputPath = [System.IO.Path]::GetFullPath($InputPath) $resolvedInputPath = [System.IO.Path]::GetFullPath($InputPath)
$filesRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Files") $filesRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Files")
$listsRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Lists") $listsRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Lists")
$manifestPath = [System.IO.Path]::Combine($resolvedInputPath, "manifest.csv") $resolvedMappingTablePath = [System.IO.Path]::GetFullPath($MappingTablePath)
$fieldMapping = Get-FieldMapping -Path $MappingCsvPath $migrationMappingTable = Get-MigrationMappingTable -Path $resolvedMappingTablePath
$containerManifestMap = Get-ContainerManifestMap -ManifestRows (Read-ManifestRows -ManifestPath $manifestPath)
if ($fieldMapping.Count -eq 0) {
throw "Die Mapping-CSV enthaelt keine gueltigen Zuordnungen."
}
$shouldImportFiles = $ImportFiles.IsPresent $shouldImportFiles = $ImportFiles.IsPresent
$shouldImportLists = $ImportLists.IsPresent $shouldImportLists = $ImportLists.IsPresent
@@ -85,11 +88,11 @@ function Invoke-MigrationImport {
$targetWeb = Get-SPWeb -Identity $TargetWebUrl -ErrorAction Stop $targetWeb = Get-SPWeb -Identity $TargetWebUrl -ErrorAction Stop
if ($shouldImportFiles) { 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) { 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) Write-Host ("Import abgeschlossen. Quelle: {0}" -f $resolvedInputPath)
@@ -825,6 +828,101 @@ function Read-ManifestRows {
return @(Import-Csv -Path $ManifestPath -Delimiter ";") 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 { function Get-ContainerManifestEntryKey {
param( param(
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
@@ -935,6 +1033,185 @@ function Read-JsonFile {
return Get-Content -Path $Path -Raw -Encoding UTF8 | ConvertFrom-Json 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 { function Resolve-SPField {
param( param(
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
@@ -1388,10 +1665,7 @@ function Import-SPDocumentLibraries {
[string]$FilesRootPath, [string]$FilesRootPath,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[System.Collections.IDictionary]$FieldMapping, $MigrationMappingTable,
[Parameter(Mandatory = $true)]
[hashtable]$ContainerManifestMap,
[switch]$OverwriteFiles [switch]$OverwriteFiles
) )
@@ -1401,6 +1675,7 @@ function Import-SPDocumentLibraries {
return return
} }
$containerMappingMap = Get-ContainerMappingMap -MigrationMappingTable $MigrationMappingTable
$metadataFiles = @(Get-ChildItem -Path $FilesRootPath -Recurse -File -Filter *.properties.json) $metadataFiles = @(Get-ChildItem -Path $FilesRootPath -Recurse -File -Filter *.properties.json)
Write-Host ("Gefundene Datei-Metadaten: {0}" -f $metadataFiles.Length) Write-Host ("Gefundene Datei-Metadaten: {0}" -f $metadataFiles.Length)
@@ -1421,14 +1696,14 @@ function Import-SPDocumentLibraries {
continue continue
} }
$targetLibraryTitle = Get-TargetContainerTitle -ContainerManifestMap $ContainerManifestMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle $targetLibraryTitle = Get-TargetContainerTitleFromMappingTable -ContainerMappingMap $containerMappingMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
$targetLibrary = $Web.Lists.TryGetList($targetLibraryTitle) $targetLibrary = $Web.Lists.TryGetList($targetLibraryTitle)
if ($null -eq $targetLibrary) { 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) Write-Warning ("Zielbibliothek '{0}' fuer Quellbibliothek '{1}' nicht gefunden. Bitte diese Bibliothek im Ziel manuell anlegen." -f $targetLibraryTitle, $sourceLibraryTitle)
} }
else { 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 continue
} }
@@ -1459,6 +1734,7 @@ function Import-SPDocumentLibraries {
$spItem = $spFile.Item $spItem = $spFile.Item
if ($null -ne $itemMetadata) { 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") Apply-FieldMappingToItem -Item $spItem -FieldMapping $FieldMapping -SourceFieldValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldValues") -SourceFieldTextValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldTextValues")
Save-SPListItem -Item $spItem Save-SPListItem -Item $spItem
} }
@@ -1474,10 +1750,7 @@ function Import-SPLists {
[string]$ListsRootPath, [string]$ListsRootPath,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[System.Collections.IDictionary]$FieldMapping, $MigrationMappingTable
[Parameter(Mandatory = $true)]
[hashtable]$ContainerManifestMap
) )
if (-not [System.IO.Directory]::Exists($ListsRootPath)) { if (-not [System.IO.Directory]::Exists($ListsRootPath)) {
@@ -1485,6 +1758,7 @@ function Import-SPLists {
return return
} }
$containerMappingMap = Get-ContainerMappingMap -MigrationMappingTable $MigrationMappingTable
$listFiles = @(Get-ChildItem -Path $ListsRootPath -File -Filter *.json) $listFiles = @(Get-ChildItem -Path $ListsRootPath -File -Filter *.json)
Write-Host ("Gefundene Listen-JSONs: {0}" -f $listFiles.Length) Write-Host ("Gefundene Listen-JSONs: {0}" -f $listFiles.Length)
@@ -1499,14 +1773,14 @@ function Import-SPLists {
continue continue
} }
$targetListTitle = Get-TargetContainerTitle -ContainerManifestMap $ContainerManifestMap -ObjectType "List" -SourceTitle $sourceListTitle $targetListTitle = Get-TargetContainerTitleFromMappingTable -ContainerMappingMap $containerMappingMap -ObjectType "List" -SourceTitle $sourceListTitle
$targetList = $Web.Lists.TryGetList($targetListTitle) $targetList = $Web.Lists.TryGetList($targetListTitle)
if ($null -eq $targetList) { 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) Write-Warning ("Zielliste '{0}' fuer Quellliste '{1}' nicht gefunden. Bitte diese Liste im Ziel manuell anlegen." -f $targetListTitle, $sourceListTitle)
} }
else { 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 continue
} }
@@ -1517,6 +1791,7 @@ function Import-SPLists {
} }
Write-Host ("Importiere Liste: {0}" -f $targetListTitle) Write-Host ("Importiere Liste: {0}" -f $targetListTitle)
$fieldMapping = Get-FieldMappingForContainer -MigrationMappingTable $MigrationMappingTable -ObjectType "List" -SourceTitle $sourceListTitle
foreach ($sourceItem in $items) { foreach ($sourceItem in $items) {
$fileSystemObjectType = [string](Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FileSystemObjectType") $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") $filesRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Files")
$listsRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Lists") $listsRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Lists")
$manifestPath = [System.IO.Path]::Combine($resolvedOutputPath, "manifest.csv") $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 = $shouldExport = $Export.IsPresent -or $ExportOnly.IsPresent
(-not [string]::IsNullOrWhiteSpace($TargetWebUrl)) -or $shouldImport = $Import.IsPresent -or $ImportOnly.IsPresent
(-not [string]::IsNullOrWhiteSpace($MappingCsvPath)) -or
$ImportFiles -or
$ImportLists -or
$OverwriteFiles
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." throw "ExportOnly und ImportOnly koennen nicht gleichzeitig gesetzt werden."
} }
if ($ImportOnly) { if (-not $shouldExport -and -not $shouldImport) {
if ([string]::IsNullOrWhiteSpace($TargetWebUrl) -or [string]::IsNullOrWhiteSpace($MappingCsvPath)) { throw "Bitte mindestens -Export oder -Import angeben."
throw "Fuer ImportOnly muessen TargetWebUrl und MappingCsvPath angegeben werden."
} }
Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetWebUrl -MappingCsvPath $MappingCsvPath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles 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 $TargetUrl -MappingTablePath $mappingTablePath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles
return return
} }
if ([string]::IsNullOrWhiteSpace($WebUrl)) { if ([string]::IsNullOrWhiteSpace($SourceUrl)) {
throw "Fuer den Export muss WebUrl angegeben werden." throw "Fuer den Export muss SourceUrl angegeben werden."
} }
Ensure-Directory -Path $resolvedOutputPath Ensure-Directory -Path $resolvedOutputPath
@@ -1578,7 +1868,7 @@ if ([System.IO.File]::Exists($manifestPath)) {
$web = $null $web = $null
try { try {
$web = Get-SPWeb -Identity $WebUrl -ErrorAction Stop $web = Get-SPWeb -Identity $SourceUrl -ErrorAction Stop
$documentLibraries = @( $documentLibraries = @(
$web.Lists | Where-Object { $web.Lists | Where-Object {
@@ -1599,6 +1889,10 @@ try {
Write-Host ("Gefundene Bibliotheken: {0}" -f $documentLibraries.Length) Write-Host ("Gefundene Bibliotheken: {0}" -f $documentLibraries.Length)
Write-Host ("Gefundene Listen: {0}" -f $lists.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) { foreach ($library in $documentLibraries) {
Export-SPDocumentLibrary -Web $web -List $library -FilesRootPath $filesRootPath -ManifestPath $manifestPath 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) { 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 return
} }
Write-Host ("Export abgeschlossen. Ausgabe: {0}" -f $resolvedOutputPath) Write-Host ("Export abgeschlossen. Ausgabe: {0}" -f $resolvedOutputPath)
if ($ExportOnly) { if (-not $shouldImport) {
Write-Host "ExportOnly ist gesetzt. Import wird uebersprungen." Write-Host "Import wurde nicht angefordert. Export ist abgeschlossen."
return return
} }
if (-not $importConfigurationProvided) { Write-Host ("Starte Import nach Export. Zielweb: {0}" -f $TargetUrl)
return Invoke-MigrationImport -InputPath $resolvedOutputPath -TargetWebUrl $TargetUrl -MappingTablePath $mappingTablePath -ImportFiles:$ImportFiles -ImportLists:$ImportLists -OverwriteFiles:$OverwriteFiles
}
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
} }
finally { finally {
if ($null -ne $web) { if ($null -ne $web) {