Initial commit

This commit is contained in:
Torsten Brendgen
2026-04-13 15:40:51 +02:00
commit 01a535eb39
17 changed files with 4897 additions and 0 deletions

View File

@@ -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]
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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()
})
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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")
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}