Public/ConvertTo-NSPClientScript.ps1
|
function ConvertTo-NSPClientScript { <# .SYNOPSIS Renders a recipe with the given parameter values and returns the script text (nothing is written). Throws if a value is invalid or the result does not parse. .DESCRIPTION Template tokens are {{ParameterName}}, rendered by the parameter's type: String a single-quoted PowerShell literal ('O''Brien'), for code; Version the same, after checking it is a valid [version]; Bool $true / $false; Json raw JSON text for a single-quoted here-string - an object is converted with ConvertTo-Json -Depth 20, a string is checked to parse; empty is allowed when the parameter is optional; Text the raw value, one line, for comments (line breaks and comment-block delimiters are refused). {{Name:Text}} renders any parameter as Text. Built-in tokens: {{GENERATED_UTC}}, {{ENGINE_VERSION}}, {{RECIPE_NAME}}, {{RECIPE_VERSION}}. The result uses CRLF line endings and is parsed before it is returned. .PARAMETER Recipe Recipe name (see Get-NSPClientScriptRecipe), or a recipe object from it. .PARAMETER Parameters Hashtable of parameter values. Unknown keys are refused, so a typo can't silently fall back to a default. .PARAMETER RecipePath Folder of custom recipes to look the name up in, instead of the built-in ones. .EXAMPLE ConvertTo-NSPClientScript -Recipe ToolShim -Parameters @{ ToolName = 'AD Manager'; ModuleName = 'NSP.ActiveDirectory'; ModuleMinimumVersion = '0.1.0' EntryFunction = 'Start-NSPADManager'; Modules = @(@{ Name = 'NSP.ActiveDirectory'; MinimumVersion = '0.1.0' }) } #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)][object]$Recipe, [hashtable]$Parameters = @{}, [string]$RecipePath ) if ($Recipe -is [string]) { $lookup = @{ Name = $Recipe } if ($RecipePath) { $lookup['RecipePath'] = $RecipePath } $found = @(Get-NSPClientScriptRecipe @lookup | Where-Object { $_.Name -eq $Recipe }) if ($found.Count -ne 1) { throw "No recipe named '$Recipe'." } $Recipe = $found[0] } $known = @($Recipe.Parameters | ForEach-Object { $_.Name }) foreach ($key in $Parameters.Keys) { if ($known -notcontains $key) { throw "Recipe '$($Recipe.Name)' has no parameter '$key'. Parameters: $($known -join ', ')." } } # Resolve every parameter to its (validated) raw value first. $values = @{} foreach ($p in $Recipe.Parameters) { # Assigned in branches, not via an if-expression, which would unroll a one-element array. if ($Parameters.ContainsKey($p.Name)) { $value = $Parameters[$p.Name] } else { $value = $p.Default } $isEmpty = ($null -eq $value) -or (($value -is [string]) -and [string]::IsNullOrWhiteSpace($value)) if ($isEmpty -and $p.Required) { throw "Recipe '$($Recipe.Name)' needs a value for '$($p.Name)'." } $values[$p.Name] = ConvertTo-NSPRecipeValue -Parameter $p -Value $value -IsEmpty:$isEmpty } $engineVersion = (Get-Module NSP.ClientScripts | Sort-Object Version -Descending | Select-Object -First 1).Version $builtIns = @{ GENERATED_UTC = [DateTime]::UtcNow.ToString('yyyy-MM-dd HH:mm:ss') + ' UTC' ENGINE_VERSION = [string]$engineVersion RECIPE_NAME = $Recipe.Name RECIPE_VERSION = $Recipe.Version } $template = [IO.File]::ReadAllText($Recipe.TemplatePath) $rendered = [regex]::Replace($template, '\{\{([A-Za-z_][A-Za-z0-9_]*)(?::(Text))?\}\}', { param($m) $tokenName = $m.Groups[1].Value $asText = $m.Groups[2].Success if ($builtIns.ContainsKey($tokenName)) { return $builtIns[$tokenName] } if (-not $values.ContainsKey($tokenName)) { throw "Template token {{$tokenName}} is not a parameter of recipe '$($Recipe.Name)'." } $v = $values[$tokenName] if ($asText) { return (ConvertTo-NSPRecipeText $v.Raw) } return $v.Rendered }) $rendered = ($rendered -replace "`r`n", "`n") -replace "`n", "`r`n" $parseErrors = $null [Management.Automation.Language.Parser]::ParseInput($rendered, [ref]$null, [ref]$parseErrors) | Out-Null if ($parseErrors) { throw "Recipe '$($Recipe.Name)' rendered a script that does not parse: $($parseErrors[0].Message) (line $($parseErrors[0].Extent.StartLineNumber))" } return $rendered } |