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:
+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")
|
||||
|
||||
Reference in New Issue
Block a user