commit 01a535eb39d682ddab7d071fb95a5375ab37ac99 Author: Torsten Brendgen Date: Mon Apr 13 15:40:51 2026 +0200 Initial commit diff --git a/Compiler.ps1 b/Compiler.ps1 new file mode 100644 index 0000000..b87e362 --- /dev/null +++ b/Compiler.ps1 @@ -0,0 +1,3814 @@ + using namespace System.Windows.Forms + using namespace System.Drawing + + #region Class Definition + + 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] SetSetting([string]$key, [object]$value) { + [BaseComponent]::SettingsManager.Set($key, $value) + } + + [void] LogInfo([string]$message) { + if ($null -ne [BaseComponent]::LogManager) { + [BaseComponent]::LogManager.Info($message) + } + } + } + + class SettingsManager { + [System.String]$SettingsPath + [System.Collections.Hashtable]$Settings + + SettingsManager([System.String]$appName) { + $appDataPath = [Environment]::GetFolderPath('ApplicationData') + $appFolder = Join-Path $appDataPath $appName + + if (-not (Test-Path $appFolder)) { + New-Item -Path $appFolder -ItemType Directory -Force | Out-Null + } + + $this.SettingsPath = Join-Path $appFolder "settings.json" + $this.Load() + $this.EnsureDefaults() + } + + [void] Load() { + if (Test-Path $this.SettingsPath) { + try { + $json = Get-Content $this.SettingsPath -Raw | ConvertFrom-Json + $this.Settings = @{} + foreach ($prop in $json.PSObject.Properties) { + $this.Settings[$prop.Name] = $prop.Value + } + } catch { + Write-Warning "Fehler beim Laden der Settings: $_" + $this.Settings = @{} + } + } else { + $this.Settings = @{} + } + } + + # NEU: Stellt sicher, dass Standard-Einstellungen vorhanden sind + [void] EnsureDefaults() { + if (-not $this.Settings.ContainsKey("MenuStructure")) { + $this.Settings["MenuStructure"] = $this.GetDefaultMenuStructure() + $this.Save() + } + + if (-not $this.Settings.ContainsKey("WindowWidth")) { + $this.Settings["WindowWidth"] = 1024 + } + + if (-not $this.Settings.ContainsKey("WindowHeight")) { + $this.Settings["WindowHeight"] = 768 + } + + if (-not $this.Settings.ContainsKey("SplitterDistance")) { + $this.Settings["SplitterDistance"] = 200 + } + + if (-not $this.Settings.ContainsKey("TemplatePath")) { + $this.Settings["TemplatePath"] = (Join-Path -Path $this.Settings["RootPath"] -ChildPath "Templates") + } + + if (-not $this.Settings.ContainsKey("FunctionsPath")) { + $this.Settings["FunctionsPath"] = (Join-Path -Path $this.Settings["RootPath"] -ChildPath "Functions") + } + + if (-not $this.Settings.ContainsKey("DeploymentPath")) { + $this.Settings["DeploymentPath"] = (Join-Path -Path $this.Settings["RootPath"] -ChildPath "Deployments") + } + } + + # NEU: Standard-Menüstruktur + [object] GetDefaultMenuStructure() { + return @( + @{ + Name = "Datei" + Text = "Datei" + Items = @( + @{ Name = "Import"; Text = "Import" } + @{ Name = "Export"; Text = "Export" } + @{ Name = "Refresh"; Text = "Aktualisieren" } + @{ Type = "Separator" } + @{ Name = "Exit"; Text = "Beenden" } + ) + }, + @{ + Name = "Settings" + Text = "Einstellungen" + Items = @( + @{ Name = "OpenFolder"; Text = "Ordner wählen..." } + @{ Name = "ResetMenu"; Text = "Menü zurücksetzen" } + ) + }, + @{ + Name = "TemplateDesigner" + Text = "Template Designer" + }, + @{ + Name = "Help" + Text = "Hilfe" + Items = @( + @{ Name = "About"; Text = "Über..." } + ) + } + ) + } + + [void] Save() { + try { + $this.Settings | ConvertTo-Json -Depth 10 | Set-Content $this.SettingsPath -Encoding UTF8 + } catch { + Write-Warning "Fehler beim Speichern der Settings: $_" + } + } + + [object] Get([System.String]$key) { + return $this.Settings[$key] + } + + [object] Get([System.String]$key, [object]$defaultValue) { + if ($this.Settings.ContainsKey($key)) { + return $this.Settings[$key] + } + return $defaultValue + } + + [void] Set([System.String]$key, [object]$value) { + $this.Settings[$key] = $value + $this.Save() + } + + [bool] Contains([System.String]$key) { + return $this.Settings.ContainsKey($key) + } + + [void] Remove([System.String]$key) { + if ($this.Settings.ContainsKey($key)) { + $this.Settings.Remove($key) + $this.Save() + } + } + + [System.Collections.Hashtable] GetAll() { + return $this.Settings + } + + # NEU: Holt die Menüstruktur + [System.Array] GetMenuStructure() { + if ($this.Settings.ContainsKey("MenuStructure")) { + # Konvertiere PSCustomObject zurück zu Hashtable + return $this.ConvertToHashtable($this.Settings["MenuStructure"]) + } + return $this.GetDefaultMenuStructure() + } + + # NEU: Setzt eine neue Menüstruktur + [void] SetMenuStructure([System.Array]$menuStructure) { + $this.Settings["MenuStructure"] = $menuStructure + $this.Save() + } + + # NEU: Setzt Menü auf Standard zurück + [void] ResetMenuStructure() { + $this.Settings["MenuStructure"] = $this.GetDefaultMenuStructure() + $this.Save() + } + + # Hilfsfunktion: Konvertiert PSCustomObject zu Hashtable (für JSON-Deserialisierung) + hidden [object] ConvertToHashtable([object]$obj) { + if ($obj -is [System.Management.Automation.PSCustomObject]) { + $hash = @{} + foreach ($property in $obj.PSObject.Properties) { + $hash[$property.Name] = $this.ConvertToHashtable($property.Value) + } + return $hash + } + elseif ($obj -is [System.Array] -or $obj -is [System.Collections.ArrayList]) { + $array = @() + foreach ($item in $obj) { + $array += $this.ConvertToHashtable($item) + } + return $array + } + else { + return $obj + } + } + } + + class StateManager { + [System.Collections.Hashtable] $State = @{} + [System.Collections.ArrayList] $Buttons = @() + [System.Collections.Hashtable] $MenuItems = @{} + [System.Collections.ArrayList] $Forms = @() + + # Registriert einen Button mit seiner Bedingung + [void] RegisterButton([ButtonBuilder]$buttonBuilder) { + $this.Buttons.Add($buttonBuilder) | Out-Null + } + + # NEU: Registriert ein Menü-Item + [void] RegisterMenuItem([string]$menuName, [MenuBuilder]$menuBuilder) { + $this.MenuItems[$menuName] = $menuBuilder + } + + # Registriert einen Button mit seiner Bedingung + [void] RegisterForm([System.Object]$form) { + $this.Forms.Add($form) | Out-Null + } + + # Setzt einen State-Wert und aktualisiert alle Buttons und Menüs + [void] SetState([string]$key, [object]$value) { + $this.State[$key] = $value + $this.UpdateAll() + } + + [void] SetStates([hashtable]$states) { + foreach ($key in $states.Keys) { + $this.State[$key] = $states[$key] + } + $this.UpdateAll() + } + + # Holt einen State-Wert + [object] GetState([string]$key) { + return $this.State[$key] + } + + [hashtable] GetAllStates() { + return $this.State.Clone() + } + + # Aktualisiert alle registrierten Buttons + [void] UpdateAllButtons() { + foreach ($button in $this.Buttons) { + $button.UpdateEnabledState() + } + } + + # NEU: Aktualisiert alle registrierten Menü-Items + [void] UpdateAllMenuItems() { + foreach ($menuName in $this.MenuItems.Keys) { + $this.MenuItems[$menuName].UpdateMenuItemState($menuName) + } + } + + # Aktualisiert alle registrierten Buttons + [void] UpdateAllForms() { + foreach ($form in $this.Forms) { + $form.UpdateVisibleState() + } + } + + # NEU: Aktualisiert alles (Buttons + Menüs) + [void] UpdateAll() { + $this.UpdateAllButtons() + $this.UpdateAllMenuItems() + $this.UpdateAllForms() + } + } + + class LogManager { + [System.String]$LogPath + [bool]$EnableConsole = $false + + LogManager([System.String]$appName) { + $appDataPath = [Environment]::GetFolderPath('ApplicationData') + $logFolder = Join-Path $appDataPath "$appName\Logs" + + if (-not (Test-Path $logFolder)) { + New-Item -Path $logFolder -ItemType Directory -Force | Out-Null + } + + $timestamp = Get-Date -Format "yyyyMMdd" + $this.LogPath = Join-Path $logFolder "log_$timestamp.txt" + } + + [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" + $logLine = "[$timestamp] [$level] $message" + + Add-Content -Path $this.LogPath -Value $logLine + + if ($this.EnableConsole) { + Write-Host $logLine + } + } + } + + class AccessibilityManager { + [SettingsManager] $SettingsManager + [LogManager] $LogManager + [System.Collections.ArrayList] $FontControls = @() + [System.Collections.ArrayList] $Forms = @() + [System.Collections.ArrayList] $LayoutContainers = @() + [hashtable] $AccessibilitySettings = @{} + + + AccessibilityManager([SettingsManager]$SettingsManager, [LogManager]$LogManager) { + $this.SettingsManager = $SettingsManager + $this.LogManager = $LogManager + $this.InitializeDefaults() + } + + # Initialisiert Standard-Accessibility-Einstellungen + [void] InitializeDefaults() { + $this.AccessibilitySettings = @{ + FontSize = @{ Min = 6; Max = 32; Default = 9; Step = 1 } + FontSizeStep = @{ Min = 1; Max = 5; Default = 1 } + HighContrast = @{ Default = $false } + KeyboardShortcuts = @{ Default = $true } + ScreenReaderSupport = @{ Default = $false } + } + + # Stelle sicher, dass alle Settings existieren + foreach ($key in $this.AccessibilitySettings.Keys) { + if (-not $this.SettingsManager.Contains($key)) { + $this.SettingsManager.Set($key, $this.AccessibilitySettings[$key].Default) + } + } + } + + # Registriert ein Form für Accessibility-Features + [void] RegisterForm([System.Windows.Forms.Form]$form) { + if (-not $this.Forms.Contains($form)) { + $this.Forms.Add($form) | Out-Null + $this.AttachMouseWheelHandler($form) + $this.AttachKeyboardShortcuts($form) + $this.LogManager.Info("Form registriert für Accessibility-Features") + } + } + + # Registriert Container für automatisches Layout-Update + [void] RegisterLayoutContainer([System.Windows.Forms.Control]$container) { + if (-not $this.LayoutContainers.Contains($container)) { + $this.LayoutContainers.Add($container) | Out-Null + $this.LogManager.Info("Layout-Container registriert: $($container.GetType().Name)") + } + } + + # Registriert ein Control für Font-Updates + [void] RegisterFontControl([System.Windows.Forms.Control]$control) { + if (-not $this.FontControls.Contains($control)) { + $this.FontControls.Add($control) | Out-Null + $this.ApplyFontSize($control) + } + } + + # Registriert mehrere Controls + [void] RegisterFontControls([System.Windows.Forms.Control[]]$controls) { + foreach ($control in $controls) { + $this.RegisterFontControl($control) + } + } + + # Registriert alle Controls eines Forms/Containers rekursiv + [void] RegisterAllControls([System.Windows.Forms.Control]$container) { + # Registriere das Container-Control selbst (wenn es Text hat) + if ($container.GetType().GetProperty("Font") -ne $null) { + $this.RegisterFontControl($container) + } + + # Durchlaufe alle Child-Controls rekursiv + foreach ($control in $container.Controls) { + $this.RegisterAllControls($control) + } + + $this.LogManager.Info("Alle Controls von '$($container.GetType().Name)' registriert (Total: $($this.FontControls.Count))") + } + + # Optional: Registriert nur bestimmte Control-Typen + [void] RegisterControlsByType([System.Windows.Forms.Control]$container, [Type[]]$types) { + foreach ($control in $container.Controls) { + if ($types -contains $control.GetType()) { + $this.RegisterFontControl($control) + } + # Rekursiv durch Child-Controls + if ($control.Controls.Count -gt 0) { + $this.RegisterControlsByType($control, $types) + } + } + } + + # Fügt MouseWheel-Handler für Strg + Mausrad hinzu + hidden [void] AttachMouseWheelHandler([System.Windows.Forms.Form]$form) { + $form.Add_MouseWheel({ + param($sender, $e) + + if ([System.Windows.Forms.Control]::ModifierKeys -eq [System.Windows.Forms.Keys]::Control) { + $delta = if ($e.Delta -gt 0) { 1 } else { -1 } + [BaseComponent]::AccessibilityManager.ChangeFontSize($delta) + $e.Handled = $true + } + }.GetNewClosure()) + } + + # Fügt Tastatur-Shortcuts hinzu + hidden [void] AttachKeyboardShortcuts([System.Windows.Forms.Form]$form) { + $form.KeyPreview = $true + $form.Add_KeyDown({ + param($sender, $e) + + # Strg + 0: Reset auf Standard + if ($e.Control -and $e.KeyCode -eq [System.Windows.Forms.Keys]::D0) { + [BaseComponent]::AccessibilityManager.ResetFontSize() + $e.Handled = $true + } + + # Strg + Plus: Größer + if ($e.Control -and ($e.KeyCode -eq [System.Windows.Forms.Keys]::Add -or $e.KeyCode -eq [System.Windows.Forms.Keys]::Oemplus)) { + [BaseComponent]::AccessibilityManager.ChangeFontSize(1) + $e.Handled = $true + } + + # Strg + Minus: Kleiner + if ($e.Control -and ($e.KeyCode -eq [System.Windows.Forms.Keys]::Subtract -or $e.KeyCode -eq [System.Windows.Forms.Keys]::OemMinus)) { + [BaseComponent]::AccessibilityManager.ChangeFontSize(-1) + $e.Handled = $true + } + + # F11: High Contrast Toggle (optional) + if ($e.KeyCode -eq [System.Windows.Forms.Keys]::F11) { + [BaseComponent]::AccessibilityManager.ToggleHighContrast() + $e.Handled = $true + } + }.GetNewClosure()) + } + + # Ändert die Schriftgröße + [void] ChangeFontSize([int]$delta) { + $settings = $this.AccessibilitySettings["FontSize"] + $currentSize = $this.SettingsManager.Get("FontSize", $settings.Default) + $step = $this.SettingsManager.Get("FontSizeStep", 1) + $newSize = $currentSize + ($delta * $step) + + # Begrenzung + if ($newSize -lt $settings.Min) { $newSize = $settings.Min } + if ($newSize -gt $settings.Max) { $newSize = $settings.Max } + + if ($newSize -ne $currentSize) { + $this.SettingsManager.Set("FontSize", $newSize) + $this.UpdateAllFonts() + $this.LogManager.Info("Schriftgröße geändert: $currentSize -> $newSize") + } + } + + # Setzt Schriftgröße zurück + [void] ResetFontSize() { + $defaultSize = $this.AccessibilitySettings["FontSize"].Default + $this.SettingsManager.Set("FontSize", $defaultSize) + $this.UpdateAllFonts() + $this.LogManager.Info("Schriftgröße zurückgesetzt auf: $defaultSize") + } + + # Wendet Schriftgröße auf ein Control an + hidden [void] ApplyFontSize([System.Windows.Forms.Control]$control) { + $fontSize = $this.SettingsManager.Get("FontSize", 9) + try { + $control.Font = New-Object System.Drawing.Font($control.Font.FontFamily, $fontSize) + } catch { + $this.LogManager.Warning("Fehler beim Setzen der Schriftgröße für Control: $_") + } + } + + # Aktualisiert alle registrierten Controls + [void] UpdateAllFonts() { + foreach ($control in $this.FontControls) { + if ($null -ne $control -and -not $control.IsDisposed) { + $this.ApplyFontSize($control) + } + } + $this.UpdateAllLayouts() + } + + # Aktualisiert das Layout aller registrierten Container + hidden [void] UpdateAllLayouts() { + foreach ($container in $this.LayoutContainers) { + if ($null -ne $container -and -not $container.IsDisposed) { + # GroupBoxes: AutoSize aktivieren + if ($container -is [System.Windows.Forms.GroupBox]) { + $container.AutoSize = $true + $container.AutoSizeMode = 'GrowAndShrink' + } + + # FlowLayoutPanel: PerformLayout erzwingen + if ($container -is [System.Windows.Forms.FlowLayoutPanel]) { + foreach ($child in $container.Controls) { + if ($child -is [System.Windows.Forms.GroupBox]) { + $child.AutoSize = $true + $child.AutoSizeMode = 'GrowAndShrink' + + # Breite anpassen + $child.Width = $container.ClientSize.Width - 20 + } + } + + $container.PerformLayout() + $container.Refresh() + } + + # Panel: PerformLayout + if ($container -is [System.Windows.Forms.Panel]) { + $container.PerformLayout() + } + } + } + + $this.LogManager.Info("Layout-Update abgeschlossen") + } + + # Schaltet High Contrast Modus um + [void] ToggleHighContrast() { + $currentState = $this.SettingsManager.Get("HighContrast", $false) + $newState = -not $currentState + $this.SettingsManager.Set("HighContrast", $newState) + $this.ApplyHighContrast($newState) + $this.LogManager.Info("High Contrast: $newState") + } + + # Wendet High Contrast auf alle Forms an + hidden [void] ApplyHighContrast([bool]$enabled) { + foreach ($form in $this.Forms) { + if ($null -ne $form -and -not $form.IsDisposed) { + if ($enabled) { + # Form-Hintergrund + $form.BackColor = [System.Drawing.Color]::Black + $form.ForeColor = [System.Drawing.Color]::White + + # NEU: Rekursiv alle Controls anpassen + $this.ApplyHighContrastToControls($form, $enabled) + } else { + $form.BackColor = [System.Drawing.SystemColors]::Control + $form.ForeColor = [System.Drawing.SystemColors]::ControlText + + # NEU: Rekursiv alle Controls zurücksetzen + $this.ApplyHighContrastToControls($form, $enabled) + } + } + } + } + + # NEU: Wendet High Contrast rekursiv auf alle Controls an + hidden [void] ApplyHighContrastToControls([System.Windows.Forms.Control]$control, [bool]$enabled) { + if ($enabled) { + # Schwarzer Hintergrund → Weiße Schrift + switch ($control.GetType().Name) { + 'Panel' { + $control.BackColor = [System.Drawing.Color]::Black + $control.ForeColor = [System.Drawing.Color]::White + } + 'FlowLayoutPanel' { + $control.BackColor = [System.Drawing.Color]::Black + $control.ForeColor = [System.Drawing.Color]::White + } + 'GroupBox' { + $control.BackColor = [System.Drawing.Color]::Black + $control.ForeColor = [System.Drawing.Color]::White + } + 'Label' { + $control.ForeColor = [System.Drawing.Color]::White + # Transparent oder schwarz + if ($control.BackColor -ne [System.Drawing.Color]::Transparent) { + $control.BackColor = [System.Drawing.Color]::Black + } + } + 'Button' { + $control.BackColor = [System.Drawing.Color]::FromArgb(40, 40, 40) + $control.ForeColor = [System.Drawing.Color]::White + $control.FlatStyle = 'Flat' + } + 'TextBox' { + $control.BackColor = [System.Drawing.Color]::FromArgb(30, 30, 30) + $control.ForeColor = [System.Drawing.Color]::White + } + 'TreeView' { + $control.BackColor = [System.Drawing.Color]::FromArgb(20, 20, 20) + $control.ForeColor = [System.Drawing.Color]::White + } + 'ListBox' { + $control.BackColor = [System.Drawing.Color]::FromArgb(20, 20, 20) + $control.ForeColor = [System.Drawing.Color]::White + } + 'DataGridView' { + $control.BackgroundColor = [System.Drawing.Color]::Black + $control.ForeColor = [System.Drawing.Color]::White + $control.DefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(30, 30, 30) + $control.DefaultCellStyle.ForeColor = [System.Drawing.Color]::White + $control.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.Color]::FromArgb(50, 50, 50) + $control.ColumnHeadersDefaultCellStyle.ForeColor = [System.Drawing.Color]::White + } + 'TabControl' { + $control.BackColor = [System.Drawing.Color]::Black + $control.ForeColor = [System.Drawing.Color]::White + } + 'TabPage' { + $control.BackColor = [System.Drawing.Color]::Black + $control.ForeColor = [System.Drawing.Color]::White + } + } + } else { + # Zurück zu Standard-Farben + switch ($control.GetType().Name) { + 'Panel' { + $control.BackColor = [System.Drawing.SystemColors]::Control + $control.ForeColor = [System.Drawing.SystemColors]::ControlText + } + 'FlowLayoutPanel' { + $control.BackColor = [System.Drawing.SystemColors]::Control + $control.ForeColor = [System.Drawing.SystemColors]::ControlText + } + 'GroupBox' { + $control.BackColor = [System.Drawing.SystemColors]::Control + $control.ForeColor = [System.Drawing.SystemColors]::ControlText + } + 'Label' { + $control.ForeColor = [System.Drawing.SystemColors]::ControlText + if ($control.BackColor -ne [System.Drawing.Color]::Transparent) { + $control.BackColor = [System.Drawing.SystemColors]::Control + } + } + 'Button' { + $control.BackColor = [System.Drawing.SystemColors]::Control + $control.ForeColor = [System.Drawing.SystemColors]::ControlText + $control.FlatStyle = 'Standard' + } + 'TextBox' { + $control.BackColor = [System.Drawing.SystemColors]::Window + $control.ForeColor = [System.Drawing.SystemColors]::WindowText + } + 'TreeView' { + $control.BackColor = [System.Drawing.SystemColors]::Window + $control.ForeColor = [System.Drawing.SystemColors]::WindowText + } + 'ListBox' { + $control.BackColor = [System.Drawing.SystemColors]::Window + $control.ForeColor = [System.Drawing.SystemColors]::WindowText + } + 'DataGridView' { + $control.BackgroundColor = [System.Drawing.SystemColors]::Control + $control.ForeColor = [System.Drawing.SystemColors]::ControlText + $control.DefaultCellStyle.BackColor = [System.Drawing.SystemColors]::Window + $control.DefaultCellStyle.ForeColor = [System.Drawing.SystemColors]::WindowText + $control.ColumnHeadersDefaultCellStyle.BackColor = [System.Drawing.SystemColors]::Control + $control.ColumnHeadersDefaultCellStyle.ForeColor = [System.Drawing.SystemColors]::ControlText + } + 'TabControl' { + $control.BackColor = [System.Drawing.SystemColors]::Control + $control.ForeColor = [System.Drawing.SystemColors]::ControlText + } + 'TabPage' { + $control.BackColor = [System.Drawing.SystemColors]::Control + $control.ForeColor = [System.Drawing.SystemColors]::ControlText + } + } + } + + # Rekursiv für alle Child-Controls + foreach ($child in $control.Controls) { + $this.ApplyHighContrastToControls($child, $enabled) + } + } + + # Gibt aktuelle Accessibility-Einstellungen zurück + [hashtable] GetCurrentSettings() { + return @{ + FontSize = $this.SettingsManager.Get("FontSize", 9) + HighContrast = $this.SettingsManager.Get("HighContrast", $false) + KeyboardShortcuts = $this.SettingsManager.Get("KeyboardShortcuts", $true) + } + } + + # Setzt eine spezifische Accessibility-Einstellung + [void] SetAccessibilitySetting([string]$key, [object]$value) { + $this.SettingsManager.Set($key, $value) + + # Wende Änderung sofort an + switch ($key) { + "FontSize" { $this.UpdateAllFonts() } + "HighContrast" { $this.ApplyHighContrast($value) } + } + } + } + + class FormBuilder : BaseComponent { + + [System.String] $Title + [System.Windows.Forms.Form] $Form + [MenuBuilder] $Menu + [StatusStripBuilder] $StatusBar + + + FormBuilder($Title){ + $this.Form = New-Object System.Windows.Forms.Form + $this.Form.Text = $Title + $this.Form.StartPosition = "CenterScreen" + + # HIER wird RegisterForm automatisch aufgerufen: + [BaseComponent]::AccessibilityManager.RegisterForm($this.Form) + } + + # Registriert ein Control für Accessibility-Features + [FormBuilder] RegisterAccessibleControl([System.Windows.Forms.Control]$control) { + [BaseComponent]::AccessibilityManager.RegisterFontControl($control) + return $this + } + + # Registriert mehrere Controls + [FormBuilder] RegisterAccessibleControls([System.Windows.Forms.Control[]]$controls) { + [BaseComponent]::AccessibilityManager.RegisterFontControls($controls) + return $this + } + + # NEU: Registriert automatisch ALLE Controls des Forms + [FormBuilder] RegisterAllAccessibleControls() { + [BaseComponent]::AccessibilityManager.RegisterAllControls($this.Form) + return $this + } + + # NEU: Registriert nur bestimmte Control-Typen (z.B. nur TextBoxen und Labels) + [FormBuilder] RegisterAccessibleControlsByType([Type[]]$types) { + [BaseComponent]::AccessibilityManager.RegisterControlsByType($this.Form, $types) + return $this + } + + [FormBuilder] AddMenu([MenuBuilder]$menuBuilder) { + $this.Menu = $menuBuilder + $this.Form.Controls.Add($menuBuilder.GetMenuStrip()) + return $this + } + + # Fügt eine StatusBar hinzu + [FormBuilder] AddStatusBar([StatusStripBuilder]$statusBar) { + $this.StatusBar = $statusBar + $this.Form.Controls.Add($statusBar.GetStatusStrip()) + return $this + } + + # Fügt ein SplitPanel hinzu + [FormBuilder] AddSplitPanel([SplitPanelBuilder]$splitPanel) { + $this.Form.Controls.Add($splitPanel.GetSplitContainer()) + return $this + } + + # Fügt ein beliebiges Control hinzu + [FormBuilder] AddControl([System.Windows.Forms.Control]$control) { + $this.Form.Controls.Add($control) + return $this + } + + # Fügt mehrere Controls hinzu + [FormBuilder] AddControls([System.Windows.Forms.Control[]]$controls) { + foreach ($control in $controls) { + $this.Form.Controls.Add($control) + } + return $this + } + + [void] SetTitle($Title) { + $this.Form.Text = $Title + } + + # Setzt die Größe des Forms + [FormBuilder] SetSize([int]$width, [int]$height) { + $this.Form.Size = New-Object System.Drawing.Size($width, $height) + return $this + } + + # Setzt nur die Breite + [FormBuilder] SetWidth([int]$width) { + $this.Form.Width = $width + return $this + } + + # Setzt nur die Höhe + [FormBuilder] SetHeight([int]$height) { + $this.Form.Height = $height + return $this + } + + # Setzt die Mindestgröße + [FormBuilder] SetMinimumSize([int]$width, [int]$height) { + $this.Form.MinimumSize = New-Object System.Drawing.Size($width, $height) + return $this + } + + # Setzt die Maximalgröße + [FormBuilder] SetMaximumSize([int]$width, [int]$height) { + $this.Form.MaximumSize = New-Object System.Drawing.Size($width, $height) + return $this + } + + # Setzt die Startposition + [FormBuilder] SetStartPosition([System.String]$position) { + $this.Form.StartPosition = [System.Windows.Forms.FormStartPosition]::$position + return $this + } + + # Setzt die Location (Position auf dem Bildschirm) + [FormBuilder] SetLocation([int]$x, [int]$y) { + $this.Form.Location = New-Object System.Drawing.Point($x, $y) + return $this + } + + # Setzt den WindowState + [FormBuilder] SetWindowState([System.String]$state) { + $this.Form.WindowState = [System.Windows.Forms.FormWindowState]::$state + return $this + } + + [FormBuilder] AddLoadHandler([scriptblock]$handler) { + $this.Form.Add_Load($handler) + return $this + } + + # Shown Event (wenn Form zum ersten Mal angezeigt wird) + [FormBuilder] AddShownHandler([scriptblock]$handler) { + $this.Form.Add_Shown($handler) + return $this + } + + # FormClosing Event (bevor Form geschlossen wird - kann abgebrochen werden) + [FormBuilder] AddFormClosingHandler([scriptblock]$handler) { + $this.Form.Add_FormClosing($handler) + return $this + } + + # FormClosed Event (nachdem Form geschlossen wurde) + [FormBuilder] AddFormClosedHandler([scriptblock]$handler) { + $this.Form.Add_FormClosed($handler) + return $this + } + + # Resize Event + [FormBuilder] AddResizeHandler([scriptblock]$handler) { + $this.Form.Add_Resize($handler) + return $this + } + + # Move Event + [FormBuilder] AddMoveHandler([scriptblock]$handler) { + $this.Form.Add_Move($handler) + return $this + } + + # KeyDown Event + [FormBuilder] AddKeyDownHandler([scriptblock]$handler) { + $this.Form.Add_KeyDown($handler) + return $this + } + + # Activate Event (wenn Form aktiviert wird) + [FormBuilder] AddActivateHandler([scriptblock]$handler) { + $this.Form.Add_Activated($handler) + return $this + } + + # Deactivate Event + [FormBuilder] AddDeactivateHandler([scriptblock]$handler) { + $this.Form.Add_Deactivate($handler) + return $this + } + + # Generischer Event-Handler für alle Events + [FormBuilder] AddEventHandler([System.String]$eventName, [scriptblock]$handler) { + $this.Form."Add_$eventName"($handler) + return $this + } + + [void] Show(){ + [void]$this.Form.ShowDialog() + } + } + + class MenuBuilder : BaseComponent{ + + [System.Windows.Forms.MenuStrip] $MenuStrip + [System.Collections.Hashtable] $MenuItems = @{} + [System.Collections.Hashtable] $MenuConditions = @{} + [StateManager] $StateManager = $null + + MenuBuilder() { + $this.MenuStrip = New-Object System.Windows.Forms.MenuStrip + $this.MenuStrip.Dock = 'Top' + } + + # NEU: Setzt den StateManager + [MenuBuilder] WithStateManager([StateManager]$stateManager) { + $this.StateManager = $stateManager + return $this + } + + # Fügt ein Hauptmenü-Element hinzu - JETZT MIT RETURN + [MenuBuilder] AddMainMenu([System.String]$name, [System.String]$text) { + $menuItem = New-Object System.Windows.Forms.ToolStripMenuItem $text + $this.MenuStrip.Items.Add($menuItem) | Out-Null + $this.MenuItems[$name] = $menuItem + return $this | Out-Null + } + + # Fügt ein Untermenü-Element hinzu - JETZT MIT RETURN + [MenuBuilder] AddSubMenu([System.String]$parentName, [System.String]$name, [System.String]$text) { + if ($this.MenuItems.ContainsKey($parentName)) { + $menuItem = New-Object System.Windows.Forms.ToolStripMenuItem $text + $this.MenuItems[$parentName].DropDownItems.Add($menuItem) | Out-Null + $this.MenuItems[$name] = $menuItem + return $this | Out-Null + } + throw "Parent menu '$parentName' nicht gefunden" + } + + # NEU: Fügt mehrere Untermenüs auf einmal hinzu + [MenuBuilder] AddSubMenus([System.String]$parentName, [hashtable[]]$items) { + foreach ($item in $items) { + if ($item.Type -eq "Separator") { + $this.AddSeparator($parentName) + } + else { + $this.AddSubMenu($parentName, $item.Name, $item.Text) + if ($item.Handler) { + $this.SetClickHandler($item.Name, $item.Handler) + } + } + } + return $this + } + + # NEU: Erstellt komplettes Menü aus einer Struktur + [MenuBuilder] BuildFromStructure([hashtable[]]$structure) { + foreach ($mainMenu in $structure) { + $this.AddMainMenu($mainMenu.Name, $mainMenu.Text) + + if ($mainMenu.Items) { + $this.AddSubMenus($mainMenu.Name, $mainMenu.Items) + } + } + return $this + } + + # Fügt einen Separator hinzu - JETZT MIT RETURN + [MenuBuilder] AddSeparator([System.String]$parentName) { + if ($this.MenuItems.ContainsKey($parentName)) { + $this.MenuItems[$parentName].DropDownItems.Add('-') | Out-Null + } + return $this | Out-Null + } + + # Setzt einen Event-Handler für ein Menü-Element - JETZT MIT RETURN + [MenuBuilder] SetClickHandler([System.String]$menuName, [scriptblock]$handler) { + if ($this.MenuItems.ContainsKey($menuName)) { + $this.MenuItems[$menuName].Add_Click($handler) + } + return $this | Out-Null + } + + # NEU: Setzt einen Click-Handler mit Enable-Bedingung + [MenuBuilder] SetClickHandler([System.String]$menuName, [scriptblock]$handler, [scriptblock]$enableCondition) { + if ($this.MenuItems.ContainsKey($menuName)) { + $this.MenuItems[$menuName].Add_Click($handler) + $this.MenuConditions[$menuName] = $enableCondition + + # Initial den Status setzen + $this.UpdateMenuItemState($menuName) + + # Bei StateManager registrieren + if ($null -ne $this.StateManager) { + $this.StateManager.RegisterMenuItem($menuName, $this) + } + } + return $this | Out-Null + } + + # NEU: Setzt mehrere Click-Handler auf einmal + [MenuBuilder] SetClickHandlers([System.Collections.Hashtable]$handlers) { + foreach ($key in $handlers.Keys) { + $this.SetClickHandler($key, $handlers[$key]) + } + return $this | Out-Null + } + + # NEU: Setzt mehrere Click-Handler mit Bedingungen + [MenuBuilder] SetClickHandlersWithConditions([System.Collections.Hashtable]$handlers) { + foreach ($key in $handlers.Keys) { + $config = $handlers[$key] + if ($config.Condition) { + $this.SetClickHandler($key, $config.Handler, $config.Condition) + } else { + $this.SetClickHandler($key, $config.Handler) + } + } + return $this | Out-Null + } + + # NEU: Aktualisiert den Enabled-Status eines Menü-Items + [void] UpdateMenuItemState([System.String]$menuName) { + if ($this.MenuItems.ContainsKey($menuName) -and $this.MenuConditions.ContainsKey($menuName)) { + try { + $result = & $this.MenuConditions[$menuName] + $this.MenuItems[$menuName].Enabled = [bool]$result + } catch { + Write-Warning "Fehler beim Evaluieren der Menu-Bedingung für '$menuName': $_" + $this.MenuItems[$menuName].Enabled = $false + } + } + } + + # NEU: Aktualisiert alle Menü-Items mit Bedingungen + [void] UpdateAllMenuItems() { + foreach ($menuName in $this.MenuConditions.Keys) { + $this.UpdateMenuItemState($menuName) + } + } + + # Gibt das MenuStrip-Objekt zurück + [System.Windows.Forms.MenuStrip] GetMenuStrip() { + return $this.MenuStrip + } + + # Aktiviert/Deaktiviert ein Menü-Element - JETZT MIT RETURN + [MenuBuilder] SetEnabled([System.String]$menuName, [bool]$enabled) { + if ($this.MenuItems.ContainsKey($menuName)) { + $this.MenuItems[$menuName].Enabled = $enabled + } + return $this + } + + # NEU: Holt ein Menü-Item + [System.Windows.Forms.ToolStripMenuItem] GetMenuItem([System.String]$menuName) { + if ($this.MenuItems.ContainsKey($menuName)) { + return $this.MenuItems[$menuName] + } + return $null + } + } + + class StatusStripBuilder : BaseComponent{ + [System.Windows.Forms.StatusStrip] $StatusStrip + [System.Collections.Hashtable] $Labels = @{} + + StatusStripBuilder() { + $this.StatusStrip = New-Object System.Windows.Forms.StatusStrip + } + + # Fügt ein Label zur StatusBar hinzu + [System.Windows.Forms.ToolStripStatusLabel] AddLabel([System.String]$name, [System.String]$text) { + $label = New-Object System.Windows.Forms.ToolStripStatusLabel + $label.Text = $text + $this.StatusStrip.Items.Add($label) | Out-Null + $this.Labels[$name] = $label + return $label + } + + # Fügt ein Label mit Spring-Eigenschaft hinzu (füllt verfügbaren Platz) + [System.Windows.Forms.ToolStripStatusLabel] AddSpringLabel([System.String]$name, [System.String]$text) { + $label = New-Object System.Windows.Forms.ToolStripStatusLabel + $label.Text = $text + $label.Spring = $true + $label.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft + $this.StatusStrip.Items.Add($label) | Out-Null + $this.Labels[$name] = $label + return $label + } + + # Setzt den Text eines Labels + [void] SetText([System.String]$labelName, [System.String]$text) { + if ($this.Labels.ContainsKey($labelName)) { + $this.Labels[$labelName].Text = $text + } + } + + # Fügt einen Separator hinzu + [void] AddSeparator() { + $separator = New-Object System.Windows.Forms.ToolStripSeparator + $this.StatusStrip.Items.Add($separator) | Out-Null + } + + # Fügt eine ProgressBar hinzu + [System.Windows.Forms.ToolStripProgressBar] AddProgressBar([System.String]$name) { + $progressBar = New-Object System.Windows.Forms.ToolStripProgressBar + $progressBar.Size = New-Object System.Drawing.Size(100, 16) + $this.StatusStrip.Items.Add($progressBar) | Out-Null + $this.Labels[$name] = $progressBar + return $progressBar + } + + # Setzt den Fortschritt einer ProgressBar + [void] SetProgress([System.String]$progressBarName, [int]$value) { + if ($this.Labels.ContainsKey($progressBarName)) { + $this.Labels[$progressBarName].Value = $value + } + } + + # Gibt das StatusStrip-Objekt zurück + [System.Windows.Forms.StatusStrip] GetStatusStrip() { + return $this.StatusStrip + } + + # Zeigt/Versteckt ein Element + [void] SetVisible([System.String]$itemName, [bool]$visible) { + if ($this.Labels.ContainsKey($itemName)) { + $this.Labels[$itemName].Visible = $visible + } + } + + # Setzt die Hintergrundfarbe eines Labels + [void] SetBackColor([System.String]$labelName, [System.Drawing.Color]$color) { + if ($this.Labels.ContainsKey($labelName)) { + $this.Labels[$labelName].BackColor = $color + } + } + + # Setzt die Textfarbe eines Labels + [void] SetForeColor([System.String]$labelName, [System.Drawing.Color]$color) { + if ($this.Labels.ContainsKey($labelName)) { + $this.Labels[$labelName].ForeColor = $color + } + } + } + + class SplitPanelBuilder : BaseComponent{ + [System.Windows.Forms.SplitContainer] $SplitContainer + + [Int] $DesiredPanel1MinSize = 100 + [Int] $DesiredPanel2MinSize = 250 + [Int] $DesiredSplitterDistance = 200 + [Double] $SplitterDistanceRatio = 0.0 + + SplitPanelBuilder([System.String]$orientation) { + $this.SplitContainer = New-Object System.Windows.Forms.SplitContainer + $this.SplitContainer.Dock = 'Fill' + + if ($orientation -eq 'Horizontal') { + $this.SplitContainer.Orientation = 'Horizontal' + } else { + $this.SplitContainer.Orientation = 'Vertical' + } + } + + # Konstruktor mit MinSize-Optionen - Verzögerte Initialisierung + SplitPanelBuilder([System.String]$orientation, [int]$splitterDistance, [int]$panel1MinSize, [int]$panel2MinSize) { + $this.SplitContainer = New-Object System.Windows.Forms.SplitContainer + $this.SplitContainer.Dock = 'Fill' + $this.SplitContainer.Orientation = $orientation + + # Speichere die gewünschten Werte für spätere Initialisierung + $this.DesiredPanel1MinSize = $panel1MinSize + $this.DesiredPanel2MinSize = $panel2MinSize + $this.DesiredSplitterDistance = $splitterDistance + + # Setze nur die absolut notwendigen Anfangswerte + $this.SplitContainer.Panel1MinSize = [Math]::Min($panel1MinSize, 25) + $this.SplitContainer.Panel2MinSize = 25 + } + + # Konstruktor mit Verhältnis (Ratio) für SplitterDistance + SplitPanelBuilder([System.String]$orientation, [double]$splitterRatio, [int]$panel1MinSize, [int]$panel2MinSize) { + $this.SplitContainer = New-Object System.Windows.Forms.SplitContainer + $this.SplitContainer.Dock = 'Fill' + $this.SplitContainer.Orientation = $orientation + + # Speichere die gewünschten Werte + $this.DesiredPanel1MinSize = $panel1MinSize + $this.DesiredPanel2MinSize = $panel2MinSize + $this.SplitterDistanceRatio = $splitterRatio + + # Setze nur die absolut notwendigen Anfangswerte + $this.SplitContainer.Panel1MinSize = [Math]::Min($panel1MinSize, 25) + $this.SplitContainer.Panel2MinSize = 25 + } + + # Initialisiert den SplitContainer mit den finalen Werten (nach Form.Shown) + [void] InitializeAfterShown([System.Windows.Forms.Form]$form) { + # Bestimme verfügbare Breite/Höhe aus dem SplitContainer selbst + $availableSize = if ($this.SplitContainer.Orientation -eq 'Vertical') { + $this.SplitContainer.Width + } else { + $this.SplitContainer.Height + } + + # Sicherheitscheck: Mindestgröße + if ($availableSize -lt 100) { + Write-Warning "SplitContainer zu klein: $availableSize px. Verwende ClientSize." + $availableSize = if ($this.SplitContainer.Orientation -eq 'Vertical') { + $form.ClientSize.Width + } else { + $form.ClientSize.Height + } + } + + # Panel2MinSize anpassen falls nötig + $proposedPanel2 = $this.DesiredPanel2MinSize + $maxAllowed = $availableSize - $this.DesiredPanel1MinSize - $this.SplitContainer.SplitterWidth + + if ($maxAllowed -lt 25) { + # Nicht genug Platz - verwende Minimalkonfiguration + $this.SplitContainer.Panel1MinSize = 25 + $this.SplitContainer.Panel2MinSize = 25 + $this.SplitContainer.SplitterDistance = 25 + Write-Warning "Nicht genug Platz für gewünschte MinSizes. Verwende Minimalwerte." + return + } + + if ($proposedPanel2 -gt $maxAllowed) { + $proposedPanel2 = [Math]::Max(25, [int]($maxAllowed / 2)) + } + + # Panel1MinSize setzen + $this.SplitContainer.Panel1MinSize = $this.DesiredPanel1MinSize + + # Panel2MinSize setzen + $this.SplitContainer.Panel2MinSize = $proposedPanel2 + + # SplitterDistance berechnen und setzen + if ($this.SplitterDistanceRatio -gt 0) { + # Wenn Ratio angegeben wurde + $desired = [int]($availableSize * $this.SplitterDistanceRatio) + } else { + # Wenn fester Wert angegeben wurde + $desired = $this.DesiredSplitterDistance + } + + # Validierung mit strengeren Grenzen + $minAllowed = $this.SplitContainer.Panel1MinSize + $maxAllowed = $availableSize - $this.SplitContainer.Panel2MinSize - $this.SplitContainer.SplitterWidth + + if ($desired -lt $minAllowed) { + $desired = $minAllowed + } + if ($desired -gt $maxAllowed) { + $desired = $maxAllowed + } + + # Finale Sicherheitsprüfung + if ($desired -lt 1) { + $desired = $minAllowed + } + + $this.SplitContainer.SplitterDistance = $desired + } + + # Setzt Padding für Panel mit Berücksichtigung der Menühöhe + [void] SetPanelPaddingWithMenu([int]$panelNumber, [System.Windows.Forms.MenuStrip]$menu, [int]$left, [int]$right, [int]$bottom) { + try { + $menuHeight = $menu.Height + $topPadding = $menuHeight + 6 + } catch { + $topPadding = 36 # Fallback + } + + $padding = New-Object System.Windows.Forms.Padding($left, $topPadding, $right, $bottom) + + if ($panelNumber -eq 1) { + $this.SplitContainer.Panel1.Padding = $padding + } else { + $this.SplitContainer.Panel2.Padding = $padding + } + } + + [System.Windows.Forms.SplitterPanel] GetPanel1() { + return $this.SplitContainer.Panel1 + } + + # KORREKTUR: Gibt $this zurück für Fluent API + [SplitPanelBuilder] AddToPanel1([System.Windows.Forms.Control]$control) { + $this.SplitContainer.Panel1.Controls.Add($control) + return $this + } + + # KORREKTUR: Gibt $this zurück für Fluent API + [SplitPanelBuilder] ClearPanel1() { + $this.SplitContainer.Panel1.Controls.Clear() + return $this + } + + [System.Windows.Forms.SplitterPanel] GetPanel2() { + return $this.SplitContainer.Panel2 + } + + # KORREKTUR: Gibt $this zurück für Fluent API + [SplitPanelBuilder] AddToPanel2([System.Windows.Forms.Control]$control) { + $this.SplitContainer.Panel2.Controls.Add($control) + return $this + } + + # KORREKTUR: Gibt $this zurück für Fluent API + [SplitPanelBuilder] ClearPanel2() { + $this.SplitContainer.Panel2.Controls.Clear() + return $this + } + + # Berechnet die optimale SplitterDistance basierend auf den Controls in Panel1 + [void] AutoSizeSplitterToPanel1Content() { + $maxSize = 0 + + foreach ($control in $this.SplitContainer.Panel1.Controls) { + if ($this.SplitContainer.Orientation -eq 'Vertical') { + # Bei vertikaler Orientierung: Breite berechnen + $requiredSize = $control.Right + $control.Margin.Right + } else { + # Bei horizontaler Orientierung: Höhe berechnen + $requiredSize = $control.Bottom + $control.Margin.Bottom + } + + if ($requiredSize -gt $maxSize) { + $maxSize = $requiredSize + } + } + + # Padding von Panel1 berücksichtigen + if ($this.SplitContainer.Orientation -eq 'Vertical') { + $maxSize += $this.SplitContainer.Panel1.Padding.Right + } else { + $maxSize += $this.SplitContainer.Panel1.Padding.Bottom + } + + # Sicherheitsabstand hinzufügen + $maxSize += 10 + + # Mit Constraints validieren und setzen + $this.SetSplitterDistance($maxSize) + } + + # Setzt die Splitter-Position mit Validierung + [SplitPanelBuilder] SetSplitterDistance([int]$distance) { + $minDistance = $this.SplitContainer.Panel1MinSize + $maxDistance = $this.SplitContainer.Width - $this.SplitContainer.Panel2MinSize + + if ($distance -lt $minDistance) { + $this.SplitContainer.SplitterDistance = $minDistance + } + elseif ($distance -gt $maxDistance) { + $this.SplitContainer.SplitterDistance = $maxDistance + } + else { + $this.SplitContainer.SplitterDistance = $distance + } + return $this + } + + # Fixiert den Splitter + [SplitPanelBuilder] SetFixedPanel([System.String]$panel) { + switch ($panel) { + 'Panel1' { $this.SplitContainer.FixedPanel = 'Panel1' } + 'Panel2' { $this.SplitContainer.FixedPanel = 'Panel2' } + default { $this.SplitContainer.FixedPanel = 'None' } + } + return $this + } + + # Verhindert das Verschieben des Splitters + [SplitPanelBuilder] SetIsSplitterFixed([bool]$fixed) { + $this.SplitContainer.IsSplitterFixed = $fixed + return $this + } + + # Setzt die Splitter-Breite + [SplitPanelBuilder] SetSplitterWidth([int]$width) { + $this.SplitContainer.SplitterWidth = $width + return $this + } + + # Gibt das SplitContainer-Objekt zurück + [System.Windows.Forms.SplitContainer] GetSplitContainer() { + return $this.SplitContainer + } + } + + class FlowLayoutPanelBuilder { + [System.Windows.Forms.FlowLayoutPanel]$control + + FlowLayoutPanelBuilder() { + $this.control = New-Object System.Windows.Forms.FlowLayoutPanel + # Standard-Einstellungen + $this.control.Dock = 'Fill' + $this.control.AutoScroll = $true + $this.control.FlowDirection = 'TopDown' + $this.control.WrapContents = $false + } + + [FlowLayoutPanelBuilder] SetDock([System.String]$dock) { + $this.control.Dock = [System.Windows.Forms.DockStyle]::$dock + return $this + } + + [FlowLayoutPanelBuilder] SetAutoScroll([bool]$autoScroll) { + $this.control.AutoScroll = $autoScroll + return $this + } + + [FlowLayoutPanelBuilder] SetFlowDirection([System.String]$direction) { + $this.control.FlowDirection = [System.Windows.Forms.FlowDirection]::$direction + return $this + } + + [FlowLayoutPanelBuilder] SetWrapContents([bool]$wrap) { + $this.control.WrapContents = $wrap + return $this + } + + [FlowLayoutPanelBuilder] SetPadding([int]$all) { + $this.control.Padding = New-Object System.Windows.Forms.Padding($all) + return $this + } + + [FlowLayoutPanelBuilder] SetPadding([int]$left, [int]$top, [int]$right, [int]$bottom) { + $this.control.Padding = New-Object System.Windows.Forms.Padding($left, $top, $right, $bottom) + return $this + } + + [FlowLayoutPanelBuilder] SetBackColor([System.Drawing.Color]$color) { + $this.control.BackColor = $color + return $this + } + + [FlowLayoutPanelBuilder] SetWidth([int]$width) { + $this.control.Width = $width + return $this + } + + [FlowLayoutPanelBuilder] SetHeight([int]$height) { + $this.control.Height = $height + return $this + } + + [FlowLayoutPanelBuilder] SetSize([int]$width, [int]$height) { + $this.control.Width = $width + $this.control.Height = $height + return $this + } + + [FlowLayoutPanelBuilder] SetBorderStyle([System.String]$style) { + $this.control.BorderStyle = [System.Windows.Forms.BorderStyle]::$style + return $this + } + + [FlowLayoutPanelBuilder] AddControl([System.Windows.Forms.Control]$childControl) { + $this.control.Controls.Add($childControl) + return $this + } + + [FlowLayoutPanelBuilder] AddControls([System.Windows.Forms.Control[]]$controls) { + foreach ($ctrl in $controls) { + $this.control.Controls.Add($ctrl) + } + return $this + } + + [FlowLayoutPanelBuilder] ClearControls() { + $this.control.Controls.Clear() + return $this + } + + [System.Windows.Forms.FlowLayoutPanel] AddTo([System.Windows.Forms.Control]$parent) { + $parent.Controls.Add($this.control) + return $this.control + } + + [System.Windows.Forms.FlowLayoutPanel] Build() { + return $this.control + } + } + + class ButtonBuilder : BaseComponent { + [System.Windows.Forms.Button] $Button + [scriptblock] $EnableCondition = $null + [StateManager] $StateManager = $null + + ButtonBuilder() { + $this.Button = New-Object System.Windows.Forms.Button + $this.Button.Font = New-Object System.Drawing.Font("Segoe UI", $this.GetSetting("FontSize")) + $this.SetSize(40, 40) + $this.Button.Margin = New-Object System.Windows.Forms.Padding(5) + } + + ButtonBuilder([System.String]$text) { + $this.Button = New-Object System.Windows.Forms.Button + $this.Button.Text = $text + $this.Button.Font = New-Object System.Drawing.Font("Segoe UI", $this.GetSetting("FontSize")) + $this.SetSize(40, 40) + } + + # Konstruktor mit Text und Höhe + ButtonBuilder([System.String]$text, [int]$height) { + $this.Button = New-Object System.Windows.Forms.Button + $this.Button.Text = $text + $this.Button.Height = $height + $this.Button.Font = New-Object System.Drawing.Font("Segoe UI", $this.GetSetting("FontSize")) + } + + # Setzt den Text + [ButtonBuilder] SetText([System.String]$text) { + $this.Button.Text = $text + return $this + } + + # Setzt die Höhe + [ButtonBuilder] SetHeight([int]$height) { + $this.Button.Height = $height + return $this + } + + # Setzt die Breite + [ButtonBuilder] SetWidth([int]$width) { + $this.Button.Width = $width + return $this + } + + # Setzt die Größe + [ButtonBuilder] SetSize([int]$width, [int]$height) { + $this.Button.Size = New-Object System.Drawing.Size($width, $height) + return $this + } + + # Setzt das Dock + [ButtonBuilder] SetDock([System.String]$dock) { + $this.Button.Dock = $dock + return $this + } + + # Setzt die Position + [ButtonBuilder] SetLocation([int]$x, [int]$y) { + $this.Button.Location = New-Object System.Drawing.Point($x, $y) + return $this + } + + # Setzt die Schriftart + [ButtonBuilder] SetFont([System.String]$fontFamily, [int]$fontSize) { + $this.Button.Font = New-Object System.Drawing.Font($fontFamily, $fontSize) + return $this + } + + # Setzt die Schriftart mit Stil + [ButtonBuilder] SetFont([System.String]$fontFamily, [int]$fontSize, [System.Drawing.FontStyle]$fontStyle) { + $this.Button.Font = New-Object System.Drawing.Font($fontFamily, $fontSize, $fontStyle) + return $this + } + + # Setzt die Hintergrundfarbe + [ButtonBuilder] SetBackColor([System.Drawing.Color]$color) { + $this.Button.BackColor = $color + return $this + } + + # Setzt die Textfarbe + [ButtonBuilder] SetForeColor([System.Drawing.Color]$color) { + $this.Button.ForeColor = $color + return $this + } + + # Setzt den Enabled-Status + [ButtonBuilder] SetEnabled([bool]$enabled) { + $this.Button.Enabled = $enabled + return $this + } + + # Setzt die Sichtbarkeit + [ButtonBuilder] SetVisible([bool]$visible) { + $this.Button.Visible = $visible + return $this + } + + # Setzt das FlatStyle + [ButtonBuilder] SetFlatStyle([System.String]$style) { + switch ($style) { + 'Flat' { $this.Button.FlatStyle = 'Flat' } + 'Popup' { $this.Button.FlatStyle = 'Popup' } + 'Standard' { $this.Button.FlatStyle = 'Standard' } + 'System' { $this.Button.FlatStyle = 'System' } + } + return $this + } + + # Setzt ein MDL2-Icon + [ButtonBuilder] SetMDL2Icon([System.String]$unicodeChar) { + $this.Button.Font = New-Object System.Drawing.Font("Segoe MDL2 Assets", 14) + $this.Button.Text = $unicodeChar + return $this + } + + # Setzt ein Fluent-Icon + [ButtonBuilder] SetFluentIcon([System.String]$unicodeChar) { + $this.Button.Font = New-Object System.Drawing.Font("Segoe Fluent Icons", 14) + $this.Button.Text = $unicodeChar + return $this + } + + # NEU: Bindet Button an StateManager (ohne Bedingung) + [ButtonBuilder] WithStateManager([StateManager]$stateManager) { + $this.StateManager = $stateManager + return $this + } + + # NEU: Kombinierte Methode - Bedingung + StateManager + [ButtonBuilder] BindEnabled([scriptblock]$condition, [StateManager]$stateManager) { + $this.EnableCondition = $condition + $this.StateManager = $stateManager + $this.UpdateEnabledState() + return $this + } + + # Original-Methode - nur Bedingung, kein StateManager + [ButtonBuilder] BindEnabled([scriptblock]$condition) { + $this.EnableCondition = $condition + $this.UpdateEnabledState() + return $this + } + + # Aktualisiert den Enabled-Status basierend auf der Bedingung + [void] UpdateEnabledState() { + if ($null -ne $this.EnableCondition) { + try { + $result = & $this.EnableCondition + $this.Button.Enabled = [bool]$result + } catch { + Write-Warning "Fehler beim Evaluieren der Enable-Bedingung: $_" + $this.Button.Enabled = $false + } + } + } + + # Setzt den Click-Handler + [ButtonBuilder] SetClickHandler([scriptblock]$handler) { + $this.Button.Add_Click($handler) + return $this + } + + # Setzt einen Tooltip + [ButtonBuilder] SetToolTip([System.String]$text) { + $tooltip = New-Object System.Windows.Forms.ToolTip + $tooltip.SetToolTip($this.Button, $text) + return $this + } + + # Setzt das Anchor + [ButtonBuilder] SetAnchor([System.Windows.Forms.AnchorStyles]$anchor) { + $this.Button.Anchor = $anchor + return $this + } + + # Gibt das Button-Objekt zurück + [System.Windows.Forms.Button] GetButton() { + return $this.Button + } + + # Fügt den Button zu einem Control hinzu + [void] AddTo([System.Windows.Forms.Control]$parent) { + $parent.Controls.Add($this.Button) + } + + # NEU: Build mit Auto-Registrierung beim StateManager + [System.Windows.Forms.Button] Build() { + # Auto-Registrierung wenn StateManager gesetzt + if ($null -ne $this.StateManager -and $null -ne $this.EnableCondition) { + $this.StateManager.RegisterButton($this) + } + return $this.Button + } + } + + class LabelBuilder : BaseComponent { + [System.Windows.Forms.Label] $Label + + LabelBuilder([System.String]$text) { + $this.Label = New-Object System.Windows.Forms.Label + $this.Label.Text = $text + $this.Label.Font = New-Object System.Drawing.Font("Segoe UI", $this.GetSetting("FontSize")) + } + + # Konstruktor mit Text und Höhe + LabelBuilder([System.String]$text, [int]$height) { + $this.Label = New-Object System.Windows.Forms.Label + $this.Label.Text = $text + $this.Label.Height = $height + $this.Label.Font = New-Object System.Drawing.Font("Segoe UI", $this.GetSetting("FontSize")) + } + + # Setzt den Text + [LabelBuilder] SetText([System.String]$text) { + $this.Label.Text = $text + return $this + } + + # Setzt die Höhe + [LabelBuilder] SetHeight([int]$height) { + $this.Label.Height = $height + return $this + } + + # Setzt die Breite + [LabelBuilder] SetWidth([int]$width) { + $this.Label.Width = $width + return $this + } + + # Setzt die Größe + [LabelBuilder] SetSize([int]$width, [int]$height) { + $this.Label.Size = New-Object System.Drawing.Size($width, $height) + return $this + } + + # Setzt das Dock + [LabelBuilder] SetDock([System.String]$dock) { + $this.Label.Dock = $dock + return $this + } + + # Setzt die Position + [LabelBuilder] SetLocation([int]$x, [int]$y) { + $this.Label.Location = New-Object System.Drawing.Point($x, $y) + return $this + } + + # Setzt die Schriftart + [LabelBuilder] SetFont([System.String]$fontFamily, [int]$fontSize) { + $this.Label.Font = New-Object System.Drawing.Font($fontFamily, $fontSize) + return $this + } + + # Setzt die Schriftart mit Stil + [LabelBuilder] SetFont([System.String]$fontFamily, [int]$fontSize, [System.Drawing.FontStyle]$fontStyle) { + $this.Label.Font = New-Object System.Drawing.Font($fontFamily, $fontSize, $fontStyle) + return $this + } + + # Setzt die Hintergrundfarbe + [LabelBuilder] SetBackColor([System.Drawing.Color]$color) { + $this.Label.BackColor = $color + return $this + } + + # Setzt die Textfarbe + [LabelBuilder] SetForeColor([System.Drawing.Color]$color) { + $this.Label.ForeColor = $color + return $this + } + + # Setzt die Textausrichtung + [LabelBuilder] SetTextAlign([System.Drawing.ContentAlignment]$alignment) { + $this.Label.TextAlign = $alignment + return $this + } + + # Setzt AutoSize + [LabelBuilder] SetAutoSize([bool]$autoSize) { + $this.Label.AutoSize = $autoSize + return $this + } + + # Setzt den Enabled-Status + [LabelBuilder] SetEnabled([bool]$enabled) { + $this.Label.Enabled = $enabled + return $this + } + + # Setzt die Sichtbarkeit + [LabelBuilder] SetVisible([bool]$visible) { + $this.Label.Visible = $visible + return $this + } + + # Setzt BorderStyle + [LabelBuilder] SetBorderStyle([System.String]$style) { + switch ($style) { + 'None' { $this.Label.BorderStyle = 'None' } + 'FixedSingle' { $this.Label.BorderStyle = 'FixedSingle' } + 'Fixed3D' { $this.Label.BorderStyle = 'Fixed3D' } + } + return $this + } + + # Setzt das Padding + [LabelBuilder] SetPadding([int]$left, [int]$top, [int]$right, [int]$bottom) { + $this.Label.Padding = New-Object System.Windows.Forms.Padding($left, $top, $right, $bottom) + return $this + } + + # Setzt das Margin + [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 + } + + # Setzt das Padding (alle Seiten gleich) + [LabelBuilder] SetPadding([int]$all) { + $this.Label.Padding = New-Object System.Windows.Forms.Padding($all) + return $this + } + + # Setzt das Anchor + [LabelBuilder] SetAnchor([System.Windows.Forms.AnchorStyles]$anchor) { + $this.Label.Anchor = $anchor + return $this + } + + # Setzt einen Tooltip + [LabelBuilder] SetToolTip([System.String]$text) { + $tooltip = New-Object System.Windows.Forms.ToolTip + $tooltip.SetToolTip($this.Label, $text) + return $this + } + + # Setzt das UseMnemonic (für & Zeichen) + [LabelBuilder] SetUseMnemonic([bool]$useMnemonic) { + $this.Label.UseMnemonic = $useMnemonic + return $this + } + + # Gibt das Label-Objekt zurück + [System.Windows.Forms.Label] GetLabel() { + return $this.Label + } + + # Fügt das Label zu einem Control hinzu + [void] AddTo([System.Windows.Forms.Control]$parent) { + $parent.Controls.Add($this.Label) + } + + [System.Windows.Forms.Label] Build() { + return $this.Label + } + } + + class TreeViewBuilder : BaseComponent { + [System.Windows.Forms.TreeView]$control + + TreeViewBuilder() { + $this.control = New-Object System.Windows.Forms.TreeView + # Standard-Einstellungen + $this.control.Dock = 'Fill' + $this.control.HideSelection = $false + $this.control.ShowPlusMinus = $true + $this.control.ShowRootLines = $true + $this.control.FullRowSelect = $true + } + + [TreeViewBuilder] SetDock([System.String]$dock) { + $this.control.Dock = [System.Windows.Forms.DockStyle]::$dock + return $this + } + + [TreeViewBuilder] SetFont([System.String]$family, [float]$size) { + $this.control.Font = New-Object System.Drawing.Font($family, $size) + return $this + } + + [TreeViewBuilder] SetFont([System.String]$family, [float]$size, [System.Drawing.FontStyle]$style) { + $this.control.Font = New-Object System.Drawing.Font($family, $size, $style) + return $this + } + + [TreeViewBuilder] SetHideSelection([bool]$hide) { + $this.control.HideSelection = $hide + return $this + } + + [TreeViewBuilder] SetShowPlusMinus([bool]$show) { + $this.control.ShowPlusMinus = $show + return $this + } + + [TreeViewBuilder] SetShowRootLines([bool]$show) { + $this.control.ShowRootLines = $show + return $this + } + + [TreeViewBuilder] SetFullRowSelect([bool]$select) { + $this.control.FullRowSelect = $select + return $this + } + + [TreeViewBuilder] SetShowLines([bool]$show) { + $this.control.ShowLines = $show + return $this + } + + [TreeViewBuilder] SetCheckBoxes([bool]$show) { + $this.control.CheckBoxes = $show + return $this + } + + [TreeViewBuilder] SetImageList([System.Windows.Forms.ImageList]$imageList) { + $this.control.ImageList = $imageList + return $this + } + + [TreeViewBuilder] SetBackColor([System.Drawing.Color]$color) { + $this.control.BackColor = $color + return $this + } + + [TreeViewBuilder] SetForeColor([System.Drawing.Color]$color) { + $this.control.ForeColor = $color + return $this + } + + [TreeViewBuilder] AddNode([System.String]$name) { + $this.control.Nodes.Add([System.Windows.Forms.TreeNode]::new($name)) | Out-Null + return $this + } + + [TreeViewBuilder] AddNode([System.Windows.Forms.TreeNode]$node) { + $this.control.Nodes.Add($node) | Out-Null + return $this + } + + [TreeViewBuilder] AddNodes([System.Windows.Forms.TreeNode[]]$nodes) { + foreach ($node in $nodes) { + $this.control.Nodes.Add($node) | Out-Null + } + return $this + } + + # NEU: Lädt Hashtable/Template in TreeView + [TreeViewBuilder] LoadTemplate([object]$template, [System.String]$rootName = "Template") { + $this.control.Nodes.Clear() + $rootNode = $this.control.Nodes.Add($rootName) + + try { + if ($template -is [System.Collections.IDictionary]) { + # WICHTIG: Keine Sortierung, Keys in Original-Reihenfolge + foreach ($key in $template.Keys) { + $this.AddTreeNodeRecursive($rootNode, $template[$key], $key, "") + } + } else { + $this.AddTreeNodeRecursive($rootNode, $template, $rootName, "") + } + } catch { + $rootNode.Nodes.Add("Fehler beim Lesen: $($_.Exception.Message)") | Out-Null + } + + $rootNode.ExpandAll() + return $this + } + + # NEU: Rekursive Methode zum Hinzufügen von Nodes + hidden [void] AddTreeNodeRecursive([System.Windows.Forms.TreeNode]$parentNode, [object]$value, [System.String]$name, [System.String]$path) { + $currentPath = if ($path -eq "") { $name } else { "$path.$name" } + + if ($value -is [System.Collections.IDictionary]) { + $node = $parentNode.Nodes.Add($name, $name) + $node.Tag = @{ Type = "Hashtable"; Path = $currentPath; Value = $value } + + # WICHTIG: KEIN Sort-Object! Behält die Original-Reihenfolge bei + foreach ($k in $value.Keys) { + $this.AddTreeNodeRecursive($node, $value[$k], $k, $currentPath) + } + } + elseif ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) { + $node = $parentNode.Nodes.Add($name, $name) + $node.Tag = @{ Type = "Array"; Path = $currentPath; Value = $value } + $i = 0 + foreach ($item in $value) { + $this.AddTreeNodeRecursive($node, $item, "[$i]", $currentPath) + $i++ + } + } + else { + $displayText = "$name = $value" + $node = $parentNode.Nodes.Add($name, $displayText) + $node.Tag = @{ Type = "Value"; Path = $currentPath; Key = $name; Value = $value } + } + } + + # NEU: Löscht alle Nodes + [TreeViewBuilder] ClearNodes() { + $this.control.Nodes.Clear() + return $this + } + + [TreeViewBuilder] AddEventHandler([System.String]$eventName, [scriptblock]$handler) { + $this.control."Add_$eventName"($handler) + return $this + } + + [System.Windows.Forms.TreeView] AddTo([System.Windows.Forms.Control]$parent) { + $parent.Controls.Add($this.control) + return $this.control + } + + [System.Windows.Forms.TreeView] Build() { + return $this.control + } + } + + class DataGridViewBuilder : BaseComponent{ + [System.Windows.Forms.DataGridView] $control + + DataGridViewBuilder() { + $this.control = New-Object System.Windows.Forms.DataGridView + $this.control.Dock = "Fill" + $this.control.ColumnHeadersHeight = 30 + $this.control.AutoSizeColumnsMode = "Fill" + } + + [DataGridViewBuilder] SetAllowAddRows([Boolean] $enabled) { + $this.control.AllowUserToAddRows = $enabled + return $this + } + + [DataGridViewBuilder] SetHeaderColumns([System.Array] $Columns) { + foreach($Column in $Columns){ + $this.control.Columns.Add($Column,$Column) + } + return $this + } + + [DataGridViewBuilder] SetHeaderColumnReadOnly([System.String] $Name) { + $this.control.Columns[$Name].ReadOnly = $true + return $this + } + + [System.Windows.Forms.DataGridView] Build() { + return $this.control + } + } + + class GroupBoxBuilder : BaseComponent { + [System.Windows.Forms.GroupBox]$control + [bool]$autoSizeToContent = $false + + GroupBoxBuilder([System.String]$text) { + $this.control = New-Object System.Windows.Forms.GroupBox + $this.control.Text = $text + $this.control.Font = New-Object System.Drawing.Font("Segoe UI", $this.GetSetting("FontSize"),[System.Drawing.FontStyle]::Bold) + #$this.control.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::GrowOnly + } + + [GroupBoxBuilder] SetText([System.String]$text) { + $this.control.Text = $text + return $this + } + + [GroupBoxBuilder] SetWidth([int]$width) { + $this.control.Width = $width + return $this + } + + [GroupBoxBuilder] SetHeight([int]$height) { + $this.control.Height = $height + return $this + } + + [GroupBoxBuilder] SetSize([int]$width, [int]$height) { + $this.control.Width = $width + $this.control.Height = $height + return $this + } + + [GroupBoxBuilder] SetFont([System.String]$family, [float]$size) { + $this.control.Font = New-Object System.Drawing.Font($family, $size) + return $this + } + + [GroupBoxBuilder] SetFont([System.String]$family, [float]$size, [System.Drawing.FontStyle]$style) { + $this.control.Font = New-Object System.Drawing.Font($family, $size, $style) + return $this + } + + [GroupBoxBuilder] SetMargin([int]$all) { + $this.control.Margin = New-Object System.Windows.Forms.Padding($all) + return $this + } + + [GroupBoxBuilder] SetMargin([int]$left, [int]$top, [int]$right, [int]$bottom) { + $this.control.Margin = New-Object System.Windows.Forms.Padding($left, $top, $right, $bottom) + return $this + } + + [GroupBoxBuilder] SetPadding([int]$all) { + $this.control.Padding = New-Object System.Windows.Forms.Padding($all) + return $this + } + + [GroupBoxBuilder] SetPadding([int]$left, [int]$top, [int]$right, [int]$bottom) { + $this.control.Padding = New-Object System.Windows.Forms.Padding($left, $top, $right, $bottom) + return $this + } + + [GroupBoxBuilder] SetDock([System.String]$dock) { + $this.control.Dock = [System.Windows.Forms.DockStyle]::$dock + return $this + } + + [GroupBoxBuilder] SetBackColor([System.Drawing.Color]$color) { + $this.control.BackColor = $color + return $this + } + + [GroupBoxBuilder] SetForeColor([System.Drawing.Color]$color) { + $this.control.ForeColor = $color + return $this + } + + [GroupBoxBuilder] SetAutoSize([bool]$autoSize) { + $this.control.AutoSize = $autoSize + return $this + } + + [GroupBoxBuilder] SetAutoSizeMode([System.String]$autoSizeMode) { + $this.control.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::$autoSizeMode + return $this + } + + [GroupBoxBuilder] AddControl([System.Windows.Forms.Control]$childControl) { + $this.control.Controls.Add($childControl) + return $this + } + + [GroupBoxBuilder] AddControls([System.Windows.Forms.Control[]]$controls) { + foreach ($ctrl in $controls) { + $this.control.Controls.Add($ctrl) + } + return $this + } + + [System.Windows.Forms.GroupBox] AddTo([System.Windows.Forms.Control]$parent) { + $parent.Controls.Add($this.control) + return $this.control + } + + [System.Windows.Forms.GroupBox] Build() { + return $this.control + } + } + + class ListBoxBuilder : BaseComponent { + [System.Windows.Forms.ListBox]$control + + ListBoxBuilder() { + $this.control = New-Object System.Windows.Forms.ListBox + # Standard-Einstellungen + $this.control.DisplayMember = 'BaseName' + $this.control.ValueMember = 'FullName' + $this.control.Dock = 'Fill' + $this.control.Font = New-Object System.Drawing.Font("Segoe UI", $this.GetSetting("FontSize")) + } + + [ListBoxBuilder] SetDock([System.String]$dock) { + $this.control.Dock = [System.Windows.Forms.DockStyle]::$dock + return $this + } + + [ListBoxBuilder] SetFont([System.String]$family, [float]$size) { + $this.control.Font = New-Object System.Drawing.Font($family, $size) + return $this + } + + [ListBoxBuilder] SetFont([System.String]$family, [float]$size, [System.Drawing.FontStyle]$style) { + $this.control.Font = New-Object System.Drawing.Font($family, $size, $style) + return $this + } + + [ListBoxBuilder] SetSelectionMode([System.String]$mode) { + $this.control.SelectionMode = [System.Windows.Forms.SelectionMode]::$mode + return $this + } + + [ListBoxBuilder] SetBackColor([System.Drawing.Color]$color) { + $this.control.BackColor = $color + return $this + } + + [ListBoxBuilder] SetForeColor([System.Drawing.Color]$color) { + $this.control.ForeColor = $color + return $this + } + + [ListBoxBuilder] SetBorderStyle([System.String]$style) { + $this.control.BorderStyle = [System.Windows.Forms.BorderStyle]::$style + return $this + } + + [ListBoxBuilder] SetHorizontalScrollbar([bool]$show) { + $this.control.HorizontalScrollbar = $show + return $this + } + + [ListBoxBuilder] SetIntegralHeight([bool]$integral) { + $this.control.IntegralHeight = $integral + return $this + } + + [ListBoxBuilder] SetSorted([bool]$sorted) { + $this.control.Sorted = $sorted + return $this + } + + [ListBoxBuilder] SetHeight([Int]$height) { + $this.control.Height = $height + return $this + } + + [ListBoxBuilder] AddItem([object]$item) { + $this.control.Items.Add($item) | Out-Null + return $this + } + + [ListBoxBuilder] AddItems([object[]]$items) { + foreach ($item in $items) { + $this.control.Items.Add($item) | Out-Null + } + return $this + } + + [ListBoxBuilder] ClearItems() { + $this.control.Items.Clear() + return $this + } + + [ListBoxBuilder] SetDataSource([object]$dataSource) { + $this.control.DataSource = $dataSource + return $this + } + + [ListBoxBuilder] SetDisplayMember([System.String]$member) { + $this.control.DisplayMember = $member + return $this + } + + [ListBoxBuilder] SetValueMember([System.String]$member) { + $this.control.ValueMember = $member + return $this + } + + [ListBoxBuilder] AddSelectedIndexChangedHandler([scriptblock]$handler) { + $this.control.Add_SelectedIndexChanged($handler) + return $this + } + + [ListBoxBuilder] AddMouseDoubleClickHandler([scriptblock]$handler) { + $this.control.Add_MouseDoubleClick($handler) + return $this + } + + [ListBoxBuilder] AddEventHandler([System.String]$eventName, [scriptblock]$handler) { + $this.control."Add_$eventName"($handler) + return $this + } + + [System.Windows.Forms.ListBox] AddTo([System.Windows.Forms.Control]$parent) { + $parent.Controls.Add($this.control) + return $this.control + } + + [System.Windows.Forms.ListBox] Build() { + return $this.control + } + } + + class PanelBuilder : BaseComponent{ + [System.Windows.Forms.Panel]$control + + [scriptblock] $EnableCondition = $null + [StateManager] $StateManager = $null + + + PanelBuilder() { + $this.control = New-Object System.Windows.Forms.Panel + } + + [PanelBuilder] SetDock([System.String]$dock) { + $this.control.Dock = [System.Windows.Forms.DockStyle]::$dock + return $this + } + + [PanelBuilder] SetWidth([int]$width) { + $this.control.Width = $width + return $this + } + + [PanelBuilder] SetHeight([int]$height) { + $this.control.Height = $height + return $this + } + + [PanelBuilder] SetSize([int]$width, [int]$height) { + $this.control.Size = New-Object System.Drawing.Size($width, $height) + return $this + } + + [PanelBuilder] SetLocation([int]$x, [int]$y) { + $this.control.Location = New-Object System.Drawing.Point($x, $y) + return $this + } + + [PanelBuilder] SetBackColor([System.Drawing.Color]$color) { + $this.control.BackColor = $color + return $this + } + + [PanelBuilder] SetForeColor([System.Drawing.Color]$color) { + $this.control.ForeColor = $color + return $this + } + + [PanelBuilder] SetBorderStyle([System.String]$style) { + $this.control.BorderStyle = [System.Windows.Forms.BorderStyle]::$style + return $this + } + + [PanelBuilder] SetAutoScroll([bool]$autoScroll) { + $this.control.AutoScroll = $autoScroll + return $this + } + + [PanelBuilder] SetPadding([int]$all) { + $this.control.Padding = New-Object System.Windows.Forms.Padding($all) + return $this + } + + [PanelBuilder] SetPadding([int]$left, [int]$top, [int]$right, [int]$bottom) { + $this.control.Padding = New-Object System.Windows.Forms.Padding($left, $top, $right, $bottom) + return $this + } + + [PanelBuilder] SetMargin([int]$all) { + $this.control.Margin = New-Object System.Windows.Forms.Padding($all) + return $this + } + + [PanelBuilder] SetMargin([int]$left, [int]$top, [int]$right, [int]$bottom) { + $this.control.Margin = New-Object System.Windows.Forms.Padding($left, $top, $right, $bottom) + return $this + } + + [PanelBuilder] SetAnchor([System.Windows.Forms.AnchorStyles]$anchor) { + $this.control.Anchor = $anchor + return $this + } + + [PanelBuilder] SetVisible([bool]$visible) { + $this.control.Visible = $visible + return $this + } + + [PanelBuilder] SetEnabled([bool]$enabled) { + $this.control.Enabled = $enabled + return $this + } + + [PanelBuilder] SetAutoSize([bool]$autoSize) { + $this.control.AutoSize = $autoSize + return $this + } + + [PanelBuilder] SetAutoSizeMode([System.String]$mode) { + $this.control.AutoSizeMode = [System.Windows.Forms.AutoSizeMode]::$mode + return $this + } + + [PanelBuilder] AddControl([System.Windows.Forms.Control]$childControl) { + $this.control.Controls.Add($childControl) + return $this + } + + [PanelBuilder] AddControls([System.Windows.Forms.Control[]]$controls) { + foreach ($ctrl in $controls) { + $this.control.Controls.Add($ctrl) + } + return $this + } + + [PanelBuilder] ClearControls() { + $this.control.Controls.Clear() + return $this + } + + [PanelBuilder] AddEventHandler([System.String]$eventName, [scriptblock]$handler) { + $this.control."Add_$eventName"($handler) + return $this + } + + [PanelBuilder] SetName([System.String]$name) { + $this.control.Name = $name + return $this + } + + [PanelBuilder] SetTag([object]$tag) { + $this.control.Tag = $tag + return $this + } + + [System.Windows.Forms.Panel] AddTo([System.Windows.Forms.Control]$parent) { + $parent.Controls.Add($this.control) + return $this.control + } + + [PanelBuilder] BindEnabled([scriptblock]$condition, [StateManager]$stateManager) { + $this.EnableCondition = $condition + $this.StateManager = $stateManager + $this.UpdateVisibleState() + return $this + } + + # Aktualisiert den Visible-Status basierend auf der Bedingung + [void] UpdateVisibleState() { + if ($null -ne $this.EnableCondition) { + try { + $result = & $this.EnableCondition + $this.control.Visible = [bool]$result + } catch { + Write-Warning "Fehler beim Evaluieren der Visible-Bedingung: $_" + $this.control.Visible = $false + } + } + } + + [System.Windows.Forms.Panel] Build() { + if ($null -ne $this.StateManager -and $null -ne $this.EnableCondition) { + $this.StateManager.RegisterForm($this) + } + return $this.control + } + + [System.Windows.Forms.Panel] GetPanel() { + return $this.control + } + } + + class DialogBuilder : BaseComponent { + [object] $Dialog # Kann FileDialog oder FolderBrowserDialog sein + [System.String] $DialogType + + # Privater Konstruktor + hidden DialogBuilder([System.String]$type) { + $this.DialogType = $type + + switch ($type) { + "Open" { + $this.Dialog = New-Object System.Windows.Forms.OpenFileDialog + $this.Dialog.Multiselect = $false + $this.Dialog.Filter = "Alle Dateien (*.*)|*.*" + $this.Dialog.FilterIndex = 1 + $this.Dialog.RestoreDirectory = $true + } + "Save" { + $this.Dialog = New-Object System.Windows.Forms.SaveFileDialog + $this.Dialog.OverwritePrompt = $true + $this.Dialog.Filter = "Alle Dateien (*.*)|*.*" + $this.Dialog.FilterIndex = 1 + $this.Dialog.RestoreDirectory = $true + } + "Folder" { + $this.Dialog = New-Object System.Windows.Forms.FolderBrowserDialog + $this.Dialog.ShowNewFolderButton = $true + } + } + } + + # === FACTORY-METHODEN === + + static [DialogBuilder] OpenFile() { + return [DialogBuilder]::new("Open") + } + + static [DialogBuilder] SaveFile() { + return [DialogBuilder]::new("Save") + } + + static [DialogBuilder] SelectFolder() { + return [DialogBuilder]::new("Folder") + } + + # === GEMEINSAME METHODEN (FileDialog) === + + # Setzt den Titel + [DialogBuilder] SetTitle([System.String]$title) { + if ($this.DialogType -eq "Folder") { + $this.Dialog.Description = $title + } else { + $this.Dialog.Title = $title + } + return $this + } + + # Setzt den Filter (nur File-Dialoge) + [DialogBuilder] SetFilter([System.String]$filter) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.Filter = $filter + } else { + Write-Warning "Filter ist nur für File-Dialoge verfügbar" + } + return $this + } + + # Setzt den aktiven Filter-Index (nur File-Dialoge) + [DialogBuilder] SetFilterIndex([int]$index) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.FilterIndex = $index + } + return $this + } + + # Setzt das Initial-Verzeichnis + [DialogBuilder] SetInitialDirectory([System.String]$path) { + if ($this.DialogType -eq "Folder") { + if (Test-Path $path) { + $this.Dialog.SelectedPath = $path + } else { + Write-Warning "Pfad existiert nicht: $path" + } + } else { + if (Test-Path $path) { + $this.Dialog.InitialDirectory = $path + } else { + Write-Warning "Pfad existiert nicht: $path" + } + } + return $this + } + + # Setzt den Standard-Dateinamen (nur File-Dialoge) + [DialogBuilder] SetFileName([System.String]$fileName) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.FileName = $fileName + } + return $this + } + + # Prüft ob Datei existieren muss (nur File-Dialoge) + [DialogBuilder] SetCheckFileExists([bool]$check) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.CheckFileExists = $check + } + return $this + } + + # Prüft ob Pfad existieren muss (nur File-Dialoge) + [DialogBuilder] SetCheckPathExists([bool]$check) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.CheckPathExists = $check + } + return $this + } + + # Wiederherstellt letztes Verzeichnis (nur File-Dialoge) + [DialogBuilder] SetRestoreDirectory([bool]$restore) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.RestoreDirectory = $restore + } + return $this + } + + # Validiert Namen gegen ungültige Zeichen (nur File-Dialoge) + [DialogBuilder] SetValidateNames([bool]$validate) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.ValidateNames = $validate + } + return $this + } + + # Dereferenziert Links/Shortcuts (nur File-Dialoge) + [DialogBuilder] SetDereferenceLinks([bool]$dereference) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.DereferenceLinks = $dereference + } + return $this + } + + # Setzt die Standard-Erweiterung (nur File-Dialoge) + [DialogBuilder] SetDefaultExt([System.String]$ext) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.DefaultExt = $ext.TrimStart('.') + } + return $this + } + + # Fügt automatisch Erweiterung hinzu (nur File-Dialoge) + [DialogBuilder] SetAddExtension([bool]$add) { + if ($this.DialogType -ne "Folder") { + $this.Dialog.AddExtension = $add + } + return $this + } + + # Setzt Custom Places (nur File-Dialoge) + [DialogBuilder] AddCustomPlace([System.String]$path) { + if ($this.DialogType -ne "Folder") { + if (Test-Path $path) { + $this.Dialog.CustomPlaces.Add($path) + } + } + return $this + } + + # Setzt mehrere Custom Places (nur File-Dialoge) + [DialogBuilder] AddCustomPlaces([System.String[]]$paths) { + if ($this.DialogType -ne "Folder") { + foreach ($path in $paths) { + if (Test-Path $path) { + $this.Dialog.CustomPlaces.Add($path) + } + } + } + return $this + } + + # === OPENFILEDIALOG-SPEZIFISCH === + + # Aktiviert/Deaktiviert Mehrfachauswahl + [DialogBuilder] SetMultiselect([bool]$multiselect) { + if ($this.DialogType -eq "Open") { + $this.Dialog.Multiselect = $multiselect + } else { + Write-Warning "Multiselect ist nur für OpenFileDialog verfügbar" + } + return $this + } + + # Zeigt Read-Only Checkbox + [DialogBuilder] SetShowReadOnly([bool]$show) { + if ($this.DialogType -eq "Open") { + $this.Dialog.ShowReadOnly = $show + } + return $this + } + + # Setzt Read-Only Status + [DialogBuilder] SetReadOnlyChecked([bool]$checked) { + if ($this.DialogType -eq "Open") { + $this.Dialog.ReadOnlyChecked = $checked + } + return $this + } + + # === SAVEFILEDIALOG-SPEZIFISCH === + + # Warnt bei Überschreiben + [DialogBuilder] SetOverwritePrompt([bool]$prompt) { + if ($this.DialogType -eq "Save") { + $this.Dialog.OverwritePrompt = $prompt + } + return $this + } + + # Warnt beim Erstellen neuer Dateien + [DialogBuilder] SetCreatePrompt([bool]$prompt) { + if ($this.DialogType -eq "Save") { + $this.Dialog.CreatePrompt = $prompt + } + return $this + } + + # === FOLDERBROWSERDIALOG-SPEZIFISCH === + + # Zeigt "Neuer Ordner"-Button + [DialogBuilder] SetShowNewFolderButton([bool]$show) { + if ($this.DialogType -eq "Folder") { + $this.Dialog.ShowNewFolderButton = $show + } + return $this + } + + # Setzt die Beschreibung (Folder-spezifisch) + [DialogBuilder] SetDescription([System.String]$description) { + if ($this.DialogType -eq "Folder") { + $this.Dialog.Description = $description + } + return $this + } + + # Setzt den Root-Folder (Folder-spezifisch) + [DialogBuilder] SetRootFolder([System.String]$specialFolder) { + if ($this.DialogType -eq "Folder") { + try { + $this.Dialog.RootFolder = [System.Environment+SpecialFolder]::$specialFolder + } catch { + Write-Warning "Ungültiger SpecialFolder: $specialFolder" + } + } + return $this + } + + # Setzt den ausgewählten Pfad (Folder-spezifisch) + [DialogBuilder] SetSelectedPath([System.String]$path) { + if ($this.DialogType -eq "Folder") { + if (Test-Path $path) { + $this.Dialog.SelectedPath = $path + } else { + Write-Warning "Pfad existiert nicht: $path" + } + } + return $this + } + + # Verwendet die neue Folder-Browser-Darstellung (Vista-Style) + [DialogBuilder] SetUseDescriptionForTitle([bool]$use) { + if ($this.DialogType -eq "Folder") { + $this.Dialog.UseDescriptionForTitle = $use + } + return $this + } + + # === VOREINGESTELLTE FILTER === + + # Schnell-Methode für häufige Filter (nur File-Dialoge) + [DialogBuilder] SetCommonFilter([System.String]$type) { + if ($this.DialogType -eq "Folder") { + Write-Warning "Filter sind für FolderBrowserDialog nicht verfügbar" + return $this + } + + switch ($type) { + 'Text' { + $this.Dialog.Filter = "Text-Dateien (*.txt)|*.txt|Alle Dateien (*.*)|*.*" + $this.Dialog.DefaultExt = "txt" + } + 'PowerShell' { + $this.Dialog.Filter = "PowerShell-Dateien (*.ps1;*.psm1;*.psd1)|*.ps1;*.psm1;*.psd1|Alle Dateien (*.*)|*.*" + $this.Dialog.DefaultExt = "ps1" + } + 'PSD1' { + $this.Dialog.Filter = "PowerShell Data-Dateien (*.psd1)|*.psd1|Alle Dateien (*.*)|*.*" + $this.Dialog.DefaultExt = "psd1" + } + 'JSON' { + $this.Dialog.Filter = "JSON-Dateien (*.json)|*.json|Alle Dateien (*.*)|*.*" + $this.Dialog.DefaultExt = "json" + } + 'XML' { + $this.Dialog.Filter = "XML-Dateien (*.xml)|*.xml|Alle Dateien (*.*)|*.*" + $this.Dialog.DefaultExt = "xml" + } + 'CSV' { + $this.Dialog.Filter = "CSV-Dateien (*.csv)|*.csv|Alle Dateien (*.*)|*.*" + $this.Dialog.DefaultExt = "csv" + } + 'Excel' { + $this.Dialog.Filter = "Excel-Dateien (*.xlsx;*.xls)|*.xlsx;*.xls|Alle Dateien (*.*)|*.*" + $this.Dialog.DefaultExt = "xlsx" + } + 'Image' { + $this.Dialog.Filter = "Bild-Dateien (*.png;*.jpg;*.jpeg;*.gif;*.bmp)|*.png;*.jpg;*.jpeg;*.gif;*.bmp|Alle Dateien (*.*)|*.*" + $this.Dialog.DefaultExt = "png" + } + 'All' { + $this.Dialog.Filter = "Alle Dateien (*.*)|*.*" + } + default { + Write-Warning "Unbekannter Filter-Typ: $type" + } + } + return $this + } + + # === SHOW UND BUILD === + + # Zeigt den Dialog und gibt das Ergebnis zurück + [System.Collections.Hashtable] Show() { + $result = @{ + Result = $false + FileName = $null + FileNames = @() + SafeFileName = $null + SafeFileNames = @() + SelectedPath = $null + } + + $dialogResult = $this.Dialog.ShowDialog() + + if ($dialogResult -eq 'OK') { + $result.Result = $true + + if ($this.DialogType -eq "Folder") { + $result.SelectedPath = $this.Dialog.SelectedPath + $result.FileName = $this.Dialog.SelectedPath + } else { + $result.FileName = $this.Dialog.FileName + $result.SafeFileName = $this.Dialog.SafeFileName + + if ($this.DialogType -eq "Open") { + $result.FileNames = $this.Dialog.FileNames + $result.SafeFileNames = $this.Dialog.SafeFileNames + } else { + $result.FileNames = @($this.Dialog.FileName) + $result.SafeFileNames = @($this.Dialog.SafeFileName) + } + } + } + + return $result + } + + # Zeigt den Dialog mit Custom Parent Window + [System.Collections.Hashtable] Show([System.Windows.Forms.IWin32Window]$owner) { + $result = @{ + Result = $false + FileName = $null + FileNames = @() + SafeFileName = $null + SafeFileNames = @() + SelectedPath = $null + } + + $dialogResult = $this.Dialog.ShowDialog($owner) + + if ($dialogResult -eq 'OK') { + $result.Result = $true + + if ($this.DialogType -eq "Folder") { + $result.SelectedPath = $this.Dialog.SelectedPath + $result.FileName = $this.Dialog.SelectedPath + } else { + $result.FileName = $this.Dialog.FileName + $result.SafeFileName = $this.Dialog.SafeFileName + + if ($this.DialogType -eq "Open") { + $result.FileNames = $this.Dialog.FileNames + $result.SafeFileNames = $this.Dialog.SafeFileNames + } else { + $result.FileNames = @($this.Dialog.FileName) + $result.SafeFileNames = @($this.Dialog.SafeFileName) + } + } + } + + return $result + } + + # Gibt das Dialog-Objekt zurück (für erweiterte Szenarien) + [object] Build() { + return $this.Dialog + } + + # Dispose des Dialogs + [void] Dispose() { + if ($null -ne $this.Dialog) { + $this.Dialog.Dispose() + } + } + } + + class TemplateBuilder : BaseComponent{ + + [System.Collections.Specialized.OrderedDictionary] $Template + + # Konstruktor: Pfad zu PSD1-Datei + TemplateBuilder([System.String]$TemplatePath) { + $this.Template = $this.ConvertFromPowerShellDataString($TemplatePath) + } + + # Konstruktor: Hashtable direkt übergeben + TemplateBuilder([hashtable]$TemplateHashtable) { + $this.Template = $this.ConvertHashtableToOrdered($TemplateHashtable) + } + + # Konstruktor: OrderedDictionary direkt übergeben + TemplateBuilder([System.Collections.Specialized.OrderedDictionary]$TemplateOrdered) { + $this.Template = $TemplateOrdered + } + + # Hilfsmethode: Konvertiert eine normale Hashtable in OrderedDictionary (rekursiv) + hidden [System.Collections.Specialized.OrderedDictionary] ConvertHashtableToOrdered([hashtable]$Hashtable) { + $ordered = New-Object System.Collections.Specialized.OrderedDictionary + + foreach ($key in $Hashtable.Keys) { + $value = $Hashtable[$key] + + # Rekursive Konvertierung für verschachtelte Hashtables + if ($value -is [hashtable]) { + $ordered[$key] = $this.ConvertHashtableToOrdered($value) + } + # Array mit Hashtables + elseif ($value -is [array]) { + $arrayResult = @() + foreach ($item in $value) { + if ($item -is [hashtable]) { + $arrayResult += $this.ConvertHashtableToOrdered($item) + } + else { + $arrayResult += $item + } + } + $ordered[$key] = $arrayResult + } + else { + $ordered[$key] = $value + } + } + + return $ordered + } + + # Concat mit einer dynamischen Anzahl von Argumenten + static [string] Concat([string[]]$input) { + return $input -join '' + } + + # ToLower-Methode + static [string] ToLower([string[]]$input) { + return $input[0].ToLower() + } + + # ToUpper-Methode + static [string] ToUpper([string[]]$input) { + return $input[0].ToUpper() + } + + # FirstIndexOf-Methode + static [string] FirstIndexOf([string[]] $input){ + return $input[0] + } + + # LastIndexOf-Methode + static [string] LastIndexOf([string[]] $input){ + return $input[-1] + } + + # IndexOf-Methode + static [string] IndexOf([string[]] $input){ + return $input[$input[-1]] + } + + # Substring-Methode + static [String] Substring([string[]] $input){ + if($input.Count -eq 2){ + $array = $input.Split(',') + return $array[0].Substring($array[1]) + + }elseif($input.Count -eq 3){ + $array = $input.Split(',') + return $array[0].Substring($array[1],$array[2]) + + }else{ + Write-Warning "Zuviele Parameter" + return $false + } + } + + # Replace-Methode + static [String] Replace([string[]] $input){ + $array = $input.Split(',') + return $array[0].Replace($array[1],$array[2]) + } + + hidden [System.Collections.Specialized.OrderedDictionary] ResolveDeploymentParameterDefaultValues(){ + $TemplateClone = $this.Template + + if($TemplateClone.Contains("Parameters")){ + foreach($Parameter in $this.Template.Parameters.GetEnumerator()){ + if(!$Parameter.Value.Contains("Value")){ + $TemplateClone.Parameters[$Parameter.Name].Value = $this.Template.Parameters[$Parameter.Name].DefaultValue + } + } + } + return $TemplateClone + } + + [System.Collections.Specialized.OrderedDictionary] ResolveDeploymentDataVariables(){ + + + $TemplateClone = $this.ResolveDeploymentParameterDefaultValues() + + # Erst Variables auflösen (können Parameter-Referenzen enthalten) + if ($TemplateClone.Variables) { + $resolvedVariables = New-Object System.Collections.Specialized.OrderedDictionary + # WICHTIG: Keys-Array erstellen, um Collection-Modification zu vermeiden + $varKeys = @($TemplateClone.Variables.Keys) + foreach ($varKey in $varKeys) { + $resolvedVariables[$varKey] = $this.ResolveValue($TemplateClone.Variables[$varKey],$TemplateClone.Parameters,$TemplateClone.Variables) + } + $TemplateClone.Variables = $resolvedVariables + } + + # Dann den Rest des Templates auflösen + # WICHTIG: Keys-Array erstellen, um Collection-Modification zu vermeiden + $templateKeys = @($TemplateClone.Keys) + foreach ($key in $templateKeys) { + if ($key -ne 'Variables' -and $key -ne 'Parameters') { + $TemplateClone[$key] = $this.ResolveValue($TemplateClone[$key],$TemplateClone.Parameters,$TemplateClone.Variables) + } + } + + return $TemplateClone + } + + hidden [System.Collections.Specialized.OrderedDictionary] ConvertFromPowerShellDataString($TemplatePath){ + $errors = $null + $tokens = $null + + $ast = [System.Management.Automation.Language.Parser]::ParseFile( + $TemplatePath, + [ref]$tokens, + [ref]$errors + ) + + $hashtableAst = $ast.Find({ + $args[0] -is [System.Management.Automation.Language.HashtableAst] + }, $true) + + if (-not $hashtableAst) { + throw "Keine Hashtable im $TemplatePath gefunden" + } + + $this.Template = $this.ConvertToOrderedHashtable($hashtableAst) + + return $this.Template + } + + hidden [System.Collections.Specialized.OrderedDictionary] ConvertToOrderedHashtable($HashtableAst){ + + # Verwende OrderedDictionary statt [ordered]@{} + $ordered = New-Object System.Collections.Specialized.OrderedDictionary + + # Sortiere KeyValuePairs nach ihrer Position im Quelltext + $sortedKvps = $HashtableAst.KeyValuePairs | Sort-Object { $_.Item1.Extent.StartOffset } + + foreach ($kvp in $sortedKvps) { + # Schlüssel extrahieren + $key = $kvp.Item1.Extent.Text -replace '[''"]', '' + + # Wert verarbeiten + $valueAst = $kvp.Item2 + + # Suche nach HashtableAst in PipelineAst + $hashtable = $null + if ($valueAst -is [System.Management.Automation.Language.HashtableAst]) { + $hashtable = $valueAst + } + elseif ($valueAst -is [System.Management.Automation.Language.PipelineAst]) { + # Suche HashtableAst innerhalb der Pipeline + $hashtable = $valueAst.Find({ + $args[0] -is [System.Management.Automation.Language.HashtableAst] + }, $false) + } + + if ($hashtable) { + $value = $this.ConvertToOrderedHashtable($hashtable) + } + elseif ($valueAst -is [System.Management.Automation.Language.ArrayLiteralAst]) { + $value = @() + foreach ($element in $valueAst.Elements) { + if ($element -is [System.Management.Automation.Language.HashtableAst]) { + $value += $this.ConvertToOrderedHashtable($element) + } + else { + $value += Invoke-Expression $element.Extent.Text + } + } + } + else { + try { + $value = Invoke-Expression $valueAst.Extent.Text + } + catch { + $value = $valueAst.Extent.Text + } + } + + $ordered.Add($key, $value) + } + + return $ordered + } + + hidden [System.Object] ResolveValue($Value, $Parameters, $Variables){ + if ($Value -is [string]) { + # Resolve Parameter-Referenzen: [Parameter('name')] + while ($Value -match "\[Parameter\('([^']*)'\)\]") { + $paramName = $matches[1] + if ($Parameters.Contains($paramName)) { + + # Prüfe erst auf Value, dann auf DefaultValue + $paramValue = $Parameters[$paramName].Value + + if ($null -ne $paramValue) { + $Value = $Value -replace "\[Parameter\('$([regex]::Escape($paramName))'\)\]", $paramValue + } + else { + break + } + } + else { + Write-Warning "Parameter '$paramName' nicht gefunden" + break + } + } + + # Resolve Variable-Referenzen: [Variable('name')] + while ($Value -match "\[Variable\('([^']*)'\)\]") { + $varName = $matches[1] + if ($Variables.Contains($varName)) { + $varValue = $this.ResolveValue($Variables[$varName],$Parameters,$Variables) + $Value = $Value -replace "\[Variable\('$([regex]::Escape($varName))'\)\]", $varValue + } + else { + Write-Warning "Variable '$varName' nicht gefunden" + break + } + } + + while ($Value -match "Parameter\('([^']*)'\)") { + $paramName = $matches[1] + if ($Parameters.Contains($paramName)) { + # Prüfe erst auf Value, dann auf DefaultValue + $paramValue = if ($Parameters[$paramName].Value) { + $Parameters[$paramName].Value + } + elseif ($Parameters[$paramName].DefaultValue) { + $Parameters[$paramName].DefaultValue + } + else { + Write-Warning "Parameter '$paramName' hat weder Value noch DefaultValue" + $null + } + + if ($null -ne $paramValue) { + $Value = $Value -replace "Parameter\('$([regex]::Escape($paramName))'\)", $("'"+$paramValue+"'") + } + else { + break + } + } + else { + Write-Warning "Parameter '$paramName' nicht gefunden" + break + } + } + + #Resolve Variable-Referenzen: Variable('name') + while ($Value -match "Variable\('([^']*)'\)"){ + $varName = $matches[1] + if ($Variables.Contains($varName)) { + $varValue = $this.ResolveValue($Variables[$varName],$Parameters,$Variables) + $Value = $Value -replace "Variable\('$([regex]::Escape($varName))'\)", $("'"+$varValue+"'") + } + else { + Write-Warning "Variable '$varName' nicht gefunden" + break + } + } + + # Resolve SSPUtility-Funktionen + foreach($Function in $([TemplateBuilder].GetMethods('Static, Public')).Name | Get-Unique) { + $pattern = "\["+$Function+"\((.*?)\)\]" + while ($Value -match $pattern) { + try { + $fullMatch = $matches[0] + $argsString = $matches[1] + + # Parse die Argumente korrekt - sie sind bereits als PowerShell-Array formatiert + # Wir müssen sie nur evaluieren + $scriptBlock = [scriptblock]::Create("@($argsString)") + $argsArray = & $scriptBlock + + # Rufe die Methode direkt auf statt Invoke-Expression + $result = [TemplateBuilder]::$Function($argsArray) + $Value = $Value.Replace($fullMatch, $result) + } + catch { + Write-Warning "Fehler beim Ausführen von $matches[0] : $_" + break + } + } + } + + return $Value + } + elseif ($Value -is [System.Collections.IDictionary]) { + $resolved = New-Object System.Collections.Specialized.OrderedDictionary + foreach ($key in $Value.Keys) { + $resolved[$key] = $this.ResolveValue($Value[$key],$Parameters,$Variables) + } + return $resolved + } + elseif ($Value -is [array]) { + $resolved = @() + foreach ($item in $Value) { + $resolved += $this.ResolveValue($item,$Parameters,$Variables) + } + return $resolved + } + else { + return $Value + } + } + } + + class TabControlBuilder : BaseComponent{ + [System.Windows.Forms.TabControl] $control + + TabControlBuilder() { + $this.control = New-Object System.Windows.Forms.TabControl + } + + [TabControlBuilder] SetDock([System.String]$dock) { + $this.control.Dock = [System.Windows.Forms.DockStyle]::$dock + return $this + } + + [TabControlBuilder] SetWidth([Int]$width) { + $this.control.Width = $width + return $this + } + + [TabControlBuilder] SetVisible([Boolean]$visible) { + $this.control.Visible = $visible + return $this + } + + [TabControlBuilder] SetSelectedIndex([int]$index) { + $this.control.SelectedIndex = $index + return $this + } + + [TabControlBuilder] AddTabPage([System.Windows.Forms.TabPage]$tabPage) { + $this.control.TabPages.Add($tabPage) | Out-Null + return $this + } + + [System.Windows.Forms.TabControl] Build() { + return $this.control + } + } + + class TabPageBuilder : BaseComponent{ + [System.Windows.Forms.TabPage]$control + + TabPageBuilder([System.String]$text) { + $this.control = New-Object System.Windows.Forms.TabPage + $this.control.Text = $text + } + + [TabPageBuilder] SetPadding([int]$all) { + $this.control.Padding = New-Object System.Windows.Forms.Padding($all) + return $this + } + + [TabPageBuilder] AddControl([System.Windows.Forms.Control]$childControl) { + $this.control.Controls.Add($childControl) + return $this + } + + [void] AddTo([System.Windows.Forms.Control]$parent) { + $parent.Controls.Add($this.control) + } + + [System.Windows.Forms.TabPage] Build() { + return $this.control + } + } + + class ListViewBuilder : BaseComponent { + [System.Windows.Forms.ListView]$control + + ListViewBuilder() { + $this.control = New-Object System.Windows.Forms.ListView + $this.control.View = 'Details' + } + + [ListViewBuilder] SetDock([System.String]$dock) { + $this.control.Dock = [System.Windows.Forms.DockStyle]::$dock + return $this + } + + [ListViewBuilder] SetView([System.String]$view) { + $this.control.View = [System.Windows.Forms.View]::$view + return $this + } + + [ListViewBuilder] SetFullRowSelect([bool]$select) { + $this.control.FullRowSelect = $select + return $this + } + + [ListViewBuilder] SetGridLines([bool]$gridLines) { + $this.control.GridLines = $gridLines + return $this + } + + [ListViewBuilder] AddColumn([System.String]$text, [int]$width) { + $this.control.Columns.Add($text, $width) | Out-Null + return $this + } + + [ListViewBuilder] AddDoubleClickHandler([scriptblock]$handler) { + $this.control.Add_DoubleClick($handler) + return $this + } + + [System.Windows.Forms.ListView] Build() { + return $this.control + } + } + #endregion + + Add-Type -AssemblyName System.Windows.Forms + Add-Type -AssemblyName System.Drawing + + $settingsManager = [SettingsManager]::new("DSC Tool") + + # Verwendung + $logManager = [LogManager]::new("DSC Tool") + $logManager.Info("Anwendung gestartet") + + [BaseComponent]::InitializeShared($settingsManager, $logManager) + + foreach($function in $(Get-ChildItem -Path $settingsManager.Get("FunctionsPath") -Recurse -Filter "*.ps1" -File)){ + $logManager.Info("Lade Funktion: $($function.FullName)") + try{ + . $function.FullName + }catch{ + $logManager.Error("Fehler beim Laden: $_") + } + } + + $stateManager = [StateManager]::new() + $stateManager.SetStates(@{ + hasSelection = $false + isMerged = $false + isResolved = $false + }) + + # Menü erstellen + $menuBuilder = ( + [MenuBuilder]::new(). + BuildFromStructure($settingsManager.GetMenuStructure()). + WithStateManager($stateManager) + ) + + $menuHandlers = @{ + "Import" = @{ + Handler = { + Import-ConfigurationData + $statusBar.SetText("Status", "Deployment importiert.") + } + Condition = { + -not $stateManager.GetState("isMerged") + } + } + "Export" = @{ + Handler = { + Export-ConfigurationData + $statusBar.SetText("Status", "Deployment exportiert.") + } + Condition = { + $stateManager.GetState("isMerged") -eq $true + } + } + "Refresh" = @{ + Handler = { + + $statusBar.SetText("Status", "Wird aktualisiert...") + } + } + "Exit" = { + $form.Form.Close() + } + "OpenFolder" = @{ + Handler = { + $result = [DialogBuilder]::SelectFolder(). + SetDescription("Wählen Sie den Root-Ordner für Templates"). + SetSelectedPath($settingsManager.Get("RootPath", "C:\")). + SetShowNewFolderButton($true). + SetUseDescriptionForTitle($true). + Show() + + if ($result.Result) { + $settingsManager.Set("RootPath", $result.SelectedPath) + $statusBar.SetText("Status", "Ordner gesetzt: $($result.SelectedPath)") + #Load-Templates + } + } + } + "ResetMenu" = { + $result = [System.Windows.Forms.MessageBox]::Show( + "Möchten Sie das Menü auf die Standardeinstellungen zurücksetzen?`n`nDie Anwendung wird danach neu gestartet.", + "Menü zurücksetzen", + [System.Windows.Forms.MessageBoxButtons]::YesNo, + [System.Windows.Forms.MessageBoxIcon]::Question + ) + + if ($result -eq 'Yes') { + $settingsManager.ResetMenuStructure() + [System.Windows.Forms.MessageBox]::Show( + "Menü wurde zurückgesetzt. Bitte starten Sie die Anwendung neu.", + "Erfolg", + [System.Windows.Forms.MessageBoxButtons]::OK, + [System.Windows.Forms.MessageBoxIcon]::Information + ) + $form.Form.Close() + } + } + "About" = { + [System.Windows.Forms.MessageBox]::Show( + "DSC Tool v1.0`n`nEin modernes Tool zur Verwaltung von DSC-Konfigurationen.`n`n© 2024", + "Über DSC Tool", + [System.Windows.Forms.MessageBoxButtons]::OK, + [System.Windows.Forms.MessageBoxIcon]::Information + ) + } + } + + # Handler setzen + $menuBuilder.SetClickHandlersWithConditions($menuHandlers) + + # StatusBar erstellen + $statusBar = [StatusStripBuilder]::new() + + # FlowLayoutPanel erstellen + $flow = ( + [FlowLayoutPanelBuilder]::new(). + SetPadding(6). + Build() + ) + + ## Rechte Seite + # Rechtes Label + + $tabControl = ( + [TabControlBuilder]::new(). + SetDock("Fill"). + Build() + ) + + $buttonClear = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xed62). + SetToolTip("Clear"). + Build() + ) + + $tabControlButtonPanel = ( + [FlowLayoutPanelBuilder]::new(). + SetDock("Right"). + SetPadding(6). + SetWidth(52). + SetFlowDirection("TopDown"). + SetWrapContents($false). + AddControls(@($buttonClear)). + Build() + ) + + $tabPanel = ( + [PanelBuilder]::new(). + SetDock("Top"). + SetHeight(200). + BindEnabled({ + $stateManager.GetState("isResolved") -eq $true + }, $stateManager). + AddControls(@($tabControl,$tabControlButtonPanel)). + Build() + ) + + $rightLabel = ( + [LabelBuilder]::new("Datei-Inhalt / Details"). + SetDock('Top'). + SetHeight(24). + SetFont("Segoe UI", 9, [System.Drawing.FontStyle]::Bold). + SetTextAlign([System.Drawing.ContentAlignment]::MiddleLeft). + SetPadding(5, 0, 0, 0). + Build() + ) + + # TreeView + $treeView = ( + [TreeViewBuilder]::new(). + SetFont("Consolas", 10). + AddNode("Keine Auswahl"). + Build() + ) + + $buttonImport = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xed25). + SetToolTip("Import - Datei laden"). + BindEnabled({ + -not $stateManager.GetState("isMerged") + }, $stateManager). + SetClickHandler({ + Import-ConfigurationData + }). + Build() + ) + + $buttonMerge = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xe9f5). + SetToolTip("Merge - Ausgewählte Dateien zusammenführen"). + BindEnabled({ + $selectionList.Items.Count -gt 0 -and $stateManager.GetState("isMerged") -eq $false + }, $stateManager). + SetClickHandler({ + param($s, $e) + + if($selectionList.Items.Count -gt 0){ + Merge-Templates + $stateManager.SetState("isResolved",$true) + }else{ + [System.Windows.Forms.MessageBox]::Show("Keine Einträge vorhanden","Info",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information) + } + }). + Build() + ) + + $buttonClear = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xed62). + SetToolTip("Clear - TreeView leeren"). + BindEnabled({ + $treeView.Nodes.Count -gt 0 + }, $stateManager). + SetClickHandler({ + $treeView.Nodes.Clear() + $treeView.Nodes.Add("Keine Auswahl") | Out-Null + $statusBar.SetText("Status", "TreeView geleert") + $stateManager.SetStates(@{ + "isMerged" = $false + "isResolved" = $false + }) + }). + Build() + ) + + $buttonExport = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xe74e). + SetToolTip("Export - Als PSD1 speichern"). + BindEnabled({ + $stateManager.GetState("isResolved") -eq $true + }, $stateManager). + SetClickHandler({ + Export-ConfigurationData + }). + Build() + ) + + $treeButtonPanel = ( + [FlowLayoutPanelBuilder]::new(). + SetDock("Right"). + SetPadding(6). + SetWidth(52). + SetFlowDirection("TopDown"). + SetWrapContents($false). + AddControls(@($buttonImport, $buttonMerge, $buttonClear, $buttonExport)). + Build() + ) + + $treeViewPanel = ( + [PanelBuilder]::new(). + SetDock("Fill"). + AddControls(@($treeView,$treeButtonPanel)). + Build() + ) + + $selectionLabel = ( + [LabelBuilder]::new("Ausgewählte Dateien"). + SetHeight(20). + SetDock("Bottom"). + Build() + ) + + $selectionList = ( + [ListBoxBuilder]::new(). + SetFont("Segoe UI", 9). + SetDock('Fill'). # Füllt die GroupBox aus + SetHeight(120). + Build() + ) + + $buttonMoveUp = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xe74a). + SetToolTip("Element nach oben verschieben"). + BindEnabled({ + $selectionList.Items.Count -gt 1 + }, $stateManager). + SetClickHandler({ + $SelectedIndex = $selectionList.SelectedIndex + if ($SelectedIndex -gt 0) { + $item = $selectionList.Items[$SelectedIndex] + $selectionList.Items.RemoveAt($SelectedIndex) + $selectionList.Items.Insert($SelectedIndex-1, $item) + $selectionList.SelectedIndex = $SelectedIndex-1 + $stateManager.SetState("isMerged",$false) + } + }). + Build() + ) + + $buttonRemoveSelection = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xe894). + SetToolTip("Element löschen"). + BindEnabled({ + $selectionList.Items.Count -gt 0 + }, $stateManager). + SetClickHandler({ + $SelectedIndex = $selectionList.SelectedIndex + if ($SelectedIndex -ge 0) { + $removed = $selectionList.Items[$SelectedIndex] + $resp = [System.Windows.Forms.MessageBox]::Show("Eintrag wirklich löschen?`r`n$($removed.FullName)","Bestätigen",[System.Windows.Forms.MessageBoxButtons]::YesNo,[System.Windows.Forms.MessageBoxIcon]::Question) + if ($resp -eq [System.Windows.Forms.DialogResult]::Yes) { + $selectionList.Items.RemoveAt($SelectedIndex) + $stateManager.SetState("hasSelection", $selectionList.Items.Count -gt 0) + $stateManager.UpdateAllButtons() + $statusBar.SetText("Status","Eintrag entfernt: $($removed.FullName)") + if ($selectionList.Items.Count -eq 0) { + Update-TreeView + } else { + Merge-Templates + } + } + } else { + [System.Windows.Forms.MessageBox]::Show("Kein Eintrag ausgewählt","Info",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information) + } + }). + Build() + ) + + $buttonClearSelection = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xe74d). + SetToolTip("Element löschen"). + BindEnabled({ + $selectionList.Items.Count -gt 0 + }, $stateManager). + SetClickHandler({ + if ($SelectedIndex -ge 0) { + $selectionList.Items.Clear() + } else { + [System.Windows.Forms.MessageBox]::Show("Kein Eintrag vorhanden","Info",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information) + } + Update-TreeView + }). + Build() + ) + + $buttonMoveDown = ( + [ButtonBuilder]::new(). + SetFluentIcon([char]0xe74b). + SetToolTip("Element nach unten verschieben"). + BindEnabled({ + $selectionList.Items.Count -gt 1 + }, $stateManager). + SetClickHandler({ + $SelectedIndex = $selectionList.SelectedIndex + if ($SelectedIndex -ge 0 -and $SelectedIndex -lt ($selectionList.Items.Count - 1)) { + $item = $selectionList.Items[$SelectedIndex] + $selectionList.Items.RemoveAt($SelectedIndex) + $selectionList.Items.Insert($SelectedIndex+1, $item) + $selectionList.SelectedIndex = $SelectedIndex+1 + $stateManager.SetState("isMerged",$false) + } + }). + Build() + ) + + $selectionButtonPanel = ( + [FlowLayoutPanelBuilder]::new(). + SetDock("Right"). + SetPadding(6). + SetWidth(48). + SetFlowDirection("TopDown"). + SetWrapContents($false). + AddControls(@($buttonMoveUp,$buttonRemoveSelection,$buttonClearSelection,$buttonMoveDown)). + Build() + ) + + $selectionBottomPanel = ( + [PanelBuilder]::new(). + SetDock('Bottom'). + SetHeight(200). + AddControls(@($selectionList,$selectionButtonPanel)). + Build() + ) + + # SplitPanel erstellen + $splitPanel = ( + [SplitPanelBuilder]::new('Vertical', 0.30, 100, 150). + AddToPanel1($flow). + ClearPanel2(). + AddToPanel2($treeViewPanel). + AddToPanel2($rightLabel). + AddToPanel2($selectionLabel). + AddToPanel2($selectionBottomPanel). + AddToPanel2($tabPanel) + ) + + # Form erstellen + $form = ( + [FormBuilder]::new("Test"). + SetSize( + $settingsManager.Get("WindowWidth", 1024), + $settingsManager.Get("WindowHeight", 768) + ). + AddMenu($menuBuilder). + AddStatusBar($statusBar). + AddSplitPanel($splitPanel). + AddKeyDownHandler({ + param($s, $e) + + # Ctrl+S für Export + if ($e.Control -and $e.KeyCode -eq 'S') { + if ($stateManager.GetState("isMerged")) { + Export-ConfigurationData + } + $e.Handled = $true + } + + # Ctrl+O für Import + if ($e.Control -and $e.KeyCode -eq 'O') { + $buttonImport.PerformClick() + $e.Handled = $true + } + + # F5 für Refresh + if ($e.KeyCode -eq 'F5') { + Load-Templates + $e.Handled = $true + } + + # Ctrl+M für Merge + if ($e.Control -and $e.KeyCode -eq 'M') { + if ($selectionList.Items.Count -gt 0) { + Merge-Templates + } + $e.Handled = $true + } + }). + AddLoadHandler({ + # Beim Laden der Form + $rootPath = $settingsManager.Get("RootPath") + if ($rootPath -and (Test-Path $rootPath)) { + $statusBar.AddLabel("Status", "Lade gespeicherten Pfad: $rootPath") + } + + Load-Templates + + # Registriere Layout-Container für automatisches Resize + [BaseComponent]::AccessibilityManager.RegisterLayoutContainer($flow) + + # Registriere alle Controls für Accessibility + $form.RegisterAllAccessibleControls() | Out-Null + }). + AddFormClosedHandler({ + # Settings speichern + $settingsManager.Set("WindowWidth", $form.Form.Width) + $settingsManager.Set("WindowHeight", $form.Form.Height) + $settingsManager.Set("SplitterDistance", $splitPanel.SplitContainer.SplitterDistance) + + # Ressourcen freigeben + $treeView.Dispose() + $selectionList.Dispose() + $flow.Dispose() + }). + AddShownHandler({ + # ZUERST: SplitPanel initialisieren + $splitPanel.InitializeAfterShown($form.Form) + + # DANN: Padding setzen + $splitPanel.SetPanelPaddingWithMenu(1, $menuBuilder.GetMenuStrip(), 6, 6, 6) + $splitPanel.SetPanelPaddingWithMenu(2, $menuBuilder.GetMenuStrip(), 6, 6, $($statusBar.GetStatusStrip().Height + 6)) + + foreach($Groupbox in $flow.Controls){ + # GroupBox-Breite anpassen + $Groupbox.Width = $flow.ClientSize.Width - 20 + } + + $splitPanel.AutoSizeSplitterToPanel1Content() + + # Focus setzen + $treeView.Focus() + + # StatusBar Update + $statusBar.SetText("Status", "Initialisierung abgeschlossen") + }) + ) + + # Initial alle Button-States aktualisieren + $stateManager.UpdateAllButtons() + + $form.Show() \ No newline at end of file diff --git a/Deployments/merged_config.psd1 b/Deployments/merged_config.psd1 new file mode 100644 index 0000000..6df11f3 --- /dev/null +++ b/Deployments/merged_config.psd1 @@ -0,0 +1,93 @@ +@{ + Resources = @{ + NonNodeData = @{ + Services = @{ + SharePoint = @{ + Farm = @{ + Passphrase = 'Use-SecureString-Or-KeyVault' + ConfigDatabaseName = 'SharePoint_Farm_Config' + ServiceApplications = @{ + AppManagementService = @{ + DatabaseName = 'SharePoint_Services_AppManagement' + Provision = 'True' + } + StateService = @{ + DatabaseName = 'SharePoint_Services_StateService' + Provision = 'True' + } + SubscriptionSettingsService = @{ + DatabaseName = 'SharePoint_Services_SubscriptionSettings' + Provision = 'True' + } + ManagedMetadataService = @{ + DatabaseName = 'SharePoint_Services_ManagedMetadata' + Name = 'Managed Metadata Service' + ApplicationPool = 'SharePoint Service Applications' + Provision = 'True' + } + SearchService = @{ + DatabaseName = 'SharePoint_Services_Search' + Name = 'Search Service Application' + ApplicationPool = 'SharePoint Service Applications' + Provision = 'True' + } + UsageAndHealthService = @{ + DatabaseName = 'SharePoint_Services_UsageAndHealth' + Provision = 'True' + } + SecureStoreService = @{ + DatabaseName = 'SharePoint_Services_SecureStore' + Name = 'Secure Store Service' + ApplicationPool = 'SharePoint Service Applications' + Provision = 'True' + } + UserProfileService = @{ + SyncDBName = 'SharePoint_Services_UserProfile_SyncDB' + ApplicationPool = 'SharePoint User Profile Services' + Provision = 'True' + Name = 'User Profile Service' + SocialDBName = 'SharePoint_Services_UserProfile_SocialDB' + ProfileDBName = 'SharePoint_Services_UserProfile_ProfileDB' + } + } + CentralAdminAuth = 'NTLM' + CentralAdminPort = '2016' + AdminContentDatabase = 'SharePoint_Farm_AdminContent' + ServiceApplicationPools = @{ + Default = @{ + Account = 'CONTOSO\sp_services' + Name = 'SharePoint Service Applications' + } + UserProfile = @{ + Account = 'CONTOSO\sp_ups' + Name = 'SharePoint User Profile Services' + } + } + } + Database = @{ + SQLAlias = @{ + SQLServer = @{ + InstanceName = '' + ServerName = '' + TcpPort = '0' + } + } + } + General = @{ + ProductKey = '0000-0000-0000-0000-0000' + } + Windows = @{ + Registry = @{ + DisableLoopbackCheck = @{ + Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' + Name = 'DisableLoopbackCheck' + Value = '1' + Type = 'DWord' + } + } + } + } + } + } + } +} diff --git a/Functions/ConvertFrom-TreeView.ps1 b/Functions/ConvertFrom-TreeView.ps1 new file mode 100644 index 0000000..42cac96 --- /dev/null +++ b/Functions/ConvertFrom-TreeView.ps1 @@ -0,0 +1,182 @@ +function ConvertFrom-TreeView { + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Windows.Forms.TreeView] + $TreeView, + + [Parameter(Mandatory = $false)] + [switch] + $SkipRootNode, + + [Parameter(Mandatory = $false)] + [switch] + $PreserveTypes, + + [Parameter(Mandatory = $false)] + [switch] + $IncludeMetadata + ) + + function ConvertTreeNodeToHashtable { + param( + [System.Windows.Forms.TreeNode]$node, + [string]$path = "" + ) + + $currentPath = if ($path -eq "") { $node.Name } else { "$path.$($node.Name)" } + + # Verwende Tag-Informationen wenn verfügbar + if ($null -ne $node.Tag -and $node.Tag -is [hashtable]) { + $nodeType = $node.Tag.Type + + switch ($nodeType) { + "Hashtable" { + $result = @{} + + if ($IncludeMetadata) { + $result["__metadata"] = @{ + Type = "Hashtable" + Path = $currentPath + NodeCount = $node.Nodes.Count + } + } + + foreach ($childNode in $node.Nodes) { + $key = if ([string]::IsNullOrEmpty($childNode.Name)) { + $childNode.Text + } + else { + $childNode.Name + } + $result[$key] = ConvertTreeNodeToHashtable $childNode $currentPath + } + return , $result + } + "Array" { + $result = @() + if ($node.Nodes.Count -gt 0) { + foreach ($childNode in $node.Nodes) { + $result += ConvertTreeNodeToHashtable $childNode $currentPath + } + } + return , $result + } + "Value" { + $value = $node.Tag.Value + + if ($PreserveTypes) { + return $value + } + else { + # Konvertiere zu String wenn kein Type-Preservation + return $value.ToString() + } + } + } + } + + # Fallback: Rekonstruktion ohne Tag-Informationen + if ($node.Nodes.Count -gt 0) { + # Array-Erkennung + $isArray = $true + $expectedIndex = 0 + foreach ($childNode in $node.Nodes) { + if ($childNode.Name -ne "[$expectedIndex]") { + $isArray = $false + break + } + $expectedIndex++ + } + + if ($isArray) { + $result = @() + foreach ($childNode in $node.Nodes) { + $result += ConvertTreeNodeToHashtable $childNode $currentPath + } + return $result + } + else { + $result = @{} + foreach ($childNode in $node.Nodes) { + $key = $childNode.Name + if ([string]::IsNullOrEmpty($key)) { + if ($childNode.Text -match '^(.+?)\s*=\s*(.*)$') { + $key = $matches[1].Trim() + } + else { + $key = $childNode.Text + } + } + $result[$key] = ConvertTreeNodeToHashtable $childNode $currentPath + } + return $result + } + } + else { + # Leaf-Node + if ($node.Text -match '^.+?\s*=\s*(.*)$') { + $value = $matches[1].Trim() + + if ($PreserveTypes) { + # Typ-Konvertierung + if ($value -eq '$true' -or $value -eq 'True') { + return $true + } + elseif ($value -eq '$false' -or $value -eq 'False') { + return $false + } + elseif ($value -match '^\d+$') { + return [int]$value + } + elseif ($value -match '^\d+\.\d+$') { + return [double]$value + } + elseif ($value -eq '$null' -or $value -eq 'null') { + return $null + } + else { + return $value + } + } + else { + return $value + } + } + else { + return $node.Text + } + } + } + + if ($TreeView.Nodes.Count -eq 0) { + Write-Warning "TreeView enthält keine Nodes" + return $null + } + + if ($SkipRootNode) { + $rootNode = $TreeView.Nodes[0] + if ($rootNode.Nodes.Count -eq 0) { + Write-Warning "Root-Node hat keine Child-Nodes" + return @{} + } + + $result = @{} + foreach ($childNode in $rootNode.Nodes) { + $key = $childNode.Name + if ([string]::IsNullOrEmpty($key)) { + if ($childNode.Text -match '^(.+?)\s*=\s*(.*)$') { + $key = $matches[1].Trim() + } + else { + $key = $childNode.Text + } + } + $result[$key] = ConvertTreeNodeToHashtable $childNode + } + return $result + } + else { + return ConvertTreeNodeToHashtable $TreeView.Nodes[0] + } +} \ No newline at end of file diff --git a/Functions/Export-ConfigurationData.ps1 b/Functions/Export-ConfigurationData.ps1 new file mode 100644 index 0000000..8a1b021 --- /dev/null +++ b/Functions/Export-ConfigurationData.ps1 @@ -0,0 +1,24 @@ +function Export-ConfigurationData { + $result = ( + [DialogBuilder]::SaveFile(). + SetTitle("Konfiguration speichern"). + SetCommonFilter("PSD1"). + SetInitialDirectory($settingsManager.Get("DeploymentPath")). + SetFileName("merged_config.psd1"). + SetOverwritePrompt($true). + Show() + ) + if ($result.Result) { + try { + $ConfigurationData = @{ + "Resources" = $(ConvertFrom-TreeView -TreeView $treeView -SkipRootNode) + } + + Export-Hashtable -Hashtable $ConfigurationData -Path $result.FileName + [System.Windows.Forms.MessageBox]::Show("Export war erfolgreich", "Info", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information) + } + catch { + [System.Windows.Forms.MessageBox]::Show("Export war nicht erfolgreich", "Info", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) + } + } +} \ No newline at end of file diff --git a/Functions/Export-Hashtable.ps1 b/Functions/Export-Hashtable.ps1 new file mode 100644 index 0000000..7e4b8d5 --- /dev/null +++ b/Functions/Export-Hashtable.ps1 @@ -0,0 +1,152 @@ +function Export-Hashtable { + <# + .SYNOPSIS + Exportiert einen PowerShell Hashtable als valide PSD1-Datei. + + .DESCRIPTION + Diese Funktion konvertiert einen PowerShell Hashtable in eine gültige PSD1-Datei + mit korrekter Formatierung und Einrückung. + + .PARAMETER Hashtable + Der zu exportierende Hashtable + + .PARAMETER Path + Der Pfad zur Ziel-PSD1-Datei + + .PARAMETER Indent + Die Anzahl der Leerzeichen für die Einrückung (Standard: 4) + + .EXAMPLE + $data = @{ + ModuleName = "MeinModul" + Version = "1.0.0" + Author = "Max Mustermann" + Settings = @{ + Debug = $true + MaxRetries = 3 + } + } + Export-Hashtable -Hashtable $data -Path "C:\config.psd1" + #> + + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable]$Hashtable, + + [Parameter(Mandatory = $true)] + [System.String]$Path, + + [Parameter(Mandatory = $false)] + [Int]$Indent = 4 + ) + + function ConvertTo-Psd1String { + param( + [Parameter(Mandatory = $true)] + $Object, + + [Parameter(Mandatory = $false)] + [int]$Level = 0 + ) + + $indentStr = " " * ($Indent * $Level) + $nextIndentStr = " " * ($Indent * ($Level + 1)) + + if ($null -eq $Object) { + return "`$null" + } + + switch ($Object.GetType().Name) { + "Hashtable" { + if ($Object.Count -eq 0) { + return "@{}" + } + + $lines = @("@{") + foreach ($key in ($Object.Keys)) { + $value = ConvertTo-Psd1String -Object $Object[$key] -Level ($Level + 1) + $lines += "$nextIndentStr$key = $value" + } + $lines += "$indentStr}" + return ($lines -join "`r`n") + } + + "Object[]" { + if ($Object.Count -eq 0) { + return "@()" + } + + $lines = @("@(") + foreach ($item in $Object) { + $value = ConvertTo-Psd1String -Object $item -Level ($Level + 1) + $lines += "$nextIndentStr$value" + } + $lines += "$indentStr)" + return ($lines -join "`r`n") + } + + "ArrayList" { + return ConvertTo-Psd1String -Object $Object.ToArray() -Level $Level + } + + "String" { + # Escape für einfache Anführungszeichen + $escaped = $Object -replace "'", "''" + return "'$escaped'" + } + + "Boolean" { + return "`$$Object" + } + + "Int32" { + return $Object.ToString() + } + + "Int64" { + return $Object.ToString() + } + + "Double" { + return $Object.ToString() + } + + default { + if ($Object -is [Array]) { + return ConvertTo-Psd1String -Object @($Object) -Level $Level + } + # Fallback für andere Typen + return "'$Object'" + } + } + } + + try { + $psd1Content = ConvertTo-Psd1String -Object $Hashtable + + # Stelle sicher, dass das Verzeichnis existiert + $directory = Split-Path -Path $Path -Parent + if ($directory -and -not (Test-Path -Path $directory)) { + New-Item -Path $directory -ItemType Directory -Force | Out-Null + } + + # Schreibe die Datei mit UTF8-BOM Encoding (Standard für PSD1) + $psd1Content | Out-File -FilePath $Path -Encoding utf8 -Force + + Write-Verbose "Hashtable erfolgreich nach '$Path' exportiert" + + # Validiere die erstellte PSD1-Datei + try { + $null = Import-PowerShellDataFile -Path $Path + Write-Verbose "PSD1-Datei erfolgreich validiert" + } + catch { + Write-Warning "Die erstellte PSD1-Datei konnte nicht validiert werden: $_" + } + } + catch { + Write-Error "Fehler beim Exportieren des Hashtables: $_" + throw + } +} \ No newline at end of file diff --git a/Functions/Import-ConfigurationData.ps1 b/Functions/Import-ConfigurationData.ps1 new file mode 100644 index 0000000..2211404 --- /dev/null +++ b/Functions/Import-ConfigurationData.ps1 @@ -0,0 +1,21 @@ +function Import-ConfigurationData { + $result = ( + [DialogBuilder]::OpenFile(). + SetTitle("Templates auswählen"). + SetCommonFilter("PSD1"). + SetMultiselect($true). + SetInitialDirectory($settingsManager.Get("DeploymentPath")). + Show() + ) + if ($result.Result) { + foreach ($file in $result.FileNames) { + $newItem = New-Object PSObject -Property @{ BaseName = [System.IO.Path]::GetFileNameWithoutExtension($file); FullName = $file } + $selectionList.Items.Add($newItem) + $stateManager.SetState("hasSelection", $true) + $stateManager.SetState("isMerged", $false) + $stateManager.UpdateAllButtons() + $statusBar.SetText("Status", "Zur Auswahl hinzugefügt: $($file.fullPath)") + } + Merge-Templates + } +} \ No newline at end of file diff --git a/Functions/Import-OrderedPowerShellDataFile.ps1 b/Functions/Import-OrderedPowerShellDataFile.ps1 new file mode 100644 index 0000000..e07adcf --- /dev/null +++ b/Functions/Import-OrderedPowerShellDataFile.ps1 @@ -0,0 +1,89 @@ +function Import-OrderedPowerShellDataFile { + param( + [Parameter(Mandatory)] + [string]$Path + ) + + function ConvertTo-OrderedHashtable { + param( + $HashtableAst + ) + + # Verwende OrderedDictionary statt [ordered]@{} + $ordered = New-Object System.Collections.Specialized.OrderedDictionary + + # Sortiere KeyValuePairs nach ihrer Position im Quelltext + $sortedKvps = $HashtableAst.KeyValuePairs | Sort-Object { $_.Item1.Extent.StartOffset } + + foreach ($kvp in $sortedKvps) { + # Schlüssel extrahieren + $key = $kvp.Item1.Extent.Text -replace '[''"]', '' + + # Wert verarbeiten + $valueAst = $kvp.Item2 + + # Suche nach HashtableAst in PipelineAst + $hashtable = $null + if ($valueAst -is [System.Management.Automation.Language.HashtableAst]) { + $hashtable = $valueAst + } + elseif ($valueAst -is [System.Management.Automation.Language.PipelineAst]) { + # Suche HashtableAst innerhalb der Pipeline + $hashtable = $valueAst.Find({ + $args[0] -is [System.Management.Automation.Language.HashtableAst] + }, $false) + } + + if ($hashtable) { + $value = ConvertTo-OrderedHashtable -HashtableAst $hashtable + } + elseif ($valueAst -is [System.Management.Automation.Language.ArrayLiteralAst]) { + $value = @() + foreach ($element in $valueAst.Elements) { + if ($element -is [System.Management.Automation.Language.HashtableAst]) { + $value += ConvertTo-OrderedHashtable -HashtableAst $element + } + else { + $value += Invoke-Expression $element.Extent.Text + } + } + } + else { + try { + $value = Invoke-Expression $valueAst.Extent.Text + } + catch { + $value = $valueAst.Extent.Text + } + } + + $ordered.Add($key, $value) + } + + return $ordered + } + + # Datei parsen + $errors = $null + $tokens = $null + $ast = [System.Management.Automation.Language.Parser]::ParseFile( + $Path, + [ref]$tokens, + [ref]$errors + ) + + if ($errors) { + throw "Fehler beim Parsen der Datei: $($errors[0].Message)" + } + + # Finde die oberste Hashtable + $hashtableAst = $ast.Find({ + $args[0] -is [System.Management.Automation.Language.HashtableAst] + }, $true) + + if (-not $hashtableAst) { + throw "Keine Hashtable in der Datei gefunden" + } + + return ConvertTo-OrderedHashtable -HashtableAst $hashtableAst +} \ No newline at end of file diff --git a/Functions/Load-Templates.ps1 b/Functions/Load-Templates.ps1 new file mode 100644 index 0000000..e513262 --- /dev/null +++ b/Functions/Load-Templates.ps1 @@ -0,0 +1,126 @@ +function Load-Templates { + + $flow.SuspendLayout() + $flow.Controls.Clear() + + # Lokales Tracking-Objekt + $selectionTracker = @{ + LastSelectedListBox = $null + } + + if (-not (Test-Path -Path $($settingsManager.Get("TemplatePath")))) { + $statusBar.SetText("Status", "Root-Ordner nicht gefunden: $rootPath") + } + else { + try { + $Categories = Get-ChildItem -Path $($settingsManager.Get("TemplatePath")) -Directory -ErrorAction Stop + } + catch { + $statusBar.SetText("Status", "Fehler beim Laden: $($_.Exception.Message)") + [System.Windows.Forms.MessageBox]::Show( + "Konnte Kategorien nicht laden: $($_.Exception.Message)", + "Fehler", + [System.Windows.Forms.MessageBoxButtons]::OK, + [System.Windows.Forms.MessageBoxIcon]::Error + ) + return + } + if ($Categories.Count -eq 0) { + $labelNoCategories = ( + [LabelBuilder]::new("Keine Unterordner gefunden.", 24). + SetDock('Top'). + SetTextAlign([System.Drawing.ContentAlignment]::MiddleLeft). + SetPadding(5, 0, 0, 0). + Build() + ) + $flow.Controls.Add($labelNoCategories) + return + } + else { + foreach ($Categorie in $Categories) { + + $GroupBox = ( + [GroupBoxBuilder]::new($Categorie.Name). + #SetAutoSize($true). + SetWidth(0). + SetHeight(90). + SetMargin(4). + SetPadding(10). + AddTo($flow) + ) + + $GroupBox.Tag = $Categorie.Name + + $Files = Get-ChildItem -Path $Categorie.FullName -Filter "*.psd1" -File -ErrorAction SilentlyContinue | Sort-Object Name + if ($Files.Count -eq 0) { + $ListBox = ( + [ListBoxBuilder]::new(). + SetDock('Fill'). # Füllt die GroupBox aus + AddItem("Keine Templates"). + AddTo($GroupBox) + ) + } + else { + + $ListBox = ( + [ListBoxBuilder]::new(). + SetDock('Fill'). + AddSelectedIndexChangedHandler({ + param($s, $e) + + if ($null -ne $selectionTracker.LastSelectedListBox -and + $selectionTracker.LastSelectedListBox -ne $s) { + + # Deselektiere die vorherige ListBox + $selectionTracker.LastSelectedListBox.ClearSelected() + } + + # Speichere die aktuelle ListBox + $selectionTracker.LastSelectedListBox = $s + + $selectedItem = $s.SelectedItem + if ($null -ne $selectedItem -and ($selectedItem -isnot [string])) { + $currentGroupBox = $s.Parent + $statusBar.SetText("Status", "Ausgewählt: $($selectedItem.FullName) [Kategorie: $($currentGroupBox.Tag)]") + Show-Template -Template $([TemplateBuilder]::new($selectedItem.FullName).ResolveDeploymentDataVariables()) + } + }.GetNewClosure()). + AddMouseDoubleClickHandler({ + param($s, $e) + $selectedItem = $s.SelectedItem + if ($null -ne $selectedItem -and ($selectedItem -isnot [string])) { + $fullPath = $selectedItem.FullName + if (-not (Test-Path $fullPath)) { + [System.Windows.Forms.MessageBox]::Show("Datei nicht gefunden: $fullPath", "Fehler", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) + return + } + $newItem = New-Object PSObject -Property @{ BaseName = [System.IO.Path]::GetFileNameWithoutExtension($fullPath); FullName = $fullPath } + if (-not ($selectionList.Items | Where-Object { $_.FullName -eq $newItem.FullName })) { + $selectionList.Items.Add($newItem) | Out-Null + $stateManager.SetState("hasSelection", $true) + $stateManager.SetState("isMerged", $false) + $stateManager.UpdateAllButtons() + $statusBar.SetText("Status", "Zur Auswahl hinzugefügt: $fullPath") + } + else { + $statusBar.SetText("Status", "Datei bereits in der Auswahl") + } + } + }) + ) + + foreach ($file in $files) { + $item = New-Object PSObject -Property @{ BaseName = $file.BaseName; FullName = $file.FullName } + $ListBox.AddItem($item) + $ListBox.AddTo($GroupBox) + } + } + } + $flow.ResumeLayout() + } + } + + $form.Form.Invoke([Action] { + $splitPanel.AutoSizeSplitterToPanel1Content() + }) +} \ No newline at end of file diff --git a/Functions/Merge-ConfigurationData.ps1 b/Functions/Merge-ConfigurationData.ps1 new file mode 100644 index 0000000..0cc1185 --- /dev/null +++ b/Functions/Merge-ConfigurationData.ps1 @@ -0,0 +1,83 @@ +function Merge-ConfigurationData { + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $Template, + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $Deployment, + [Parameter(Mandatory = $false)] + [System.Collections.Hashtable] + $Output + ) + + # Prüfe auf zirkuläre Referenzen + if ($Template -eq $Deployment) { + throw "Zirkuläre Referenz erkannt" + } + + if ($Output -eq $null) { + $Output = $Deployment + } + + foreach ($Property in $Template.GetEnumerator()) { + if ($Property.Value -is [System.Collections.Hashtable]) { + Write-Verbose "Key [$($Property.Name)] is a Hashtable" + if ($null -ne $Deployment.$($Property.Name)) { + Write-Verbose "Key [$($Property.Name)] is present in Deployment Data" + $Output.($Property.Name) = Merge-ConfigurationData -Template $Template.$($Property.Name) -Deployment $Deployment.$($Property.Name) -Output $Output.$($Property.Name) + } + else { + Write-Verbose "Key [$($Property.Name)] is not present in Deployment Data" + $Output.Add($($Property.Name), $Template.$($Property.Name)) + } + } + elseif ($Property.Value -is [System.Collections.Specialized.OrderedDictionary]) { + Write-Verbose "Key [$($Property.Name)] is a Ordered Dictionary" + if ($null -ne $Deployment.$($Property.Name)) { + Write-Verbose "Key [$($Property.Name)] is present in Deployment Data" + $Output.($Property.Name) = Merge-ConfigurationData -Template $Template.$($Property.Name) -Deployment $Deployment.$($Property.Name) -Output $Output.$($Property.Name) + } + else { + Write-Verbose "Key [$($Property.Name)] is not present in Deployment Data" + $Output.Add($($Property.Name), $Template.$($Property.Name)) + } + } + elseif ($Property.Value -is [System.Array]) { + Write-Verbose "$($Property.Name) is ein Array" + Write-Verbose "Total Items in Template Array [$($Property.Value.Count)]" + Write-Verbose "Total Items in Deployment Array [$($Deployment.$($Property.Name).Count)]" + if ($null -ne $Deployment.$($Property.Name)) { + Write-Verbose "Array is defined in Deployment" + for ($i = 0; $i -lt $Property.Value.Count; $i++) { + + $SearchItem = $($Property.Value[$i].GetEnumerator() | Where-Object { ($_.Value -is [String]) -and ($_.Name -like "*Name") })[0] + if ($($Deployment.$($Property.Name) | ? { $_.($SearchItem.Name) -eq $SearchItem.Value })) { + Merge-ConfigurationData -Template $Property.Value[$i] -Deployment $($Deployment.$($Property.Name) | ? { $_.($SearchItem.Name) -eq $SearchItem.Value }) -Output $($Output.$($Property.Name) | ? { $_.($SearchItem.Name) -eq $SearchItem.Value }) | Out-Null + } + else { + Write-Verbose "Pair $($Key.Name) - $($Key.Value) not present" + $Output.$($Property.Name) += $Property.Value[$i] + } + } + } + else { + Write-Verbose "Array is not defined in Deployment" + $Output.$($Property.Name) = $Template.$($Property.Name) + } + } + else { + Write-Verbose "$($Property.Name) is a String or Integer Value" + if ($null -eq $Deployment.$($Property.Name)) { + + $Output.Add($Property.Name, $Template.($Property.Name)) + + } + elseif ($Deployment.$($Property.Name) -ne $Property.Value) { + + } + } + } + return $Output +} \ No newline at end of file diff --git a/Functions/Merge-Templates.ps1 b/Functions/Merge-Templates.ps1 new file mode 100644 index 0000000..c5aa630 --- /dev/null +++ b/Functions/Merge-Templates.ps1 @@ -0,0 +1,43 @@ +function Merge-Templates { + $Errors = @() + [System.Collections.Hashtable] $ConfigurationData = @{} + #[System.Collections.Specialized.OrderedDictionary] $ConfigurationData = @{} + + for ($i = 0; $i -lt $selectionList.Items.Count; $i++) { + $item = $selectionList.Items[$i] + $file = $item.FullName + if (-not (Test-Path $file)) { $Errors += "Datei nicht gefunden: $file"; continue } + + try { + try { + $TemplateData = Import-OrderedPowerShellDataFile -Path $file -ErrorAction Stop + } + catch { + $Errors += "Datei konnte nicht importiert werden" + } + + $ConfigurationData = Merge-ConfigurationData -Template $ConfigurationData -Deployment $TemplateData + } + catch { + $Errors += "Fehler beim Verarbeiten $($file): $($_.Exception.Message)" + } + } + + if ($null -ne $ConfigurationData) { + Update-TreeView -ConfigurationData $([TemplateBuilder]::new($ConfigurationData).ResolveDeploymentDataVariables()) + $statusBar.SetText("Status", "Merge erfolgreich: $($selectionList.Items.Count) Dateien") + } + else { + + Update-TreeView -ConfigurationData $ConfigurationData + $statusLabel.Text = "Merge ergab kein Ergebnis" + } + + if ($Errors.Count -gt 0) { + [System.Windows.Forms.MessageBox]::Show(($Errors -join "`r`n"), "Merge Warnungen", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) + return + } + else { + return $ConfigurationData + } +} \ No newline at end of file diff --git a/Functions/Show-Template.ps1 b/Functions/Show-Template.ps1 new file mode 100644 index 0000000..561d5d4 --- /dev/null +++ b/Functions/Show-Template.ps1 @@ -0,0 +1,10 @@ +function Show-Template { + Param( + [System.Collections.Hashtable] $Template + ) + $treeView.Nodes.Clear() + + $tempBuilder = [TreeViewBuilder]::new() + $tempBuilder.control = $treeView + $tempBuilder.LoadTemplate($Template, "Template") +} \ No newline at end of file diff --git a/Functions/Update-ParametersPanel.ps1 b/Functions/Update-ParametersPanel.ps1 new file mode 100644 index 0000000..9c899e9 --- /dev/null +++ b/Functions/Update-ParametersPanel.ps1 @@ -0,0 +1,55 @@ +function Update-ParametersPanel { + Param( + [System.Collections.Hashtable] $ConfigurationData + ) + + if ($ConfigurationData.Contains("Parameters") -or $ConfigurationData.Contains("Variables")) { + if ($ConfigurationData.Contains("Parameters")) { + if (!($tabControl.Controls | ? { $_.Text -eq "Parameters" })) { + $ParametersTabPage = ( + [TabPageBuilder]::new("Parameters"). + Build() + ) + } + + $GridView = ( + [DataGridViewBuilder]::new(). + SetHeaderColumns(@("Name", "Value")). + SetHeaderColumnReadOnly("Name"). + Build() + ) + + foreach ($Parameter in $ConfigurationData.Parameters.GetEnumerator()) { + $GridView.Rows.Add($Parameter.Name, $($Parameter.Value.Value)) | Out-Null + + } + $ParametersTabPage.Controls.Add($GridView) + $tabControl.Controls.Add($ParametersTabPage) + + } + + if ($ConfigurationData.Contains("Variables")) { + if (!($tabControl.Controls | ? { $_.Text -eq "Variables" })) { + $VariablesTabPage = ( + [TabPageBuilder]::new("Variables"). + Build() + ) + } + + $GridView = ( + [DataGridViewBuilder]::new(). + SetHeaderColumns(@("Name", "Value")). + SetHeaderColumnReadOnly("Name"). + Build() + ) + + foreach ($Variable in $ConfigurationData.Variables.GetEnumerator()) { + $GridView.Rows.Add($Variable.Name, $($Variable.Value)) | Out-Null + + } + $VariablesTabPage.Controls.Add($GridView) + $tabControl.Controls.Add($VariablesTabPage) + + } + } +} \ No newline at end of file diff --git a/Functions/Update-Treeview.ps1 b/Functions/Update-Treeview.ps1 new file mode 100644 index 0000000..69ae69a --- /dev/null +++ b/Functions/Update-Treeview.ps1 @@ -0,0 +1,25 @@ +function Update-TreeView { + Param( + [System.Collections.Hashtable] $ConfigurationData + ) + if ($null -ne $ConfigurationData) { + $tempBuilder = [TreeViewBuilder]::new() + $tempBuilder.control = $treeView + $tempBuilder.LoadTemplate($ConfigurationData['Resources'], "Deployment") + $statusBar.SetText("Status", "Merged: Anzeige aktualisiert") + $stateManager.SetState("isMerged", $true) + $stateManager.UpdateAllButtons() + + # Parameter und Variablen Panel aktualisieren + Update-ParametersPanel -ConfigurationData $ConfigurationData + + } + else { + $treeView.Nodes.Clear() + $treeView.Nodes.Add('No merged data') | Out-Null + $statusBar.SetText("Status", "Kein Merge vorhanden") + + # Panel ausblenden + $parametersPanel.Visible = $false + } +} \ No newline at end of file diff --git a/Templates/Environment/Contoso.psd1 b/Templates/Environment/Contoso.psd1 new file mode 100644 index 0000000..9c6d041 --- /dev/null +++ b/Templates/Environment/Contoso.psd1 @@ -0,0 +1,14 @@ +@{ + Resources = @{ + NonNodeData = @{ + Services = @{ + ActiveDirectory = @{ + Domain = @{ + FQDN = "contoso.local" + NetBIOS = "Contoso" + } + } + } + } + } +} \ No newline at end of file diff --git a/Templates/Service/SharePointServer.psd1 b/Templates/Service/SharePointServer.psd1 new file mode 100644 index 0000000..1ecba6f --- /dev/null +++ b/Templates/Service/SharePointServer.psd1 @@ -0,0 +1,142 @@ +@{ + Parameters = @{ + DatabasePrefix = @{ + Type = 'string' + Value = 'SharePoint' + DefaultValue = 'SP' + Metadata = @{ + Description = 'Prefix für alle Datenbanknamen' + } + } + ServiceDatabasePrefix = @{ + Type = 'string' + DefaultValue = 'Services' + } + ContentDatabasePrefix = @{ + Type = 'string' + DefaultValue = 'Content' + } + ServiceApplicationPoolDefault = @{ + Type = 'string' + DefaultValue = 'SharePoint Service Applications' + } + ServiceApplicationPoolUserProfileService = @{ + Type = 'string' + DefaultValue = 'SharePoint User Profile Services' + } + DatbaseInstanceName = @{ + Type = 'string' + DefaultValue = 'mssqlserver' + Metadata = @{ + Description = 'Instanz Name für die Standart Instanz' + } + } + } + Variables = @{ + ServiceDbPrefix = "[Concat(Parameter('DatabasePrefix'),'_',Parameter('ServiceDatabasePrefix'),'_')]" + ContentDbPrefix = "[Concat(Parameter('DatabasePrefix'),'_',Parameter('ServiceDatabasePrefix'),'_')]" + ConfigDbName = "[Concat(Parameter('DatabasePrefix'),'_','Farm_Config')]" + AdminDbName = "[Concat(Parameter('DatabasePrefix'),'_','Farm_AdminContent')]" + HZDTest = "[Substring(Parameter('DatbaseInstanceName'),0,5)]" + Test = "[ToUpper(Parameter('DatbaseInstanceName'),0,5)]" + } + Resources = @{ + NonNodeData = @{ + Services = @{ + SharePoint = @{ + General = @{ + ProductKey = '0000-0000-0000-0000-0000' + } + Windows = @{ + Registry = @{ + DisableLoopbackCheck = @{ + Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa' + Name = 'DisableLoopbackCheck' + Type = 'DWord' + Value = 1 + } + } + } + Database = @{ + SQLAlias = @{ + SQLServer = @{ + ServerName = '' + InstanceName = '' + TcpPort = 0 + } + } + } + Farm = @{ + Passphrase = 'Use-SecureString-Or-KeyVault' + ConfigDatabaseName = "[Variable('ConfigDbName')]" + AdminContentDatabase = "[Variable('AdminDbName')]" + CentralAdminPort = 2016 + CentralAdminAuth = 'NTLM' # oder Kerberos + ServiceApplicationPools = @{ + Default = @{ + Name = "[Parameter('ServiceApplicationPoolDefault')]" + Account = 'CONTOSO\sp_services' + } + + UserProfile = @{ + Name = "[Parameter('ServiceApplicationPoolUserProfileService')]" + Account = 'CONTOSO\sp_ups' + } + } + ServiceApplications = @{ + ManagedMetadataService = @{ + Provision = $true + Name = 'Managed Metadata Service' + ApplicationPool = 'SharePoint Service Applications' + DatabaseName = "[Concat(Variable('ServiceDbPrefix'), 'ManagedMetadata')]" + } + + UserProfileService = @{ + Provision = $true + Name = 'User Profile Service' + ApplicationPool = 'SharePoint User Profile Services' + ProfileDBName = "[Concat(Variable('ServiceDbPrefix'), 'UserProfile_ProfileDB')]" + SocialDBName = "[Concat(Variable('ServiceDbPrefix'), 'UserProfile_SocialDB')]" + SyncDBName = "[Concat(Variable('ServiceDbPrefix'), 'UserProfile_SyncDB')]" + } + + SearchService = @{ + Provision = $true + Name = 'Search Service Application' + ApplicationPool = 'SharePoint Service Applications' + DatabaseName = "[Concat(Variable('ServiceDbPrefix'),'Search')]" + } + + StateService = @{ + Provision = $true + DatabaseName = "[Concat(Variable('ServiceDbPrefix'),'StateService')]" + } + + UsageAndHealthService = @{ + Provision = $true + DatabaseName = "[Concat(Variable('ServiceDbPrefix'),'UsageAndHealth')]" + } + + AppManagementService = @{ + Provision = $true + DatabaseName = "[Concat(Variable('ServiceDbPrefix'),'AppManagement')]" + } + + SubscriptionSettingsService = @{ + Provision = $true + DatabaseName = "[Concat(Variable('ServiceDbPrefix'),'SubscriptionSettings')]" + } + + SecureStoreService = @{ + Provision = $true + Name = 'Secure Store Service' + ApplicationPool = 'SharePoint Service Applications' + DatabaseName = "[Concat(Variable('ServiceDbPrefix'),'SecureStore')]" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Templates/Stage/Install.psd1 b/Templates/Stage/Install.psd1 new file mode 100644 index 0000000..069232c --- /dev/null +++ b/Templates/Stage/Install.psd1 @@ -0,0 +1,12 @@ +@{ + Resources = @{ + NonNodeData = @{ + LocalConfigurationManager = @{ + ConfigurationMode = "ApplyOnly" + ConfigurationModeFrequencyMins = "120" + RefreshMode = "PUSH" + RefreshFrequencyMins = "30" + } + } + } +} \ No newline at end of file diff --git a/Templates/Stage/Release.psd1 b/Templates/Stage/Release.psd1 new file mode 100644 index 0000000..1466b41 --- /dev/null +++ b/Templates/Stage/Release.psd1 @@ -0,0 +1,12 @@ +@{ + Resources = @{ + NonNodeData = @{ + LocalConfigurationManager = @{ + ConfigurationMode = "ApplyAndAutocorrect" + ConfigurationModeFrequencyMins = "15" + RefreshMode = "PULL" + RefreshFrequencyMins = "30" + } + } + } +} \ No newline at end of file