17 KiB
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:
Valuewins overDefaultValue.Type,Required,AllowedValues,MinLength,MaxLength,MinValue,MaxValue, andPatternare validated by the resolver.AllowedValuesvalidates scalar values directly and array values item by item.Sensitiveis currently metadata only.Sealed = $trueon a parameter seals the whole parameter definition during merge. Child templates cannot change any property of that parameter.Deprecated.Messagecan 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:
stringint/integerbool/booleanarrayhashtable/objectsecureStringcredential
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: usesMicrosoft.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'