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

            # Prevents child templates from changing this parameter definition during merge.
            Sealed = $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.
  • Sealed = $true on a parameter seals the whole parameter definition during merge. Child templates cannot change any property of that parameter.
  • 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 = 'SecretManagement'
        Vault = 'Contoso'
        Name = 'SharePoint/FarmPassphrase'
    }
}

SetupCredential = @{
    Type = 'credential'
    Required = $true
        Sensitive = $true
        Value = @{
        Provider = 'SecretManagement'
        Vault = 'Contoso'
        Name = 'Application/SetupAccount'
        UserName = 'CONTOSO\svc-app-setup'
    }
}

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

$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged -ProviderSettings @{
    SecretManagement = @{
        DefaultVault = 'Contoso'
    }
}

Use -SkipSecrets when you only want structural validation/resolution without loading provider secrets. Secret references are replaced with typed dummy values, so references such as reference(..., 'UserName') keep working in tests and previews.

$preview = Resolve-DSCConfigurationData -ConfigurationData $merged -SkipSecrets

SecretManagement is the only built-in secret resolver provider. KeePass, SecretStore, Azure Key Vault, and other backends should be registered as SecretManagement vaults.

KeePass can be registered through the SecretManagement.KeePass vault extension:

Register-DSCConfigurationDataCredentialProvider `
    -Provider SecretManagement `
    -Vault Contoso `
    -VaultType KeePass `
    -DatabasePath 'C:\DSC\Contoso\Secrets.kdbx' `
    -KeyPath 'C:\DSC\Contoso\Secrets.key' `
    -UseMasterPassword `
    -RegisterVault `
    -DefaultVault `
    -SettingsPath 'C:\DSC\Contoso'

For unattended KeePass vaults that still require a master password, store the master password as a protected SecureString in the provider settings. The password is encrypted with an AES key file and the vault is unlocked automatically during Resolve-DSCConfigurationData:

$masterPassword = Read-Host -Prompt 'KeePass Master Password' -AsSecureString

Register-DSCConfigurationDataCredentialProvider `
    -Provider SecretManagement `
    -Vault Contoso `
    -VaultType KeePass `
    -DatabasePath 'C:\DSC\Contoso\Secrets.kdbx' `
    -KeyPath 'C:\DSC\Contoso\Secrets.key' `
    -UseMasterPassword `
    -MasterPassword $masterPassword `
    -MasterPasswordKeyPath 'C:\DSC\Contoso\KeePass-MasterPassword.key' `
    -RegisterVault `
    -DefaultVault `
    -SettingsPath 'C:\DSC\Contoso'

Generic SecretManagement vaults can be registered through the same entry point:

Register-DSCConfigurationDataCredentialProvider `
    -Provider SecretManagement `
    -Vault LocalStore `
    -ModuleName Microsoft.PowerShell.SecretStore `
    -RegisterVault `
    -DefaultVault `
    -SettingsPath 'C:\DSC\Contoso'

Provider setup can be removed again:

Unregister-DSCConfigurationDataCredentialProvider `
    -Provider SecretManagement `
    -Vault Contoso `
    -SettingsPath 'C:\DSC\Contoso' `
    -UnregisterVault

Then use it directly:

$resolved = Resolve-DSCConfigurationData `
    -ConfigurationData $merged `
    -ProviderSettingsPath 'C:\DSC\Contoso\ProviderSettings.SecretManagement.psd1'

Sealed Template Blocks

Sealed = $true can also be placed on any hashtable block in the configuration data. During merge, child templates cannot add or overwrite anything at that node or below it.

Resources = @{
    NonNodeData = @{
        Services = @{
            SharePoint = @{
                Farm = @{
                    ManagedAccounts = @{
                        Sealed = $true

                        FarmAccount = "[parameters('FarmCredential')]"
                    }
                }
            }
        }
    }
}

The Sealed marker is kept during merge so later merge steps can enforce it. Resolve-DSCConfigurationData removes the marker from the final resolved data so DSC resource loops do not see it as a normal configuration item.

Inspect the configured provider and registered vault:

Get-DSCConfigurationDataCredentialProvider `
    -Provider SecretManagement `
    -SettingsPath 'C:\DSC\Contoso' `
    -TestVault

Update an existing KeePass-backed SecretManagement provider registration, for example to enable master-password based unlocks:

$masterPassword = Read-Host -Prompt 'KeePass Master Password' -AsSecureString

Set-DSCConfigurationDataCredentialProvider `
    -Provider SecretManagement `
    -SettingsPath 'C:\DSC\Contoso' `
    -UseMasterPassword `
    -MasterPassword $masterPassword `
    -MasterPasswordKeyPath 'C:\DSC\Contoso\KeePass-MasterPassword.key' `
    -VaultParameters @{
        ShowFullTitle = $true
    } `
    -RegisterVault `
    -AllowClobber `
    -Force

Secret Provider

Only one built-in provider is registered:

  • SecretManagement: uses Microsoft.PowerShell.SecretManagement / Get-Secret

Backend-specific behavior belongs to the registered SecretManagement vault extension. For example, KeePass is handled by SecretManagement.KeePass, SecretStore by Microsoft.PowerShell.SecretStore, and Azure Key Vault by the matching SecretManagement vault extension.

SecretManagement example:

SetupCredential = @{
    Type = 'credential'
    Required = $true
    Sensitive = $true
    Value = @{
        Provider = 'SecretManagement'
        Vault = 'LocalStore'
        Name = 'SharePointSetupCredential'
    }
}

SecretStore example through SecretManagement:

FarmPassphrase = @{
    Type = 'secureString'
    Required = $true
    Sensitive = $true
    Value = @{
        Provider = 'SecretManagement'
        Vault = 'LocalStore'
        Name = 'SharePointFarmPassphrase'
    }
}

Azure Key Vault example through SecretManagement:

SetupCredential = @{
    Type = 'credential'
    Required = $true
    Sensitive = $true
    Value = @{
        Provider = 'SecretManagement'
        Vault = 'contoso-kv'
        Name = 'app-setup-password'
        UserName = 'CONTOSO\svc-app-setup'
    }
}

FarmPassphrase = @{
    Type = 'secureString'
    Required = $true
    Sensitive = $true
    Value = @{
        Provider = 'SecretManagement'
        Vault = 'contoso-kv'
        Name = 'farm-passphrase'
    }
}

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')]"
"[reference('Resources.NonNodeData.Services.SharePoint.Farm.ManagedAccounts.FarmAccount')]"
"[reference('Resources.NonNodeData.Services.SharePoint.Farm.ManagedAccounts.FarmAccount', 'UserName')]"

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

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

reference(path) resolves another value from the configuration data by path. reference(path, property) resolves the value and then returns a property from it, such as UserName from a PSCredential.

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)
  • reference(path)
  • reference(path, property)
  • 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%