Enhance Start-SPMigration GUI for SharePoint SSOM compatibility and update mapping schema
- Added version checks to ensure the script runs in 64-bit Windows PowerShell Desktop Edition. - Implemented checks for STA thread requirement and SharePoint environment validation. - Updated application version to 1.2.0 and supported mapping schema version to 2. - Introduced a new function to retrieve SharePoint product support information based on build version. - Modified mapping table structure to include ToolVersion and additional metadata fields. - Updated sample mapping JSON to reflect new schema version and added new fields for metadata column mappings.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# Start-SPMigration
|
||||
|
||||
PowerShell-Skript fuer die Migration von SharePoint On-Premise Inhalten ueber das serverseitige Objektmodell.
|
||||
Version 1.2.0
|
||||
|
||||
PowerShell-Werkzeug fuer die Migration von SharePoint-On-Premises-Inhalten ueber das serverseitige Objektmodell (SSOM). Unterstuetzt werden SharePoint Server 2016 und SharePoint Server 2019.
|
||||
|
||||
Das Skript kann:
|
||||
|
||||
@@ -10,24 +12,46 @@ Das Skript kann:
|
||||
- exportierte Inhalte wieder in ein Ziel-Web importieren
|
||||
- beim Export automatisch eine JSON-`MappingTable` fuer Feld-, Listen- und Bibliotheks-Mappings erzeugen
|
||||
- beim Import dieselbe `MappingTable` fuer Container- und Metadaten-Mappings verwenden
|
||||
- grosse Dokumente streambasiert exportieren und importieren, ohne die komplette Datei in den Arbeitsspeicher zu laden
|
||||
- Fehler einzelner Dateien in `ImportErrors-yyyyMMdd-HHmmss.csv` protokollieren und mit der naechsten Datei fortfahren
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- SharePoint On-Premise Server
|
||||
- Ausfuehrung auf einem Server mit installiertem SharePoint PowerShell-Modul
|
||||
- Berechtigungen auf Quell- und Ziel-Web
|
||||
- PowerShell mit Zugriff auf `SharePointServer`
|
||||
- SharePoint Server 2016 oder SharePoint Server 2019
|
||||
- lokale Ausfuehrung auf einem SharePoint-Farmserver; SSOM kann keine entfernte Farm adressieren
|
||||
- 64-Bit Windows PowerShell (Desktop Edition), nicht PowerShell 7
|
||||
- Hauptskript: Windows PowerShell 4.0 oder neuer
|
||||
- GUI: Windows PowerShell 5.0 oder neuer im STA-Modus; Windows PowerShell 5.1 wird empfohlen
|
||||
- Farm- und Web-Berechtigungen fuer die ausgefuehrten Export-/Importaktionen
|
||||
|
||||
Das Skript laedt intern:
|
||||
Das Werkzeug versucht zuerst das SharePoint-Modul und anschliessend den auf klassischen Farmen ueblichen Snap-in-Fallback zu laden:
|
||||
|
||||
```powershell
|
||||
Import-Module SharePointServer
|
||||
Add-PSSnapin Microsoft.SharePoint.PowerShell
|
||||
```
|
||||
|
||||
Die lokale Farmversion wird anhand von `SPFarm.Local.BuildVersion` erkannt. Andere Versionen, einschliesslich Subscription Edition, werden bewusst abgelehnt. Die Umgebung kann vor einer Migration separat geprueft werden:
|
||||
|
||||
```powershell
|
||||
.\Start-SPMigration.ps1 -ValidateEnvironment
|
||||
```
|
||||
|
||||
### Migration zwischen zwei Farmen
|
||||
|
||||
Eine Migration von SharePoint 2016 nach SharePoint 2019 erfolgt mit SSOM zwingend zweistufig:
|
||||
|
||||
1. Export lokal auf einem Server der SharePoint-2016-Quellfarm ausfuehren.
|
||||
2. Den kompletten Exportordner auf einen Server der SharePoint-2019-Zielfarm kopieren.
|
||||
3. Import dort lokal gegen das Ziel-Web ausfuehren.
|
||||
|
||||
Ein kombinierter Export und Import in demselben Prozess ist nur sinnvoll, wenn Quelle und Ziel in derselben lokalen Farm liegen. Ein Import von 2019 nach 2016 ist ein nicht garantierter Downgrade und erzeugt eine Warnung.
|
||||
|
||||
## Dateien im Projekt
|
||||
|
||||
- `Start-SPMigration.ps1`: Hauptskript fuer Export und optionalen Import
|
||||
- `FieldMapping.sample.csv`: altes CSV-Beispiel fuer Feldmapping, weiterhin als Fallback lesbar
|
||||
- `Start-SPMigrationGUI.ps1`: grafische Oberflaeche; die aktuelle Programmversion wird im Fenstertitel angezeigt
|
||||
- `mapping.sample.json`: Beispiel fuer die aktuelle MappingTable
|
||||
- `column-mapping-defaults.sample.json`: Beispiel fuer wiederverwendbare Column-Mapping-Defaults
|
||||
|
||||
## Exportierte Struktur
|
||||
@@ -72,7 +96,7 @@ Fuer den Import sind vor allem diese Informationen relevant:
|
||||
- `ContentTypeId`
|
||||
- interne Feldnamen (`InternalName`)
|
||||
|
||||
Der Name des Inhaltstyps wird bewusst nicht als fuehrende Importinformation verwendet, da er sich im Zielsystem unterscheiden kann.
|
||||
Der Inhaltstyp wird zuerst ueber eine exakt passende ID beziehungsweise einen gleichnamigen Site-Content-Type aufgeloest. Ein beliebiger Basis-Content-Type wie `Item` oder `Document` wird nicht mehr still als Ersatz fuer einen fehlenden eigenen Content-Type verwendet.
|
||||
|
||||
## MappingTable.json
|
||||
|
||||
@@ -87,9 +111,12 @@ Beispiel:
|
||||
|
||||
```json
|
||||
{
|
||||
"SchemaVersion": 1,
|
||||
"SchemaVersion": 2,
|
||||
"ToolVersion": "1.2.0",
|
||||
"GeneratedAtUtc": "2026-04-15T08:00:00.0000000Z",
|
||||
"SourceWebUrl": "http://sharepoint/sites/Quelle",
|
||||
"SourceSharePointProduct": "SharePoint Server 2016",
|
||||
"SourceSharePointBuild": "16.0.5508.1000",
|
||||
"LibraryMappings": [
|
||||
{
|
||||
"ObjectType": "DocumentLibrary",
|
||||
@@ -155,6 +182,8 @@ Beispiel:
|
||||
|
||||
Fuer das Mapping ist vor allem relevant:
|
||||
|
||||
- `SchemaVersion`: aktuell Version 2; Version 1 wird beim Laden auf Version 2 normalisiert, neuere unbekannte Versionen werden abgelehnt
|
||||
- `ToolVersion`, `SourceSharePointProduct` und `SourceSharePointBuild`: dokumentieren die erzeugende Tool- und Quellfarm-Version
|
||||
- `TargetTitle`: Zielname von Liste oder Bibliothek
|
||||
- `TargetInternalName`: Zielfeld fuer die exportierte Metadatenspalte
|
||||
- `SystemColumns`: SharePoint-Standardfelder wie `EventDate`, `EndDate`, `Title` oder `Created`
|
||||
@@ -163,6 +192,7 @@ Fuer das Mapping ist vor allem relevant:
|
||||
- `ImportSupported`: steuert, ob eine Spalte beim Import ueberhaupt beruecksichtigt wird
|
||||
- Taxonomy-Felder (Einfach- und Mehrfachwerte) werden beim Import ueber den exportierten `TermGuid` gesetzt; dabei wird angenommen, dass dieselben Terme im Ziel bereits mit identischer GUID existieren
|
||||
- Die exportierte `WssId` wird bewusst nicht uebernommen: Sie ist nur innerhalb der Quell-SiteCollection gueltig. SharePoint loest beim Setzen des Feldwerts die zielseitige `WssId` auf und legt den Eintrag in der `TaxonomyHiddenList` bei Bedarf selbst an. Die Hidden List soll deshalb nicht separat migriert oder vorab befuellt werden.
|
||||
- Klassische `Lookup`- und `LookupMulti`-Spalten werden derzeit explizit als nicht importierbar markiert. Quellseitige Lookup-IDs sind in einer anderen Farm nicht portabel; ein korrekter Ausbau benoetigt ein fachliches Schluessel-Mapping und einen zweiphasigen Import.
|
||||
|
||||
Wenn eine Ziel-Liste oder Ziel-Bibliothek nicht existiert, gibt das Skript eine Warnung aus, dass dieser Container manuell angelegt werden soll.
|
||||
|
||||
@@ -204,13 +234,17 @@ Beispiel:
|
||||
|
||||
## Parameter
|
||||
|
||||
### Pflichtparameter
|
||||
### Web- und Pfadparameter
|
||||
|
||||
- `SourceUrl`: Quell-Web fuer den Export
|
||||
- `TargetUrl`: Ziel-Web fuer den Import
|
||||
- `SourceUrl`: Quell-Web; beim Export erforderlich
|
||||
- `TargetUrl`: Ziel-Web; beim Import erforderlich
|
||||
|
||||
`OutputPath` ist optional. Standard ist `.\SPMigrationOutput` im aktuellen Verzeichnis.
|
||||
|
||||
### Umgebungspruefung
|
||||
|
||||
- `ValidateEnvironment`: prueft Windows PowerShell, x64-Prozess, SharePoint-Cmdlets, Taxonomy-Assembly und die lokale SharePoint-2016/2019-Farm, ohne eine Migration zu starten
|
||||
|
||||
### Exportparameter
|
||||
|
||||
- `Export`: fuehrt den Export aus
|
||||
@@ -234,10 +268,17 @@ Hinweis:
|
||||
- Wenn `Overwrite` nicht gesetzt ist und eine Datei bereits existiert, wird bei aktiver Versionierung eine neue Version geschrieben; ohne Versionierung wird die Datei uebersprungen.
|
||||
- Nicht importierbare Systemfelder wie `Attachments`, `Created`, `Modified`, `Author` oder `Editor` werden automatisch aus der MappingTable herausgehalten bzw. beim Import uebersprungen.
|
||||
- Vor dem Speichern prueft das Skript fehlende Pflichtfelder. Wenn ein einzelnes Listen- oder Datei-Metadatenobjekt trotzdem nicht gespeichert werden kann, wird es mit Kontext-Warnung uebersprungen und der Import laeuft weiter.
|
||||
- Fehler bei einzelnen Dateien stoppen den restlichen Dateiimport nicht. Sie werden im Exportordner in einer Datei `ImportErrors-yyyyMMdd-HHmmss.csv` mit Importphase, Datei, Bibliothek, Ziel-Web und Fehlerursache protokolliert.
|
||||
- Beim Listenimport legt das Skript bei Bedarf ein verstecktes Textfeld `StartSPMigrationSourceUniqueId` in der Zielliste an. Wiederholte Imports aktualisieren damit vorhandene Elemente anhand der exportierten Source-`UniqueId` statt sie erneut anzulegen.
|
||||
|
||||
## Beispiele
|
||||
|
||||
### SSOM-Umgebung pruefen
|
||||
|
||||
```powershell
|
||||
.\Start-SPMigration.ps1 -ValidateEnvironment
|
||||
```
|
||||
|
||||
### Nur Export
|
||||
|
||||
```powershell
|
||||
@@ -257,7 +298,7 @@ Hinweis:
|
||||
-MappingTable "C:\Temp\SP-Export\MappingTable.json"
|
||||
```
|
||||
|
||||
### Export, MappingTable anpassen, dann Import
|
||||
### SharePoint 2016 exportieren, MappingTable anpassen und auf SharePoint 2019 importieren
|
||||
|
||||
```powershell
|
||||
.\Start-SPMigration.ps1 `
|
||||
@@ -266,7 +307,7 @@ Hinweis:
|
||||
-Export
|
||||
```
|
||||
|
||||
Danach `MappingTable.json` bearbeiten und dort `TargetTitle` sowie `TargetInternalName` anpassen.
|
||||
Danach den kompletten Ordner `C:\Temp\SP-Export` auf den SharePoint-2019-Farmserver kopieren und `MappingTable.json` bearbeiten. Dort insbesondere `TargetTitle` sowie `TargetInternalName` pruefen.
|
||||
|
||||
Anschliessend:
|
||||
|
||||
@@ -322,10 +363,26 @@ Anschliessend:
|
||||
- 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.
|
||||
- `Lookup`- und `LookupMulti`-Spalten werden nicht importiert, weil Quell-IDs auf der Zielfarm keine stabile Bedeutung haben.
|
||||
- SharePoint-Gruppen in Personenfeldern werden exportseitig erkannt, aber noch nicht automatisch einer Zielgruppe zugeordnet; ein nicht aufloesbarer nichtleerer Wert wird als Feldfehler gemeldet.
|
||||
- Fehlende Ziellisten oder Zielbibliotheken werden derzeit nicht automatisch angelegt; das Skript gibt stattdessen eine Warnung zur manuellen Anlage aus.
|
||||
- Bereits vorhandene Dateien unter `Files` und `Lists` werden vor einem erneuten Export nicht automatisch entfernt. Fuer jeden produktiven Lauf sollte deshalb ein neuer, leerer `OutputPath` verwendet werden.
|
||||
- Schlaegt das Setzen von Metadaten nach dem Datei-Upload fehl, kann die bereits hochgeladene Datei als Teilimport im Ziel verbleiben. Der Fehler wird protokolliert; eine automatische Rollback-/Quarantaene-Logik ist noch nicht vorhanden.
|
||||
- Dateiversionen, Check-in/Publish/Approve-Zustaende und Listen-Versionen werden noch nicht vollstaendig rekonstruiert.
|
||||
- Grosse Listen werden derzeit als eine JSON-Datei verarbeitet; fuer sehr grosse Datenmengen fehlt noch ein paginierter Chunk-Export.
|
||||
- Sehr spezielle Feldtypen koennen je nach Farm-Konfiguration zusaetzliche Anpassungen benoetigen.
|
||||
- Das Skript ist fuer SharePoint On-Premise mit serverseitigem Objektmodell gedacht, nicht fuer SharePoint Online.
|
||||
- Das Skript ist fuer SharePoint Server 2016/2019 mit SSOM gedacht, nicht fuer SharePoint Online, CSOM, PnP PowerShell oder Subscription Edition.
|
||||
- SharePoint Server 2016 und 2019 haben das Ende des erweiterten Microsoft-Supports erreicht. Das Werkzeug unterstuetzt sie technisch weiter, ersetzt aber keine Plattform-Sicherheitsstrategie.
|
||||
|
||||
## Empfohlene naechste Ausbaustufen
|
||||
|
||||
1. Manifestgesteuerte Run-Ordner mit SHA-256-Pruefung, damit keine Altdateien oder falschen Sidecars importiert werden.
|
||||
2. Transaktionaler Dokumentstatus mit Metadaten-Save vor finalem Check-in sowie Rollback oder Quarantaene bei Teilfehlern.
|
||||
3. Zweiphasiges Lookup-Mapping ueber konfigurierbare fachliche Schluessel statt ueber lokale Item-IDs.
|
||||
4. Paginierter Listenexport und chunkweiser Import fuer grosse Listen.
|
||||
5. Einheitliches Errorlog und Abschlussreport fuer Export, Dateien, Listen und einzelne Listenelemente.
|
||||
6. Automatisierte Integrationstests auf je einer SharePoint-2016- und SharePoint-2019-Testfarm.
|
||||
|
||||
## Empfehlung
|
||||
|
||||
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.
|
||||
Zuerst `-ValidateEnvironment` ausfuehren. Danach immer in einen neuen, leeren Ausgabeordner exportieren und die exportierten `*.properties.json`, Listen-JSONs und die `MappingTable.json` pruefen. Anschliessend Zielnamen und Feldmappings vervollstaendigen und den Import zuerst gegen ein Test-Web laufen lassen.
|
||||
|
||||
+454
-115
@@ -26,32 +26,148 @@ param(
|
||||
[switch]$ImportLists,
|
||||
|
||||
[Alias("OverwriteFiles")]
|
||||
[switch]$Overwrite
|
||||
[switch]$Overwrite,
|
||||
|
||||
[switch]$ValidateEnvironment
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$script:ApplicationVersion = "1.2.0"
|
||||
$script:SupportedMappingSchemaVersion = 2
|
||||
$script:SkippedImportFieldWarnings = @{}
|
||||
$script:ListImportTrackingFieldInternalName = "StartSPMigrationSourceUniqueId"
|
||||
$script:ListImportTrackingFieldDisplayName = "Start-SPMigration Source UniqueId"
|
||||
$script:FileImportErrorCount = 0
|
||||
$script:SharePointEnvironment = $null
|
||||
|
||||
function Test-IsWindowsPowerShellDesktop {
|
||||
$editionProperty = $PSVersionTable.PSObject.Properties["PSEdition"]
|
||||
if ($null -eq $editionProperty) {
|
||||
# PSEdition was introduced after the early Windows PowerShell releases.
|
||||
return $true
|
||||
}
|
||||
|
||||
return [string]$editionProperty.Value -eq "Desktop"
|
||||
}
|
||||
|
||||
function Import-SharePointServerPowerShell {
|
||||
if ($null -ne (Get-Command -Name Get-SPWeb -ErrorAction SilentlyContinue)) {
|
||||
return
|
||||
}
|
||||
|
||||
$loadErrors = @()
|
||||
|
||||
try {
|
||||
Import-Module SharePointServer -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
$loadErrors += ("Import-Module SharePointServer: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
if ($null -eq (Get-Command -Name Get-SPWeb -ErrorAction SilentlyContinue)) {
|
||||
try {
|
||||
$registeredSnapIn = Get-PSSnapin -Registered -Name Microsoft.SharePoint.PowerShell -ErrorAction Stop
|
||||
if ($null -ne $registeredSnapIn -and $null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)) {
|
||||
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$loadErrors += ("Add-PSSnapin Microsoft.SharePoint.PowerShell: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq (Get-Command -Name Get-SPWeb -ErrorAction SilentlyContinue)) {
|
||||
throw ("Die SharePoint Server PowerShell-Cmdlets konnten nicht geladen werden. Das Tool muss lokal auf einem SharePoint-Server ausgefuehrt werden. {0}" -f ($loadErrors -join " | "))
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SharePointProductSupportInfo {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[version]$BuildVersion
|
||||
)
|
||||
|
||||
$productName = "Nicht unterstuetzte SharePoint-Version"
|
||||
$isSupported = $false
|
||||
|
||||
if ($BuildVersion.Major -eq 16 -and $BuildVersion.Build -ge 4351 -and $BuildVersion.Build -lt 10337) {
|
||||
$productName = "SharePoint Server 2016"
|
||||
$isSupported = $true
|
||||
}
|
||||
elseif ($BuildVersion.Major -eq 16 -and $BuildVersion.Build -ge 10337 -and $BuildVersion.Build -lt 14326) {
|
||||
$productName = "SharePoint Server 2019"
|
||||
$isSupported = $true
|
||||
}
|
||||
elseif ($BuildVersion.Major -eq 16 -and $BuildVersion.Build -ge 14326) {
|
||||
$productName = "SharePoint Server Subscription Edition"
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
ProductName = $productName
|
||||
IsSupported = $isSupported
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SharePointServerEnvironment {
|
||||
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
|
||||
if ($null -eq $farm) {
|
||||
throw "Keine lokale SharePoint-Farm gefunden. SSOM kann nur lokal auf einem SharePoint-Farmserver verwendet werden."
|
||||
}
|
||||
|
||||
$buildVersion = [version]$farm.BuildVersion
|
||||
$supportInfo = Get-SharePointProductSupportInfo -BuildVersion $buildVersion
|
||||
|
||||
return [PSCustomObject]@{
|
||||
ProductName = $supportInfo.ProductName
|
||||
BuildVersion = $buildVersion.ToString()
|
||||
IsSupported = $supportInfo.IsSupported
|
||||
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
|
||||
Is64BitProcess = [Environment]::Is64BitProcess
|
||||
}
|
||||
}
|
||||
|
||||
function Initialize-SharePointPowerShell {
|
||||
if($null -eq $(Get-Module SharePointServer)) {
|
||||
Import-Module SharePointServer
|
||||
}else{
|
||||
Write-Host "SharePoint Server PowerShell-Modul bereits geladen."
|
||||
if (-not (Test-IsWindowsPowerShellDesktop)) {
|
||||
throw "SharePoint SSOM wird fuer dieses Tool nur unter 64-Bit Windows PowerShell (Desktop Edition), nicht unter PowerShell 7, unterstuetzt."
|
||||
}
|
||||
|
||||
if (-not [Environment]::Is64BitProcess) {
|
||||
throw "SharePoint SSOM erfordert fuer dieses Tool einen 64-Bit-Windows-PowerShell-Prozess."
|
||||
}
|
||||
|
||||
if ($PSVersionTable.PSVersion -lt [version]"4.0") {
|
||||
throw ("Windows PowerShell 4.0 oder neuer ist erforderlich. Gefunden: {0}" -f $PSVersionTable.PSVersion)
|
||||
}
|
||||
|
||||
Import-SharePointServerPowerShell
|
||||
|
||||
try {
|
||||
[void][System.Reflection.Assembly]::Load("Microsoft.SharePoint.Taxonomy")
|
||||
}
|
||||
catch {
|
||||
try {
|
||||
Add-Type -AssemblyName "Microsoft.SharePoint.Taxonomy"
|
||||
Add-Type -AssemblyName "Microsoft.SharePoint.Taxonomy" -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
throw ("Die SSOM-Assembly Microsoft.SharePoint.Taxonomy konnte nicht geladen werden. {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$Host.Runspace.ThreadOptions = "ReuseThread"
|
||||
}
|
||||
catch {
|
||||
Write-Warning ("Der SharePoint-kompatible Runspace-Modus ReuseThread konnte nicht gesetzt werden: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
$environment = Get-SharePointServerEnvironment
|
||||
if (-not $environment.IsSupported) {
|
||||
throw ("Nicht unterstuetzte lokale SharePoint-Farm: {0}, Build {1}. Unterstuetzt werden SharePoint Server 2016 und 2019." -f $environment.ProductName, $environment.BuildVersion)
|
||||
}
|
||||
|
||||
Write-Host ("SSOM-Umgebung: ToolVersion={0}; Produkt='{1}'; FarmBuild={2}; WindowsPowerShell={3}; 64Bit={4}" -f $script:ApplicationVersion, $environment.ProductName, $environment.BuildVersion, $environment.PowerShellVersion, $environment.Is64BitProcess)
|
||||
return $environment
|
||||
}
|
||||
|
||||
function Ensure-Directory {
|
||||
@@ -88,6 +204,27 @@ function Invoke-MigrationImport {
|
||||
$listsRootPath = [System.IO.Path]::Combine($resolvedInputPath, "Lists")
|
||||
$resolvedMappingTablePath = [System.IO.Path]::GetFullPath($MappingTablePath)
|
||||
$migrationMappingTable = Get-MigrationMappingTable -Path $resolvedMappingTablePath
|
||||
$fileImportErrorLogPath = [System.IO.Path]::Combine(
|
||||
$resolvedInputPath,
|
||||
("ImportErrors-{0}.csv" -f (Get-Date -Format "yyyyMMdd-HHmmss"))
|
||||
)
|
||||
$script:FileImportErrorCount = 0
|
||||
|
||||
$sourceSharePointProduct = [string](Get-ObjectPropertyValue -Object $migrationMappingTable -PropertyName "SourceSharePointProduct" -DefaultValue "Unbekannt")
|
||||
$sourceSharePointBuild = [string](Get-ObjectPropertyValue -Object $migrationMappingTable -PropertyName "SourceSharePointBuild" -DefaultValue "Unbekannt")
|
||||
if ([string]::IsNullOrWhiteSpace($sourceSharePointProduct)) {
|
||||
$sourceSharePointProduct = "Unbekannt"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($sourceSharePointBuild)) {
|
||||
$sourceSharePointBuild = "Unbekannt"
|
||||
}
|
||||
|
||||
Write-Host ("Migrationskompatibilitaet: Quelle='{0}' Build={1}; Ziel='{2}' Build={3}" -f $sourceSharePointProduct, $sourceSharePointBuild, $script:SharePointEnvironment.ProductName, $script:SharePointEnvironment.BuildVersion)
|
||||
|
||||
if ($sourceSharePointProduct -eq "SharePoint Server 2019" -and $script:SharePointEnvironment.ProductName -eq "SharePoint Server 2016") {
|
||||
Write-Warning "Ein Import von SharePoint Server 2019 nach SharePoint Server 2016 ist ein nicht garantierter Downgrade. In 2019 vorhandene Feldtypen, Content-Types oder Listentemplates koennen im Ziel fehlen."
|
||||
}
|
||||
|
||||
$shouldImportFiles = $ImportFiles.IsPresent
|
||||
$shouldImportLists = $ImportLists.IsPresent
|
||||
@@ -103,7 +240,11 @@ function Invoke-MigrationImport {
|
||||
$targetWeb = Get-SPWeb -Identity $TargetWebUrl -ErrorAction Stop
|
||||
|
||||
if ($shouldImportFiles) {
|
||||
Import-SPDocumentLibraries -Web $targetWeb -FilesRootPath $filesRootPath -MigrationMappingTable $migrationMappingTable -Overwrite:$Overwrite
|
||||
Import-SPDocumentLibraries -Web $targetWeb -FilesRootPath $filesRootPath -MigrationMappingTable $migrationMappingTable -ErrorLogPath $fileImportErrorLogPath -Overwrite:$Overwrite
|
||||
|
||||
if ($script:FileImportErrorCount -gt 0) {
|
||||
Write-Warning ("Dateiimport mit {0} Fehler(n) abgeschlossen. Errorlog: {1}" -f $script:FileImportErrorCount, $fileImportErrorLogPath)
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldImportLists) {
|
||||
@@ -278,11 +419,13 @@ function Convert-SPFieldValue {
|
||||
}
|
||||
|
||||
if ($Value -is [Microsoft.SharePoint.SPFieldUserValue]) {
|
||||
$user = $Value.User
|
||||
return @{
|
||||
LookupId = $Value.LookupId
|
||||
LookupValue = $Value.LookupValue
|
||||
Email = $Value.User.Email
|
||||
LoginName = $Value.User.LoginName
|
||||
Email = if ($null -ne $user) { [string]$user.Email } else { "" }
|
||||
LoginName = if ($null -ne $user) { [string]$user.LoginName } else { "" }
|
||||
IsSharePointGroup = ($null -eq $user)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +863,23 @@ function Export-SPFile {
|
||||
$localFilePath = [System.IO.Path]::Combine($fileDirectory, $fileName)
|
||||
$metadataPath = [System.IO.Path]::Combine($fileDirectory, "$fileName.properties.json")
|
||||
|
||||
[System.IO.File]::WriteAllBytes($localFilePath, $File.OpenBinary())
|
||||
$sourceStream = $null
|
||||
$destinationStream = $null
|
||||
|
||||
try {
|
||||
$sourceStream = $File.OpenBinaryStream()
|
||||
$destinationStream = [System.IO.File]::Open($localFilePath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None)
|
||||
$sourceStream.CopyTo($destinationStream)
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $destinationStream) {
|
||||
$destinationStream.Dispose()
|
||||
}
|
||||
|
||||
if ($null -ne $sourceStream) {
|
||||
$sourceStream.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
$metadata = Get-FileMetadataObject -Web $Web -List $List -File $File
|
||||
Write-MetadataJson -MetadataObject $metadata -Path $metadataPath
|
||||
@@ -999,7 +1158,9 @@ function Test-SPFieldSupportedForImport {
|
||||
|
||||
$unsupportedFieldTypes = @(
|
||||
"Attachments",
|
||||
"Computed"
|
||||
"Computed",
|
||||
"Lookup",
|
||||
"LookupMulti"
|
||||
)
|
||||
|
||||
if ($Field.ReadOnlyField) {
|
||||
@@ -1215,6 +1376,13 @@ function New-MigrationMappingTable {
|
||||
$listMappings = @()
|
||||
$systemColumnMappings = @()
|
||||
$customColumnMappings = @()
|
||||
$sourceSharePointProduct = ""
|
||||
$sourceSharePointBuild = ""
|
||||
|
||||
if ($null -ne $script:SharePointEnvironment) {
|
||||
$sourceSharePointProduct = [string]$script:SharePointEnvironment.ProductName
|
||||
$sourceSharePointBuild = [string]$script:SharePointEnvironment.BuildVersion
|
||||
}
|
||||
|
||||
foreach ($library in @($DocumentLibraries)) {
|
||||
$libraryMappings += New-MigrationContainerMappingEntry -ObjectType "DocumentLibrary" -List $library
|
||||
@@ -1231,9 +1399,12 @@ function New-MigrationMappingTable {
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
SchemaVersion = 1
|
||||
SchemaVersion = $script:SupportedMappingSchemaVersion
|
||||
ToolVersion = $script:ApplicationVersion
|
||||
GeneratedAtUtc = [System.DateTime]::UtcNow.ToString("o")
|
||||
SourceWebUrl = $SourceWebUrl
|
||||
SourceSharePointProduct = $sourceSharePointProduct
|
||||
SourceSharePointBuild = $sourceSharePointBuild
|
||||
LibraryMappings = $libraryMappings
|
||||
ListMappings = $listMappings
|
||||
MetadataColumnMappings = [PSCustomObject]@{
|
||||
@@ -1403,9 +1574,12 @@ function Get-MigrationMappingTable {
|
||||
}
|
||||
|
||||
$mappingTable = [PSCustomObject]@{
|
||||
SchemaVersion = 1
|
||||
SchemaVersion = $script:SupportedMappingSchemaVersion
|
||||
ToolVersion = $script:ApplicationVersion
|
||||
GeneratedAtUtc = [System.DateTime]::UtcNow.ToString("o")
|
||||
SourceWebUrl = ""
|
||||
SourceSharePointProduct = ""
|
||||
SourceSharePointBuild = ""
|
||||
LibraryMappings = @()
|
||||
ListMappings = @()
|
||||
MetadataColumnMappings = [PSCustomObject]@{
|
||||
@@ -1422,6 +1596,33 @@ function Get-MigrationMappingTable {
|
||||
throw "MappingTable konnte nicht gelesen werden."
|
||||
}
|
||||
|
||||
$schemaVersionValue = Get-ObjectPropertyValue -Object $mappingTable -PropertyName "SchemaVersion" -DefaultValue 1
|
||||
try {
|
||||
$schemaVersion = [int]$schemaVersionValue
|
||||
}
|
||||
catch {
|
||||
throw ("Ungueltige SchemaVersion in der MappingTable: '{0}'." -f $schemaVersionValue)
|
||||
}
|
||||
|
||||
if ($schemaVersion -lt 1) {
|
||||
throw ("Ungueltige SchemaVersion in der MappingTable: {0}." -f $schemaVersion)
|
||||
}
|
||||
|
||||
if ($schemaVersion -gt $script:SupportedMappingSchemaVersion) {
|
||||
throw ("MappingTable-SchemaVersion {0} ist neuer als die von Tool v{1} unterstuetzte Version {2}. Bitte eine kompatible Toolversion verwenden." -f $schemaVersion, $script:ApplicationVersion, $script:SupportedMappingSchemaVersion)
|
||||
}
|
||||
|
||||
if ($schemaVersion -lt $script:SupportedMappingSchemaVersion) {
|
||||
Write-Host ("MappingTable-SchemaVersion {0} wird beim Laden auf Version {1} normalisiert." -f $schemaVersion, $script:SupportedMappingSchemaVersion)
|
||||
}
|
||||
|
||||
if (Test-ObjectHasProperty -Object $mappingTable -PropertyName "SchemaVersion") {
|
||||
$mappingTable.SchemaVersion = $script:SupportedMappingSchemaVersion
|
||||
}
|
||||
else {
|
||||
$mappingTable | Add-Member -NotePropertyName "SchemaVersion" -NotePropertyValue $script:SupportedMappingSchemaVersion
|
||||
}
|
||||
|
||||
return $mappingTable
|
||||
}
|
||||
|
||||
@@ -1464,9 +1665,8 @@ function Test-MetadataColumnMappingRowImportSupported {
|
||||
$Row
|
||||
)
|
||||
|
||||
if (Test-ObjectHasProperty -Object $Row -PropertyName "ImportSupported") {
|
||||
return [bool](Get-ObjectPropertyValue -Object $Row -PropertyName "ImportSupported" -DefaultValue $false)
|
||||
}
|
||||
$hasExplicitImportSupported = Test-ObjectHasProperty -Object $Row -PropertyName "ImportSupported"
|
||||
$explicitImportSupported = [bool](Get-ObjectPropertyValue -Object $Row -PropertyName "ImportSupported" -DefaultValue $false)
|
||||
|
||||
$sourceInternalName = [string](Get-ObjectPropertyValue -Object $Row -PropertyName "SourceInternalName")
|
||||
$typeAsString = [string](Get-ObjectPropertyValue -Object $Row -PropertyName "TypeAsString")
|
||||
@@ -1565,10 +1765,14 @@ function Test-MetadataColumnMappingRowImportSupported {
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($typeAsString -in @("Attachments", "Computed")) {
|
||||
if ($typeAsString -in @("Attachments", "Computed", "Lookup", "LookupMulti")) {
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($hasExplicitImportSupported) {
|
||||
return $explicitImportSupported
|
||||
}
|
||||
|
||||
return -not ($unsupportedInternalNames -contains $sourceInternalName)
|
||||
}
|
||||
|
||||
@@ -1765,22 +1969,32 @@ function Resolve-SPAvailableContentTypeForImport {
|
||||
}
|
||||
}
|
||||
|
||||
$idMatches = @()
|
||||
$availableContentTypes = @($Web.AvailableContentTypes)
|
||||
|
||||
if ($null -ne $sourceId) {
|
||||
foreach ($contentType in @($Web.AvailableContentTypes)) {
|
||||
if ($contentType.Id.Equals($sourceId) -or $contentType.Id.IsParentOf($sourceId)) {
|
||||
$idMatches += $contentType
|
||||
foreach ($contentType in $availableContentTypes) {
|
||||
if ($contentType.Id.Equals($sourceId)) {
|
||||
return $contentType
|
||||
}
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($SourceContentTypeName)) {
|
||||
$namedParentMatches = @()
|
||||
foreach ($contentType in $availableContentTypes) {
|
||||
if ($contentType.Id.IsParentOf($sourceId) -and [string]$contentType.Name -ieq $SourceContentTypeName) {
|
||||
$namedParentMatches += $contentType
|
||||
}
|
||||
}
|
||||
|
||||
if ($namedParentMatches.Count -gt 0) {
|
||||
return @($namedParentMatches | Sort-Object { $_.Id.ToString().Length } -Descending)[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($idMatches.Count -gt 0) {
|
||||
return @($idMatches | Sort-Object { $_.Id.ToString().Length } -Descending)[0]
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($SourceContentTypeName)) {
|
||||
foreach ($contentType in @($Web.AvailableContentTypes)) {
|
||||
if ([string]$contentType.Name -eq $SourceContentTypeName) {
|
||||
foreach ($contentType in $availableContentTypes) {
|
||||
if ([string]$contentType.Name -ieq $SourceContentTypeName) {
|
||||
return $contentType
|
||||
}
|
||||
}
|
||||
@@ -1804,10 +2018,19 @@ function Ensure-SPListContentTypeForImport {
|
||||
throw ("Kein passender publizierter Site-Content-Type gefunden. SourceContentTypeId='{0}', SourceContentTypeName='{1}'" -f $SourceContentTypeId, $SourceContentTypeName)
|
||||
}
|
||||
|
||||
$matchingListContentTypes = @()
|
||||
foreach ($listContentType in @($List.ContentTypes)) {
|
||||
if ($listContentType.Id.Equals($siteContentType.Id) -or $siteContentType.Id.IsParentOf($listContentType.Id)) {
|
||||
if ($listContentType.Id.Equals($siteContentType.Id)) {
|
||||
return $listContentType
|
||||
}
|
||||
|
||||
if ($siteContentType.Id.IsParentOf($listContentType.Id) -and [string]$listContentType.Name -ieq [string]$siteContentType.Name) {
|
||||
$matchingListContentTypes += $listContentType
|
||||
}
|
||||
}
|
||||
|
||||
if ($matchingListContentTypes.Count -gt 0) {
|
||||
return @($matchingListContentTypes | Sort-Object { $_.Id.ToString().Length })[0]
|
||||
}
|
||||
|
||||
if (-not $List.ContentTypesEnabled) {
|
||||
@@ -1825,6 +2048,28 @@ function Ensure-SPListContentTypeForImport {
|
||||
return $listContentType
|
||||
}
|
||||
|
||||
function Set-SPItemContentTypeForImport {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Microsoft.SharePoint.SPListItem]$Item,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Microsoft.SharePoint.SPList]$List,
|
||||
|
||||
[string]$SourceContentTypeId,
|
||||
|
||||
[string]$SourceContentTypeName
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SourceContentTypeId) -and [string]::IsNullOrWhiteSpace($SourceContentTypeName)) {
|
||||
return
|
||||
}
|
||||
|
||||
$targetContentType = Ensure-SPListContentTypeForImport -List $List -SourceContentTypeId $SourceContentTypeId -SourceContentTypeName $SourceContentTypeName
|
||||
$Item["ContentTypeId"] = $targetContentType.Id
|
||||
Write-Host ("Content-Type am Element gesetzt: Liste='{0}'; ContentType='{1}'; ContentTypeId='{2}'" -f $List.Title, $targetContentType.Name, $targetContentType.Id)
|
||||
}
|
||||
|
||||
function Get-FieldMapping {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
@@ -1989,6 +2234,10 @@ function Convert-ToUserFieldValue {
|
||||
|
||||
$user = Resolve-SPUser -Web $Item.Web -RawUserValue $RawValue -TextValue $TextValue
|
||||
if ($null -eq $user) {
|
||||
if ((Test-SPValuePresent -Value $RawValue) -or -not [string]::IsNullOrWhiteSpace($TextValue)) {
|
||||
throw ("Benutzer oder SharePoint-Gruppe konnte im Ziel-Web nicht aufgeloest werden. Exportwert='{0}'" -f $TextValue)
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
@@ -2229,14 +2478,14 @@ function Set-SPTaxonomyFieldValue {
|
||||
$taxonomyCollection = $null
|
||||
|
||||
try {
|
||||
$taxonomyCollection = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection($Item.Web, "", $taxonomyField)
|
||||
# Start-SPMigration uses the SharePoint server object model. Its
|
||||
# collection constructors are bound directly to SPField and do not
|
||||
# accept an SPWeb (that signature belongs to CSOM).
|
||||
$taxonomyCollection = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection($taxonomyField)
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
if ($null -eq $taxonomyCollection) {
|
||||
try {
|
||||
$taxonomyCollection = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection($Item.Web, $pairString, $taxonomyField)
|
||||
$taxonomyCollection = New-Object Microsoft.SharePoint.Taxonomy.TaxonomyFieldValueCollection("", $taxonomyField)
|
||||
}
|
||||
catch {
|
||||
}
|
||||
@@ -2270,13 +2519,6 @@ function Set-SPTaxonomyFieldValue {
|
||||
throw ("Konnte die TaxonomyFieldValueCollection fuer Feld '{0}' nicht befuellen." -f $Field.InternalName)
|
||||
}
|
||||
|
||||
try {
|
||||
$taxonomyField.SetFieldValueByValueCollection($Item, $taxonomyCollection)
|
||||
return
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
try {
|
||||
$taxonomyField.SetFieldValue($Item, $taxonomyCollection)
|
||||
return
|
||||
@@ -2290,13 +2532,6 @@ function Set-SPTaxonomyFieldValue {
|
||||
|
||||
$taxonomyValue = New-TaxonomyFieldValueObject -Field $Field -TermEntry $termEntries[0]
|
||||
|
||||
try {
|
||||
$taxonomyField.SetFieldValueByValue($Item, $taxonomyValue)
|
||||
return
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
try {
|
||||
$taxonomyField.SetFieldValue($Item, $taxonomyValue)
|
||||
return
|
||||
@@ -2419,7 +2654,9 @@ function Apply-FieldMappingToItem {
|
||||
|
||||
$SourceFieldTextValues,
|
||||
|
||||
[string]$Context = ""
|
||||
[string]$Context = "",
|
||||
|
||||
[System.Collections.Generic.List[string]]$Errors = $null
|
||||
)
|
||||
|
||||
foreach ($sourceInternalName in $FieldMapping.Keys) {
|
||||
@@ -2463,7 +2700,12 @@ function Apply-FieldMappingToItem {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning ("Fehler beim Setzen eines Columnwerts: {0}; SourceColumn='{1}'; TargetColumn='{2}'; Fehler={3}" -f $Context, $sourceInternalName, $targetInternalName, $_.Exception.Message)
|
||||
$fieldError = "Fehler beim Setzen eines Columnwerts: {0}; SourceColumn='{1}'; TargetColumn='{2}'; Fehler={3}" -f $Context, $sourceInternalName, $targetInternalName, $_.Exception.Message
|
||||
Write-Warning $fieldError
|
||||
|
||||
if ($null -ne $Errors) {
|
||||
[void]$Errors.Add($fieldError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2665,7 +2907,7 @@ function Update-SPExistingFileAsNewVersion {
|
||||
[Microsoft.SharePoint.SPFile]$ExistingFile,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[byte[]]$FileBytes
|
||||
[System.IO.Stream]$FileStream
|
||||
)
|
||||
|
||||
$requiresCheckout = $false
|
||||
@@ -2681,7 +2923,7 @@ function Update-SPExistingFileAsNewVersion {
|
||||
$ExistingFile.CheckOut()
|
||||
}
|
||||
|
||||
$existingFile.SaveBinary($FileBytes)
|
||||
$existingFile.SaveBinary($FileStream)
|
||||
|
||||
if ($ExistingFile.CheckOutType -ne [Microsoft.SharePoint.SPFile+SPCheckOutType]::None) {
|
||||
$checkInType = [Microsoft.SharePoint.SPCheckinType]::MajorCheckIn
|
||||
@@ -2712,28 +2954,91 @@ function Import-SPFileToFolder {
|
||||
[string]$FileName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[byte[]]$FileBytes,
|
||||
[string]$SourceFilePath,
|
||||
|
||||
[switch]$Overwrite
|
||||
)
|
||||
|
||||
$existingFile = Get-SPFolderFileByName -Folder $TargetFolder -FileName $FileName
|
||||
$fileStream = $null
|
||||
|
||||
if ($null -eq $existingFile) {
|
||||
return $TargetFolder.Files.Add($FileName, $FileBytes, $false)
|
||||
try {
|
||||
$fileStream = [System.IO.File]::Open($SourceFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
|
||||
|
||||
if ($null -eq $existingFile) {
|
||||
return $TargetFolder.Files.Add($FileName, $fileStream, $false)
|
||||
}
|
||||
|
||||
if ($Overwrite.IsPresent) {
|
||||
return $TargetFolder.Files.Add($FileName, $fileStream, $true)
|
||||
}
|
||||
|
||||
if (-not [bool]$TargetLibrary.EnableVersioning) {
|
||||
Write-Warning ("Datei uebersprungen, da sie bereits existiert und in der Zielbibliothek keine Versionierung aktiv ist: {0}/{1}" -f $TargetLibrary.Title, $FileName)
|
||||
return $null
|
||||
}
|
||||
|
||||
Write-Host ("Datei existiert bereits. Erzeuge neue Version: {0}/{1}" -f $TargetLibrary.Title, $FileName)
|
||||
return Update-SPExistingFileAsNewVersion -ExistingFile $existingFile -FileStream $fileStream
|
||||
}
|
||||
|
||||
if ($Overwrite.IsPresent) {
|
||||
return $TargetFolder.Files.Add($FileName, $FileBytes, $true)
|
||||
finally {
|
||||
if ($null -ne $fileStream) {
|
||||
$fileStream.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not [bool]$TargetLibrary.EnableVersioning) {
|
||||
Write-Warning ("Datei uebersprungen, da sie bereits existiert und in der Zielbibliothek keine Versionierung aktiv ist: {0}/{1}" -f $TargetLibrary.Title, $FileName)
|
||||
return $null
|
||||
function Write-SPFileImportError {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ErrorLogPath,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Stage,
|
||||
|
||||
[string]$SourceFilePath = "",
|
||||
|
||||
[string]$MetadataFilePath = "",
|
||||
|
||||
[string]$SourceLibrary = "",
|
||||
|
||||
[string]$TargetLibrary = "",
|
||||
|
||||
[string]$TargetWebUrl = "",
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ErrorMessage
|
||||
)
|
||||
|
||||
try {
|
||||
$logDirectory = [System.IO.Path]::GetDirectoryName($ErrorLogPath)
|
||||
if (-not [string]::IsNullOrWhiteSpace($logDirectory)) {
|
||||
Ensure-Directory -Path $logDirectory
|
||||
}
|
||||
|
||||
$errorRecord = [PSCustomObject]@{
|
||||
Timestamp = (Get-Date).ToString("o")
|
||||
Stage = $Stage
|
||||
SourceFile = $SourceFilePath
|
||||
MetadataFile = $MetadataFilePath
|
||||
SourceLibrary = $SourceLibrary
|
||||
TargetLibrary = $TargetLibrary
|
||||
TargetWebUrl = $TargetWebUrl
|
||||
Error = $ErrorMessage
|
||||
}
|
||||
|
||||
if ([System.IO.File]::Exists($ErrorLogPath)) {
|
||||
$errorRecord | Export-Csv -LiteralPath $ErrorLogPath -NoTypeInformation -Append -Encoding UTF8
|
||||
}
|
||||
else {
|
||||
$errorRecord | Export-Csv -LiteralPath $ErrorLogPath -NoTypeInformation -Encoding UTF8
|
||||
}
|
||||
|
||||
$script:FileImportErrorCount++
|
||||
}
|
||||
catch {
|
||||
Write-Warning ("Errorlog konnte nicht geschrieben werden: {0}; urspruenglicher Fehler: {1}" -f $_.Exception.Message, $ErrorMessage)
|
||||
}
|
||||
|
||||
Write-Host ("Datei existiert bereits. Erzeuge neue Version: {0}/{1}" -f $TargetLibrary.Title, $FileName)
|
||||
return Update-SPExistingFileAsNewVersion -ExistingFile $existingFile -FileBytes $FileBytes
|
||||
}
|
||||
|
||||
function Import-SPDocumentLibraries {
|
||||
@@ -2747,6 +3052,9 @@ function Import-SPDocumentLibraries {
|
||||
[Parameter(Mandatory = $true)]
|
||||
$MigrationMappingTable,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ErrorLogPath,
|
||||
|
||||
[switch]$Overwrite
|
||||
)
|
||||
|
||||
@@ -2761,71 +3069,90 @@ function Import-SPDocumentLibraries {
|
||||
|
||||
foreach ($metadataFile in $metadataFiles) {
|
||||
$sourceFilePath = $metadataFile.FullName.Substring(0, $metadataFile.FullName.Length - ".properties.json".Length)
|
||||
if (-not [System.IO.File]::Exists($sourceFilePath)) {
|
||||
Write-Warning ("Quelldatei zur Metadatei nicht gefunden: {0}" -f $metadataFile.FullName)
|
||||
continue
|
||||
}
|
||||
$sourceLibraryTitle = ""
|
||||
$targetLibraryTitle = ""
|
||||
$importStage = "Initialisierung"
|
||||
|
||||
$metadata = Read-JsonFile -Path $metadataFile.FullName
|
||||
$libraryMetadata = Get-ObjectPropertyValue -Object $metadata -PropertyName "Library"
|
||||
$itemMetadata = Get-ObjectPropertyValue -Object $metadata -PropertyName "Item"
|
||||
$sourceLibraryTitle = [string](Get-ObjectPropertyValue -Object $libraryMetadata -PropertyName "Title")
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($sourceLibraryTitle)) {
|
||||
Write-Warning ("Bibliothekstitel fehlt in: {0}" -f $metadataFile.FullName)
|
||||
continue
|
||||
}
|
||||
|
||||
$targetLibraryTitle = Get-TargetContainerTitleFromMappingTable -ContainerMappingMap $containerMappingMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
|
||||
$targetLibrary = $Web.Lists.TryGetList($targetLibraryTitle)
|
||||
if ($null -eq $targetLibrary) {
|
||||
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)
|
||||
try {
|
||||
$importStage = "Quelldatei pruefen"
|
||||
if (-not [System.IO.File]::Exists($sourceFilePath)) {
|
||||
throw ("Quelldatei zur Metadatei nicht gefunden: {0}" -f $metadataFile.FullName)
|
||||
}
|
||||
else {
|
||||
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)
|
||||
|
||||
$importStage = "Metadatendatei lesen"
|
||||
$metadata = Read-JsonFile -Path $metadataFile.FullName
|
||||
$libraryMetadata = Get-ObjectPropertyValue -Object $metadata -PropertyName "Library"
|
||||
$itemMetadata = Get-ObjectPropertyValue -Object $metadata -PropertyName "Item"
|
||||
$sourceLibraryTitle = [string](Get-ObjectPropertyValue -Object $libraryMetadata -PropertyName "Title")
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($sourceLibraryTitle)) {
|
||||
throw ("Bibliothekstitel fehlt in: {0}" -f $metadataFile.FullName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ($targetLibrary.BaseType -ne [Microsoft.SharePoint.SPBaseType]::DocumentLibrary) {
|
||||
Write-Warning ("Zielliste ist keine Dokumentbibliothek: {0}" -f $targetLibraryTitle)
|
||||
continue
|
||||
}
|
||||
$importStage = "Zielbibliothek aufloesen"
|
||||
$targetLibraryTitle = Get-TargetContainerTitleFromMappingTable -ContainerMappingMap $containerMappingMap -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
|
||||
$targetLibrary = $Web.Lists.TryGetList($targetLibraryTitle)
|
||||
if ($null -eq $targetLibrary) {
|
||||
throw ("Zielbibliothek '{0}' fuer Quellbibliothek '{1}' nicht gefunden." -f $targetLibraryTitle, $sourceLibraryTitle)
|
||||
}
|
||||
|
||||
$relativeSegments = @(Get-RelativeSegmentsFromMetadataPath -MetadataFilePath $metadataFile.FullName -FilesRootPath $FilesRootPath)
|
||||
if ($relativeSegments.Length -lt 2) {
|
||||
Write-Warning ("Dateipfad konnte nicht relativ zur Bibliothek ermittelt werden: {0}" -f $metadataFile.FullName)
|
||||
continue
|
||||
}
|
||||
if ($targetLibrary.BaseType -ne [Microsoft.SharePoint.SPBaseType]::DocumentLibrary) {
|
||||
throw ("Zielliste ist keine Dokumentbibliothek: {0}" -f $targetLibraryTitle)
|
||||
}
|
||||
|
||||
$folderSegments = @()
|
||||
if ($relativeSegments.Length -gt 2) {
|
||||
$folderSegments = $relativeSegments[1..($relativeSegments.Length - 2)]
|
||||
}
|
||||
$importStage = "Zielordner aufloesen"
|
||||
$relativeSegments = @(Get-RelativeSegmentsFromMetadataPath -MetadataFilePath $metadataFile.FullName -FilesRootPath $FilesRootPath)
|
||||
if ($relativeSegments.Length -lt 2) {
|
||||
throw ("Dateipfad konnte nicht relativ zur Bibliothek ermittelt werden: {0}" -f $metadataFile.FullName)
|
||||
}
|
||||
|
||||
$targetFolder = Ensure-SPFolderHierarchy -List $targetLibrary -FolderSegments $folderSegments
|
||||
$fileName = [System.IO.Path]::GetFileName($sourceFilePath)
|
||||
$fileBytes = [System.IO.File]::ReadAllBytes($sourceFilePath)
|
||||
$folderSegments = @()
|
||||
if ($relativeSegments.Length -gt 2) {
|
||||
$folderSegments = $relativeSegments[1..($relativeSegments.Length - 2)]
|
||||
}
|
||||
|
||||
Write-Host ("Importiere Datei: {0} -> {1}" -f $sourceFilePath, $targetLibrary.Title)
|
||||
$targetFolder = Ensure-SPFolderHierarchy -List $targetLibrary -FolderSegments $folderSegments
|
||||
$fileName = [System.IO.Path]::GetFileName($sourceFilePath)
|
||||
|
||||
$spFile = Import-SPFileToFolder -TargetFolder $targetFolder -TargetLibrary $targetLibrary -FileName $fileName -FileBytes $fileBytes -Overwrite:$Overwrite
|
||||
if ($null -eq $spFile) {
|
||||
continue
|
||||
}
|
||||
$importStage = "Datei hochladen"
|
||||
Write-Host ("Importiere Datei: {0} -> {1}" -f $sourceFilePath, $targetLibrary.Title)
|
||||
$spFile = Import-SPFileToFolder -TargetFolder $targetFolder -TargetLibrary $targetLibrary -FileName $fileName -SourceFilePath $sourceFilePath -Overwrite:$Overwrite
|
||||
if ($null -eq $spFile) {
|
||||
continue
|
||||
}
|
||||
|
||||
$spItem = $spFile.Item
|
||||
$spItem = $spFile.Item
|
||||
|
||||
if ($null -ne $itemMetadata) {
|
||||
$importStage = "Metadaten setzen"
|
||||
$fieldErrors = New-Object 'System.Collections.Generic.List[string]'
|
||||
|
||||
try {
|
||||
Set-SPItemContentTypeForImport -Item $spItem -List $targetLibrary -SourceContentTypeId ([string](Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "ContentTypeId")) -SourceContentTypeName ([string](Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "ContentTypeName"))
|
||||
}
|
||||
catch {
|
||||
$contentTypeError = "Content-Type konnte fuer Bibliothek='{0}'; Datei='{1}' nicht gesetzt werden. {2}" -f $targetLibrary.Title, $fileName, $_.Exception.Message
|
||||
Write-Warning $contentTypeError
|
||||
[void]$fieldErrors.Add($contentTypeError)
|
||||
}
|
||||
|
||||
if ($null -ne $itemMetadata) {
|
||||
try {
|
||||
$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") -Context ("Bibliothek='{0}'; Datei='{1}'" -f $targetLibrary.Title, $fileName)
|
||||
Write-Host ("Gefundene Metadaten-Mappings: Bibliothek='{0}'; Anzahl={1}" -f $targetLibrary.Title, $fieldMapping.Count)
|
||||
Apply-FieldMappingToItem -Item $spItem -FieldMapping $fieldMapping -SourceFieldValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldValues") -SourceFieldTextValues (Get-ObjectPropertyValue -Object $itemMetadata -PropertyName "FieldTextValues") -Context ("Bibliothek='{0}'; Datei='{1}'" -f $targetLibrary.Title, $fileName) -Errors $fieldErrors
|
||||
|
||||
foreach ($fieldError in $fieldErrors) {
|
||||
Write-SPFileImportError -ErrorLogPath $ErrorLogPath -Stage "Metadatenfeld setzen" -SourceFilePath $sourceFilePath -MetadataFilePath $metadataFile.FullName -SourceLibrary $sourceLibraryTitle -TargetLibrary $targetLibraryTitle -TargetWebUrl $Web.Url -ErrorMessage $fieldError
|
||||
}
|
||||
|
||||
$importStage = "Metadaten speichern"
|
||||
Save-SPListItem -Item $spItem -Context ("Bibliothek '{0}', Datei '{1}'" -f $targetLibrary.Title, $fileName)
|
||||
}
|
||||
catch {
|
||||
Write-Warning ("Metadaten fuer Datei konnten nicht gespeichert werden: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$errorMessage = $_.Exception.Message
|
||||
Write-SPFileImportError -ErrorLogPath $ErrorLogPath -Stage $importStage -SourceFilePath $sourceFilePath -MetadataFilePath $metadataFile.FullName -SourceLibrary $sourceLibraryTitle -TargetLibrary $targetLibraryTitle -TargetWebUrl $Web.Url -ErrorMessage $errorMessage
|
||||
Write-Warning ("Datei konnte nicht vollstaendig importiert werden; der Import wird fortgesetzt. Datei='{0}'; Phase='{1}'; Fehler={2}" -f $sourceFilePath, $importStage, $errorMessage)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2970,6 +3297,13 @@ function Import-SPLists {
|
||||
$targetItem = $targetList.Items.Add()
|
||||
}
|
||||
|
||||
try {
|
||||
Set-SPItemContentTypeForImport -Item $targetItem -List $targetList -SourceContentTypeId ([string](Get-ObjectPropertyValue -Object $sourceItem -PropertyName "ContentTypeId")) -SourceContentTypeName ([string](Get-ObjectPropertyValue -Object $sourceItem -PropertyName "ContentTypeName"))
|
||||
}
|
||||
catch {
|
||||
Write-Warning ("Content-Type fuer Listenelement konnte nicht gesetzt werden; der Standard-Content-Type wird verwendet. Liste='{0}'; SourceItemId='{1}'; Fehler={2}" -f $targetListTitle, $sourceItemId, $_.Exception.Message)
|
||||
}
|
||||
|
||||
Apply-FieldMappingToItem -Item $targetItem -FieldMapping $fieldMapping -SourceFieldValues (Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FieldValues") -SourceFieldTextValues (Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FieldTextValues") -Context ("Liste='{0}'; SourceItemId='{1}'; Titel='{2}'" -f $targetListTitle, $sourceItemId, $sourceItemTitle)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($sourceItemUniqueId)) {
|
||||
@@ -2986,7 +3320,12 @@ function Import-SPLists {
|
||||
}
|
||||
}
|
||||
|
||||
Initialize-SharePointPowerShell
|
||||
$script:SharePointEnvironment = Initialize-SharePointPowerShell
|
||||
|
||||
if ($ValidateEnvironment.IsPresent -and -not $Export.IsPresent -and -not $Import.IsPresent -and [string]::IsNullOrWhiteSpace($SourceUrl) -and [string]::IsNullOrWhiteSpace($TargetUrl)) {
|
||||
Write-Host "Umgebungspruefung erfolgreich."
|
||||
return
|
||||
}
|
||||
|
||||
$resolvedOutputPath = [System.IO.Path]::GetFullPath($OutputPath)
|
||||
$filesRootPath = [System.IO.Path]::Combine($resolvedOutputPath, "Files")
|
||||
|
||||
+158
-16
@@ -1,4 +1,6 @@
|
||||
|
||||
#requires -Version 5.0
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$NoGui
|
||||
@@ -6,6 +8,21 @@ param(
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$script:ApplicationVersion = "1.2.0"
|
||||
$script:SupportedMappingSchemaVersion = 2
|
||||
|
||||
$editionProperty = $PSVersionTable.PSObject.Properties["PSEdition"]
|
||||
if ($null -ne $editionProperty -and [string]$editionProperty.Value -ne "Desktop") {
|
||||
throw "Start-SPMigration GUI muss fuer SharePoint SSOM mit Windows PowerShell (Desktop Edition), nicht mit PowerShell 7, gestartet werden."
|
||||
}
|
||||
|
||||
if (-not [Environment]::Is64BitProcess) {
|
||||
throw "Start-SPMigration GUI muss in 64-Bit Windows PowerShell gestartet werden."
|
||||
}
|
||||
|
||||
if ([System.Threading.Thread]::CurrentThread.ApartmentState -ne [System.Threading.ApartmentState]::STA) {
|
||||
throw "Start-SPMigration GUI benoetigt einen STA-Thread. Bitte mit 'powershell.exe -STA -File .\Start-SPMigrationGUI.ps1' starten."
|
||||
}
|
||||
|
||||
class SettingsManager {
|
||||
[string]$SettingsPath
|
||||
@@ -781,7 +798,7 @@ class DialogBuilder : BaseComponent {
|
||||
}
|
||||
}
|
||||
|
||||
# PowerShell 7 can fail to resolve WinForms via `using assembly`, but runtime loading works reliably.
|
||||
# WinForms and SharePoint SSOM are loaded at runtime in 64-bit Windows PowerShell.
|
||||
[void](Add-Type -AssemblyName System.Windows.Forms)
|
||||
[void](Add-Type -AssemblyName System.Drawing)
|
||||
[System.Windows.Forms.Application]::EnableVisualStyles()
|
||||
@@ -794,10 +811,14 @@ $script:MigrationScriptPath = Join-Path -Path (Split-Path -Parent $PSCommandPath
|
||||
$script:IsUpdatingContainerChecks = $false
|
||||
$script:LastSuggestedMappingPath = ""
|
||||
$script:CurrentMappingMeta = [ordered]@{
|
||||
SchemaVersion = 1
|
||||
SchemaVersion = $script:SupportedMappingSchemaVersion
|
||||
ToolVersion = $script:ApplicationVersion
|
||||
GeneratedAtUtc = ""
|
||||
SourceWebUrl = ""
|
||||
SourceSharePointProduct = ""
|
||||
SourceSharePointBuild = ""
|
||||
}
|
||||
$script:SharePointEnvironment = $null
|
||||
$script:IsMigrationRunning = $false
|
||||
$script:MigrationProcess = $null
|
||||
$script:MigrationOutputPath = ""
|
||||
@@ -844,9 +865,95 @@ function Get-ObjectPropertyValue {
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Get-SharePointProductSupportInfo {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[version]$BuildVersion
|
||||
)
|
||||
|
||||
$productName = "Nicht unterstuetzte SharePoint-Version"
|
||||
$isSupported = $false
|
||||
|
||||
if ($BuildVersion.Major -eq 16 -and $BuildVersion.Build -ge 4351 -and $BuildVersion.Build -lt 10337) {
|
||||
$productName = "SharePoint Server 2016"
|
||||
$isSupported = $true
|
||||
}
|
||||
elseif ($BuildVersion.Major -eq 16 -and $BuildVersion.Build -ge 10337 -and $BuildVersion.Build -lt 14326) {
|
||||
$productName = "SharePoint Server 2019"
|
||||
$isSupported = $true
|
||||
}
|
||||
elseif ($BuildVersion.Major -eq 16 -and $BuildVersion.Build -ge 14326) {
|
||||
$productName = "SharePoint Server Subscription Edition"
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
ProductName = $productName
|
||||
IsSupported = $isSupported
|
||||
}
|
||||
}
|
||||
|
||||
function Initialize-SharePointPowerShellForGui {
|
||||
if ($null -eq (Get-Module -Name SharePointServer)) {
|
||||
Import-Module SharePointServer -ErrorAction Stop
|
||||
$editionProperty = $PSVersionTable.PSObject.Properties["PSEdition"]
|
||||
if ($null -ne $editionProperty -and [string]$editionProperty.Value -ne "Desktop") {
|
||||
throw "Die GUI und SharePoint SSOM werden nur unter 64-Bit Windows PowerShell (Desktop Edition), nicht unter PowerShell 7, unterstuetzt."
|
||||
}
|
||||
|
||||
if (-not [Environment]::Is64BitProcess) {
|
||||
throw "Die GUI muss fuer SharePoint SSOM in einem 64-Bit-Windows-PowerShell-Prozess ausgefuehrt werden."
|
||||
}
|
||||
|
||||
if ($PSVersionTable.PSVersion -lt [version]"5.0") {
|
||||
throw ("Die GUI erfordert Windows PowerShell 5.0 oder neuer. Gefunden: {0}" -f $PSVersionTable.PSVersion)
|
||||
}
|
||||
|
||||
if ($null -eq (Get-Command -Name Get-SPWeb -ErrorAction SilentlyContinue)) {
|
||||
$loadErrors = @()
|
||||
|
||||
try {
|
||||
Import-Module SharePointServer -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
$loadErrors += ("Import-Module SharePointServer: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
if ($null -eq (Get-Command -Name Get-SPWeb -ErrorAction SilentlyContinue)) {
|
||||
try {
|
||||
$registeredSnapIn = Get-PSSnapin -Registered -Name Microsoft.SharePoint.PowerShell -ErrorAction Stop
|
||||
if ($null -ne $registeredSnapIn -and $null -eq (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue)) {
|
||||
Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction Stop
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$loadErrors += ("Add-PSSnapin Microsoft.SharePoint.PowerShell: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq (Get-Command -Name Get-SPWeb -ErrorAction SilentlyContinue)) {
|
||||
throw ("Die SharePoint Server PowerShell-Cmdlets konnten nicht geladen werden. Die GUI muss lokal auf einem SharePoint-Server ausgefuehrt werden. {0}" -f ($loadErrors -join " | "))
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$Host.Runspace.ThreadOptions = "ReuseThread"
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
|
||||
if ($null -eq $farm) {
|
||||
throw "Keine lokale SharePoint-Farm gefunden. SSOM kann nur lokal auf einem SharePoint-Farmserver verwendet werden."
|
||||
}
|
||||
|
||||
$buildVersion = [version]$farm.BuildVersion
|
||||
$supportInfo = Get-SharePointProductSupportInfo -BuildVersion $buildVersion
|
||||
if (-not $supportInfo.IsSupported) {
|
||||
throw ("Nicht unterstuetzte lokale SharePoint-Farm: {0}, Build {1}. Unterstuetzt werden SharePoint Server 2016 und 2019." -f $supportInfo.ProductName, $buildVersion)
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
ProductName = $supportInfo.ProductName
|
||||
BuildVersion = $buildVersion.ToString()
|
||||
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,12 +1196,7 @@ function Get-MigrationHostPath {
|
||||
}
|
||||
}
|
||||
|
||||
$currentProcessPath = (Get-Process -Id $PID).Path
|
||||
if (-not [string]::IsNullOrWhiteSpace($currentProcessPath) -and [System.IO.File]::Exists($currentProcessPath)) {
|
||||
return $currentProcessPath
|
||||
}
|
||||
|
||||
throw "Kein PowerShell-Host fuer die Migration gefunden."
|
||||
throw "Keine 64-Bit-Windows-PowerShell fuer die SSOM-Migration gefunden."
|
||||
}
|
||||
|
||||
function Get-SingleQuotedPowerShellLiteral {
|
||||
@@ -1414,7 +1516,7 @@ function Get-SourceContainers {
|
||||
[switch]$IncludeHiddenLists
|
||||
)
|
||||
|
||||
Initialize-SharePointPowerShellForGui
|
||||
$script:SharePointEnvironment = Initialize-SharePointPowerShellForGui
|
||||
|
||||
$web = $null
|
||||
|
||||
@@ -1996,9 +2098,12 @@ function New-EmptyMappingTable {
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
SchemaVersion = 1
|
||||
SchemaVersion = $script:SupportedMappingSchemaVersion
|
||||
ToolVersion = $script:ApplicationVersion
|
||||
GeneratedAtUtc = [System.DateTime]::UtcNow.ToString("o")
|
||||
SourceWebUrl = $sourceWebUrl
|
||||
SourceSharePointProduct = ""
|
||||
SourceSharePointBuild = ""
|
||||
LibraryMappings = @()
|
||||
ListMappings = @()
|
||||
MetadataColumnMappings = [PSCustomObject]@{
|
||||
@@ -2013,10 +2118,33 @@ function Set-MappingMetaFromObject {
|
||||
$MappingTable
|
||||
)
|
||||
|
||||
$schemaVersionValue = Get-ObjectPropertyValue -Object $MappingTable -PropertyName "SchemaVersion" -DefaultValue 1
|
||||
try {
|
||||
$schemaVersion = [int]$schemaVersionValue
|
||||
}
|
||||
catch {
|
||||
throw ("Ungueltige SchemaVersion in der MappingTable: '{0}'." -f $schemaVersionValue)
|
||||
}
|
||||
|
||||
if ($schemaVersion -lt 1) {
|
||||
throw ("Ungueltige SchemaVersion in der MappingTable: {0}." -f $schemaVersion)
|
||||
}
|
||||
|
||||
if ($schemaVersion -gt $script:SupportedMappingSchemaVersion) {
|
||||
throw ("MappingTable-SchemaVersion {0} ist neuer als die von GUI v{1} unterstuetzte Version {2}." -f $schemaVersion, $script:ApplicationVersion, $script:SupportedMappingSchemaVersion)
|
||||
}
|
||||
|
||||
if ($schemaVersion -lt $script:SupportedMappingSchemaVersion) {
|
||||
Write-UILog -Message ("MappingTable-SchemaVersion {0} wird beim Speichern auf Version {1} aktualisiert." -f $schemaVersion, $script:SupportedMappingSchemaVersion) -Level "WARN"
|
||||
}
|
||||
|
||||
$script:CurrentMappingMeta = [ordered]@{
|
||||
SchemaVersion = [int](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "SchemaVersion" -DefaultValue 1)
|
||||
SchemaVersion = $script:SupportedMappingSchemaVersion
|
||||
ToolVersion = [string](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "ToolVersion" -DefaultValue $script:ApplicationVersion)
|
||||
GeneratedAtUtc = [string](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "GeneratedAtUtc" -DefaultValue "")
|
||||
SourceWebUrl = [string](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "SourceWebUrl" -DefaultValue "")
|
||||
SourceSharePointProduct = [string](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "SourceSharePointProduct" -DefaultValue "")
|
||||
SourceSharePointBuild = [string](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "SourceSharePointBuild" -DefaultValue "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2057,9 +2185,12 @@ function Get-MappingTableFromUi {
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
SchemaVersion = [int]$script:CurrentMappingMeta.SchemaVersion
|
||||
SchemaVersion = $script:SupportedMappingSchemaVersion
|
||||
ToolVersion = [string]$script:CurrentMappingMeta.ToolVersion
|
||||
GeneratedAtUtc = if ([string]::IsNullOrWhiteSpace($script:CurrentMappingMeta.GeneratedAtUtc)) { [System.DateTime]::UtcNow.ToString("o") } else { $script:CurrentMappingMeta.GeneratedAtUtc }
|
||||
SourceWebUrl = $sourceWebUrl
|
||||
SourceSharePointProduct = [string]$script:CurrentMappingMeta.SourceSharePointProduct
|
||||
SourceSharePointBuild = [string]$script:CurrentMappingMeta.SourceSharePointBuild
|
||||
LibraryMappings = @(Convert-DataTableToObjects -Table $script:LibraryMappingsTable -Schema $script:GridSchemas.LibraryMappings)
|
||||
ListMappings = @(Convert-DataTableToObjects -Table $script:ListMappingsTable -Schema $script:GridSchemas.ListMappings)
|
||||
MetadataColumnMappings = [PSCustomObject]@{
|
||||
@@ -2599,7 +2730,7 @@ function Invoke-ImportFromGui {
|
||||
}
|
||||
}
|
||||
|
||||
$formBuilder = [FormBuilder]::new("Start-SPMigration GUI")
|
||||
$formBuilder = [FormBuilder]::new(("Start-SPMigration GUI v{0}" -f $script:ApplicationVersion))
|
||||
$null = $formBuilder.SetSize([int]$script:SettingsManager.Get("WindowWidth", 1400), [int]$script:SettingsManager.Get("WindowHeight", 940)).SetMinimumSize(1200, 820).SetStartPosition("CenterScreen")
|
||||
$script:MainForm = $formBuilder.Build()
|
||||
$script:MainForm.KeyPreview = $true
|
||||
@@ -3033,7 +3164,18 @@ $script:txtOutputPath.Add_TextChanged({
|
||||
})
|
||||
|
||||
$script:MainForm.Add_Shown({
|
||||
Write-UILog -Message "Start-SPMigration GUI gestartet."
|
||||
Write-UILog -Message ("Start-SPMigration GUI v{0} gestartet." -f $script:ApplicationVersion)
|
||||
try {
|
||||
$script:SharePointEnvironment = Initialize-SharePointPowerShellForGui
|
||||
Write-UILog -Message ("SSOM-Umgebung erkannt: {0}, FarmBuild={1}, WindowsPowerShell={2}" -f $script:SharePointEnvironment.ProductName, $script:SharePointEnvironment.BuildVersion, $script:SharePointEnvironment.PowerShellVersion)
|
||||
}
|
||||
catch {
|
||||
Write-UILog -Message ("SSOM-Umgebungspruefung fehlgeschlagen: {0}" -f $_.Exception.Message) -Level "ERROR"
|
||||
$btnLoadContainers.Enabled = $false
|
||||
$btnRunExport.Enabled = $false
|
||||
$btnRunImport.Enabled = $false
|
||||
Show-UiMessage -Message ("Die Migration wurde deaktiviert, weil keine unterstuetzte lokale SharePoint-2016/2019-SSOM-Umgebung erkannt wurde. Fehler: {0}" -f $_.Exception.Message) -Caption "SSOM-Umgebung nicht verfuegbar" -Icon ([System.Windows.Forms.MessageBoxIcon]::Error)
|
||||
}
|
||||
Load-UiSettings
|
||||
Set-MappingTableToUi -MappingTable (New-EmptyMappingTable)
|
||||
Load-ExistingMappingTableIfAvailable
|
||||
|
||||
+24
-14
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"SchemaVersion": 1,
|
||||
"SchemaVersion": 2,
|
||||
"ToolVersion": "1.2.0",
|
||||
"GeneratedAtUtc": "2026-04-15T08:00:00.0000000Z",
|
||||
"SourceWebUrl": "http://sharepoint/sites/Quelle",
|
||||
"SourceSharePointProduct": "SharePoint Server 2016",
|
||||
"SourceSharePointBuild": "16.0.5508.1000",
|
||||
"LibraryMappings": [
|
||||
{
|
||||
"ObjectType": "DocumentLibrary",
|
||||
@@ -24,17 +27,24 @@
|
||||
"Hidden": false
|
||||
}
|
||||
],
|
||||
"MetadataColumnMappings": [
|
||||
{
|
||||
"ObjectType": "DocumentLibrary",
|
||||
"ContainerSourceTitle": "Documents",
|
||||
"SourceInternalName": "DHS2016Persdat",
|
||||
"TargetInternalName": "Persdat",
|
||||
"DisplayName": "Personaldaten",
|
||||
"TypeAsString": "Text",
|
||||
"Hidden": false,
|
||||
"ReadOnly": false,
|
||||
"Sealed": false
|
||||
}
|
||||
]
|
||||
"MetadataColumnMappings": {
|
||||
"SystemColumns": [],
|
||||
"CustomColumns": [
|
||||
{
|
||||
"ObjectType": "DocumentLibrary",
|
||||
"ContainerSourceTitle": "Documents",
|
||||
"SourceInternalName": "DHS2016Persdat",
|
||||
"SourceCanonicalInternalName": "DHS2016Persdat",
|
||||
"SourceSupportingInternalNames": [],
|
||||
"TargetInternalName": "Persdat",
|
||||
"DisplayName": "Personaldaten",
|
||||
"TypeAsString": "Text",
|
||||
"Hidden": false,
|
||||
"ReadOnly": false,
|
||||
"Sealed": false,
|
||||
"IsSystemColumn": false,
|
||||
"ImportSupported": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user