Add parameter validation functions and enhance tests for configuration data

This commit is contained in:
Torsten Brendgen
2026-06-27 14:22:37 +02:00
parent 3e017fc926
commit 086020c3df
7 changed files with 371 additions and 28 deletions

View File

@@ -84,6 +84,54 @@ Describe "Resolve-DSCConfigurationData" {
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
}
It "validates array parameter items against AllowedValues" {
$ConfigurationData = @{
Parameters = @{
ServerRoles = @{
Type = "array"
Value = @(
"WebFrontEnd",
"Application"
)
AllowedValues = @(
"WebFrontEnd",
"Application",
"Search"
)
}
}
Resources = @{
Roles = "[parameters('ServerRoles')]"
}
}
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
$Result.Resources.Roles[0] | Should Be "WebFrontEnd"
$Result.Resources.Roles[1] | Should Be "Application"
}
It "throws when an array parameter item is not listed in AllowedValues" {
$ConfigurationData = @{
Parameters = @{
ServerRoles = @{
Type = "array"
Value = @(
"WebFrontEnd",
"InvalidRole"
)
AllowedValues = @(
"WebFrontEnd",
"Application",
"Search"
)
}
}
}
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
}
It "validates required parameters" {
$ConfigurationData = @{
Parameters = @{
@@ -124,6 +172,107 @@ Describe "Resolve-DSCConfigurationData" {
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
}
It "validates supported parameter types" {
$ConfigurationData = @{
Parameters = @{
Name = @{
Type = "string"
Value = "SharePoint"
}
Port = @{
Type = "int"
Value = 1433
}
Enabled = @{
Type = "bool"
Value = $true
}
Roles = @{
Type = "array"
Value = @(
"WebFrontEnd",
"Application"
)
}
SecretReference = @{
Type = "secureString"
Value = @{
SecretName = "SharePointFarmPassphrase"
}
Sensitive = $true
}
CredentialReference = @{
Type = "credential"
Value = @{
CredentialName = "SharePointSetup"
}
Sensitive = $true
}
}
Resources = @{
Name = "[parameters('Name')]"
Port = "[parameters('Port')]"
Enabled = "[parameters('Enabled')]"
RoleCount = "[length(parameters('Roles'))]"
}
}
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
$Result.Resources.Name | Should Be "SharePoint"
$Result.Resources.Port | Should Be 1433
$Result.Resources.Enabled | Should Be $true
$Result.Resources.RoleCount | Should Be 2
}
It "throws when parameter type validation fails" {
$ConfigurationData = @{
Parameters = @{
Port = @{
Type = "int"
Value = "1433"
}
}
}
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
}
It "validates numeric ranges" {
$ConfigurationData = @{
Parameters = @{
Port = @{
Type = "int"
Value = 1433
MinValue = 1
MaxValue = 65535
}
}
Resources = @{
Port = "[parameters('Port')]"
}
}
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
$Result.Resources.Port | Should Be 1433
}
It "throws when numeric range validation fails" {
$ConfigurationData = @{
Parameters = @{
Port = @{
Type = "int"
Value = 70000
MinValue = 1
MaxValue = 65535
}
}
}
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
}
It "supports localized deprecated parameter messages" {
$ConfigurationData = @{
Parameters = @{

View File

@@ -42,6 +42,7 @@ function Assert-ConfigurationDataParameter {
return
}
Assert-ConfigurationDataParameterType -Name $Name -Definition $Definition -Value $Value
Assert-ConfigurationDataParameterAllowedValue -Name $Name -Definition $Definition -Value $Value
if(Test-ConfigurationDataMapContainsKey -Map $Definition -Key "MinLength"){
@@ -64,4 +65,20 @@ function Assert-ConfigurationDataParameter {
throw "Parameter [$Name] value [$Value] does not match pattern [$Pattern]."
}
}
if(Test-ConfigurationDataMapContainsKey -Map $Definition -Key "MinValue"){
Assert-ConfigurationDataParameterNumericValue -Name $Name -Value $Value
$MinValue = [decimal](Get-ConfigurationDataMapValue -Map $Definition -Key "MinValue")
if(([decimal]$Value) -lt $MinValue){
throw "Parameter [$Name] value [$Value] is less than MinValue [$MinValue]."
}
}
if(Test-ConfigurationDataMapContainsKey -Map $Definition -Key "MaxValue"){
Assert-ConfigurationDataParameterNumericValue -Name $Name -Value $Value
$MaxValue = [decimal](Get-ConfigurationDataMapValue -Map $Definition -Key "MaxValue")
if(([decimal]$Value) -gt $MaxValue){
throw "Parameter [$Name] value [$Value] is greater than MaxValue [$MaxValue]."
}
}
}

View File

@@ -21,6 +21,16 @@ function Assert-ConfigurationDataParameterAllowedValue {
return
}
if($Value -is [System.Array] -and $Value -isnot [string]){
foreach($Item in @($Value)){
if($AllowedValues -notcontains $Item){
throw "Parameter [$Name] value [$Item] is not allowed. Allowed values are: $($AllowedValues -join ', ')."
}
}
return
}
if($AllowedValues -notcontains $Value){
throw "Parameter [$Name] value [$Value] is not allowed. Allowed values are: $($AllowedValues -join ', ')."
}

View File

@@ -0,0 +1,21 @@
function Assert-ConfigurationDataParameterNumericValue {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Name,
[AllowNull()]
$Value
)
if($null -eq $Value -or $Value -is [bool] -or ($Value -isnot [ValueType])){
throw "Parameter [$Name] value [$Value] is not numeric."
}
try {
[void]([decimal]$Value)
} catch {
throw "Parameter [$Name] value [$Value] is not numeric."
}
}

View File

@@ -0,0 +1,61 @@
function Assert-ConfigurationDataParameterType {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Name,
[Parameter(Mandatory=$true)]
$Definition,
[AllowNull()]
$Value
)
if(-not (Test-ConfigurationDataMapContainsKey -Map $Definition -Key "Type")){
return
}
$TypeName = ([string](Get-ConfigurationDataMapValue -Map $Definition -Key "Type")).ToLowerInvariant()
switch($TypeName){
"string" {
if($Value -isnot [string]){
throw "Parameter [$Name] expects type [string], but received [$($Value.GetType().Name)]."
}
}
{ $_ -in @("int", "integer") } {
if(-not (Test-ConfigurationDataValueIsInteger -Value $Value)){
throw "Parameter [$Name] expects type [int], but received [$($Value.GetType().Name)]."
}
}
{ $_ -in @("bool", "boolean") } {
if($Value -isnot [bool]){
throw "Parameter [$Name] expects type [bool], but received [$($Value.GetType().Name)]."
}
}
"array" {
if($Value -isnot [System.Array] -or $Value -is [string]){
throw "Parameter [$Name] expects type [array], but received [$($Value.GetType().Name)]."
}
}
{ $_ -in @("hashtable", "object") } {
if(-not (Test-ConfigurationDataMap -Value $Value)){
throw "Parameter [$Name] expects type [hashtable], but received [$($Value.GetType().Name)]."
}
}
"securestring" {
if(($Value -isnot [string]) -and ($Value -isnot [System.Security.SecureString]) -and (-not (Test-ConfigurationDataMap -Value $Value))){
throw "Parameter [$Name] expects type [secureString], but received [$($Value.GetType().Name)]."
}
}
"credential" {
if(($Value -isnot [System.Management.Automation.PSCredential]) -and (-not (Test-ConfigurationDataMap -Value $Value))){
throw "Parameter [$Name] expects type [credential], but received [$($Value.GetType().Name)]."
}
}
default {
throw "Parameter [$Name] uses unsupported type [$TypeName]."
}
}
}

View File

@@ -0,0 +1,20 @@
function Test-ConfigurationDataValueIsInteger {
[CmdletBinding()]
Param(
[AllowNull()]
$Value
)
if($null -eq $Value -or $Value -is [bool]){
return $false
}
return ($Value -is [byte]) -or
($Value -is [sbyte]) -or
($Value -is [int16]) -or
($Value -is [uint16]) -or
($Value -is [int]) -or
($Value -is [uint32]) -or
($Value -is [long]) -or
($Value -is [uint64])
}

121
Readme.md
View File

@@ -1,4 +1,4 @@
# Resolve-DSCConfigurationData
# Resolve-DSCConfigurationData
`Resolve-DSCConfigurationData` resolves parameter, variable, and expression references in DSC configuration data.
@@ -47,6 +47,10 @@ This block shows all currently supported parameter properties.
MinLength = 2
MaxLength = 32
# Optional numeric range validation. Applies to numeric values such as Type = 'int'.
MinValue = 1
MaxValue = 65535
# Optional regex validation.
Pattern = '^[A-Za-z][A-Za-z0-9_-]*$'
@@ -75,11 +79,72 @@ This block shows all currently supported parameter properties.
Notes:
- `Value` wins over `DefaultValue`.
- `Required`, `AllowedValues`, `MinLength`, `MaxLength`, and `Pattern` are validated by the resolver.
- `Type`, `Required`, `AllowedValues`, `MinLength`, `MaxLength`, `MinValue`, `MaxValue`, and `Pattern` are validated by the resolver.
- `AllowedValues` validates scalar values directly and array values item by item.
- `Sensitive` is currently metadata only.
- `Deprecated.Message` can be a string or a localized hashtable.
- Optional string parameters can allow empty values with a pattern like `'^$|^[A-Za-z][A-Za-z0-9_-]*$'`.
Supported parameter types:
- `string`
- `int` / `integer`
- `bool` / `boolean`
- `array`
- `hashtable` / `object`
- `secureString`
- `credential`
`secureString` and `credential` are intended for secret references, not raw secrets in PSD1 files:
```powershell
FarmPassphrase = @{
Type = 'secureString'
Required = $true
Sensitive = $true
Value = @{
SecretName = 'SharePointFarmPassphrase'
}
}
SetupCredential = @{
Type = 'credential'
Required = $true
Sensitive = $true
Value = @{
CredentialName = 'SharePointSetup'
}
}
```
Array values can be restricted item by item:
```powershell
ServerRoles = @{
Type = 'array'
Value = @(
'WebFrontEnd',
'Application'
)
AllowedValues = @(
'WebFrontEnd',
'Application',
'Search'
)
}
```
Numeric values can be restricted with `MinValue` and `MaxValue`:
```powershell
SqlPort = @{
Type = 'int'
DefaultValue = 1433
MinValue = 1
MaxValue = 65535
}
```
## Variable Example
Variables may reference parameters and other variables. Nested variable references are supported. Circular references are rejected.
@@ -93,7 +158,7 @@ Variables may reference parameters and other variables. Nested variable referenc
}
DomainLabel = @{
Type = 'string'
Value = 'LAN'
Value = 'corp'
}
Landscape = @{
Type = 'string'
@@ -122,9 +187,9 @@ Example output:
```text
StageCode : TST
DatabasePrefix : SharePoint_LAN_TST
ServiceDbPrefix : SharePoint_LAN_TST_Services
ConfigDbName : SharePoint_LAN_TST_Farm_Config
DatabasePrefix : SharePoint_corp_TST
ServiceDbPrefix : SharePoint_corp_TST_Services
ConfigDbName : SharePoint_corp_TST_Farm_Config
```
## Functions
@@ -149,12 +214,12 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```powershell
"[format('{0}_{1}_{2}', parameters('DatabasePrefix'), parameters('DomainLabel'), parameters('Landscape'))]"
# SharePoint_LAN_Test
# SharePoint_corp_Test
```
```powershell
"[joinNotEmpty('_', parameters('DatabasePrefix'), parameters('DomainLabel'), '', 'Services')]"
# SharePoint_LAN_Services
# SharePoint_corp_Services
```
```powershell
@@ -185,13 +250,13 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
### Case And Text
```powershell
"[toLower('BGW-LAN')]"
# bgw-lan
"[toLower('Contoso-CORP')]"
# contoso-corp
```
```powershell
"[toUpper('bgw-lan')]"
# BGW-LAN
"[toUpper('contoso-corp')]"
# CONTOSO-CORP
```
```powershell
@@ -201,8 +266,8 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```
```powershell
"[replace('BGW-LAN', '-', '_')]"
# BGW_LAN
"[replace('Contoso-CORP', '-', '_')]"
# Contoso_CORP
```
```powershell
@@ -211,7 +276,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```
```powershell
"[indexOf('BGW', 1)]"
"[indexOf('CON', 1)]"
# G
```
@@ -228,23 +293,23 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
### Name Cleanup
```powershell
"[sanitizeName(' SharePoint LAN/Test DB ')]"
# SharePoint_LAN_Test_DB
"[sanitizeName(' SharePoint corp/Test DB ')]"
# SharePoint_corp_Test_DB
```
```powershell
"[sanitizeName(' SharePoint LAN/Test DB ', '-')]"
# SharePoint-LAN-Test-DB
"[sanitizeName(' SharePoint corp/Test DB ', '-')]"
# SharePoint-corp-Test-DB
```
```powershell
"[normalizeSeparator('__SharePoint___LAN_Test__', '_')]"
# SharePoint_LAN_Test
"[normalizeSeparator('__SharePoint___corp_Test__', '_')]"
# SharePoint_corp_Test
```
```powershell
"[prefixIfNotEmpty('LAN', 'BGW-')]"
# BGW-LAN
"[prefixIfNotEmpty('corp', 'Contoso-')]"
# Contoso-corp
```
```powershell
@@ -256,17 +321,17 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```powershell
"[split(parameters('DomainFQDN'), '.')]"
# @('bgw-online', 'de')
# @('contoso', 'com')
```
```powershell
"[join(split(parameters('DomainFQDN'), '.'), '_')]"
# bgw-online_de
# contoso_com
```
```powershell
"[first(split(parameters('DomainFQDN'), '.'))]"
# bgw-online
# contoso
```
```powershell
@@ -276,7 +341,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```powershell
"[take(split(parameters('DomainFQDN'), '.'), 1)]"
# @('bgw-online')
# @('contoso')
```
```powershell
@@ -307,7 +372,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```
```powershell
"[startsWith(parameters('DomainFQDN'), 'bgw')]"
"[startsWith(parameters('DomainFQDN'), 'contoso')]"
# True
```