initial dev commit

This commit is contained in:
2026-04-14 15:01:50 +02:00
parent 01a535eb39
commit 5a57021e6a
3 changed files with 358 additions and 267 deletions

View File

@@ -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]@{}
hidden [System.Collections.Specialized.OrderedDictionary] ConvertToOrderedHashtable([System.Management.Automation.Language.HashtableAst]$HashtableAst){
$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
}
}
$key = [string]$this.ConvertAstValue($kvp.Item1)
$value = $this.ConvertAstValue($kvp.Item2)
$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)) {
hidden [System.Object] ConvertAstValue([System.Management.Automation.Language.Ast]$Ast) {
if ($null -eq $Ast) {
return $null
}
# Prüfe erst auf Value, dann auf DefaultValue
$paramValue = $Parameters[$paramName].Value
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)
}
if ($null -ne $paramValue) {
$Value = $Value -replace "\[Parameter\('$([regex]::Escape($paramName))'\)\]", $paramValue
return $Ast.SafeGetValue()
}
else {
break
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)
}
else {
Write-Warning "Parameter '$paramName' nicht gefunden"
break
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 }
}
}
# 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)
return $Ast.SafeGetValue()
}
catch {
Write-Warning "Fehler beim Ausführen von $matches[0] : $_"
break
}
return $Ast.Extent.Text
}
}
return $Value
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]) {
$trimmedValue = $Value.Trim()
if ($this.IsTemplateExpression($trimmedValue)) {
return $this.EvaluateTemplateExpression($trimmedValue.Substring(1, $trimmedValue.Length - 2), $Parameters, $Variables, $ResolutionStack)
}
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{

View File

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

View File

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