From 5a57021e6ae629e5f1bf79ff93977b95b0d35c7a Mon Sep 17 00:00:00 2001 From: Torsten Date: Tue, 14 Apr 2026 15:01:50 +0200 Subject: [PATCH] initial dev commit --- Compiler.ps1 | 531 ++++++++++++------ .../Import-OrderedPowerShellDataFile.ps1 | 89 --- Functions/Merge-Templates.ps1 | 5 +- 3 files changed, 358 insertions(+), 267 deletions(-) delete mode 100644 Functions/Import-OrderedPowerShellDataFile.ps1 diff --git a/Compiler.ps1 b/Compiler.ps1 index b87e362..194dcbe 100644 --- a/Compiler.ps1 +++ b/Compiler.ps1 @@ -2923,55 +2923,75 @@ } # Concat mit einer dynamischen Anzahl von Argumenten - static [string] Concat([string[]]$input) { - return $input -join '' + static [string] Concat([object[]]$input) { + return (($input | ForEach-Object { [string]$_ }) -join '') } # ToLower-Methode - static [string] ToLower([string[]]$input) { - return $input[0].ToLower() + static [string] ToLower([object[]]$input) { + if ($input.Count -lt 1) { + return "" + } + + return ([string]$input[0]).ToLowerInvariant() } # ToUpper-Methode - static [string] ToUpper([string[]]$input) { - return $input[0].ToUpper() + static [string] ToUpper([object[]]$input) { + if ($input.Count -lt 1) { + return "" + } + + return ([string]$input[0]).ToUpperInvariant() } # FirstIndexOf-Methode - static [string] FirstIndexOf([string[]] $input){ - return $input[0] + static [string] FirstIndexOf([object[]] $input){ + if ($input.Count -lt 1) { + return "" + } + + return [string]$input[0] } # LastIndexOf-Methode - static [string] LastIndexOf([string[]] $input){ - return $input[-1] + static [string] LastIndexOf([object[]] $input){ + if ($input.Count -lt 1) { + return "" + } + + return [string]$input[-1] } # IndexOf-Methode - static [string] IndexOf([string[]] $input){ - return $input[$input[-1]] + static [string] IndexOf([object[]] $input){ + if ($input.Count -lt 2) { + throw "IndexOf benötigt mindestens zwei Argumente." + } + + return [string]$input[[int]$input[-1]] } # Substring-Methode - static [String] Substring([string[]] $input){ + static [String] Substring([object[]] $input){ if($input.Count -eq 2){ - $array = $input.Split(',') - return $array[0].Substring($array[1]) + return ([string]$input[0]).Substring([int]$input[1]) }elseif($input.Count -eq 3){ - $array = $input.Split(',') - return $array[0].Substring($array[1],$array[2]) + return ([string]$input[0]).Substring([int]$input[1],[int]$input[2]) }else{ - Write-Warning "Zuviele Parameter" - return $false + throw "Substring erwartet zwei oder drei Argumente." } } # Replace-Methode - static [String] Replace([string[]] $input){ - $array = $input.Split(',') - return $array[0].Replace($array[1],$array[2]) + static [String] Replace([object[]] $input){ + if ($input.Count -lt 3) { + throw "Replace erwartet drei Argumente." + } + + return ([string]$input[0]).Replace([string]$input[1],[string]$input[2]) } hidden [System.Collections.Specialized.OrderedDictionary] ResolveDeploymentParameterDefaultValues(){ @@ -3025,6 +3045,10 @@ [ref]$errors ) + if ($errors) { + throw "Fehler beim Parsen der Datei: $($errors[0].Message)" + } + $hashtableAst = $ast.Find({ $args[0] -is [System.Management.Automation.Language.HashtableAst] }, $true) @@ -3038,176 +3062,108 @@ 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) + hidden [System.Collections.Specialized.OrderedDictionary] ConvertToOrderedHashtable([System.Management.Automation.Language.HashtableAst]$HashtableAst){ + $ordered = New-Object System.Collections.Specialized.OrderedDictionary + + $sortedKvps = $HashtableAst.KeyValuePairs | Sort-Object { $_.Item1.Extent.StartOffset } + + foreach ($kvp in $sortedKvps) { + $key = [string]$this.ConvertAstValue($kvp.Item1) + $value = $this.ConvertAstValue($kvp.Item2) + $ordered.Add($key, $value) + } + + return $ordered + } + + hidden [System.Object] ConvertAstValue([System.Management.Automation.Language.Ast]$Ast) { + if ($null -eq $Ast) { + return $null + } + + if ($Ast -is [System.Management.Automation.Language.PipelineAst]) { + if ($Ast.PipelineElements.Count -eq 1 -and $Ast.PipelineElements[0] -is [System.Management.Automation.Language.CommandExpressionAst]) { + return $this.ConvertAstValue($Ast.PipelineElements[0].Expression) } - - return $ordered + + return $Ast.SafeGetValue() + } + + if ($Ast -is [System.Management.Automation.Language.CommandExpressionAst]) { + return $this.ConvertAstValue($Ast.Expression) + } + + if ($Ast -is [System.Management.Automation.Language.ParenExpressionAst]) { + return $this.ConvertAstValue($Ast.Pipeline) + } + + if ($Ast -is [System.Management.Automation.Language.ArrayExpressionAst]) { + return $this.ConvertAstValue($Ast.SubExpression) + } + + if ($Ast -is [System.Management.Automation.Language.StatementBlockAst]) { + if ($Ast.Statements.Count -eq 1 -and $Ast.Statements[0] -is [System.Management.Automation.Language.PipelineAst]) { + return $this.ConvertAstValue($Ast.Statements[0]) + } + + throw "Nicht unterstützter Ausdruck in PSD1-Datei: $($Ast.Extent.Text)" + } + + if ($Ast -is [System.Management.Automation.Language.ArrayLiteralAst]) { + $result = @() + foreach ($element in $Ast.Elements) { + $result += $this.ConvertAstValue($element) + } + + return $result + } + + if ($Ast -is [System.Management.Automation.Language.HashtableAst]) { + return $this.ConvertToOrderedHashtable($Ast) + } + + if ($Ast -is [System.Management.Automation.Language.VariableExpressionAst]) { + switch ($Ast.VariablePath.UserPath) { + 'true' { return $true } + 'false' { return $false } + 'null' { return $null } + default { return $Ast.Extent.Text } + } + } + + try { + return $Ast.SafeGetValue() + } + catch { + return $Ast.Extent.Text + } } hidden [System.Object] ResolveValue($Value, $Parameters, $Variables){ + $resolutionStack = [System.Collections.Generic.List[string]]::new() + return $this.ResolveValueInternal($Value, $Parameters, $Variables, $resolutionStack) + } + + hidden [System.Object] ResolveValueInternal($Value, $Parameters, $Variables, [System.Collections.Generic.List[string]]$ResolutionStack){ 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 - } + $trimmedValue = $Value.Trim() + if ($this.IsTemplateExpression($trimmedValue)) { + return $this.EvaluateTemplateExpression($trimmedValue.Substring(1, $trimmedValue.Length - 2), $Parameters, $Variables, $ResolutionStack) } - #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 + return $this.ResolveInlineTemplateExpressions($Value, $Parameters, $Variables, $ResolutionStack) } 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) + $resolved[$key] = $this.ResolveValueInternal($Value[$key], $Parameters, $Variables, $ResolutionStack) } return $resolved } elseif ($Value -is [array]) { $resolved = @() foreach ($item in $Value) { - $resolved += $this.ResolveValue($item,$Parameters,$Variables) + $resolved += $this.ResolveValueInternal($item, $Parameters, $Variables, $ResolutionStack) } return $resolved } @@ -3215,6 +3171,229 @@ return $Value } } + + hidden [bool] IsTemplateExpression([string]$Value) { + if ([string]::IsNullOrWhiteSpace($Value)) { + return $false + } + + if (-not ($Value.StartsWith('[') -and $Value.EndsWith(']'))) { + return $false + } + + return $this.IsFunctionInvocation($Value.Substring(1, $Value.Length - 2)) + } + + hidden [bool] IsFunctionInvocation([string]$Value) { + return $Value.Trim() -match '^[A-Za-z_][A-Za-z0-9_]*\s*\(.*\)$' + } + + hidden [string] ResolveInlineTemplateExpressions([string]$Value, $Parameters, $Variables, [System.Collections.Generic.List[string]]$ResolutionStack) { + $resolved = $Value + $matches = [regex]::Matches($Value, '\[[^\[\]]+\]') + + for ($i = $matches.Count - 1; $i -ge 0; $i--) { + $match = $matches[$i] + $expressionText = $match.Value.Substring(1, $match.Value.Length - 2) + if (-not $this.IsFunctionInvocation($expressionText)) { + continue + } + + $replacement = $this.EvaluateTemplateExpression($expressionText, $Parameters, $Variables, $ResolutionStack) + $resolved = $resolved.Remove($match.Index, $match.Length).Insert($match.Index, [string]$replacement) + } + + return $resolved + } + + hidden [System.Object] EvaluateTemplateExpression([string]$ExpressionText, $Parameters, $Variables, [System.Collections.Generic.List[string]]$ResolutionStack) { + $trimmedExpression = $ExpressionText.Trim() + if ($trimmedExpression -notmatch '^([A-Za-z_][A-Za-z0-9_]*)\s*\((.*)\)$') { + return $ExpressionText + } + + $functionName = $matches[1] + $argumentsText = $matches[2] + $resolvedArguments = @() + $result = $null + + foreach ($argument in $this.SplitExpressionArguments($argumentsText)) { + $resolvedArguments += ,($this.ResolveExpressionToken($argument, $Parameters, $Variables, $ResolutionStack)) + } + + switch ($functionName.ToLowerInvariant()) { + 'parameter' { $result = $this.GetParameterValue([string]$resolvedArguments[0], $Parameters); break } + 'parameters' { $result = $this.GetParameterValue([string]$resolvedArguments[0], $Parameters); break } + 'variable' { $result = $this.GetVariableValue([string]$resolvedArguments[0], $Parameters, $Variables, $ResolutionStack); break } + 'variables' { $result = $this.GetVariableValue([string]$resolvedArguments[0], $Parameters, $Variables, $ResolutionStack); break } + 'concat' { $result = [TemplateBuilder]::Concat($resolvedArguments); break } + 'tolower' { $result = [TemplateBuilder]::ToLower($resolvedArguments); break } + 'toupper' { $result = [TemplateBuilder]::ToUpper($resolvedArguments); break } + 'firstindexof' { $result = [TemplateBuilder]::FirstIndexOf($resolvedArguments); break } + 'lastindexof' { $result = [TemplateBuilder]::LastIndexOf($resolvedArguments); break } + 'indexof' { $result = [TemplateBuilder]::IndexOf($resolvedArguments); break } + 'substring' { $result = [TemplateBuilder]::Substring($resolvedArguments); break } + 'replace' { $result = [TemplateBuilder]::Replace($resolvedArguments); break } + default { + throw "Nicht unterstützte Template-Funktion '$functionName'" + return $null + } + } + + return $result + } + + hidden [string[]] SplitExpressionArguments([string]$ArgumentsText) { + $arguments = New-Object 'System.Collections.Generic.List[string]' + if ([string]::IsNullOrWhiteSpace($ArgumentsText)) { + return $arguments.ToArray() + } + + $builder = New-Object System.Text.StringBuilder + $depth = 0 + $inSingleQuote = $false + $inDoubleQuote = $false + + for ($i = 0; $i -lt $ArgumentsText.Length; $i++) { + $char = $ArgumentsText[$i] + + if ($char -eq "'" -and -not $inDoubleQuote) { + [void]$builder.Append($char) + if ($inSingleQuote -and $i + 1 -lt $ArgumentsText.Length -and $ArgumentsText[$i + 1] -eq "'") { + $i++ + [void]$builder.Append($ArgumentsText[$i]) + continue + } + + $inSingleQuote = -not $inSingleQuote + continue + } + + if ($char -eq '"' -and -not $inSingleQuote) { + $inDoubleQuote = -not $inDoubleQuote + [void]$builder.Append($char) + continue + } + + if (-not $inSingleQuote -and -not $inDoubleQuote) { + if ($char -eq '(') { + $depth++ + } + elseif ($char -eq ')') { + $depth-- + } + elseif ($char -eq ',' -and $depth -eq 0) { + $arguments.Add($builder.ToString().Trim()) + [void]$builder.Clear() + continue + } + } + + [void]$builder.Append($char) + } + + if ($builder.Length -gt 0 -or $ArgumentsText.Trim().Length -gt 0) { + $arguments.Add($builder.ToString().Trim()) + } + + return $arguments.ToArray() + } + + hidden [System.Object] ResolveExpressionToken([string]$Token, $Parameters, $Variables, [System.Collections.Generic.List[string]]$ResolutionStack) { + $trimmedToken = $Token.Trim() + if ([string]::IsNullOrWhiteSpace($trimmedToken)) { + return '' + } + + if ($this.IsTemplateExpression($trimmedToken)) { + return $this.EvaluateTemplateExpression($trimmedToken.Substring(1, $trimmedToken.Length - 2), $Parameters, $Variables, $ResolutionStack) + } + + if ($this.IsFunctionInvocation($trimmedToken)) { + return $this.EvaluateTemplateExpression($trimmedToken, $Parameters, $Variables, $ResolutionStack) + } + + if (($trimmedToken.StartsWith("'") -and $trimmedToken.EndsWith("'")) -or ($trimmedToken.StartsWith('"') -and $trimmedToken.EndsWith('"'))) { + return $this.UnquoteString($trimmedToken) + } + + if ($trimmedToken -match '^\$?true$') { + return $true + } + + if ($trimmedToken -match '^\$?false$') { + return $false + } + + if ($trimmedToken -match '^\$?null$') { + return $null + } + + if ($trimmedToken -match '^-?\d+$') { + return [int]$trimmedToken + } + + if ($trimmedToken -match '^-?\d+\.\d+$') { + return [double]$trimmedToken + } + + return $trimmedToken + } + + hidden [string] UnquoteString([string]$Value) { + if ($Value.StartsWith("'") -and $Value.EndsWith("'")) { + return ($Value.Substring(1, $Value.Length - 2) -replace "''", "'") + } + + if ($Value.StartsWith('"') -and $Value.EndsWith('"')) { + return $Value.Substring(1, $Value.Length - 2) + } + + return $Value + } + + hidden [System.Object] GetParameterValue([string]$ParameterName, $Parameters) { + if ($null -eq $Parameters -or -not $Parameters.Contains($ParameterName)) { + throw "Parameter '$ParameterName' nicht gefunden" + } + + $parameter = $Parameters[$ParameterName] + if ($parameter -is [System.Collections.IDictionary]) { + if ($parameter.Contains('Value') -and $null -ne $parameter.Value) { + return $parameter.Value + } + + if ($parameter.Contains('DefaultValue')) { + return $parameter.DefaultValue + } + } + + return $parameter + } + + hidden [System.Object] GetVariableValue([string]$VariableName, $Parameters, $Variables, [System.Collections.Generic.List[string]]$ResolutionStack) { + if ($null -eq $Variables -or -not $Variables.Contains($VariableName)) { + throw "Variable '$VariableName' nicht gefunden" + return $null + } + + if ($ResolutionStack.Contains($VariableName)) { + $cycle = @($ResolutionStack.ToArray()) + $VariableName + throw "Zirkuläre Variablenreferenz erkannt: $($cycle -join ' -> ')" + return $null + } + + $ResolutionStack.Add($VariableName) + $resolvedValue = $null + try { + $resolvedValue = $this.ResolveValueInternal($Variables[$VariableName], $Parameters, $Variables, $ResolutionStack) + } + finally { + [void]$ResolutionStack.Remove($VariableName) + } + + return $resolvedValue + } } class TabControlBuilder : BaseComponent{ @@ -3811,4 +3990,4 @@ # Initial alle Button-States aktualisieren $stateManager.UpdateAllButtons() - $form.Show() \ No newline at end of file + $form.Show() diff --git a/Functions/Import-OrderedPowerShellDataFile.ps1 b/Functions/Import-OrderedPowerShellDataFile.ps1 deleted file mode 100644 index e07adcf..0000000 --- a/Functions/Import-OrderedPowerShellDataFile.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -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/Merge-Templates.ps1 b/Functions/Merge-Templates.ps1 index c5aa630..462a9a1 100644 --- a/Functions/Merge-Templates.ps1 +++ b/Functions/Merge-Templates.ps1 @@ -10,10 +10,11 @@ function Merge-Templates { try { try { - $TemplateData = Import-OrderedPowerShellDataFile -Path $file -ErrorAction Stop + $TemplateData = [TemplateBuilder]::new($file).Template } catch { $Errors += "Datei konnte nicht importiert werden" + continue } $ConfigurationData = Merge-ConfigurationData -Template $ConfigurationData -Deployment $TemplateData @@ -40,4 +41,4 @@ function Merge-Templates { else { return $ConfigurationData } -} \ No newline at end of file +}