16 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
# 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.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 = 'KeePass'
Vault = 'Contoso'
Name = 'SharePoint/FarmPassphrase'
}
}
SetupCredential = @{
Type = 'credential'
Required = $true
Sensitive = $true
Value = @{
Provider = 'KeePass'
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 @{
KeePass = @{
DefaultVault = 'Contoso'
}
}
Use -SkipSecrets when you only want structural validation/resolution without loading provider secrets.
For unattended KeePass access, pass the KeePass master key through ProviderSettings, not through the PSD1 parameter definition:
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged -ProviderSettings @{
KeePass = @{
DefaultVault = 'Contoso'
MasterKey = (Get-Secret -Name 'KeePass-Contoso-MasterKey')
}
}
You can generate the provider settings file with:
Register-DSCConfigurationDataCredentialProvider `
-Provider KeePass `
-Vault Contoso `
-KeyPath 'C:\DSC\Contoso\KeePass-Contoso.key' `
-SettingsPath 'C:\DSC\Contoso'
SecretManagement can be initialized through the same entry point:
Register-DSCConfigurationDataCredentialProvider `
-Provider SecretManagement `
-Vault LocalStore `
-ModuleName Microsoft.PowerShell.SecretStore `
-RegisterVault `
-DefaultVault `
-SettingsPath 'C:\DSC\Contoso'
SecretStore can also be configured for unattended local usage:
Register-DSCConfigurationDataCredentialProvider `
-Provider SecretStore `
-Vault LocalStore `
-ConfigureSecretStore `
-Authentication None `
-Interaction None `
-RegisterVault `
-DefaultVault `
-SettingsPath 'C:\DSC\Contoso'
Azure Key Vault can be registered as a settings file without storing Azure credentials. Authentication is expected to come from the active Az context, managed identity, service principal login, or another external Azure authentication flow:
Register-DSCConfigurationDataCredentialProvider `
-Provider AzureKeyVault `
-Vault contoso-kv `
-SubscriptionId '00000000-0000-0000-0000-000000000000' `
-TenantId '11111111-1111-1111-1111-111111111111' `
-SettingsPath 'C:\DSC\Contoso'
Provider setup can be removed again:
Unregister-DSCConfigurationDataCredentialProvider `
-Provider KeePass `
-Vault Contoso `
-SettingsPath 'C:\DSC\Contoso' `
-KeyPath 'C:\DSC\Contoso\KeePass-Contoso.key' `
-RemoveKeyFile
For SecretManagement or SecretStore vault registration:
Unregister-DSCConfigurationDataCredentialProvider `
-Provider SecretStore `
-Vault LocalStore `
-SettingsPath 'C:\DSC\Contoso' `
-UnregisterVault
Use -ResetSecretStore only when you intentionally want to delete all secrets from the local SecretStore.
Then use it directly:
$resolved = Resolve-DSCConfigurationData `
-ConfigurationData $merged `
-ProviderSettingsPath 'C:\DSC\Contoso\ProviderSettings.KeePass.psd1'
For portable unattended tests, use an AES key file with ConvertFrom-SecureString -Key:
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged -ProviderSettings @{
KeePass = @{
DefaultVault = 'Test'
MasterKey = @{
ProtectedValue = '76492d1116743f0423413b16050a5345...'
KeyPath = 'F:\Secrets\KeePass-Test.key'
}
}
}
MasterKey also supports:
MasterKey = @{
EnvironmentVariable = 'KEEPASS_TEST_MASTERKEY'
}
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
Built-in providers:
KeePass: usesPoShKeePass/Get-KeePassEntrySecretManagement: usesMicrosoft.PowerShell.SecretManagement/Get-SecretSecretStore: convenience provider for local SecretStore vaults throughGet-SecretAzureKeyVault: usesAz.KeyVault/Get-AzKeyVaultSecret
SecretManagement example:
SetupCredential = @{
Type = 'credential'
Required = $true
Sensitive = $true
Value = @{
Provider = 'SecretManagement'
Vault = 'LocalStore'
Name = 'SharePointSetupCredential'
}
}
SecretStore example:
FarmPassphrase = @{
Type = 'secureString'
Required = $true
Sensitive = $true
Value = @{
Provider = 'SecretStore'
Vault = 'LocalStore'
Name = 'SharePointFarmPassphrase'
}
}
Azure Key Vault example:
SetupCredential = @{
Type = 'credential'
Required = $true
Sensitive = $true
Value = @{
Provider = 'AzureKeyVault'
Vault = 'contoso-kv'
Name = 'app-setup-password'
UserName = 'CONTOSO\svc-app-setup'
}
}
FarmPassphrase = @{
Type = 'secureString'
Required = $true
Sensitive = $true
Value = @{
Provider = 'AzureKeyVault'
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')]"
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'