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:
Torsten Brendgen
2026-08-26 21:39:39 +02:00
parent e02a38c926
commit 90d17be932
4 changed files with 710 additions and 162 deletions
+454 -115
View File
@@ -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")