Erweitere README mit Informationen zu WssId und Taxonomie-Feldverarbeitung; füge Kontextmenü zum Löschen von Mapping-Zeilen in der GUI hinzu

This commit is contained in:
Torsten Brendgen
2026-07-15 21:30:15 +02:00
parent 2cf6d47ccd
commit bc3c72f64a
3 changed files with 294 additions and 54 deletions

View File

@@ -255,6 +255,28 @@ function Convert-SPFieldValue {
return $Value.ToString("o")
}
# Taxonomy values must be handled before the generic lookup/enumerable
# branches. Their WssId is only local to the source site collection; the
# portable part of the value is the label plus TermGuid.
$typeName = $Value.GetType().FullName
if ($typeName -like "*TaxonomyFieldValueCollection") {
return @($Value | ForEach-Object {
@{
Label = $_.Label
TermGuid = $_.TermGuid
WssId = $_.WssId
}
})
}
if ($typeName -like "*TaxonomyFieldValue") {
return @{
Label = $Value.Label
TermGuid = $Value.TermGuid
WssId = $Value.WssId
}
}
if ($Value -is [Microsoft.SharePoint.SPFieldUserValue]) {
return @{
LookupId = $Value.LookupId
@@ -294,25 +316,6 @@ function Convert-SPFieldValue {
return @($Value | ForEach-Object { Convert-SPFieldValue $_ })
}
$typeName = $Value.GetType().FullName
if ($typeName -like "*TaxonomyFieldValue") {
return @{
Label = $Value.Label
TermGuid = $Value.TermGuid
WssId = $Value.WssId
}
}
if ($typeName -like "*TaxonomyFieldValueCollection") {
return @($Value | ForEach-Object {
@{
Label = $_.Label
TermGuid = $_.TermGuid
WssId = $_.WssId
}
})
}
return $Value
}
@@ -486,6 +489,7 @@ function Get-ListItemMetadataObject {
UniqueId = $Item.UniqueId.ToString()
Title = if ($Item.Fields.ContainsField("Title")) { $Item["Title"] } else { $null }
ContentTypeId = $Item.ContentTypeId.ToString()
ContentTypeName = if ($null -ne $Item.ContentType) { $Item.ContentType.Name } else { "" }
FileSystemObjectType = $Item.FileSystemObjectType.ToString()
DisplayName = $Item.DisplayName
Name = if ($Item.Name) { $Item.Name } else { $null }
@@ -976,6 +980,8 @@ function Test-SPFieldSupportedForImport {
"SMTotalSize",
"SortBehavior",
"SyncClientId",
"TaxCatchAll",
"TaxCatchAllLabel",
"TimeZone",
"UID",
"UniqueId",
@@ -1530,6 +1536,8 @@ function Test-MetadataColumnMappingRowImportSupported {
"SMTotalSize",
"SortBehavior",
"SyncClientId",
"TaxCatchAll",
"TaxCatchAllLabel",
"TimeZone",
"UID",
"UniqueId",
@@ -1737,6 +1745,86 @@ 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)
}
}
$idMatches = @()
if ($null -ne $sourceId) {
foreach ($contentType in @($Web.AvailableContentTypes)) {
if ($contentType.Id.Equals($sourceId) -or $contentType.Id.IsParentOf($sourceId)) {
$idMatches += $contentType
}
}
}
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) {
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)
}
foreach ($listContentType in @($List.ContentTypes)) {
if ($listContentType.Id.Equals($siteContentType.Id) -or $siteContentType.Id.IsParentOf($listContentType.Id)) {
return $listContentType
}
}
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 Get-FieldMapping {
param(
[Parameter(Mandatory = $true)]
@@ -1989,7 +2077,7 @@ function Convert-ToTaxonomyTermEntries {
$label = $matches[1]
$termGuid = $matches[2]
}
elseif ([guid]::TryParse($rawTermString, [ref]([guid]::Empty))) {
elseif ($rawTermString -match "^[0-9a-fA-F-]{36}$") {
$termGuid = $rawTermString
}
}
@@ -1998,10 +2086,13 @@ function Convert-ToTaxonomyTermEntries {
$label = $TextValue
}
if ([string]::IsNullOrWhiteSpace($termGuid)) {
$parsedTermGuid = [Guid]::Empty
if ([string]::IsNullOrWhiteSpace($termGuid) -or -not [Guid]::TryParse($termGuid, [ref]$parsedTermGuid)) {
continue
}
$termGuid = $parsedTermGuid.ToString()
if ([string]::IsNullOrWhiteSpace($label)) {
$label = $termGuid
}
@@ -2070,6 +2161,10 @@ function New-TaxonomyFieldValueObject {
try {
$taxonomyValue.PopulateFromLabelGuidPair($labelGuidPair)
# WssIds belong to the site collection and must never be carried over
# from the source TaxonomyHiddenList. -1 lets SharePoint resolve (and,
# when necessary, create) the target-site entry from the TermGuid.
$taxonomyValue.WssId = -1
$isPopulated = $true
}
catch {
@@ -2110,6 +2205,10 @@ function Set-SPTaxonomyFieldValue {
$termEntries = @(Convert-ToTaxonomyTermEntries -RawValue $RawValue -TextValue $TextValue)
if ($termEntries.Count -eq 0) {
if ((Test-SPValuePresent -Value $RawValue) -or -not [string]::IsNullOrWhiteSpace($TextValue)) {
throw ("Taxonomy-Wert fuer Feld '{0}' enthaelt keine gueltige TermGuid. Der Wert kann nicht in die Ziel-SiteCollection aufgeloest werden." -f $Field.InternalName)
}
$Item[$Field.InternalName] = $null
return
}
@@ -2220,51 +2319,57 @@ function Set-SPItemFieldValue {
$field = Resolve-SPField -Fields $Item.Fields -InternalName $TargetInternalName
if ($null -eq $field) {
Write-SkippedImportFieldWarning -FieldInternalName $TargetInternalName -Message ("Zielfeld nicht gefunden: {0}" -f $TargetInternalName)
return
throw ("Zielfeld nicht gefunden: {0}" -f $TargetInternalName)
}
if (-not (Test-SPFieldSupportedForImport -Field $field)) {
Write-SkippedImportFieldWarning -FieldInternalName $TargetInternalName -Message ("Zielfeld wird nicht importiert und wird uebersprungen: {0}" -f $TargetInternalName)
return
throw ("Zielfeld wird nicht importiert und wird uebersprungen: {0}" -f $TargetInternalName)
}
try {
switch ($field.TypeAsString) {
"Boolean" {
$Item[$field.InternalName] = Convert-ToBoolean -RawValue $RawValue -TextValue $TextValue
return
return $true
}
"Integer" {
$Item[$field.InternalName] = Convert-ToInt32 -RawValue $RawValue -TextValue $TextValue
return
return $true
}
"Counter" {
return
throw ("Counter-Feld kann nicht gesetzt werden: {0}" -f $TargetInternalName)
}
"Number" {
$Item[$field.InternalName] = Convert-ToDouble -RawValue $RawValue -TextValue $TextValue
return
return $true
}
"Currency" {
$Item[$field.InternalName] = Convert-ToDouble -RawValue $RawValue -TextValue $TextValue
return
return $true
}
"DateTime" {
$Item[$field.InternalName] = Convert-ToDateTimeValue -RawValue $RawValue -TextValue $TextValue
return
return $true
}
"URL" {
$Item[$field.InternalName] = Convert-ToUrlFieldValue -RawValue $RawValue -TextValue $TextValue
return
return $true
}
"User" {
$Item[$field.InternalName] = Convert-ToUserFieldValue -Item $Item -RawValue $RawValue -TextValue $TextValue
return
return $true
}
"UserMulti" {
$Item[$field.InternalName] = Convert-ToUserMultiFieldValue -Item $Item -RawValue $RawValue -TextValue $TextValue
return
return $true
}
"TaxonomyFieldType" {
Set-SPTaxonomyFieldValue -Item $Item -Field $field -RawValue $RawValue -TextValue $TextValue
return $true
}
"TaxonomyFieldTypeMulti" {
Set-SPTaxonomyFieldValue -Item $Item -Field $field -RawValue $RawValue -TextValue $TextValue
return $true
}
default {
if ($null -eq $RawValue) {
@@ -2274,22 +2379,28 @@ function Set-SPItemFieldValue {
$Item[$field.InternalName] = $RawValue
}
return
return $true
}
}
}
catch {
$setErrorMessage = $_.Exception.Message
if ($field.TypeAsString -in @("TaxonomyFieldType", "TaxonomyFieldTypeMulti")) {
throw ("Konnte Taxonomy-Feld '{0}' nicht ueber die TermGuid setzen. {1}" -f $TargetInternalName, $setErrorMessage)
}
if (-not [string]::IsNullOrWhiteSpace($TextValue)) {
try {
$field.ParseAndSetValue($Item, $TextValue)
return
return $true
}
catch {
throw ("Konnte Feld '{0}' weder direkt noch ueber ParseAndSetValue setzen. Direkt: {1}; ParseAndSetValue: {2}" -f $TargetInternalName, $setErrorMessage, $_.Exception.Message)
}
}
Write-SkippedImportFieldWarning -FieldInternalName $TargetInternalName -Message ("Konnte Feld '{0}' nicht setzen und ueberspringe es. {1}" -f $TargetInternalName, $_.Exception.Message)
return
throw ("Konnte Feld '{0}' nicht setzen. {1}" -f $TargetInternalName, $setErrorMessage)
}
}
@@ -2303,7 +2414,9 @@ function Apply-FieldMappingToItem {
$SourceFieldValues,
$SourceFieldTextValues
$SourceFieldTextValues,
[string]$Context = ""
)
foreach ($sourceInternalName in $FieldMapping.Keys) {
@@ -2340,7 +2453,15 @@ function Apply-FieldMappingToItem {
$null
}
Set-SPItemFieldValue -Item $Item -TargetInternalName $targetInternalName -RawValue $rawValue -TextValue $textValue
try {
$fieldWasSet = Set-SPItemFieldValue -Item $Item -TargetInternalName $targetInternalName -RawValue $rawValue -TextValue $textValue
if ($fieldWasSet) {
Write-Host ("Columnwert erfolgreich gesetzt: {0}; SourceColumn='{1}'; TargetColumn='{2}'" -f $Context, $sourceInternalName, $targetInternalName)
}
}
catch {
Write-Warning ("Fehler beim Setzen eines Columnwerts: {0}; SourceColumn='{1}'; TargetColumn='{2}'; Fehler={3}" -f $Context, $sourceInternalName, $targetInternalName, $_.Exception.Message)
}
}
}
@@ -2696,7 +2817,7 @@ function Import-SPDocumentLibraries {
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")
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)
Save-SPListItem -Item $spItem -Context ("Bibliothek '{0}', Datei '{1}'" -f $targetLibrary.Title, $fileName)
}
catch {
@@ -2846,7 +2967,7 @@ function Import-SPLists {
$targetItem = $targetList.Items.Add()
}
Apply-FieldMappingToItem -Item $targetItem -FieldMapping $fieldMapping -SourceFieldValues (Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FieldValues") -SourceFieldTextValues (Get-ObjectPropertyValue -Object $sourceItem -PropertyName "FieldTextValues")
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)) {
$targetItem[$trackingField.InternalName] = $sourceItemUniqueId