Update application version to 1.3.4 and enhance support for SharePoint Subscription Edition in migration scripts and documentation
This commit is contained in:
+117
-151
@@ -33,7 +33,7 @@ param(
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$script:ApplicationVersion = "1.2.0"
|
||||
$script:ApplicationVersion = "1.3.4"
|
||||
$script:SupportedMappingSchemaVersion = 2
|
||||
$script:SkippedImportFieldWarnings = @{}
|
||||
$script:ListImportTrackingFieldInternalName = "StartSPMigrationSourceUniqueId"
|
||||
@@ -101,6 +101,7 @@ function Get-SharePointProductSupportInfo {
|
||||
}
|
||||
elseif ($BuildVersion.Major -eq 16 -and $BuildVersion.Build -ge 14326) {
|
||||
$productName = "SharePoint Server Subscription Edition"
|
||||
$isSupported = $true
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
@@ -163,7 +164,7 @@ function Initialize-SharePointPowerShell {
|
||||
|
||||
$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)
|
||||
throw ("Nicht unterstuetzte lokale SharePoint-Farm: {0}, Build {1}. Unterstuetzt werden SharePoint Server 2016, 2019 und Subscription Edition." -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)
|
||||
@@ -222,8 +223,14 @@ function Invoke-MigrationImport {
|
||||
|
||||
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."
|
||||
$productLevels = @{
|
||||
"SharePoint Server 2016" = 1
|
||||
"SharePoint Server 2019" = 2
|
||||
"SharePoint Server Subscription Edition" = 3
|
||||
}
|
||||
|
||||
if ($productLevels.ContainsKey($sourceSharePointProduct) -and $productLevels.ContainsKey($script:SharePointEnvironment.ProductName) -and $productLevels[$sourceSharePointProduct] -gt $productLevels[$script:SharePointEnvironment.ProductName]) {
|
||||
Write-Warning ("Ein Import von '{0}' nach '{1}' ist ein nicht garantierter Downgrade. Neuere Feldtypen, Content-Types oder Listentemplates koennen im Ziel fehlen." -f $sourceSharePointProduct, $script:SharePointEnvironment.ProductName)
|
||||
}
|
||||
|
||||
$shouldImportFiles = $ImportFiles.IsPresent
|
||||
@@ -462,6 +469,31 @@ function Convert-SPFieldValue {
|
||||
return $Value
|
||||
}
|
||||
|
||||
function Convert-SPItemFieldValueForExport {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Microsoft.SharePoint.SPField]$Field,
|
||||
|
||||
$Value
|
||||
)
|
||||
|
||||
if ($null -ne $Value -and [string]$Field.TypeAsString -in @("TaxonomyFieldType", "TaxonomyFieldTypeMulti")) {
|
||||
$valueTypeName = [string]$Value.GetType().FullName
|
||||
if ($valueTypeName -notlike "*TaxonomyFieldValue*") {
|
||||
try {
|
||||
$Value = ([Microsoft.SharePoint.Taxonomy.TaxonomyField]$Field).GetFieldValue([string]$Value)
|
||||
}
|
||||
catch {
|
||||
# Existing export packages can still contain SharePoint's
|
||||
# serialized WssId;#Label|TermGuid representation. The import
|
||||
# parser handles that representation as a compatibility path.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Convert-SPFieldValue $Value
|
||||
}
|
||||
|
||||
function Get-FieldTextValue {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
@@ -553,7 +585,7 @@ function Get-ListItemFieldMetadata {
|
||||
InternalName = $field.InternalName
|
||||
TypeAsString = $field.TypeAsString
|
||||
TextValue = Get-FieldTextValue -Field $field -RawValue $rawValue
|
||||
Value = Convert-SPFieldValue $rawValue
|
||||
Value = Convert-SPItemFieldValueForExport -Field $field -Value $rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,7 +621,7 @@ function Get-ListItemFieldValueMap {
|
||||
$fieldValues[$field.InternalName] = Get-FieldTextValue -Field $field -RawValue $rawValue
|
||||
}
|
||||
else {
|
||||
$fieldValues[$field.InternalName] = Convert-SPFieldValue $rawValue
|
||||
$fieldValues[$field.InternalName] = Convert-SPItemFieldValueForExport -Field $field -Value $rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1949,127 +1981,6 @@ function Resolve-SPField {
|
||||
return $null
|
||||
}
|
||||
|
||||
function Resolve-SPAvailableContentTypeForImport {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Microsoft.SharePoint.SPWeb]$Web,
|
||||
|
||||
[string]$SourceContentTypeId,
|
||||
|
||||
[string]$SourceContentTypeName
|
||||
)
|
||||
|
||||
$sourceId = $null
|
||||
if (-not [string]::IsNullOrWhiteSpace($SourceContentTypeId)) {
|
||||
try {
|
||||
$sourceId = New-Object Microsoft.SharePoint.SPContentTypeId($SourceContentTypeId)
|
||||
}
|
||||
catch {
|
||||
throw ("Ungueltige Source-ContentTypeId: {0}" -f $SourceContentTypeId)
|
||||
}
|
||||
}
|
||||
|
||||
$availableContentTypes = @($Web.AvailableContentTypes)
|
||||
|
||||
if ($null -ne $sourceId) {
|
||||
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 (-not [string]::IsNullOrWhiteSpace($SourceContentTypeName)) {
|
||||
foreach ($contentType in $availableContentTypes) {
|
||||
if ([string]$contentType.Name -ieq $SourceContentTypeName) {
|
||||
return $contentType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Ensure-SPListContentTypeForImport {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Microsoft.SharePoint.SPList]$List,
|
||||
|
||||
[string]$SourceContentTypeId,
|
||||
|
||||
[string]$SourceContentTypeName
|
||||
)
|
||||
|
||||
$siteContentType = Resolve-SPAvailableContentTypeForImport -Web $List.ParentWeb -SourceContentTypeId $SourceContentTypeId -SourceContentTypeName $SourceContentTypeName
|
||||
if ($null -eq $siteContentType) {
|
||||
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)) {
|
||||
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) {
|
||||
$List.ContentTypesEnabled = $true
|
||||
$List.Update()
|
||||
}
|
||||
|
||||
if (-not $List.IsContentTypeAllowed($siteContentType)) {
|
||||
throw ("Content-Type '{0}' ({1}) ist fuer die Zielliste '{2}' nicht zulaessig." -f $siteContentType.Name, $siteContentType.Id, $List.Title)
|
||||
}
|
||||
|
||||
$listContentType = $List.ContentTypes.Add($siteContentType)
|
||||
$List.Update()
|
||||
Write-Host ("Content-Type zur Zielliste hinzugefuegt: Liste='{0}'; ContentType='{1}'; SiteContentTypeId='{2}'; ListContentTypeId='{3}'" -f $List.Title, $siteContentType.Name, $siteContentType.Id, $listContentType.Id)
|
||||
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)]
|
||||
@@ -2322,13 +2233,28 @@ function Convert-ToTaxonomyTermEntries {
|
||||
if ([string]::IsNullOrWhiteSpace($termGuid) -and ($rawTerm -is [string])) {
|
||||
$rawTermString = [string]$rawTerm
|
||||
|
||||
if ($rawTermString -match "^(.*)\|([0-9a-fA-F-]{36})$") {
|
||||
$label = $matches[1]
|
||||
$termGuid = $matches[2]
|
||||
}
|
||||
elseif ($rawTermString -match "^[0-9a-fA-F-]{36}$") {
|
||||
if ($rawTermString -match "^[0-9a-fA-F-]{36}$") {
|
||||
$termGuid = $rawTermString
|
||||
}
|
||||
else {
|
||||
# SharePoint can expose taxonomy values as either
|
||||
# Label|TermGuid or WssId;#Label|TermGuid. Multi-value strings
|
||||
# repeat those pairs with ;# delimiters.
|
||||
$serializedSegments = @($rawTermString -split ";#|;")
|
||||
foreach ($serializedSegment in $serializedSegments) {
|
||||
$candidate = [string]$serializedSegment
|
||||
if ($candidate -match "^\s*(.*?)\|([0-9a-fA-F-]{36})\s*$") {
|
||||
$termEntries += [PSCustomObject]@{
|
||||
Label = $matches[1].Trim()
|
||||
TermGuid = ([Guid]$matches[2]).ToString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($termEntries.Count -gt 0) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($label) -and -not [string]::IsNullOrWhiteSpace($TextValue) -and $rawTerms.Length -le 1) {
|
||||
@@ -2353,7 +2279,7 @@ function Convert-ToTaxonomyTermEntries {
|
||||
}
|
||||
|
||||
if ($termEntries.Count -eq 0 -and -not [string]::IsNullOrWhiteSpace($TextValue)) {
|
||||
$segments = @($TextValue -split ";#")
|
||||
$segments = @($TextValue -split ";#|;")
|
||||
|
||||
foreach ($segment in $segments) {
|
||||
if ([string]::IsNullOrWhiteSpace([string]$segment)) {
|
||||
@@ -2369,7 +2295,7 @@ function Convert-ToTaxonomyTermEntries {
|
||||
}
|
||||
}
|
||||
|
||||
return @($termEntries)
|
||||
return @($termEntries | Group-Object -Property TermGuid | ForEach-Object { $_.Group[0] })
|
||||
}
|
||||
|
||||
function New-TaxonomyFieldValueObject {
|
||||
@@ -2455,6 +2381,16 @@ function Set-SPTaxonomyFieldValue {
|
||||
|
||||
if ($termEntries.Count -eq 0) {
|
||||
if ((Test-SPValuePresent -Value $RawValue) -or -not [string]::IsNullOrWhiteSpace($TextValue)) {
|
||||
try {
|
||||
$currentTextValue = [string]$taxonomyField.GetFieldValueAsText($Item[$Field.InternalName])
|
||||
if (-not [string]::IsNullOrWhiteSpace($currentTextValue) -and $currentTextValue.Trim() -ieq $TextValue.Trim()) {
|
||||
Write-Host ("Taxonomy-Zielwert ist bereits gesetzt: Feld='{0}'" -f $Field.InternalName)
|
||||
return
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
throw ("Taxonomy-Wert fuer Feld '{0}' enthaelt keine gueltige TermGuid. Der Wert kann nicht in die Ziel-SiteCollection aufgeloest werden." -f $Field.InternalName)
|
||||
}
|
||||
|
||||
@@ -2693,6 +2629,11 @@ function Apply-FieldMappingToItem {
|
||||
$null
|
||||
}
|
||||
|
||||
if (-not (Test-SPValuePresent -Value $rawValue) -and -not (Test-SPValuePresent -Value $textValue)) {
|
||||
Write-Host ("Leerer Columnwert uebersprungen: {0}; SourceColumn='{1}'; TargetColumn='{2}'" -f $Context, $sourceInternalName, $targetInternalName)
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
$fieldWasSet = Set-SPItemFieldValue -Item $Item -TargetInternalName $targetInternalName -RawValue $rawValue -TextValue $textValue
|
||||
if ($fieldWasSet) {
|
||||
@@ -2745,8 +2686,49 @@ function Test-SPValuePresent {
|
||||
return -not [string]::IsNullOrWhiteSpace($Value)
|
||||
}
|
||||
|
||||
if ((Test-ObjectHasProperty -Object $Value -PropertyName "TermGuid") -or (Test-ObjectHasProperty -Object $Value -PropertyName "Label")) {
|
||||
$termGuid = Get-ObjectPropertyValue -Object $Value -PropertyName "TermGuid"
|
||||
$label = Get-ObjectPropertyValue -Object $Value -PropertyName "Label"
|
||||
return (Test-SPValuePresent -Value $termGuid) -or (Test-SPValuePresent -Value $label)
|
||||
}
|
||||
|
||||
if ($Value -is [System.Collections.IDictionary]) {
|
||||
if ($Value.Count -eq 0) {
|
||||
return $false
|
||||
}
|
||||
|
||||
foreach ($dictionaryValue in $Value.Values) {
|
||||
if (Test-SPValuePresent -Value $dictionaryValue) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) {
|
||||
return @($Value).Count -gt 0
|
||||
foreach ($entry in $Value) {
|
||||
if (Test-SPValuePresent -Value $entry) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($Value -is [System.Management.Automation.PSCustomObject]) {
|
||||
$properties = @($Value.PSObject.Properties)
|
||||
if ($properties.Count -eq 0) {
|
||||
return $false
|
||||
}
|
||||
|
||||
foreach ($property in $properties) {
|
||||
if (Test-SPValuePresent -Value $property.Value) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
@@ -3127,15 +3109,6 @@ function Import-SPDocumentLibraries {
|
||||
$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)
|
||||
}
|
||||
|
||||
$fieldMapping = Get-FieldMappingForContainer -MigrationMappingTable $MigrationMappingTable -ObjectType "DocumentLibrary" -SourceTitle $sourceLibraryTitle
|
||||
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
|
||||
@@ -3297,13 +3270,6 @@ 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)) {
|
||||
|
||||
Reference in New Issue
Block a user