Resolve-DSCConfigurationData

Resolve-DSCConfigurationData resolves parameter, variable, and expression references in DSC configuration data.

Typical flow:

$merged = Merge-DSCConfigurationData -Template $service -Deployment $environment
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged

Pipeline flow:

$resolved = Merge-DSCConfigurationData -Template $service -Deployment $environment |
    Resolve-DSCConfigurationData

Expressions use an ARM-like syntax:

"[parameters('DatabasePrefix')]"
"[variables('ServiceDbPrefix')]"
"[concat(parameters('DatabasePrefix'), '_', parameters('ServiceDatabaseSegment'))]"

Parameter Example

This block shows all currently supported parameter properties.

@{
    Parameters = @{
        DatabasePrefix = @{
            Type = 'string'

            # Optional explicit value. If Value is present, it wins over DefaultValue.
            Value = 'SharePoint'

            # Used when Value is not present.
            DefaultValue = 'SharePoint'

            # If true, Value or DefaultValue must be present and not empty.
            Required = $true

            # Optional fixed set of valid values.
            AllowedValues = @(
                'SharePoint',
                'ProjectServer',
                'Search'
            )

            # Optional string length validation.
            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_-]*$'

            # Metadata for later reporting/output tooling. The resolver does not mask values yet.
            Sensitive = $false

            # Emits a warning when the parameter is present.
            Deprecated = @{
                Message = @{
                    'de-DE' = 'Der Parameter [DatabasePrefix] ist veraltet. Verwende [SharePointDatabasePrefix].'
                    'en-US' = 'Parameter [DatabasePrefix] is deprecated. Use [SharePointDatabasePrefix].'
                }
            }

            Metadata = @{
                Description = @{
                    'de-DE' = 'Praefix fuer alle von der SharePoint-Farm angelegten Datenbanken.'
                    'en-US' = 'Prefix for all databases created by the SharePoint farm.'
                }
            }
        }
    }
}

Notes:

  • Value wins over DefaultValue.
  • 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:

FarmPassphrase = @{
    Type = 'secureString'
    Required = $true
    Sensitive = $true
    Value = @{
        Provider = 'KeePass'
        Vault = 'BGW'
        Name = 'SharePoint/FarmPassphrase'
    }
}

SetupCredential = @{
    Type = 'credential'
    Required = $true
    Sensitive = $true
    Value = @{
        Provider = 'KeePass'
        Vault = 'BGW'
        Name = 'SharePoint/SetupAccount'
        UserName = 'BGW\SVC_SHP_SETUP'
    }
}

Secret references are validated and resolved by Resolve-DSCConfigurationData before the normal parameter and variable resolve step:

$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged -ProviderSettings @{
    KeePass = @{
        DefaultVault = 'BGW'
    }
}

Use -SkipSecrets when you only want structural validation/resolution without loading provider secrets.

Secret Provider Files

Secret providers are loaded automatically from the module folder Providers. The naming convention is:

Providers\Provider.<Name>.ps1

Each provider registers itself with the same schema:

$MyProvider = @{
    Name = 'MyProvider'
    SupportedTypes = @(
        'credential',
        'securestring',
        'string'
    )
    Resolver = {
        param(
            [System.Collections.IDictionary] $Reference,
            [string] $ExpectedType,
            [hashtable] $ProviderSettings
        )

        # Return a PSCredential for ExpectedType = credential,
        # a SecureString for ExpectedType = securestring,
        # or a string for ExpectedType = string.
    }
}

Register-ConfigurationDataSecretProvider @MyProvider

The KeePass provider is implemented in:

Providers\Provider.KeePass.ps1

Array values can be restricted item by item:

ServerRoles = @{
    Type = 'array'
    Value = @(
        'WebFrontEnd',
        'Application'
    )
    AllowedValues = @(
        'WebFrontEnd',
        'Application',
        'Search'
    )
}

Numeric values can be restricted with MinValue and MaxValue:

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.

@{
    Parameters = @{
        DatabasePrefix = @{
            Type = 'string'
            DefaultValue = 'SharePoint'
        }
        DomainLabel = @{
            Type = 'string'
            Value = 'corp'
        }
        Landscape = @{
            Type = 'string'
            Value = 'Test'
            AllowedValues = @(
                'Prod',
                'Test'
            )
        }
        ServiceDatabaseSegment = @{
            Type = 'string'
            DefaultValue = 'Services'
        }
    }

    Variables = @{
        StageCode = "[if(equals(parameters('Landscape'), 'Test'), 'TST', 'PRD')]"
        DatabasePrefix = "[joinNotEmpty('_', parameters('DatabasePrefix'), parameters('DomainLabel'), variables('StageCode'))]"
        ServiceDbPrefix = "[joinNotEmpty('_', variables('DatabasePrefix'), parameters('ServiceDatabaseSegment'))]"
        ConfigDbName = "[joinNotEmpty('_', variables('DatabasePrefix'), 'Farm_Config')]"
    }
}

Example output:

StageCode       : TST
DatabasePrefix  : SharePoint_corp_TST
ServiceDbPrefix : SharePoint_corp_TST_Services
ConfigDbName    : SharePoint_corp_TST_Farm_Config

Functions

References

"[parameters('DatabasePrefix')]"
"[variables('ServiceDbPrefix')]"

parameters(name) returns the effective parameter value. Value is used before DefaultValue.

variables(name) resolves another variable. Variables may reference other variables.

String Composition

"[concat('SharePoint', '_', 'Services')]"
# SharePoint_Services
"[format('{0}_{1}_{2}', parameters('DatabasePrefix'), parameters('DomainLabel'), parameters('Landscape'))]"
# SharePoint_corp_Test
"[joinNotEmpty('_', parameters('DatabasePrefix'), parameters('DomainLabel'), '', 'Services')]"
# SharePoint_corp_Services
"[defaultIfEmpty(parameters('DatabasePrefix'), 'SharePoint')]"
# SharePoint, when DatabasePrefix is empty
"[coalesce(parameters('CustomPrefix'), parameters('DatabasePrefix'), 'SharePoint')]"
# First non-empty value

Conditions And Boolean Logic

"[if(equals(parameters('Landscape'), 'Test'), 'TST', 'PRD')]"
# TST
"[equals(parameters('Landscape'), 'Test')]"
"[notEquals(parameters('Landscape'), 'Prod')]"
"[and(equals(parameters('Landscape'), 'Test'), not(empty(parameters('DatabasePrefix'))))]"
"[or(equals(parameters('Landscape'), 'Prod'), equals(parameters('Landscape'), 'Test'))]"
"[not(empty(parameters('DatabasePrefix')))]"

Case And Text

"[toLower('Contoso-CORP')]"
# contoso-corp
"[toUpper('contoso-corp')]"
# CONTOSO-CORP
"[trim('  SharePoint  ')]"
"[trimStart('  SharePoint')]"
"[trimEnd('SharePoint  ')]"
"[replace('Contoso-CORP', '-', '_')]"
# Contoso_CORP
"[substring('SharePoint', 5, 5)]"
# Point
"[indexOf('CON', 1)]"
# G

firstIndexOf and lastIndexOf return the first or last function argument:

"[firstIndexOf('a', 'b', 'c')]"
# a

"[lastIndexOf('a', 'b', 'c')]"
# c

Name Cleanup

"[sanitizeName(' SharePoint corp/Test DB ')]"
# SharePoint_corp_Test_DB
"[sanitizeName(' SharePoint corp/Test DB ', '-')]"
# SharePoint-corp-Test-DB
"[normalizeSeparator('__SharePoint___corp_Test__', '_')]"
# SharePoint_corp_Test
"[prefixIfNotEmpty('corp', 'Contoso-')]"
# Contoso-corp
"[suffixIfNotEmpty('SharePoint', '_DB')]"
# SharePoint_DB

Collections

"[split(parameters('DomainFQDN'), '.')]"
# @('contoso', 'com')
"[join(split(parameters('DomainFQDN'), '.'), '_')]"
# contoso_com
"[first(split(parameters('DomainFQDN'), '.'))]"
# contoso
"[last(split(parameters('DomainFQDN'), '.'))]"
# de
"[take(split(parameters('DomainFQDN'), '.'), 1)]"
# @('contoso')
"[skip(split(parameters('DomainFQDN'), '.'), 1)]"
# @('de')
"[unique(split('SP.SP.SQL', '.'))]"
# @('SP', 'SQL')
"[sort(split('SQL.SP.APP', '.'))]"
# @('APP', 'SP', 'SQL')

Inspection

"[contains(parameters('DomainFQDN'), 'online')]"
# True
"[contains(split(parameters('DomainFQDN'), '.'), 'de')]"
# True
"[startsWith(parameters('DomainFQDN'), 'contoso')]"
# True
"[endsWith(parameters('DomainFQDN'), 'de')]"
# True
"[length(split(parameters('DomainFQDN'), '.'))]"
# 2
"[empty(parameters('OptionalValue'))]"
# True, when OptionalValue is empty

Padding

"[padLeft('1', 2, '0')]"
# 01
"[padRight('SP', 4, '0')]"
# SP00

Full Function List

  • parameters(name)
  • variables(name)
  • concat(value1, value2, ...)
  • format(formatString, value1, value2, ...)
  • coalesce(value1, value2, ...)
  • defaultIfEmpty(value, defaultValue)
  • if(condition, trueValue, falseValue)
  • equals(left, right)
  • notEquals(left, right)
  • and(value1, value2, ...)
  • or(value1, value2, ...)
  • not(value)
  • toLower(value)
  • toUpper(value)
  • firstIndexOf(value1, value2, ...)
  • lastIndexOf(value1, value2, ...)
  • indexOf(value, index)
  • substring(value, startIndex)
  • substring(value, startIndex, length)
  • replace(value, oldValue, newValue)
  • sanitizeName(value)
  • sanitizeName(value, separator)
  • normalizeSeparator(value, separator)
  • prefixIfNotEmpty(value, prefix)
  • suffixIfNotEmpty(value, suffix)
  • contains(value, search)
  • startsWith(value, search)
  • endsWith(value, search)
  • split(value, separator)
  • join(array, separator)
  • joinNotEmpty(separator, value1, value2, ...)
  • take(array, count)
  • skip(array, count)
  • first(array)
  • last(array)
  • unique(array)
  • sort(array)
  • trim(value)
  • trimStart(value)
  • trimEnd(value)
  • padLeft(value, totalWidth)
  • padLeft(value, totalWidth, paddingCharacter)
  • padRight(value, totalWidth)
  • padRight(value, totalWidth, paddingCharacter)
  • length(value)
  • empty(value)

Validation

Run the tests:

Invoke-Pester -Script '.\PowerShell\Resolve-DSCConfigurationData\.tests\Resolve-DSCConfigurationData.Tests.ps1'
Description
No description provided
Readme 135 KiB
Languages
PowerShell 100%