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

@@ -162,6 +162,7 @@ Fuer das Mapping ist vor allem relevant:
- `SourceSupportingInternalNames`: technische Begleitspalten wie `_0` werden nur noch informativ aufgefuehrt und nicht separat gemappt
- `ImportSupported`: steuert, ob eine Spalte beim Import ueberhaupt beruecksichtigt wird
- Taxonomy-Felder werden beim Import ueber den exportierten `TermGuid` gesetzt; dabei wird angenommen, dass derselbe Term im Ziel bereits mit identischer GUID existiert
- Die exportierte `WssId` wird bewusst nicht uebernommen: Sie ist nur innerhalb der Quell-SiteCollection gueltig. SharePoint loest beim Setzen des Feldwerts die zielseitige `WssId` auf und legt den Eintrag in der `TaxonomyHiddenList` bei Bedarf selbst an. Die Hidden List soll deshalb nicht separat migriert oder vorab befuellt werden.
Wenn eine Ziel-Liste oder Ziel-Bibliothek nicht existiert, gibt das Skript eine Warnung aus, dass dieser Container manuell angelegt werden soll.
@@ -311,6 +312,11 @@ Anschliessend:
- Listeneintraege werden neu angelegt oder bei erneutem Import ueber `StartSPMigrationSourceUniqueId` wiedergefunden und aktualisiert.
- Feldwerte werden ueber `FieldValues`, `FieldTextValues` und `MetadataColumnMappings` gemappt.
### GUI-Logging und Mapping-Bearbeitung
- Beim Import protokolliert die GUI fuer jeden verarbeiteten Columnwert, ob die Zuweisung am Item erfolgreich war oder mit welchem Fehler sie uebersprungen wurde. Das Log nennt Container bzw. Item sowie Quell- und Zielspalte, schreibt aber nicht den eigentlichen Feldinhalt mit.
- In den Mapping-Tabs `Bibliotheken`, `Listen`, `System Columns` und `Custom Columns` kann eine Zeile per Rechtsklick und `Zeile loeschen` vollstaendig entfernt werden. Beim Speichern wird sie entsprechend nicht mehr in die `MappingTable.json` geschrieben.
## Bekannte Einschraenkungen
- Die Zielaufloesung kann ueber `MappingTable.json` mit `TargetTitle` ueberschrieben werden.

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

View File

@@ -808,6 +808,7 @@ $script:MigrationStreamPositions = @{}
$script:MigrationCompletedHandler = $null
$script:MigrationActionLabel = ""
$script:LastMigrationErrorMessage = ""
$script:PendingExportMappingPath = ""
function Get-ObjectPropertyValue {
param(
@@ -1257,6 +1258,7 @@ function Complete-MigrationScriptAsync {
}
}
else {
$script:PendingExportMappingPath = ""
Show-UiMessage -Message $errorMessage -Caption ("{0} fehlgeschlagen" -f $actionLabel) -Icon ([System.Windows.Forms.MessageBoxIcon]::Error)
}
}
@@ -1791,6 +1793,103 @@ function Set-DataTableRows {
}
}
function Add-MappingGridRowDeleteContextMenu {
param(
[Parameter(Mandatory = $true)]
[System.Windows.Forms.DataGridView]$Grid,
[Parameter(Mandatory = $true)]
[System.Data.DataTable]$Table,
[Parameter(Mandatory = $true)]
[string]$MappingName
)
$contextMenu = New-Object System.Windows.Forms.ContextMenuStrip
$deleteRowItem = New-Object System.Windows.Forms.ToolStripMenuItem("Zeile loeschen")
[void]$contextMenu.Items.Add($deleteRowItem)
$context = [PSCustomObject]@{
Grid = $Grid
Table = $Table
MappingName = $MappingName
ClickedRowIndex = -1
}
$contextMenu.Tag = $context
$Grid.Add_MouseDown({
param($sender, $e)
if ($e.Button -ne [System.Windows.Forms.MouseButtons]::Right) {
return
}
$menuContext = $sender.ContextMenuStrip.Tag
$hit = $sender.HitTest($e.X, $e.Y)
$menuContext.ClickedRowIndex = $hit.RowIndex
if ($hit.RowIndex -lt 0) {
$sender.ClearSelection()
return
}
$columnIndex = if ($hit.ColumnIndex -ge 0) { $hit.ColumnIndex } else { 0 }
$sender.ClearSelection()
$sender.CurrentCell = $sender.Rows[$hit.RowIndex].Cells[$columnIndex]
$sender.Rows[$hit.RowIndex].Selected = $true
})
$contextMenu.Add_Opening({
param($sender, $e)
$menuContext = $sender.Tag
if ($menuContext.ClickedRowIndex -lt 0 -or $menuContext.ClickedRowIndex -ge $menuContext.Grid.Rows.Count) {
$e.Cancel = $true
}
})
$deleteRowItem.Add_Click({
param($sender, $e)
$menuContext = $sender.Owner.Tag
$rowIndex = [int]$menuContext.ClickedRowIndex
if ($rowIndex -lt 0 -or $rowIndex -ge $menuContext.Grid.Rows.Count) {
return
}
$gridRow = $menuContext.Grid.Rows[$rowIndex]
if ($gridRow.IsNewRow) {
return
}
$rowLabel = "Zeile {0}" -f ($rowIndex + 1)
$dataRowView = $gridRow.DataBoundItem -as [System.Data.DataRowView]
if ($null -ne $dataRowView) {
foreach ($candidateColumn in @("SourceTitle", "SourceInternalName", "DisplayName")) {
if (-not $menuContext.Table.Columns.Contains($candidateColumn)) {
continue
}
$candidateValue = [string]$dataRowView.Row[$candidateColumn]
if (-not [string]::IsNullOrWhiteSpace($candidateValue)) {
$rowLabel = "{0}='{1}'" -f $candidateColumn, $candidateValue
break
}
}
$menuContext.Table.Rows.Remove($dataRowView.Row)
}
else {
$menuContext.Grid.Rows.Remove($gridRow)
}
$menuContext.ClickedRowIndex = -1
Write-UILog -Message ("Mapping-Zeile geloescht: Bereich='{0}'; {1}" -f $menuContext.MappingName, $rowLabel)
})
$Grid.ContextMenuStrip = $contextMenu
}
function Configure-MappingGrid {
param(
[Parameter(Mandatory = $true)]
@@ -1800,7 +1899,10 @@ function Configure-MappingGrid {
[System.Data.DataTable]$Table,
[Parameter(Mandatory = $true)]
[object[]]$Schema
[object[]]$Schema,
[Parameter(Mandatory = $true)]
[string]$MappingName
)
$Grid.DataSource = $Table
@@ -1816,6 +1918,8 @@ function Configure-MappingGrid {
}
}
}
Add-MappingGridRowDeleteContextMenu -Grid $Grid -Table $Table -MappingName $MappingName
}
function Convert-DataTableToObjects {
@@ -2418,18 +2522,27 @@ function Invoke-ExportFromGui {
}
$mappingPath = Join-Path -Path $outputPath -ChildPath "MappingTable.json"
$script:PendingExportMappingPath = $mappingPath
$onCompleted = {
if (Test-Path -LiteralPath $mappingPath) {
Load-MappingTableFromPath -Path $mappingPath
Apply-ColumnDefaultsIfAvailable
Save-MappingTableToPath -Path $mappingPath
Show-MappingTab
$completedMappingPath = [string]$script:PendingExportMappingPath
try {
if (Test-Path -LiteralPath $completedMappingPath) {
Load-MappingTableFromPath -Path $completedMappingPath
Apply-ColumnDefaultsIfAvailable
Save-MappingTableToPath -Path $completedMappingPath
Show-MappingTab
}
}
}.GetNewClosure()
finally {
$script:PendingExportMappingPath = ""
}
}
Invoke-MigrationScript -Parameters $parameters -ActionLabel "Export" -OnCompleted $onCompleted
}
catch {
$script:PendingExportMappingPath = ""
Write-UILog -Message $_.Exception.Message -Level "ERROR"
Show-UiMessage -Message $_.Exception.Message -Caption "Export fehlgeschlagen" -Icon ([System.Windows.Forms.MessageBoxIcon]::Error)
}
@@ -2653,7 +2766,7 @@ $mappingLayout.ColumnCount = 1
[void]$mappingLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 34)))
[void]$mappingLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100)))
$mappingInfoLabel = ([LabelBuilder]::new("TargetTitle und TargetInternalName koennen hier direkt angepasst werden. Vor dem Import wird die MappingTable automatisch gespeichert.")).Build()
$mappingInfoLabel = ([LabelBuilder]::new("TargetTitle und TargetInternalName koennen hier direkt angepasst werden. Mit Rechtsklick kann eine ganze Mapping-Zeile geloescht werden. Vor dem Import wird automatisch gespeichert.")).Build()
$mappingInfoLabel.Dock = [System.Windows.Forms.DockStyle]::Fill
$mappingTabs = ([TabControlBuilder]::new()).SetDock("Fill").Build()
@@ -2670,10 +2783,10 @@ $script:gridCustomColumns = ([DataGridViewBuilder]::new()).SetDock("Fill").SetAl
Initialize-MappingTables
Configure-MappingGrid -Grid $script:gridLibraryMappings -Table $script:LibraryMappingsTable -Schema $script:GridSchemas.LibraryMappings
Configure-MappingGrid -Grid $script:gridListMappings -Table $script:ListMappingsTable -Schema $script:GridSchemas.ListMappings
Configure-MappingGrid -Grid $script:gridSystemColumns -Table $script:SystemColumnsTable -Schema $script:GridSchemas.SystemColumns
Configure-MappingGrid -Grid $script:gridCustomColumns -Table $script:CustomColumnsTable -Schema $script:GridSchemas.CustomColumns
Configure-MappingGrid -Grid $script:gridLibraryMappings -Table $script:LibraryMappingsTable -Schema $script:GridSchemas.LibraryMappings -MappingName "Bibliotheken"
Configure-MappingGrid -Grid $script:gridListMappings -Table $script:ListMappingsTable -Schema $script:GridSchemas.ListMappings -MappingName "Listen"
Configure-MappingGrid -Grid $script:gridSystemColumns -Table $script:SystemColumnsTable -Schema $script:GridSchemas.SystemColumns -MappingName "System Columns"
Configure-MappingGrid -Grid $script:gridCustomColumns -Table $script:CustomColumnsTable -Schema $script:GridSchemas.CustomColumns -MappingName "Custom Columns"
$libraryMappingTab.Controls.Add($script:gridLibraryMappings)
$listMappingTab.Controls.Add($script:gridListMappings)