From 8de604771959825bdd9fa36474a90a640e603356 Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Thu, 16 Apr 2026 19:19:24 +0200 Subject: [PATCH] Adding Start-SPMigrationGUI.ps1 --- .gui-state/Start-SPMigration/settings.json | 9 + Start-SPMigration.ps1 | 47 + Start-SPMigrationGUI.ps1 | 2211 ++++++++++++++++++++ 3 files changed, 2267 insertions(+) create mode 100644 .gui-state/Start-SPMigration/settings.json create mode 100644 Start-SPMigrationGUI.ps1 diff --git a/.gui-state/Start-SPMigration/settings.json b/.gui-state/Start-SPMigration/settings.json new file mode 100644 index 0000000..79debaa --- /dev/null +++ b/.gui-state/Start-SPMigration/settings.json @@ -0,0 +1,9 @@ +{ + "WindowHeight": 940, + "FontSize": 9, + "LastOutputPath": "C:\\Users\\CodexSandboxOffline\\.codex\\.sandbox\\cwd\\c85dc9d4b6e088fe\\SPMigrationOutput", + "LastMappingTablePath": "", + "LastSourceUrl": "", + "WindowWidth": 1400, + "LastTargetUrl": "" +} \ No newline at end of file diff --git a/Start-SPMigration.ps1 b/Start-SPMigration.ps1 index 7895ab7..92bd83e 100644 --- a/Start-SPMigration.ps1 +++ b/Start-SPMigration.ps1 @@ -9,6 +9,8 @@ param( [switch]$IncludeHiddenLists, + [string[]]$SelectedContainerUrls = @(), + [Alias("TargetWebUrl")] [string]$TargetUrl, @@ -151,6 +153,49 @@ function Test-IsCatalogList { } } +function Test-SPContainerSelected { + param( + [Parameter(Mandatory = $true)] + [Microsoft.SharePoint.SPList]$List, + + [string[]]$SelectedContainerUrls = @() + ) + + if ($null -eq $SelectedContainerUrls -or $SelectedContainerUrls.Count -eq 0) { + return $true + } + + $selectedLookup = @{} + foreach ($selectedUrl in @($SelectedContainerUrls)) { + if (-not [string]::IsNullOrWhiteSpace([string]$selectedUrl)) { + $selectedLookup[[string]$selectedUrl.Trim().ToLowerInvariant()] = $true + } + } + + if ($selectedLookup.Count -eq 0) { + return $true + } + + $candidateUrls = @( + [string]$List.RootFolder.ServerRelativeUrl, + [string]$List.DefaultViewUrl, + [string]$List.Title + ) + + foreach ($candidateUrl in $candidateUrls) { + if ([string]::IsNullOrWhiteSpace([string]$candidateUrl)) { + continue + } + + $normalizedCandidateUrl = [string]$candidateUrl.Trim().ToLowerInvariant() + if ($selectedLookup.ContainsKey($normalizedCandidateUrl)) { + return $true + } + } + + return $false +} + function Get-SafeCollectionCount { param( $Value @@ -2796,6 +2841,7 @@ try { $web.Lists | Where-Object { $_.BaseType -eq [Microsoft.SharePoint.SPBaseType]::DocumentLibrary -and ($IncludeHiddenLibraries -or -not $_.Hidden) -and + (Test-SPContainerSelected -List $_ -SelectedContainerUrls $SelectedContainerUrls) -and -not (Test-IsCatalogList -List $_) } ) @@ -2804,6 +2850,7 @@ try { $web.Lists | Where-Object { $_.BaseType -ne [Microsoft.SharePoint.SPBaseType]::DocumentLibrary -and ($IncludeHiddenLists -or -not $_.Hidden) -and + (Test-SPContainerSelected -List $_ -SelectedContainerUrls $SelectedContainerUrls) -and -not (Test-IsCatalogList -List $_) } ) diff --git a/Start-SPMigrationGUI.ps1 b/Start-SPMigrationGUI.ps1 new file mode 100644 index 0000000..4165f30 --- /dev/null +++ b/Start-SPMigrationGUI.ps1 @@ -0,0 +1,2211 @@ +using assembly System.Windows.Forms +using assembly System.Drawing +using namespace System.Windows.Forms +using namespace System.Drawing + +[CmdletBinding()] +param( + [switch]$NoGui +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" +$script:ApplicationDataRoot = Join-Path -Path (Split-Path -Parent $PSCommandPath) -ChildPath ".gui-state" + +class SettingsManager { + [string]$SettingsPath + [hashtable]$Settings = @{} + + SettingsManager([string]$AppName) { + $appRoot = if ([string]::IsNullOrWhiteSpace($script:ApplicationDataRoot)) { + [Environment]::GetFolderPath("ApplicationData") + } + else { + $script:ApplicationDataRoot + } + $appFolder = Join-Path -Path $appRoot -ChildPath $AppName + + if (-not (Test-Path -LiteralPath $appFolder)) { + New-Item -Path $appFolder -ItemType Directory -Force | Out-Null + } + + $this.SettingsPath = Join-Path -Path $appFolder -ChildPath "settings.json" + $this.Load() + $this.EnsureDefaults() + } + + [void] Load() { + if (-not (Test-Path -LiteralPath $this.SettingsPath)) { + $this.Settings = @{} + return + } + + try { + $json = Get-Content -LiteralPath $this.SettingsPath -Raw -Encoding UTF8 | ConvertFrom-Json + $this.Settings = @{} + + if ($null -ne $json) { + foreach ($property in $json.PSObject.Properties) { + $this.Settings[$property.Name] = $this.ConvertToHashtable($property.Value) + } + } + } + catch { + $this.Settings = @{} + } + } + + [void] EnsureDefaults() { + $defaults = [ordered]@{ + FontSize = 9 + WindowWidth = 1400 + WindowHeight = 940 + LastSourceUrl = "" + LastTargetUrl = "" + LastOutputPath = (Join-Path -Path (Get-Location).Path -ChildPath "SPMigrationOutput") + LastMappingTablePath = "" + } + + foreach ($key in $defaults.Keys) { + if (-not $this.Settings.ContainsKey($key)) { + $this.Settings[$key] = $defaults[$key] + } + } + + $this.Save() + } + + [void] Save() { + $directory = Split-Path -Parent $this.SettingsPath + if (-not (Test-Path -LiteralPath $directory)) { + New-Item -Path $directory -ItemType Directory -Force | Out-Null + } + + $json = $this.Settings | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText($this.SettingsPath, $json, [System.Text.Encoding]::UTF8) + } + + [object] Get([string]$Key) { + if ($this.Settings.ContainsKey($Key)) { + return $this.Settings[$Key] + } + + return $null + } + + [object] Get([string]$Key, [object]$DefaultValue) { + if ($this.Settings.ContainsKey($Key)) { + return $this.Settings[$Key] + } + + return $DefaultValue + } + + [void] Set([string]$Key, [object]$Value) { + $this.Settings[$Key] = $Value + $this.Save() + } + + [bool] Contains([string]$Key) { + return $this.Settings.ContainsKey($Key) + } + + hidden [object] ConvertToHashtable([object]$Value) { + if ($null -eq $Value) { + return $null + } + + if ($Value -is [System.Collections.IDictionary]) { + $hash = @{} + foreach ($key in $Value.Keys) { + $hash[[string]$key] = $this.ConvertToHashtable($Value[$key]) + } + + return $hash + } + + if ($Value -is [System.Management.Automation.PSCustomObject]) { + $hash = @{} + foreach ($property in $Value.PSObject.Properties) { + $hash[$property.Name] = $this.ConvertToHashtable($property.Value) + } + + return $hash + } + + if ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) { + $items = @() + foreach ($item in $Value) { + $items += $this.ConvertToHashtable($item) + } + + return $items + } + + return $Value + } +} + +class LogManager { + [string]$LogPath + [bool]$EnableConsole = $false + + LogManager([string]$AppName) { + $appRoot = if ([string]::IsNullOrWhiteSpace($script:ApplicationDataRoot)) { + [Environment]::GetFolderPath("ApplicationData") + } + else { + $script:ApplicationDataRoot + } + $logFolder = Join-Path -Path $appRoot -ChildPath (Join-Path -Path $AppName -ChildPath "Logs") + + if (-not (Test-Path -LiteralPath $logFolder)) { + New-Item -Path $logFolder -ItemType Directory -Force | Out-Null + } + + $timestamp = Get-Date -Format "yyyyMMdd" + $this.LogPath = Join-Path -Path $logFolder -ChildPath ("gui_{0}.log" -f $timestamp) + } + + [void] Info([string]$Message) { + $this.Write("INFO", $Message) + } + + [void] Warning([string]$Message) { + $this.Write("WARN", $Message) + } + + [void] Error([string]$Message) { + $this.Write("ERROR", $Message) + } + + hidden [void] Write([string]$Level, [string]$Message) { + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $line = "[{0}] [{1}] {2}" -f $timestamp, $Level, $Message + Add-Content -LiteralPath $this.LogPath -Value $line -Encoding UTF8 + + if ($this.EnableConsole) { + Write-Host $line + } + } +} + +class AccessibilityManager { + [SettingsManager]$SettingsManager + [LogManager]$LogManager + + AccessibilityManager([SettingsManager]$SettingsManager, [LogManager]$LogManager) { + $this.SettingsManager = $SettingsManager + $this.LogManager = $LogManager + } + + [void] RegisterForm([object]$Form) { + if ($null -eq $Form) { + return + } + + $this.ApplyFont($Form) + } + + [void] RegisterFontControl([object]$Control) { + if ($null -eq $Control) { + return + } + + $this.ApplyFont($Control) + } + + [void] RegisterFontControls([object[]]$Controls) { + foreach ($control in $Controls) { + $this.RegisterFontControl($control) + } + } + + [void] RegisterAllControls([object]$Container) { + if ($null -eq $Container) { + return + } + + $this.ApplyFont($Container) + foreach ($control in $Container.Controls) { + $this.RegisterAllControls($control) + } + } + + hidden [void] ApplyFont([object]$Control) { + try { + $fontSize = [single]$this.SettingsManager.Get("FontSize", 9) + $fontFamily = $Control.Font.FontFamily + $fontStyle = $Control.Font.Style + $Control.Font = New-Object System.Drawing.Font($fontFamily, $fontSize, $fontStyle) + } + catch { + } + } +} + +class BaseComponent { + static [SettingsManager]$SettingsManager + static [LogManager]$LogManager + static [AccessibilityManager]$AccessibilityManager + + static [void] InitializeShared([SettingsManager]$SettingsManager, [LogManager]$LogManager) { + [BaseComponent]::SettingsManager = $SettingsManager + [BaseComponent]::LogManager = $LogManager + [BaseComponent]::AccessibilityManager = [AccessibilityManager]::new($SettingsManager, $LogManager) + } + + [object] GetSetting([string]$Key) { + return [BaseComponent]::SettingsManager.Get($Key) + } + + [object] GetSetting([string]$Key, [object]$DefaultValue) { + return [BaseComponent]::SettingsManager.Get($Key, $DefaultValue) + } + + [void] LogInfo([string]$Message) { + if ($null -ne [BaseComponent]::LogManager) { + [BaseComponent]::LogManager.Info($Message) + } + } +} + +class FormBuilder : BaseComponent { + [object]$Form + + FormBuilder([string]$Title) { + $this.Form = New-Object System.Windows.Forms.Form + $this.Form.Text = $Title + $this.Form.StartPosition = "CenterScreen" + [BaseComponent]::AccessibilityManager.RegisterForm($this.Form) + } + + [FormBuilder] SetSize([int]$Width, [int]$Height) { + $this.Form.Size = New-Object System.Drawing.Size($Width, $Height) + return $this + } + + [FormBuilder] SetMinimumSize([int]$Width, [int]$Height) { + $this.Form.MinimumSize = New-Object System.Drawing.Size($Width, $Height) + return $this + } + + [FormBuilder] SetStartPosition([string]$Position) { + $this.Form.StartPosition = $Position + return $this + } + + [FormBuilder] SetWindowState([string]$WindowState) { + $this.Form.WindowState = $WindowState + return $this + } + + [FormBuilder] AddControl([object]$Control) { + $this.Form.Controls.Add($Control) + return $this + } + + [FormBuilder] AddControls([object[]]$Controls) { + foreach ($control in $Controls) { + $this.Form.Controls.Add($control) + } + + return $this + } + + [FormBuilder] RegisterAllAccessibleControls() { + [BaseComponent]::AccessibilityManager.RegisterAllControls($this.Form) + return $this + } + + [FormBuilder] AddShownHandler([scriptblock]$Handler) { + $this.Form.Add_Shown($Handler) + return $this + } + + [FormBuilder] AddFormClosingHandler([scriptblock]$Handler) { + $this.Form.Add_FormClosing($Handler) + return $this + } + + [object] Build() { + return $this.Form + } + + [void] Show() { + [void]$this.Form.ShowDialog() + } +} + +class FlowLayoutPanelBuilder { + [object]$Control + + FlowLayoutPanelBuilder() { + $this.Control = New-Object System.Windows.Forms.FlowLayoutPanel + $this.Control.Dock = "Fill" + $this.Control.AutoScroll = $true + $this.Control.FlowDirection = "LeftToRight" + $this.Control.WrapContents = $false + } + + [FlowLayoutPanelBuilder] SetDock([string]$Dock) { + $this.Control.Dock = $Dock + return $this + } + + [FlowLayoutPanelBuilder] SetAutoScroll([bool]$AutoScroll) { + $this.Control.AutoScroll = $AutoScroll + return $this + } + + [FlowLayoutPanelBuilder] SetFlowDirection([string]$Direction) { + $this.Control.FlowDirection = $Direction + return $this + } + + [FlowLayoutPanelBuilder] SetWrapContents([bool]$WrapContents) { + $this.Control.WrapContents = $WrapContents + return $this + } + + [FlowLayoutPanelBuilder] SetPadding([int]$All) { + $this.Control.Padding = New-Object System.Windows.Forms.Padding($All) + return $this + } + + [FlowLayoutPanelBuilder] AddControl([object]$Control) { + $this.Control.Controls.Add($Control) + return $this + } + + [FlowLayoutPanelBuilder] AddControls([object[]]$Controls) { + foreach ($control in $Controls) { + $this.Control.Controls.Add($control) + } + + return $this + } + + [object] Build() { + return $this.Control + } +} + +class ButtonBuilder : BaseComponent { + [object]$Button + + ButtonBuilder([string]$Text) { + $this.Button = New-Object System.Windows.Forms.Button + $this.Button.Text = $Text + $this.Button.Font = New-Object System.Drawing.Font("Segoe UI", [single]$this.GetSetting("FontSize", 9)) + $this.Button.Margin = New-Object System.Windows.Forms.Padding(6) + $this.Button.AutoSize = $false + } + + [ButtonBuilder] SetSize([int]$Width, [int]$Height) { + $this.Button.Size = New-Object System.Drawing.Size($Width, $Height) + return $this + } + + [ButtonBuilder] SetWidth([int]$Width) { + $this.Button.Width = $Width + return $this + } + + [ButtonBuilder] SetHeight([int]$Height) { + $this.Button.Height = $Height + return $this + } + + [ButtonBuilder] SetDock([string]$Dock) { + $this.Button.Dock = $Dock + return $this + } + + [ButtonBuilder] SetEnabled([bool]$Enabled) { + $this.Button.Enabled = $Enabled + return $this + } + + [ButtonBuilder] AddClickHandler([scriptblock]$Handler) { + $this.Button.Add_Click($Handler) + return $this + } + + [object] Build() { + return $this.Button + } +} + +class LabelBuilder : BaseComponent { + [object]$Label + + LabelBuilder([string]$Text) { + $this.Label = New-Object System.Windows.Forms.Label + $this.Label.Text = $Text + $this.Label.Font = New-Object System.Drawing.Font("Segoe UI", [single]$this.GetSetting("FontSize", 9)) + $this.Label.AutoSize = $true + $this.Label.Margin = New-Object System.Windows.Forms.Padding(6, 8, 6, 6) + } + + [LabelBuilder] SetAutoSize([bool]$AutoSize) { + $this.Label.AutoSize = $AutoSize + return $this + } + + [LabelBuilder] SetWidth([int]$Width) { + $this.Label.Width = $Width + return $this + } + + [LabelBuilder] SetHeight([int]$Height) { + $this.Label.Height = $Height + return $this + } + + [LabelBuilder] SetDock([string]$Dock) { + $this.Label.Dock = $Dock + return $this + } + + [LabelBuilder] SetTextAlign([object]$Alignment) { + $this.Label.TextAlign = $Alignment + return $this + } + + [LabelBuilder] SetMargin([int]$Left, [int]$Top, [int]$Right, [int]$Bottom) { + $this.Label.Margin = New-Object System.Windows.Forms.Padding($Left, $Top, $Right, $Bottom) + return $this + } + + [object] Build() { + return $this.Label + } +} + +class TreeViewBuilder : BaseComponent { + [object]$Control + + TreeViewBuilder() { + $this.Control = New-Object System.Windows.Forms.TreeView + $this.Control.Dock = "Fill" + $this.Control.HideSelection = $false + $this.Control.ShowPlusMinus = $true + $this.Control.ShowRootLines = $true + $this.Control.FullRowSelect = $true + $this.Control.Font = New-Object System.Drawing.Font("Segoe UI", [single]$this.GetSetting("FontSize", 9)) + } + + [TreeViewBuilder] SetDock([string]$Dock) { + $this.Control.Dock = $Dock + return $this + } + + [TreeViewBuilder] SetCheckBoxes([bool]$CheckBoxes) { + $this.Control.CheckBoxes = $CheckBoxes + return $this + } + + [TreeViewBuilder] AddEventHandler([string]$EventName, [scriptblock]$Handler) { + $this.Control."Add_$EventName"($Handler) + return $this + } + + [object] Build() { + return $this.Control + } +} + +class DataGridViewBuilder : BaseComponent { + [object]$Control + + DataGridViewBuilder() { + $this.Control = New-Object System.Windows.Forms.DataGridView + $this.Control.Dock = "Fill" + $this.Control.AutoSizeColumnsMode = "Fill" + $this.Control.ColumnHeadersHeight = 32 + $this.Control.Font = New-Object System.Drawing.Font("Segoe UI", [single]$this.GetSetting("FontSize", 9)) + $this.Control.RowHeadersVisible = $false + $this.Control.AllowUserToDeleteRows = $false + $this.Control.MultiSelect = $false + $this.Control.SelectionMode = "CellSelect" + $this.Control.EditMode = "EditOnEnter" + } + + [DataGridViewBuilder] SetAllowAddRows([bool]$Enabled) { + $this.Control.AllowUserToAddRows = $Enabled + return $this + } + + [DataGridViewBuilder] SetDock([string]$Dock) { + $this.Control.Dock = $Dock + return $this + } + + [object] Build() { + return $this.Control + } +} + +class GroupBoxBuilder : BaseComponent { + [object]$Control + + GroupBoxBuilder([string]$Text) { + $this.Control = New-Object System.Windows.Forms.GroupBox + $this.Control.Text = $Text + $this.Control.Font = New-Object System.Drawing.Font("Segoe UI", [single]$this.GetSetting("FontSize", 9)) + $this.Control.Dock = "Fill" + $this.Control.Padding = New-Object System.Windows.Forms.Padding(10) + } + + [GroupBoxBuilder] SetDock([string]$Dock) { + $this.Control.Dock = $Dock + return $this + } + + [GroupBoxBuilder] SetPadding([int]$All) { + $this.Control.Padding = New-Object System.Windows.Forms.Padding($All) + return $this + } + + [GroupBoxBuilder] AddControl([object]$Control) { + $this.Control.Controls.Add($Control) + return $this + } + + [GroupBoxBuilder] AddControls([object[]]$Controls) { + foreach ($control in $Controls) { + $this.Control.Controls.Add($control) + } + + return $this + } + + [object] Build() { + return $this.Control + } +} + +class PanelBuilder : BaseComponent { + [object]$Control + + PanelBuilder() { + $this.Control = New-Object System.Windows.Forms.Panel + } + + [PanelBuilder] SetDock([string]$Dock) { + $this.Control.Dock = $Dock + return $this + } + + [PanelBuilder] SetPadding([int]$All) { + $this.Control.Padding = New-Object System.Windows.Forms.Padding($All) + return $this + } + + [PanelBuilder] SetAutoScroll([bool]$AutoScroll) { + $this.Control.AutoScroll = $AutoScroll + return $this + } + + [PanelBuilder] AddControl([object]$Control) { + $this.Control.Controls.Add($Control) + return $this + } + + [PanelBuilder] AddControls([object[]]$Controls) { + foreach ($control in $Controls) { + $this.Control.Controls.Add($control) + } + + return $this + } + + [object] Build() { + return $this.Control + } +} + +class TabControlBuilder : BaseComponent { + [object]$Control + + TabControlBuilder() { + $this.Control = New-Object System.Windows.Forms.TabControl + } + + [TabControlBuilder] SetDock([string]$Dock) { + $this.Control.Dock = $Dock + return $this + } + + [TabControlBuilder] AddTabPage([object]$TabPage) { + $this.Control.TabPages.Add($TabPage) | Out-Null + return $this + } + + [object] Build() { + return $this.Control + } +} + +class TabPageBuilder : BaseComponent { + [object]$Control + + TabPageBuilder([string]$Text) { + $this.Control = New-Object System.Windows.Forms.TabPage + $this.Control.Text = $Text + $this.Control.Padding = New-Object System.Windows.Forms.Padding(10) + } + + [TabPageBuilder] AddControl([object]$Control) { + $this.Control.Controls.Add($Control) + return $this + } + + [TabPageBuilder] SetPadding([int]$All) { + $this.Control.Padding = New-Object System.Windows.Forms.Padding($All) + return $this + } + + [object] Build() { + return $this.Control + } +} + +class DialogBuilder : BaseComponent { + [object]$Dialog + [string]$DialogType + + hidden DialogBuilder([string]$Type) { + $this.DialogType = $Type + + switch ($Type) { + "Open" { + $this.Dialog = New-Object System.Windows.Forms.OpenFileDialog + $this.Dialog.Filter = "Alle Dateien (*.*)|*.*" + $this.Dialog.RestoreDirectory = $true + } + "Save" { + $this.Dialog = New-Object System.Windows.Forms.SaveFileDialog + $this.Dialog.Filter = "Alle Dateien (*.*)|*.*" + $this.Dialog.RestoreDirectory = $true + $this.Dialog.OverwritePrompt = $true + } + "Folder" { + $this.Dialog = New-Object System.Windows.Forms.FolderBrowserDialog + $this.Dialog.ShowNewFolderButton = $true + } + } + } + + static [DialogBuilder] OpenFile() { + return [DialogBuilder]::new("Open") + } + + static [DialogBuilder] SaveFile() { + return [DialogBuilder]::new("Save") + } + + static [DialogBuilder] SelectFolder() { + return [DialogBuilder]::new("Folder") + } + + [DialogBuilder] SetTitle([string]$Title) { + if ($this.DialogType -eq "Folder") { + $this.Dialog.Description = $Title + } + else { + $this.Dialog.Title = $Title + } + + return $this + } + + [DialogBuilder] SetFilter([string]$Filter) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.Filter = $Filter + } + + return $this + } + + [DialogBuilder] SetInitialDirectory([string]$Path) { + if ([string]::IsNullOrWhiteSpace($Path)) { + return $this + } + + if ($this.DialogType -eq "Folder") { + if (Test-Path -LiteralPath $Path) { + $this.Dialog.SelectedPath = $Path + } + } + elseif (Test-Path -LiteralPath $Path) { + $this.Dialog.InitialDirectory = $Path + } + + return $this + } + + [DialogBuilder] SetSelectedPath([string]$Path) { + if ([string]::IsNullOrWhiteSpace($Path)) { + return $this + } + + if ($this.DialogType -eq "Folder" -and (Test-Path -LiteralPath $Path)) { + $this.Dialog.SelectedPath = $Path + } + + return $this + } + + [DialogBuilder] SetFileName([string]$FileName) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.FileName = $FileName + } + + return $this + } + + [DialogBuilder] SetCheckFileExists([bool]$CheckFileExists) { + if ($this.DialogType -eq "Open") { + $this.Dialog.CheckFileExists = $CheckFileExists + } + + return $this + } + + [DialogBuilder] SetOverwritePrompt([bool]$OverwritePrompt) { + if ($this.DialogType -eq "Save") { + $this.Dialog.OverwritePrompt = $OverwritePrompt + } + + return $this + } + + [object] ShowDialog() { + return $this.Dialog.ShowDialog() + } + + [string] GetPath() { + if ($this.DialogType -eq "Folder") { + return [string]$this.Dialog.SelectedPath + } + + return [string]$this.Dialog.FileName + } +} + +[void](Add-Type -AssemblyName System.Windows.Forms) +[void](Add-Type -AssemblyName System.Drawing) +[System.Windows.Forms.Application]::EnableVisualStyles() +[System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false) + +$script:SettingsManager = [SettingsManager]::new("Start-SPMigration") +$script:LogManager = [LogManager]::new("Start-SPMigration") +[BaseComponent]::InitializeShared($script:SettingsManager, $script:LogManager) + +$script:MigrationScriptPath = Join-Path -Path (Split-Path -Parent $PSCommandPath) -ChildPath "Start-SPMigration.ps1" +$script:IsUpdatingContainerChecks = $false +$script:LastSuggestedMappingPath = "" +$script:CurrentMappingMeta = [ordered]@{ + SchemaVersion = 1 + GeneratedAtUtc = "" + SourceWebUrl = "" +} + +function Get-ObjectPropertyValue { + param( + $Object, + + [Parameter(Mandatory = $true)] + [string]$PropertyName, + + $DefaultValue = $null + ) + + if ($null -eq $Object) { + return $DefaultValue + } + + if ($Object -is [System.Collections.IDictionary]) { + if ($Object.Contains($PropertyName)) { + return $Object[$PropertyName] + } + + return $DefaultValue + } + + $property = $Object.PSObject.Properties[$PropertyName] + if ($null -eq $property) { + return $DefaultValue + } + + if ($null -eq $property.Value) { + return $DefaultValue + } + + return $property.Value +} + +function Initialize-SharePointPowerShellForGui { + if ($null -eq (Get-Module -Name SharePointServer)) { + Import-Module SharePointServer -ErrorAction Stop + } +} + +function Test-IsCatalogList { + param( + [Parameter(Mandatory = $true)] + [Microsoft.SharePoint.SPList]$List + ) + + try { + return [bool]$List.IsCatalog + } + catch { + return $false + } +} + +function Write-UILog { + param( + [Parameter(Mandatory = $true)] + [string]$Message, + + [ValidateSet("INFO", "WARN", "ERROR")] + [string]$Level = "INFO" + ) + + $timestamp = Get-Date -Format "HH:mm:ss" + $line = "[{0}] [{1}] {2}" -f $timestamp, $Level, $Message + + if ($null -ne $script:txtLog) { + if ($script:txtLog.TextLength -gt 0) { + $script:txtLog.AppendText([Environment]::NewLine) + } + + $script:txtLog.AppendText($line) + $script:txtLog.SelectionStart = $script:txtLog.TextLength + $script:txtLog.ScrollToCaret() + [System.Windows.Forms.Application]::DoEvents() + } + + switch ($Level) { + "WARN" { [BaseComponent]::LogManager.Warning($Message) } + "ERROR" { [BaseComponent]::LogManager.Error($Message) } + default { [BaseComponent]::LogManager.Info($Message) } + } +} + +function Show-UiMessage { + param( + [Parameter(Mandatory = $true)] + [string]$Message, + + [string]$Caption = "Start-SPMigration GUI", + + [System.Windows.Forms.MessageBoxIcon]$Icon = [System.Windows.Forms.MessageBoxIcon]::Information + ) + + [void][System.Windows.Forms.MessageBox]::Show($script:MainForm, $Message, $Caption, [System.Windows.Forms.MessageBoxButtons]::OK, $Icon) +} + +function Get-FullPathSafe { + param( + [string]$Path + ) + + if ([string]::IsNullOrWhiteSpace($Path)) { + return "" + } + + try { + return [System.IO.Path]::GetFullPath($Path) + } + catch { + return $Path + } +} + +function Get-ParentDirectorySafe { + param( + [string]$Path + ) + + if ([string]::IsNullOrWhiteSpace($Path)) { + return "" + } + + if (Test-Path -LiteralPath $Path -PathType Container) { + return (Get-FullPathSafe -Path $Path) + } + + $parent = Split-Path -Parent $Path + if ([string]::IsNullOrWhiteSpace($parent)) { + return "" + } + + return (Get-FullPathSafe -Path $parent) +} + +function Ensure-ParentDirectory { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + $directory = Split-Path -Parent $Path + if (-not [string]::IsNullOrWhiteSpace($directory) -and -not (Test-Path -LiteralPath $directory)) { + New-Item -Path $directory -ItemType Directory -Force | Out-Null + } +} + +function Sync-MappingPathWithOutputPath { + param( + [switch]$Force + ) + + $outputPath = Get-FullPathSafe -Path $script:txtOutputPath.Text + if ([string]::IsNullOrWhiteSpace($outputPath)) { + return + } + + $suggestedMappingPath = Join-Path -Path $outputPath -ChildPath "MappingTable.json" + $currentMappingPath = [string]$script:txtMappingPath.Text + + if ( + $Force.IsPresent -or + [string]::IsNullOrWhiteSpace($currentMappingPath) -or + ($currentMappingPath -eq $script:LastSuggestedMappingPath) + ) { + $script:txtMappingPath.Text = $suggestedMappingPath + } + + $script:LastSuggestedMappingPath = $suggestedMappingPath +} + +function Convert-RecordToLogText { + param( + $Record + ) + + if ($null -eq $Record) { + return $null + } + + if ($Record -is [System.Management.Automation.WarningRecord]) { + return "WARNING: {0}" -f $Record.Message + } + + if ($Record -is [System.Management.Automation.ErrorRecord]) { + return [string]$Record + } + + if ($Record -is [System.Management.Automation.VerboseRecord]) { + return "VERBOSE: {0}" -f $Record.Message + } + + if ($Record -is [System.Management.Automation.DebugRecord]) { + return "DEBUG: {0}" -f $Record.Message + } + + if ($Record -is [System.Management.Automation.InformationRecord]) { + return [string]$Record.MessageData + } + + return [string]$Record +} + +function Invoke-MigrationScript { + param( + [Parameter(Mandatory = $true)] + [hashtable]$Parameters, + + [Parameter(Mandatory = $true)] + [string]$ActionLabel + ) + + if (-not (Test-Path -LiteralPath $script:MigrationScriptPath)) { + throw ("Start-SPMigration.ps1 nicht gefunden: {0}" -f $script:MigrationScriptPath) + } + + $script:MainForm.UseWaitCursor = $true + [System.Windows.Forms.Application]::DoEvents() + + try { + Write-UILog -Message ("Starte {0}..." -f $ActionLabel) + & $script:MigrationScriptPath @Parameters *>&1 | ForEach-Object { + $text = Convert-RecordToLogText -Record $_ + if (-not [string]::IsNullOrWhiteSpace($text)) { + Write-UILog -Message $text + } + } + Write-UILog -Message ("{0} abgeschlossen." -f $ActionLabel) + } + finally { + $script:MainForm.UseWaitCursor = $false + [System.Windows.Forms.Application]::DoEvents() + } +} + +function Get-SourceContainers { + param( + [Parameter(Mandatory = $true)] + [string]$SourceUrl, + + [switch]$IncludeHiddenLibraries, + + [switch]$IncludeHiddenLists + ) + + Initialize-SharePointPowerShellForGui + + $web = $null + + try { + $web = Get-SPWeb -Identity $SourceUrl -ErrorAction Stop + + $documentLibraries = @( + $web.Lists | Where-Object { + $_.BaseType -eq [Microsoft.SharePoint.SPBaseType]::DocumentLibrary -and + ($IncludeHiddenLibraries -or -not $_.Hidden) -and + -not (Test-IsCatalogList -List $_) + } + ) + + $lists = @( + $web.Lists | Where-Object { + $_.BaseType -ne [Microsoft.SharePoint.SPBaseType]::DocumentLibrary -and + ($IncludeHiddenLists -or -not $_.Hidden) -and + -not (Test-IsCatalogList -List $_) + } + ) + + $items = @() + + foreach ($library in @($documentLibraries | Sort-Object -Property Title)) { + $items += [PSCustomObject]@{ + ObjectType = "DocumentLibrary" + Title = [string]$library.Title + RootFolderUrl = [string]$library.RootFolder.ServerRelativeUrl + Hidden = [bool]$library.Hidden + BaseTemplate = [int]$library.BaseTemplate + ItemCount = [int]$library.ItemCount + } + } + + foreach ($list in @($lists | Sort-Object -Property Title)) { + $items += [PSCustomObject]@{ + ObjectType = "List" + Title = [string]$list.Title + RootFolderUrl = [string]$list.RootFolder.ServerRelativeUrl + Hidden = [bool]$list.Hidden + BaseTemplate = [int]$list.BaseTemplate + ItemCount = [int]$list.ItemCount + } + } + + return $items + } + finally { + if ($null -ne $web) { + $web.Dispose() + } + } +} + +function Set-TreeChildrenChecked { + param( + [Parameter(Mandatory = $true)] + [System.Windows.Forms.TreeNode]$Node, + + [Parameter(Mandatory = $true)] + [bool]$Checked + ) + + foreach ($childNode in $Node.Nodes) { + $childNode.Checked = $Checked + if ($childNode.Nodes.Count -gt 0) { + Set-TreeChildrenChecked -Node $childNode -Checked $Checked + } + } +} + +function Update-ParentNodeCheckedState { + param( + [Parameter(Mandatory = $true)] + [System.Windows.Forms.TreeNode]$Node + ) + + $parentNode = $Node.Parent + if ($null -eq $parentNode) { + return + } + + $allChecked = $true + foreach ($childNode in $parentNode.Nodes) { + if (-not $childNode.Checked) { + $allChecked = $false + break + } + } + + $parentNode.Checked = $allChecked +} + +function Set-AllContainerNodesChecked { + param( + [Parameter(Mandatory = $true)] + [bool]$Checked + ) + + $script:IsUpdatingContainerChecks = $true + try { + foreach ($rootNode in $script:tvContainers.Nodes) { + $rootNode.Checked = $Checked + Set-TreeChildrenChecked -Node $rootNode -Checked $Checked + } + } + finally { + $script:IsUpdatingContainerChecks = $false + } +} + +function Populate-ContainerTree { + param( + [Parameter(Mandatory = $true)] + [object[]]$Containers + ) + + $script:IsUpdatingContainerChecks = $true + + try { + $script:tvContainers.Nodes.Clear() + + $libraries = @($Containers | Where-Object { $_.ObjectType -eq "DocumentLibrary" }) + $lists = @($Containers | Where-Object { $_.ObjectType -eq "List" }) + + $libraryRootNode = New-Object System.Windows.Forms.TreeNode ("Bibliotheken ({0})" -f $libraries.Count) + $libraryRootNode.Checked = $true + + foreach ($library in $libraries) { + $text = "{0} [{1}] Items: {2}" -f $library.Title, $library.RootFolderUrl, $library.ItemCount + $node = New-Object System.Windows.Forms.TreeNode $text + $node.Checked = $true + $node.Tag = [PSCustomObject]@{ + ObjectType = $library.ObjectType + Title = $library.Title + RootFolderUrl = $library.RootFolderUrl + } + [void]$libraryRootNode.Nodes.Add($node) + } + + $listRootNode = New-Object System.Windows.Forms.TreeNode ("Listen ({0})" -f $lists.Count) + $listRootNode.Checked = $true + + foreach ($list in $lists) { + $text = "{0} [{1}] Items: {2}" -f $list.Title, $list.RootFolderUrl, $list.ItemCount + $node = New-Object System.Windows.Forms.TreeNode $text + $node.Checked = $true + $node.Tag = [PSCustomObject]@{ + ObjectType = $list.ObjectType + Title = $list.Title + RootFolderUrl = $list.RootFolderUrl + } + [void]$listRootNode.Nodes.Add($node) + } + + [void]$script:tvContainers.Nodes.Add($libraryRootNode) + [void]$script:tvContainers.Nodes.Add($listRootNode) + $script:tvContainers.ExpandAll() + } + finally { + $script:IsUpdatingContainerChecks = $false + } +} + +function Get-SelectedContainerUrls { + $urls = @() + + foreach ($rootNode in $script:tvContainers.Nodes) { + foreach ($childNode in $rootNode.Nodes) { + if ($childNode.Checked -and $null -ne $childNode.Tag) { + $rootFolderUrl = [string](Get-ObjectPropertyValue -Object $childNode.Tag -PropertyName "RootFolderUrl" -DefaultValue "") + if (-not [string]::IsNullOrWhiteSpace($rootFolderUrl)) { + $urls += $rootFolderUrl.Trim() + } + } + } + } + + return @($urls | Sort-Object -Unique) +} + +function Get-SelectableContainerCount { + $count = 0 + + foreach ($rootNode in $script:tvContainers.Nodes) { + $count += $rootNode.Nodes.Count + } + + return $count +} + +function Convert-ToDelimitedString { + param( + $Value + ) + + if ($null -eq $Value) { + return "" + } + + if ($Value -is [string]) { + return $Value + } + + $parts = @() + foreach ($item in @($Value)) { + if (-not [string]::IsNullOrWhiteSpace([string]$item)) { + $parts += [string]$item + } + } + + return ($parts -join ", ") +} + +function Convert-ToStringArray { + param( + $Value + ) + + if ($null -eq $Value) { + return @() + } + + if ($Value -is [System.Collections.IEnumerable] -and -not ($Value -is [string])) { + $items = @() + foreach ($item in $Value) { + if (-not [string]::IsNullOrWhiteSpace([string]$item)) { + $items += [string]$item + } + } + + return $items + } + + $text = [string]$Value + if ([string]::IsNullOrWhiteSpace($text)) { + return @() + } + + return @($text -split "\s*[,;]\s*" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) +} + +$script:GridSchemas = @{ + LibraryMappings = @( + @{ Name = "ObjectType"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "SourceTitle"; Type = [string]; ReadOnly = $true; FillWeight = 20 } + @{ Name = "TargetTitle"; Type = [string]; ReadOnly = $false; FillWeight = 20 } + @{ Name = "BaseType"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "BaseTemplate"; Type = [int]; ReadOnly = $true; FillWeight = 10 } + @{ Name = "RootFolderUrl"; Type = [string]; ReadOnly = $true; FillWeight = 20 } + @{ Name = "Hidden"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + ) + ListMappings = @( + @{ Name = "ObjectType"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "SourceTitle"; Type = [string]; ReadOnly = $true; FillWeight = 20 } + @{ Name = "TargetTitle"; Type = [string]; ReadOnly = $false; FillWeight = 20 } + @{ Name = "BaseType"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "BaseTemplate"; Type = [int]; ReadOnly = $true; FillWeight = 10 } + @{ Name = "RootFolderUrl"; Type = [string]; ReadOnly = $true; FillWeight = 20 } + @{ Name = "Hidden"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + ) + SystemColumns = @( + @{ Name = "ObjectType"; Type = [string]; ReadOnly = $true; FillWeight = 8 } + @{ Name = "ContainerSourceTitle"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "SourceInternalName"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "SourceCanonicalInternalName"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "SourceSupportingInternalNames"; Type = [string]; ReadOnly = $true; FillWeight = 15 } + @{ Name = "TargetInternalName"; Type = [string]; ReadOnly = $false; FillWeight = 14 } + @{ Name = "DisplayName"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "TypeAsString"; Type = [string]; ReadOnly = $true; FillWeight = 10 } + @{ Name = "Hidden"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + @{ Name = "ReadOnly"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + @{ Name = "Sealed"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + @{ Name = "IsSystemColumn"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + @{ Name = "ImportSupported"; Type = [bool]; ReadOnly = $false; FillWeight = 8 } + ) + CustomColumns = @( + @{ Name = "ObjectType"; Type = [string]; ReadOnly = $true; FillWeight = 8 } + @{ Name = "ContainerSourceTitle"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "SourceInternalName"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "SourceCanonicalInternalName"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "SourceSupportingInternalNames"; Type = [string]; ReadOnly = $true; FillWeight = 15 } + @{ Name = "TargetInternalName"; Type = [string]; ReadOnly = $false; FillWeight = 14 } + @{ Name = "DisplayName"; Type = [string]; ReadOnly = $true; FillWeight = 12 } + @{ Name = "TypeAsString"; Type = [string]; ReadOnly = $true; FillWeight = 10 } + @{ Name = "Hidden"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + @{ Name = "ReadOnly"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + @{ Name = "Sealed"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + @{ Name = "IsSystemColumn"; Type = [bool]; ReadOnly = $true; FillWeight = 6 } + @{ Name = "ImportSupported"; Type = [bool]; ReadOnly = $false; FillWeight = 8 } + ) +} + +function New-DataTableFromSchema { + param( + [Parameter(Mandatory = $true)] + [object[]]$Schema + ) + + $table = New-Object System.Data.DataTable + + foreach ($columnDefinition in $Schema) { + $column = New-Object System.Data.DataColumn($columnDefinition.Name, $columnDefinition.Type) + [void]$table.Columns.Add($column) + } + + Write-Output -NoEnumerate $table +} + +function Convert-ToTableCellValue { + param( + $Value, + + [Parameter(Mandatory = $true)] + [type]$TargetType, + + [Parameter(Mandatory = $true)] + [string]$ColumnName + ) + + if ($ColumnName -eq "SourceSupportingInternalNames") { + return (Convert-ToDelimitedString -Value $Value) + } + + if ($null -eq $Value) { + if ($TargetType -eq [bool]) { + return $false + } + + if ($TargetType -eq [int]) { + return 0 + } + + return "" + } + + if ($TargetType -eq [bool]) { + return [bool]$Value + } + + if ($TargetType -eq [int]) { + try { + return [int]$Value + } + catch { + return 0 + } + } + + return [string]$Value +} + +function Set-DataTableRows { + param( + [Parameter(Mandatory = $true)] + [System.Data.DataTable]$Table, + + [Parameter(Mandatory = $true)] + [object[]]$Schema, + + [object[]]$Rows = @() + ) + + $Table.Rows.Clear() + + foreach ($item in @($Rows)) { + $row = $Table.NewRow() + + foreach ($columnDefinition in $Schema) { + $value = Get-ObjectPropertyValue -Object $item -PropertyName $columnDefinition.Name + $row[$columnDefinition.Name] = Convert-ToTableCellValue -Value $value -TargetType $columnDefinition.Type -ColumnName $columnDefinition.Name + } + + [void]$Table.Rows.Add($row) + } +} + +function Configure-MappingGrid { + param( + [Parameter(Mandatory = $true)] + [System.Windows.Forms.DataGridView]$Grid, + + [Parameter(Mandatory = $true)] + [System.Data.DataTable]$Table, + + [Parameter(Mandatory = $true)] + [object[]]$Schema + ) + + $Grid.DataSource = $Table + + foreach ($columnDefinition in $Schema) { + if ($Grid.Columns.Contains($columnDefinition.Name)) { + $column = $Grid.Columns[$columnDefinition.Name] + $column.ReadOnly = [bool]$columnDefinition.ReadOnly + $column.FillWeight = [single]$columnDefinition.FillWeight + + if ($columnDefinition.Type -eq [bool]) { + $column.AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::AllCells + } + } + } +} + +function Convert-DataTableToObjects { + param( + [Parameter(Mandatory = $true)] + [System.Data.DataTable]$Table, + + [Parameter(Mandatory = $true)] + [object[]]$Schema + ) + + $items = @() + + foreach ($row in $Table.Rows) { + $item = [ordered]@{} + + foreach ($columnDefinition in $Schema) { + $value = $row[$columnDefinition.Name] + if ($value -eq [System.DBNull]::Value) { + $value = $null + } + + if ($columnDefinition.Name -eq "SourceSupportingInternalNames") { + $item[$columnDefinition.Name] = @(Convert-ToStringArray -Value $value) + continue + } + + if ($columnDefinition.Type -eq [bool]) { + $item[$columnDefinition.Name] = [bool]$value + continue + } + + if ($columnDefinition.Type -eq [int]) { + $item[$columnDefinition.Name] = [int]$value + continue + } + + $item[$columnDefinition.Name] = [string]$value + } + + $items += [PSCustomObject]$item + } + + return $items +} + +$script:LibraryMappingsTable = New-DataTableFromSchema -Schema $script:GridSchemas.LibraryMappings +$script:ListMappingsTable = New-DataTableFromSchema -Schema $script:GridSchemas.ListMappings +$script:SystemColumnsTable = New-DataTableFromSchema -Schema $script:GridSchemas.SystemColumns +$script:CustomColumnsTable = New-DataTableFromSchema -Schema $script:GridSchemas.CustomColumns + +function Initialize-MappingTables { + if ($null -eq $script:LibraryMappingsTable) { + $script:LibraryMappingsTable = New-DataTableFromSchema -Schema $script:GridSchemas.LibraryMappings + } + + if ($null -eq $script:ListMappingsTable) { + $script:ListMappingsTable = New-DataTableFromSchema -Schema $script:GridSchemas.ListMappings + } + + if ($null -eq $script:SystemColumnsTable) { + $script:SystemColumnsTable = New-DataTableFromSchema -Schema $script:GridSchemas.SystemColumns + } + + if ($null -eq $script:CustomColumnsTable) { + $script:CustomColumnsTable = New-DataTableFromSchema -Schema $script:GridSchemas.CustomColumns + } +} + +function New-EmptyMappingTable { + $sourceWebUrl = "" + if ($null -ne $script:txtSourceUrl) { + $sourceWebUrl = [string]$script:txtSourceUrl.Text + } + + return [PSCustomObject]@{ + SchemaVersion = 1 + GeneratedAtUtc = [System.DateTime]::UtcNow.ToString("o") + SourceWebUrl = $sourceWebUrl + LibraryMappings = @() + ListMappings = @() + MetadataColumnMappings = [PSCustomObject]@{ + SystemColumns = @() + CustomColumns = @() + } + } +} + +function Set-MappingMetaFromObject { + param( + $MappingTable + ) + + $script:CurrentMappingMeta = [ordered]@{ + SchemaVersion = [int](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "SchemaVersion" -DefaultValue 1) + GeneratedAtUtc = [string](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "GeneratedAtUtc" -DefaultValue "") + SourceWebUrl = [string](Get-ObjectPropertyValue -Object $MappingTable -PropertyName "SourceWebUrl" -DefaultValue "") + } +} + +function Set-MappingTableToUi { + param( + $MappingTable + ) + + Initialize-MappingTables + + if ($null -eq $MappingTable) { + $MappingTable = New-EmptyMappingTable + } + + Set-MappingMetaFromObject -MappingTable $MappingTable + + $metadataColumnMappings = Get-ObjectPropertyValue -Object $MappingTable -PropertyName "MetadataColumnMappings" -DefaultValue ([PSCustomObject]@{ + SystemColumns = @() + CustomColumns = @() + }) + + Set-DataTableRows -Table $script:LibraryMappingsTable -Schema $script:GridSchemas.LibraryMappings -Rows @(Get-ObjectPropertyValue -Object $MappingTable -PropertyName "LibraryMappings" -DefaultValue @()) + Set-DataTableRows -Table $script:ListMappingsTable -Schema $script:GridSchemas.ListMappings -Rows @(Get-ObjectPropertyValue -Object $MappingTable -PropertyName "ListMappings" -DefaultValue @()) + Set-DataTableRows -Table $script:SystemColumnsTable -Schema $script:GridSchemas.SystemColumns -Rows @(Get-ObjectPropertyValue -Object $metadataColumnMappings -PropertyName "SystemColumns" -DefaultValue @()) + Set-DataTableRows -Table $script:CustomColumnsTable -Schema $script:GridSchemas.CustomColumns -Rows @(Get-ObjectPropertyValue -Object $metadataColumnMappings -PropertyName "CustomColumns" -DefaultValue @()) + + if ([string]::IsNullOrWhiteSpace($script:txtSourceUrl.Text) -and -not [string]::IsNullOrWhiteSpace($script:CurrentMappingMeta.SourceWebUrl)) { + $script:txtSourceUrl.Text = $script:CurrentMappingMeta.SourceWebUrl + } +} + +function Get-MappingTableFromUi { + Initialize-MappingTables + + $sourceWebUrl = $script:CurrentMappingMeta.SourceWebUrl + if ([string]::IsNullOrWhiteSpace($sourceWebUrl)) { + $sourceWebUrl = [string]$script:txtSourceUrl.Text + } + + return [PSCustomObject]@{ + SchemaVersion = [int]$script:CurrentMappingMeta.SchemaVersion + GeneratedAtUtc = if ([string]::IsNullOrWhiteSpace($script:CurrentMappingMeta.GeneratedAtUtc)) { [System.DateTime]::UtcNow.ToString("o") } else { $script:CurrentMappingMeta.GeneratedAtUtc } + SourceWebUrl = $sourceWebUrl + LibraryMappings = @(Convert-DataTableToObjects -Table $script:LibraryMappingsTable -Schema $script:GridSchemas.LibraryMappings) + ListMappings = @(Convert-DataTableToObjects -Table $script:ListMappingsTable -Schema $script:GridSchemas.ListMappings) + MetadataColumnMappings = [PSCustomObject]@{ + SystemColumns = @(Convert-DataTableToObjects -Table $script:SystemColumnsTable -Schema $script:GridSchemas.SystemColumns) + CustomColumns = @(Convert-DataTableToObjects -Table $script:CustomColumnsTable -Schema $script:GridSchemas.CustomColumns) + } + } +} + +function Load-MappingTableFromPath { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if ([string]::IsNullOrWhiteSpace($Path)) { + throw "Bitte einen gueltigen MappingTable-Pfad angeben." + } + + if (-not (Test-Path -LiteralPath $Path)) { + throw ("MappingTable nicht gefunden: {0}" -f $Path) + } + + if ([System.IO.Path]::GetExtension($Path) -ine ".json") { + throw "Die GUI kann nur JSON-MappingTables bearbeiten." + } + + $mappingTable = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json + Set-MappingTableToUi -MappingTable $mappingTable + $script:txtMappingPath.Text = (Get-FullPathSafe -Path $Path) + + $candidateOutputPath = Get-ParentDirectorySafe -Path $Path + if (-not [string]::IsNullOrWhiteSpace($candidateOutputPath)) { + $script:txtOutputPath.Text = $candidateOutputPath + } + + Sync-MappingPathWithOutputPath -Force + $script:txtMappingPath.Text = (Get-FullPathSafe -Path $Path) + Write-UILog -Message ("MappingTable geladen: {0}" -f $Path) +} + +function Save-MappingTableToPath { + param( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + if ([string]::IsNullOrWhiteSpace($Path)) { + throw "Bitte einen Pfad fuer die MappingTable angeben." + } + + $resolvedPath = Get-FullPathSafe -Path $Path + Ensure-ParentDirectory -Path $resolvedPath + $mappingTable = Get-MappingTableFromUi + $json = $mappingTable | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText($resolvedPath, $json, [System.Text.Encoding]::UTF8) + $script:txtMappingPath.Text = $resolvedPath + Write-UILog -Message ("MappingTable gespeichert: {0}" -f $resolvedPath) +} + +function Save-UiSettings { + $script:SettingsManager.Set("WindowWidth", $script:MainForm.Width) + $script:SettingsManager.Set("WindowHeight", $script:MainForm.Height) + $script:SettingsManager.Set("LastSourceUrl", [string]$script:txtSourceUrl.Text) + $script:SettingsManager.Set("LastTargetUrl", [string]$script:txtTargetUrl.Text) + $script:SettingsManager.Set("LastOutputPath", [string]$script:txtOutputPath.Text) + $script:SettingsManager.Set("LastMappingTablePath", [string]$script:txtMappingPath.Text) +} + +function Load-UiSettings { + $script:txtSourceUrl.Text = [string]$script:SettingsManager.Get("LastSourceUrl", "") + $script:txtTargetUrl.Text = [string]$script:SettingsManager.Get("LastTargetUrl", "") + $script:txtOutputPath.Text = [string]$script:SettingsManager.Get("LastOutputPath", (Join-Path -Path (Get-Location).Path -ChildPath "SPMigrationOutput")) + $script:txtMappingPath.Text = [string]$script:SettingsManager.Get("LastMappingTablePath", "") + $script:MainForm.Width = [int]$script:SettingsManager.Get("WindowWidth", 1400) + $script:MainForm.Height = [int]$script:SettingsManager.Get("WindowHeight", 940) + Sync-MappingPathWithOutputPath -Force +} + +function Load-ExistingMappingTableIfAvailable { + $mappingPath = [string]$script:txtMappingPath.Text + if ([string]::IsNullOrWhiteSpace($mappingPath)) { + return + } + + if (-not (Test-Path -LiteralPath $mappingPath)) { + return + } + + try { + Load-MappingTableFromPath -Path $mappingPath + } + catch { + Write-UILog -Message $_.Exception.Message -Level "WARN" + } +} + +function Invoke-LoadContainers { + $sourceUrl = [string]$script:txtSourceUrl.Text + if ([string]::IsNullOrWhiteSpace($sourceUrl)) { + Show-UiMessage -Message "Bitte zuerst eine SourceUrl angeben." -Icon ([System.Windows.Forms.MessageBoxIcon]::Warning) + return + } + + $script:MainForm.UseWaitCursor = $true + [System.Windows.Forms.Application]::DoEvents() + + try { + Write-UILog -Message ("Lese Listen und Bibliotheken aus: {0}" -f $sourceUrl) + $containers = Get-SourceContainers -SourceUrl $sourceUrl -IncludeHiddenLibraries:$script:chkIncludeHiddenLibraries.Checked -IncludeHiddenLists:$script:chkIncludeHiddenLists.Checked + Populate-ContainerTree -Containers $containers + Write-UILog -Message ("Container geladen. Bibliotheken und Listen gesamt: {0}" -f $containers.Count) + } + catch { + Write-UILog -Message $_.Exception.Message -Level "ERROR" + Show-UiMessage -Message $_.Exception.Message -Caption "Container konnten nicht geladen werden" -Icon ([System.Windows.Forms.MessageBoxIcon]::Error) + } + finally { + $script:MainForm.UseWaitCursor = $false + [System.Windows.Forms.Application]::DoEvents() + } +} + +function Invoke-ExportFromGui { + try { + $sourceUrl = [string]$script:txtSourceUrl.Text + if ([string]::IsNullOrWhiteSpace($sourceUrl)) { + throw "Bitte eine SourceUrl angeben." + } + + $outputPath = Get-FullPathSafe -Path $script:txtOutputPath.Text + if ([string]::IsNullOrWhiteSpace($outputPath)) { + throw "Bitte einen OutputPath angeben." + } + + if ((Get-SelectableContainerCount) -eq 0) { + throw "Bitte zuerst die Quell-Listen und Bibliotheken laden." + } + + $selectedContainerUrls = @(Get-SelectedContainerUrls) + if ($selectedContainerUrls.Count -eq 0) { + throw "Bitte mindestens eine Liste oder Bibliothek fuer den Export auswaehlen." + } + + $script:txtOutputPath.Text = $outputPath + Sync-MappingPathWithOutputPath -Force + + $parameters = @{ + SourceUrl = $sourceUrl + OutputPath = $outputPath + Export = $true + SelectedContainerUrls = $selectedContainerUrls + } + + if ($script:chkIncludeHiddenLibraries.Checked) { + $parameters.IncludeHiddenLibraries = $true + } + + if ($script:chkIncludeHiddenLists.Checked) { + $parameters.IncludeHiddenLists = $true + } + + Invoke-MigrationScript -Parameters $parameters -ActionLabel "Export" + + $mappingPath = Join-Path -Path $outputPath -ChildPath "MappingTable.json" + if (Test-Path -LiteralPath $mappingPath) { + Load-MappingTableFromPath -Path $mappingPath + $script:MainTabControl.SelectedTab = $script:tabMapping + } + } + catch { + Write-UILog -Message $_.Exception.Message -Level "ERROR" + Show-UiMessage -Message $_.Exception.Message -Caption "Export fehlgeschlagen" -Icon ([System.Windows.Forms.MessageBoxIcon]::Error) + } +} + +function Invoke-ImportFromGui { + try { + $targetUrl = [string]$script:txtTargetUrl.Text + if ([string]::IsNullOrWhiteSpace($targetUrl)) { + throw "Bitte eine TargetUrl angeben." + } + + $outputPath = Get-FullPathSafe -Path $script:txtOutputPath.Text + if ([string]::IsNullOrWhiteSpace($outputPath)) { + throw "Bitte einen OutputPath angeben." + } + + $mappingPath = [string]$script:txtMappingPath.Text + if ([string]::IsNullOrWhiteSpace($mappingPath)) { + Sync-MappingPathWithOutputPath -Force + $mappingPath = [string]$script:txtMappingPath.Text + } + + if ([string]::IsNullOrWhiteSpace($mappingPath)) { + throw "Bitte einen MappingTable-Pfad angeben." + } + + Save-MappingTableToPath -Path $mappingPath + + $parameters = @{ + TargetUrl = $targetUrl + OutputPath = $outputPath + Import = $true + MappingTable = $mappingPath + } + + if ($script:chkImportFiles.Checked) { + $parameters.ImportFiles = $true + } + + if ($script:chkImportLists.Checked) { + $parameters.ImportLists = $true + } + + if ($script:chkOverwrite.Checked) { + $parameters.Overwrite = $true + } + + Invoke-MigrationScript -Parameters $parameters -ActionLabel "Import" + } + catch { + Write-UILog -Message $_.Exception.Message -Level "ERROR" + Show-UiMessage -Message $_.Exception.Message -Caption "Import fehlgeschlagen" -Icon ([System.Windows.Forms.MessageBoxIcon]::Error) + } +} + +$formBuilder = [FormBuilder]::new("Start-SPMigration GUI") +$null = $formBuilder.SetSize([int]$script:SettingsManager.Get("WindowWidth", 1400), [int]$script:SettingsManager.Get("WindowHeight", 940)).SetMinimumSize(1200, 820).SetStartPosition("CenterScreen") +$script:MainForm = $formBuilder.Build() +$script:MainForm.KeyPreview = $true + +$mainLayout = New-Object System.Windows.Forms.TableLayoutPanel +$mainLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$mainLayout.RowCount = 3 +$mainLayout.ColumnCount = 1 +[void]$mainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 110))) +[void]$mainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) +[void]$mainLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 230))) + +$workspaceGroup = ([GroupBoxBuilder]::new("Arbeitsbereich")).Build() +$workspaceLayout = New-Object System.Windows.Forms.TableLayoutPanel +$workspaceLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$workspaceLayout.RowCount = 2 +$workspaceLayout.ColumnCount = 4 +[void]$workspaceLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 105))) +[void]$workspaceLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) +[void]$workspaceLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 110))) +[void]$workspaceLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 110))) +[void]$workspaceLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 36))) +[void]$workspaceLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 36))) + +$lblOutputPath = ([LabelBuilder]::new("OutputPath")).SetTextAlign([System.Drawing.ContentAlignment]::MiddleLeft).Build() +$lblOutputPath.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtOutputPath = New-Object System.Windows.Forms.TextBox +$script:txtOutputPath.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtOutputPath.Margin = New-Object System.Windows.Forms.Padding(6) +$btnBrowseOutputPath = ([ButtonBuilder]::new("Ordner...")).SetSize(95, 28).Build() +$btnBrowseOutputPath.Margin = New-Object System.Windows.Forms.Padding(6) + +$btnSaveMappingGlobal = ([ButtonBuilder]::new("Speichern")).SetSize(95, 28).Build() +$btnSaveMappingGlobal.Margin = New-Object System.Windows.Forms.Padding(6) + +$lblMappingPath = ([LabelBuilder]::new("MappingTable")).SetTextAlign([System.Drawing.ContentAlignment]::MiddleLeft).Build() +$lblMappingPath.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtMappingPath = New-Object System.Windows.Forms.TextBox +$script:txtMappingPath.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtMappingPath.Margin = New-Object System.Windows.Forms.Padding(6) +$btnLoadMapping = ([ButtonBuilder]::new("Laden...")).SetSize(95, 28).Build() +$btnLoadMapping.Margin = New-Object System.Windows.Forms.Padding(6) + +[void]$workspaceLayout.Controls.Add($lblOutputPath, 0, 0) +[void]$workspaceLayout.Controls.Add($script:txtOutputPath, 1, 0) +[void]$workspaceLayout.Controls.Add($btnBrowseOutputPath, 2, 0) +[void]$workspaceLayout.Controls.Add($btnSaveMappingGlobal, 3, 0) +[void]$workspaceLayout.Controls.Add($lblMappingPath, 0, 1) +[void]$workspaceLayout.Controls.Add($script:txtMappingPath, 1, 1) +[void]$workspaceLayout.Controls.Add($btnLoadMapping, 2, 1) +$workspaceGroup.Controls.Add($workspaceLayout) + +$script:MainTabControl = ([TabControlBuilder]::new()).SetDock("Fill").Build() +$script:tabExport = ([TabPageBuilder]::new("Export")).Build() +$script:tabMapping = ([TabPageBuilder]::new("Mapping")).Build() +$script:tabImport = ([TabPageBuilder]::new("Import")).Build() +$script:MainTabControl.TabPages.AddRange(@($script:tabExport, $script:tabMapping, $script:tabImport)) + +$exportLayout = New-Object System.Windows.Forms.TableLayoutPanel +$exportLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$exportLayout.RowCount = 3 +$exportLayout.ColumnCount = 1 +[void]$exportLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 116))) +[void]$exportLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) +[void]$exportLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 54))) + +$exportConfigGroup = ([GroupBoxBuilder]::new("Exportkonfiguration")).Build() +$exportConfigLayout = New-Object System.Windows.Forms.TableLayoutPanel +$exportConfigLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$exportConfigLayout.RowCount = 2 +$exportConfigLayout.ColumnCount = 2 +[void]$exportConfigLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 105))) +[void]$exportConfigLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) +[void]$exportConfigLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 34))) +[void]$exportConfigLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 42))) + +$lblSourceUrl = ([LabelBuilder]::new("SourceUrl")).SetTextAlign([System.Drawing.ContentAlignment]::MiddleLeft).Build() +$lblSourceUrl.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtSourceUrl = New-Object System.Windows.Forms.TextBox +$script:txtSourceUrl.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtSourceUrl.Margin = New-Object System.Windows.Forms.Padding(6) + +$exportOptionsPanel = ([FlowLayoutPanelBuilder]::new()).SetDock("Fill").SetFlowDirection("LeftToRight").SetWrapContents($false).Build() +$script:chkIncludeHiddenLibraries = New-Object System.Windows.Forms.CheckBox +$script:chkIncludeHiddenLibraries.Text = "Hidden Libraries einbeziehen" +$script:chkIncludeHiddenLibraries.AutoSize = $true +$script:chkIncludeHiddenLibraries.Margin = New-Object System.Windows.Forms.Padding(6, 8, 18, 6) +$script:chkIncludeHiddenLibraries.Font = New-Object System.Drawing.Font("Segoe UI", [single]$script:SettingsManager.Get("FontSize", 9)) +$script:chkIncludeHiddenLists = New-Object System.Windows.Forms.CheckBox +$script:chkIncludeHiddenLists.Text = "Hidden Lists einbeziehen" +$script:chkIncludeHiddenLists.AutoSize = $true +$script:chkIncludeHiddenLists.Margin = New-Object System.Windows.Forms.Padding(6, 8, 18, 6) +$script:chkIncludeHiddenLists.Font = New-Object System.Drawing.Font("Segoe UI", [single]$script:SettingsManager.Get("FontSize", 9)) +$exportOptionsHint = ([LabelBuilder]::new("Zuerst SourceUrl eintragen, dann die Container laden und gezielt auswaehlen.")).Build() +$exportOptionsHint.Margin = New-Object System.Windows.Forms.Padding(6, 8, 6, 6) +[void]$exportOptionsPanel.Controls.AddRange(@($script:chkIncludeHiddenLibraries, $script:chkIncludeHiddenLists, $exportOptionsHint)) + +[void]$exportConfigLayout.Controls.Add($lblSourceUrl, 0, 0) +[void]$exportConfigLayout.Controls.Add($script:txtSourceUrl, 1, 0) +[void]$exportConfigLayout.Controls.Add((New-Object System.Windows.Forms.Label), 0, 1) +[void]$exportConfigLayout.Controls.Add($exportOptionsPanel, 1, 1) +$exportConfigGroup.Controls.Add($exportConfigLayout) + +$containerSelectionGroup = ([GroupBoxBuilder]::new("Listen und Bibliotheken fuer den Export")).Build() +$containerSelectionLayout = New-Object System.Windows.Forms.TableLayoutPanel +$containerSelectionLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$containerSelectionLayout.RowCount = 1 +$containerSelectionLayout.ColumnCount = 2 +[void]$containerSelectionLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) +[void]$containerSelectionLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 150))) + +$script:tvContainers = ([TreeViewBuilder]::new()).SetDock("Fill").SetCheckBoxes($true).Build() + +$containerButtonsPanel = New-Object System.Windows.Forms.FlowLayoutPanel +$containerButtonsPanel.Dock = [System.Windows.Forms.DockStyle]::Fill +$containerButtonsPanel.FlowDirection = [System.Windows.Forms.FlowDirection]::TopDown +$containerButtonsPanel.WrapContents = $false +$containerButtonsPanel.AutoScroll = $true + +$btnSelectAllContainers = ([ButtonBuilder]::new("Alle markieren")).SetSize(126, 32).Build() +$btnClearContainerSelection = ([ButtonBuilder]::new("Alle abwaehlen")).SetSize(126, 32).Build() +[void]$containerButtonsPanel.Controls.AddRange(@($btnSelectAllContainers, $btnClearContainerSelection)) + +[void]$containerSelectionLayout.Controls.Add($script:tvContainers, 0, 0) +[void]$containerSelectionLayout.Controls.Add($containerButtonsPanel, 1, 0) +$containerSelectionGroup.Controls.Add($containerSelectionLayout) + +$exportActionsPanel = New-Object System.Windows.Forms.FlowLayoutPanel +$exportActionsPanel.Dock = [System.Windows.Forms.DockStyle]::Fill +$exportActionsPanel.FlowDirection = [System.Windows.Forms.FlowDirection]::RightToLeft +$exportActionsPanel.WrapContents = $false +$btnRunExport = ([ButtonBuilder]::new("Export starten")).SetSize(150, 34).Build() +$btnLoadContainers = ([ButtonBuilder]::new("Container laden")).SetSize(150, 34).Build() +[void]$exportActionsPanel.Controls.AddRange(@($btnRunExport, $btnLoadContainers)) + +[void]$exportLayout.Controls.Add($exportConfigGroup, 0, 0) +[void]$exportLayout.Controls.Add($containerSelectionGroup, 0, 1) +[void]$exportLayout.Controls.Add($exportActionsPanel, 0, 2) +$script:tabExport.Controls.Add($exportLayout) + +$mappingLayout = New-Object System.Windows.Forms.TableLayoutPanel +$mappingLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$mappingLayout.RowCount = 2 +$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.Dock = [System.Windows.Forms.DockStyle]::Fill + +$mappingTabs = ([TabControlBuilder]::new()).SetDock("Fill").Build() +$libraryMappingTab = ([TabPageBuilder]::new("Bibliotheken")).Build() +$listMappingTab = ([TabPageBuilder]::new("Listen")).Build() +$systemColumnTab = ([TabPageBuilder]::new("System Columns")).Build() +$customColumnTab = ([TabPageBuilder]::new("Custom Columns")).Build() +$mappingTabs.TabPages.AddRange(@($libraryMappingTab, $listMappingTab, $systemColumnTab, $customColumnTab)) + +$script:gridLibraryMappings = ([DataGridViewBuilder]::new()).SetDock("Fill").SetAllowAddRows($false).Build() +$script:gridListMappings = ([DataGridViewBuilder]::new()).SetDock("Fill").SetAllowAddRows($false).Build() +$script:gridSystemColumns = ([DataGridViewBuilder]::new()).SetDock("Fill").SetAllowAddRows($false).Build() +$script:gridCustomColumns = ([DataGridViewBuilder]::new()).SetDock("Fill").SetAllowAddRows($false).Build() + +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 + +$libraryMappingTab.Controls.Add($script:gridLibraryMappings) +$listMappingTab.Controls.Add($script:gridListMappings) +$systemColumnTab.Controls.Add($script:gridSystemColumns) +$customColumnTab.Controls.Add($script:gridCustomColumns) + +[void]$mappingLayout.Controls.Add($mappingInfoLabel, 0, 0) +[void]$mappingLayout.Controls.Add($mappingTabs, 0, 1) +$script:tabMapping.Controls.Add($mappingLayout) + +$importLayout = New-Object System.Windows.Forms.TableLayoutPanel +$importLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$importLayout.RowCount = 3 +$importLayout.ColumnCount = 1 +[void]$importLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 122))) +[void]$importLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 54))) +[void]$importLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) + +$importConfigGroup = ([GroupBoxBuilder]::new("Importkonfiguration")).Build() +$importConfigLayout = New-Object System.Windows.Forms.TableLayoutPanel +$importConfigLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$importConfigLayout.RowCount = 2 +$importConfigLayout.ColumnCount = 2 +[void]$importConfigLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Absolute, 105))) +[void]$importConfigLayout.ColumnStyles.Add((New-Object System.Windows.Forms.ColumnStyle([System.Windows.Forms.SizeType]::Percent, 100))) +[void]$importConfigLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 34))) +[void]$importConfigLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 42))) + +$lblTargetUrl = ([LabelBuilder]::new("TargetUrl")).SetTextAlign([System.Drawing.ContentAlignment]::MiddleLeft).Build() +$lblTargetUrl.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtTargetUrl = New-Object System.Windows.Forms.TextBox +$script:txtTargetUrl.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtTargetUrl.Margin = New-Object System.Windows.Forms.Padding(6) + +$importOptionsPanel = ([FlowLayoutPanelBuilder]::new()).SetDock("Fill").SetFlowDirection("LeftToRight").SetWrapContents($false).Build() +$script:chkImportFiles = New-Object System.Windows.Forms.CheckBox +$script:chkImportFiles.Text = "Dateien/Bibliotheken importieren" +$script:chkImportFiles.AutoSize = $true +$script:chkImportFiles.Margin = New-Object System.Windows.Forms.Padding(6, 8, 18, 6) +$script:chkImportFiles.Font = New-Object System.Drawing.Font("Segoe UI", [single]$script:SettingsManager.Get("FontSize", 9)) +$script:chkImportLists = New-Object System.Windows.Forms.CheckBox +$script:chkImportLists.Text = "Listen importieren" +$script:chkImportLists.AutoSize = $true +$script:chkImportLists.Margin = New-Object System.Windows.Forms.Padding(6, 8, 18, 6) +$script:chkImportLists.Font = New-Object System.Drawing.Font("Segoe UI", [single]$script:SettingsManager.Get("FontSize", 9)) +$script:chkOverwrite = New-Object System.Windows.Forms.CheckBox +$script:chkOverwrite.Text = "Overwrite" +$script:chkOverwrite.AutoSize = $true +$script:chkOverwrite.Margin = New-Object System.Windows.Forms.Padding(6, 8, 18, 6) +$script:chkOverwrite.Font = New-Object System.Drawing.Font("Segoe UI", [single]$script:SettingsManager.Get("FontSize", 9)) +$importHintLabel = ([LabelBuilder]::new("Wenn weder ImportFiles noch ImportLists gesetzt sind, verarbeitet das Skript beides.")).Build() +$importHintLabel.Margin = New-Object System.Windows.Forms.Padding(6, 8, 6, 6) +[void]$importOptionsPanel.Controls.AddRange(@($script:chkImportFiles, $script:chkImportLists, $script:chkOverwrite, $importHintLabel)) + +[void]$importConfigLayout.Controls.Add($lblTargetUrl, 0, 0) +[void]$importConfigLayout.Controls.Add($script:txtTargetUrl, 1, 0) +[void]$importConfigLayout.Controls.Add((New-Object System.Windows.Forms.Label), 0, 1) +[void]$importConfigLayout.Controls.Add($importOptionsPanel, 1, 1) +$importConfigGroup.Controls.Add($importConfigLayout) + +$importActionsPanel = New-Object System.Windows.Forms.FlowLayoutPanel +$importActionsPanel.Dock = [System.Windows.Forms.DockStyle]::Fill +$importActionsPanel.FlowDirection = [System.Windows.Forms.FlowDirection]::RightToLeft +$importActionsPanel.WrapContents = $false +$btnRunImport = ([ButtonBuilder]::new("Import starten")).SetSize(150, 34).Build() +[void]$importActionsPanel.Controls.Add($btnRunImport) + +$importNoteLabel = ([LabelBuilder]::new("Die aktuell in der GUI sichtbaren Mapping-Aenderungen werden vor dem Import in die JSON-Datei geschrieben.")).Build() +$importNoteLabel.Dock = [System.Windows.Forms.DockStyle]::Top + +[void]$importLayout.Controls.Add($importConfigGroup, 0, 0) +[void]$importLayout.Controls.Add($importActionsPanel, 0, 1) +[void]$importLayout.Controls.Add($importNoteLabel, 0, 2) +$script:tabImport.Controls.Add($importLayout) + +$logGroup = ([GroupBoxBuilder]::new("Log")).Build() +$logLayout = New-Object System.Windows.Forms.TableLayoutPanel +$logLayout.Dock = [System.Windows.Forms.DockStyle]::Fill +$logLayout.RowCount = 2 +$logLayout.ColumnCount = 1 +[void]$logLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Absolute, 40))) +[void]$logLayout.RowStyles.Add((New-Object System.Windows.Forms.RowStyle([System.Windows.Forms.SizeType]::Percent, 100))) + +$logButtonsPanel = New-Object System.Windows.Forms.FlowLayoutPanel +$logButtonsPanel.Dock = [System.Windows.Forms.DockStyle]::Fill +$logButtonsPanel.FlowDirection = [System.Windows.Forms.FlowDirection]::RightToLeft +$logButtonsPanel.WrapContents = $false +$btnClearLog = ([ButtonBuilder]::new("Log leeren")).SetSize(120, 28).Build() +[void]$logButtonsPanel.Controls.Add($btnClearLog) + +$script:txtLog = New-Object System.Windows.Forms.TextBox +$script:txtLog.Dock = [System.Windows.Forms.DockStyle]::Fill +$script:txtLog.Multiline = $true +$script:txtLog.ReadOnly = $true +$script:txtLog.ScrollBars = [System.Windows.Forms.ScrollBars]::Vertical +$script:txtLog.WordWrap = $false +$script:txtLog.Font = New-Object System.Drawing.Font("Consolas", 9) + +[void]$logLayout.Controls.Add($logButtonsPanel, 0, 0) +[void]$logLayout.Controls.Add($script:txtLog, 0, 1) +$logGroup.Controls.Add($logLayout) + +[void]$mainLayout.Controls.Add($workspaceGroup, 0, 0) +[void]$mainLayout.Controls.Add($script:MainTabControl, 0, 1) +[void]$mainLayout.Controls.Add($logGroup, 0, 2) +$script:MainForm.Controls.Add($mainLayout) + +$btnBrowseOutputPath.Add_Click({ + $dialog = [DialogBuilder]::SelectFolder().SetTitle("OutputPath waehlen").SetSelectedPath((Get-ParentDirectorySafe -Path $script:txtOutputPath.Text)) + if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { + $script:txtOutputPath.Text = (Get-FullPathSafe -Path $dialog.GetPath()) + Sync-MappingPathWithOutputPath -Force + } +}) + +$btnSaveMappingGlobal.Add_Click({ + try { + $currentMappingPath = [string]$script:txtMappingPath.Text + $dialog = [DialogBuilder]::SaveFile().SetTitle("MappingTable speichern").SetFilter("JSON (*.json)|*.json|Alle Dateien (*.*)|*.*").SetOverwritePrompt($true) + if (-not [string]::IsNullOrWhiteSpace($currentMappingPath)) { + $dialog.SetInitialDirectory((Get-ParentDirectorySafe -Path $currentMappingPath)) | Out-Null + $dialog.SetFileName([System.IO.Path]::GetFileName($currentMappingPath)) | Out-Null + } + + if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { + Save-MappingTableToPath -Path $dialog.GetPath() + } + } + catch { + Write-UILog -Message $_.Exception.Message -Level "ERROR" + Show-UiMessage -Message $_.Exception.Message -Caption "MappingTable konnte nicht gespeichert werden" -Icon ([System.Windows.Forms.MessageBoxIcon]::Error) + } +}) + +$btnLoadMapping.Add_Click({ + try { + $dialog = [DialogBuilder]::OpenFile().SetTitle("MappingTable laden").SetFilter("JSON (*.json)|*.json|Alle Dateien (*.*)|*.*").SetCheckFileExists($true) + $initialDirectory = Get-ParentDirectorySafe -Path $script:txtMappingPath.Text + if ([string]::IsNullOrWhiteSpace($initialDirectory)) { + $initialDirectory = Get-ParentDirectorySafe -Path $script:txtOutputPath.Text + } + if (-not [string]::IsNullOrWhiteSpace($initialDirectory)) { + $dialog.SetInitialDirectory($initialDirectory) | Out-Null + } + + if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { + Load-MappingTableFromPath -Path $dialog.GetPath() + $script:MainTabControl.SelectedTab = $script:tabMapping + } + } + catch { + Write-UILog -Message $_.Exception.Message -Level "ERROR" + Show-UiMessage -Message $_.Exception.Message -Caption "MappingTable konnte nicht geladen werden" -Icon ([System.Windows.Forms.MessageBoxIcon]::Error) + } +}) + +$script:tvContainers.Add_AfterCheck({ + param($sender, $e) + + if ($script:IsUpdatingContainerChecks) { + return + } + + $script:IsUpdatingContainerChecks = $true + try { + if ($e.Node.Level -eq 0) { + Set-TreeChildrenChecked -Node $e.Node -Checked $e.Node.Checked + } + else { + Update-ParentNodeCheckedState -Node $e.Node + } + } + finally { + $script:IsUpdatingContainerChecks = $false + } +}) + +$btnSelectAllContainers.Add_Click({ Set-AllContainerNodesChecked -Checked $true }) +$btnClearContainerSelection.Add_Click({ Set-AllContainerNodesChecked -Checked $false }) +$btnLoadContainers.Add_Click({ Invoke-LoadContainers }) +$btnRunExport.Add_Click({ Invoke-ExportFromGui }) +$btnRunImport.Add_Click({ Invoke-ImportFromGui }) +$btnClearLog.Add_Click({ $script:txtLog.Clear() }) + +$script:txtOutputPath.Add_Leave({ Sync-MappingPathWithOutputPath }) +$script:txtOutputPath.Add_TextChanged({ + if ([string]::IsNullOrWhiteSpace($script:txtOutputPath.Text)) { + return + } + + Sync-MappingPathWithOutputPath +}) + +$script:MainForm.Add_Shown({ + Write-UILog -Message "Start-SPMigration GUI gestartet." + Load-UiSettings + Set-MappingTableToUi -MappingTable (New-EmptyMappingTable) + Load-ExistingMappingTableIfAvailable + $formBuilder.RegisterAllAccessibleControls() | Out-Null +}) + +$script:MainForm.Add_FormClosing({ + Save-UiSettings +}) + +[void]$script:txtSourceUrl.Focus() +if ($NoGui.IsPresent) { + return +} + +$formBuilder.Show()