Compare commits

...

11 Commits

Author SHA1 Message Date
Torsten Brendgen
4b67c74ac0 Enhance configuration data handling with new reference resolution functions, dummy secret support, and update module version to 1.1.0 2026-07-07 21:50:50 +02:00
Torsten Brendgen
45e71c093c Refactor secret management providers and introduce unified handling
- Removed KeePass and SecretStore provider implementations.
- Integrated KeePass and SecretStore as vaults under the SecretManagement provider.
- Added new functions: Get-DSCConfigurationDataCredentialProvider and Set-DSCConfigurationDataCredentialProvider for managing credential providers.
- Implemented Unlock-ConfigurationDataSecretManagementVault to handle vault unlocking with master passwords.
- Updated README to reflect changes in provider usage and examples.
- Enhanced error handling and validation for vault registration and settings.
2026-07-06 22:50:39 +02:00
Torsten Brendgen
3fa52d7ada Remove obsolete test file for credential providers 2026-07-03 12:52:45 +02:00
Torsten Brendgen
4b7ebe59ef Bump module version to 1.0.1 in the module manifest 2026-07-03 11:59:16 +02:00
Torsten Brendgen
943894b8fe Add Azure Key Vault support and enhance credential handling in configuration data 2026-07-03 10:34:25 +02:00
Torsten Brendgen
9b36693444 Add support for SecretManagement and SecretStore providers, enhance configuration data handling, and introduce new utility functions 2026-07-02 23:57:14 +02:00
Torsten Brendgen
115b8be385 Add secret provider functionality and enhance configuration data resolution 2026-07-02 14:36:04 +02:00
Torsten Brendgen
1cd225c7a0 Enhance configuration data validation and add new utility functions
- Added validation for empty values in Assert-ConfigurationDataParameter.
- Introduced Assert-ConfigurationDataSecretReference for secret reference validation.
- Implemented Test-ConfigurationDataExpressionParentheses to validate expression syntax.
- Added Test-ConfigurationDataMissingReferenceError to check for missing parameter or variable definitions.
- Updated Invoke-ConfigurationDataExpression to handle new validation logic.
- Revised Readme.md to reflect changes in secret reference handling.
2026-07-01 20:34:59 +02:00
88b842c8e9 .tests/Resolve-DSCConfigurationData.Tests.ps1 gelöscht 2026-06-27 12:30:18 +00:00
Torsten Brendgen
a96fb5bd07 Merge branch 'master' of https://git.local.unique-studios.de/PowerShell-Projekte/Resolve-DSCConfigurationData 2026-06-27 14:23:18 +02:00
Torsten Brendgen
086020c3df Add parameter validation functions and enhance tests for configuration data 2026-06-27 14:22:37 +02:00
40 changed files with 2188 additions and 35 deletions

View File

@@ -42,6 +42,11 @@ function Assert-ConfigurationDataParameter {
return
}
if((Test-ConfigurationDataValueIsEmpty -Value $Value) -and (-not $Required)){
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 +69,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,70 @@
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(Test-ConfigurationDataSecretReference -Value $Value){
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "string" -Value $Value
return
}
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)]."
}
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "securestring" -Value $Value
}
"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)]."
}
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "credential" -Value $Value
}
default {
throw "Parameter [$Name] uses unsupported type [$TypeName]."
}
}
}

View File

@@ -0,0 +1,47 @@
function Assert-ConfigurationDataSecretReference {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Name,
[Parameter(Mandatory=$true)]
[ValidateSet("credential", "securestring", "string")]
[string]
$TypeName,
[Parameter(Mandatory=$true)]
$Value
)
if(-not (Test-ConfigurationDataMap -Value $Value)){
return
}
if(-not (Test-ConfigurationDataMapContainsKey -Map $Value -Key "Provider")){
throw "Parameter [$Name] uses type [$TypeName] and a secret reference, but [Provider] is not defined."
}
if(-not (Test-ConfigurationDataMapContainsKey -Map $Value -Key "Name")){
throw "Parameter [$Name] uses type [$TypeName] and a secret reference, but [Name] is not defined."
}
$Provider = [string](Get-ConfigurationDataMapValue -Map $Value -Key "Provider")
if([string]::IsNullOrWhiteSpace($Provider)){
throw "Parameter [$Name] secret reference [Provider] must not be empty."
}
$SecretName = [string](Get-ConfigurationDataMapValue -Map $Value -Key "Name")
if([string]::IsNullOrWhiteSpace($SecretName)){
throw "Parameter [$Name] secret reference [Name] must not be empty."
}
$ProviderDefinition = Get-ConfigurationDataSecretProvider -Name $Provider
if($null -eq $ProviderDefinition){
throw "Parameter [$Name] uses unsupported secret provider [$Provider]."
}
if($ProviderDefinition.SupportedTypes -notcontains $TypeName.ToLowerInvariant()){
throw "Parameter [$Name] uses provider [$Provider], but it does not support type [$TypeName]."
}
}

View File

@@ -0,0 +1,101 @@
function ConvertFrom-ConfigurationDataSecretValue {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[AllowNull()]
$Secret,
[Parameter(Mandatory=$true)]
[ValidateSet("credential", "securestring", "string")]
[string]
$ExpectedType,
[Parameter(Mandatory=$false)]
[string]
$Name = "",
[Parameter(Mandatory=$false)]
[string]
$UserName = ""
)
if($null -eq $Secret){
throw "Secret [$Name] was not found."
}
if($ExpectedType -eq "credential"){
if($Secret -is [System.Management.Automation.PSCredential]){
return $Secret
}
if($Secret -is [System.Collections.IDictionary]){
if([string]::IsNullOrWhiteSpace($UserName) -and $Secret.Contains("UserName")){
$UserName = [string]$Secret["UserName"]
}
if($Secret.Contains("Password")){
return ConvertTo-ConfigurationDataCredential -UserName $UserName -Password $Secret["Password"]
}
}
if($Secret.PSObject.Properties["UserName"] -and [string]::IsNullOrWhiteSpace($UserName)){
$UserName = [string]$Secret.UserName
}
if($Secret.PSObject.Properties["Password"]){
return ConvertTo-ConfigurationDataCredential -UserName $UserName -Password $Secret.Password
}
if([string]::IsNullOrWhiteSpace($UserName)){
throw "Secret [$Name] cannot be converted to [PSCredential] because no username was provided."
}
return ConvertTo-ConfigurationDataCredential -UserName $UserName -Password $Secret
}
if($ExpectedType -eq "securestring"){
if($Secret -is [System.Security.SecureString]){
return $Secret
}
if($Secret -is [System.Management.Automation.PSCredential]){
return $Secret.Password
}
if($Secret -is [System.Collections.IDictionary] -and $Secret.Contains("Password")){
$Secret = $Secret["Password"]
}elseif($Secret.PSObject.Properties["Password"]){
$Secret = $Secret.Password
}
if($Secret -is [System.Security.SecureString]){
return $Secret
}
return ConvertTo-SecureString -String ([string]$Secret) -AsPlainText -Force
}
if($Secret -is [System.Management.Automation.PSCredential]){
return $Secret.GetNetworkCredential().Password
}
if($Secret -is [System.Security.SecureString]){
$Pointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secret)
try {
return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($Pointer)
}
finally {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Pointer)
}
}
if($Secret -is [System.Collections.IDictionary] -and $Secret.Contains("Password")){
return [string]$Secret["Password"]
}
if($Secret.PSObject.Properties["Password"]){
return [string]$Secret.Password
}
return [string]$Secret
}

View File

@@ -0,0 +1,19 @@
function ConvertTo-ConfigurationDataCredential {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$UserName,
[Parameter(Mandatory=$true)]
$Password
)
if($Password -is [System.Security.SecureString]){
$SecurePassword = $Password
}else{
$SecurePassword = ConvertTo-SecureString -String ([string]$Password) -AsPlainText -Force
}
return [System.Management.Automation.PSCredential]::new($UserName, $SecurePassword)
}

View File

@@ -0,0 +1,63 @@
function ConvertTo-PowerShellDataFileText {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[AllowNull()]
$InputObject,
[Parameter(Mandatory=$false)]
[int]
$Indent = 0
)
$IndentText = " " * $Indent
$ChildIndent = $Indent + 4
$ChildIndentText = " " * $ChildIndent
if($null -eq $InputObject){
return '$null'
}
if($InputObject -is [bool]){
if($InputObject){
return '$true'
}
return '$false'
}
if($InputObject -is [int] -or $InputObject -is [long] -or $InputObject -is [decimal] -or $InputObject -is [double]){
return ([string]$InputObject)
}
if($InputObject -is [string]){
return "'$($InputObject.Replace("'", "''"))'"
}
if($InputObject -is [System.Collections.IDictionary]){
$Lines = @("@{")
foreach($Key in $InputObject.Keys){
$ValueText = ConvertTo-PowerShellDataFileText -InputObject $InputObject[$Key] -Indent $ChildIndent
$Lines += "$ChildIndentText$Key = $ValueText"
}
$Lines += "$IndentText}"
return ($Lines -join [Environment]::NewLine)
}
if($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]){
$Items = @($InputObject)
if($Items.Count -eq 0){
return '@()'
}
$Lines = @("@(")
foreach($Item in $Items){
$ValueText = ConvertTo-PowerShellDataFileText -InputObject $Item -Indent $ChildIndent
$Lines += "$ChildIndentText$ValueText"
}
$Lines += "$IndentText)"
return ($Lines -join [Environment]::NewLine)
}
return "'$(([string]$InputObject).Replace("'", "''"))'"
}

View File

@@ -0,0 +1,28 @@
function Export-PowerShellDataFile {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[System.Collections.IDictionary]
$InputObject,
[Parameter(Mandatory=$true)]
[string]
$Path,
[Parameter(Mandatory=$false)]
[switch]
$Force
)
if((Test-Path -Path $Path -PathType Leaf) -and (-not $Force)){
throw "File [$Path] already exists. Use -Force to overwrite it."
}
$Parent = Split-Path -Path $Path -Parent
if(-not [string]::IsNullOrWhiteSpace($Parent) -and -not (Test-Path -Path $Parent)){
New-Item -Path $Parent -ItemType Directory -Force | Out-Null
}
$Text = ConvertTo-PowerShellDataFileText -InputObject $InputObject
Set-Content -Path $Path -Value $Text -Encoding UTF8
}

View File

@@ -6,8 +6,15 @@ function Get-ConfigurationDataMapValue {
[Parameter(Mandatory=$true)]
[string]
$Key
$Key,
[AllowNull()]
$DefaultValue = $null
)
if(-not (Test-ConfigurationDataMapContainsKey -Map $Map -Key $Key)){
return $DefaultValue
}
return $Map[$Key]
}

View File

@@ -0,0 +1,39 @@
function Get-ConfigurationDataObjectPropertyValue {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[AllowNull()]
$Value,
[Parameter(Mandatory=$true)]
[string]
$Property,
[Parameter(Mandatory=$false)]
[string]
$Path = ""
)
if([string]::IsNullOrWhiteSpace($Property)){
throw "Configuration data reference property must not be empty."
}
if($null -eq $Value){
throw "Configuration data reference property [$Property] was requested from a null value at path [$Path]."
}
if($Value -is [System.Collections.IDictionary]){
if(-not $Value.Contains($Property)){
throw "Configuration data reference property [$Property] was not found at path [$Path]."
}
return $Value[$Property]
}
$ObjectProperty = $Value.PSObject.Properties[$Property]
if($null -eq $ObjectProperty){
throw "Configuration data reference property [$Property] was not found at path [$Path]."
}
return $ObjectProperty.Value
}

View File

@@ -0,0 +1,61 @@
function Get-ConfigurationDataPathValue {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[AllowNull()]
$Value,
[Parameter(Mandatory=$true)]
[string]
$Path
)
if([string]::IsNullOrWhiteSpace($Path)){
throw "Configuration data reference path must not be empty."
}
$Current = $Value
$Segments = @($Path -split '\.')
foreach($Segment in $Segments){
if([string]::IsNullOrWhiteSpace($Segment)){
throw "Configuration data reference path [$Path] contains an empty segment."
}
if($null -eq $Current){
throw "Configuration data reference path [$Path] was not found. Segment [$Segment] resolved from a null value."
}
if($Current -is [System.Collections.IDictionary]){
if(-not $Current.Contains($Segment)){
throw "Configuration data reference path [$Path] was not found. Segment [$Segment] is missing."
}
$Current = $Current[$Segment]
continue
}
if($Current -is [System.Array] -and $Current -isnot [string]){
$Index = 0
if(-not [int]::TryParse($Segment, [ref]$Index)){
throw "Configuration data reference path [$Path] expected a numeric array index at segment [$Segment]."
}
if($Index -lt 0 -or $Index -ge @($Current).Count){
throw "Configuration data reference path [$Path] array index [$Index] is out of range."
}
$Current = @($Current)[$Index]
continue
}
$Property = $Current.PSObject.Properties[$Segment]
if($null -eq $Property){
throw "Configuration data reference path [$Path] was not found. Property [$Segment] is missing."
}
$Current = $Property.Value
}
return $Current
}

View File

@@ -0,0 +1,19 @@
function Get-ConfigurationDataSecretProvider {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Name
)
if($null -eq $script:ConfigurationDataSecretProviders){
$script:ConfigurationDataSecretProviders = @{}
}
$ProviderName = $Name.ToLowerInvariant()
if(-not $script:ConfigurationDataSecretProviders.ContainsKey($ProviderName)){
return $null
}
return $script:ConfigurationDataSecretProviders[$ProviderName]
}

View File

@@ -0,0 +1,14 @@
function Get-ConfigurationDataSecretProviderName {
[CmdletBinding()]
Param()
if($null -eq $script:ConfigurationDataSecretProviders){
$script:ConfigurationDataSecretProviders = @{}
}
return @(
$script:ConfigurationDataSecretProviders.Values |
Sort-Object -Property Name |
ForEach-Object { $_.Name }
)
}

View File

@@ -23,12 +23,40 @@ function Invoke-ConfigurationDataExpression {
return [bool]::Parse($Expression)
}
if(-not (Test-ConfigurationDataExpressionParentheses -Expression $Expression)){
throw "Invalid configuration data expression [$Expression]."
}
if($Expression -notmatch "^([A-Za-z][A-Za-z0-9]*)\((.*)\)$"){
throw "Invalid configuration data expression [$Expression]."
}
$FunctionName = $Matches[1]
$ArgumentText = $Matches[2]
if($FunctionName -ieq "coalesce"){
$RawArguments = @(Split-ConfigurationDataExpressionArguments -ArgumentText $ArgumentText)
Assert-ConfigurationDataExpressionMinimumArgumentCount -Name $FunctionName -Arguments $RawArguments -Count 1
foreach($Argument in $RawArguments){
try {
$Value = Invoke-ConfigurationDataExpressionArgument -Argument $Argument -Context $Context
if(-not (Test-ConfigurationDataValueIsEmpty -Value $Value)){
return $Value
}
}
catch {
if(Test-ConfigurationDataMissingReferenceError -ErrorRecord $_){
continue
}
throw
}
}
return $null
}
$Arguments = @()
foreach($Argument in @(Split-ConfigurationDataExpressionArguments -ArgumentText $ArgumentText)){
$Arguments += ,(Invoke-ConfigurationDataExpressionArgument -Argument $Argument -Context $Context)

View File

@@ -24,6 +24,17 @@ function Invoke-ConfigurationDataExpressionFunction {
Assert-ConfigurationDataExpressionArgumentCount -Name $Name -Arguments $Arguments -Count 1
return Resolve-ConfigurationDataVariable -Name ([string]$Arguments[0]) -Context $Context
}
"reference" {
if($Arguments.Count -eq 1){
return Resolve-ConfigurationDataReference -Path ([string]$Arguments[0]) -Context $Context
}
if($Arguments.Count -eq 2){
return Resolve-ConfigurationDataReference -Path ([string]$Arguments[0]) -Property ([string]$Arguments[1]) -Context $Context
}
throw "Function [$Name] expects 1 or 2 arguments, but received [$($Arguments.Count)]."
}
"concat" {
return (@($Arguments) | ForEach-Object { [string]$_ }) -join ""
}

View File

@@ -0,0 +1,31 @@
function New-ConfigurationDataAesKeyFile {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Path,
[Parameter(Mandatory=$false)]
[switch]
$Force
)
if((Test-Path -Path $Path -PathType Leaf) -and (-not $Force)){
$Key = [Convert]::FromBase64String((Get-Content -Path $Path -Raw).Trim())
if($Key.Length -notin @(16, 24, 32)){
throw "Existing key file [$Path] does not contain a valid AES key length."
}
return $Key
}
$Parent = Split-Path -Path $Path -Parent
if(-not [string]::IsNullOrWhiteSpace($Parent) -and -not (Test-Path -Path $Parent)){
New-Item -Path $Parent -ItemType Directory -Force | Out-Null
}
$Key = New-Object byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($Key)
Set-Content -Path $Path -Value ([Convert]::ToBase64String($Key)) -Encoding ASCII
return $Key
}

View File

@@ -0,0 +1,68 @@
function New-ConfigurationDataDummySecretValue {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[ValidateSet("credential", "securestring", "string")]
[string]
$ExpectedType,
[Parameter(Mandatory=$false)]
[System.Collections.IDictionary]
$Reference,
[Parameter(Mandatory=$false)]
[string]
$ParameterName = ""
)
$Name = ""
if($null -ne $Reference -and (Test-ConfigurationDataMapContainsKey -Map $Reference -Key "Name")){
$Name = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Name" -DefaultValue "")
}
$UserName = ""
if($null -ne $Reference -and (Test-ConfigurationDataMapContainsKey -Map $Reference -Key "UserName")){
$UserName = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "UserName" -DefaultValue "")
}
if([string]::IsNullOrWhiteSpace($UserName)){
$UserNameSource = $ParameterName
if([string]::IsNullOrWhiteSpace($UserNameSource)){
$UserNameSource = $Name
}
if([string]::IsNullOrWhiteSpace($UserNameSource)){
$UserNameSource = "Credential"
}
$UserNameLeaf = @($UserNameSource -split '[\\/]+')[-1]
if([string]::IsNullOrWhiteSpace($UserNameLeaf)){
$UserNameLeaf = "Credential"
}
$UserName = "DUMMY\$UserNameLeaf"
}
switch($ExpectedType.ToLowerInvariant()){
"credential" {
return [pscredential]::new(
$UserName,
(ConvertTo-SecureString -String "DummyPassword!" -AsPlainText -Force)
)
}
"securestring" {
return ConvertTo-SecureString -String "DummySecret!" -AsPlainText -Force
}
"string" {
if([string]::IsNullOrWhiteSpace($Name)){
$Name = $ParameterName
}
if([string]::IsNullOrWhiteSpace($Name)){
return "DummySecret"
}
return "DummySecret:$Name"
}
}
}

View File

@@ -7,10 +7,13 @@ function New-ConfigurationDataResolutionContext {
)
$Context = [PSCustomObject]@{
ConfigurationData = $ConfigurationData
Parameters = @{}
Variables = @{}
VariableDefinitions = @{}
ResolvingVariables = @{}
ReferenceCache = @{}
ResolvingReferences = @{}
}
if($ConfigurationData.ContainsKey("Parameters") -and $null -ne $ConfigurationData.Parameters){

View File

@@ -0,0 +1,40 @@
function Register-ConfigurationDataSecretProvider {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Name,
[Parameter(Mandatory=$true)]
[string[]]
$SupportedTypes,
[Parameter(Mandatory=$true)]
[scriptblock]
$Resolver
)
if([string]::IsNullOrWhiteSpace($Name)){
throw "Secret provider [Name] must not be empty."
}
if($SupportedTypes.Count -eq 0){
throw "Secret provider [$Name] must define at least one supported type."
}
foreach($Type in $SupportedTypes){
if($Type -notin @("credential", "securestring", "string")){
throw "Secret provider [$Name] uses unsupported type [$Type]."
}
}
if($null -eq $script:ConfigurationDataSecretProviders){
$script:ConfigurationDataSecretProviders = @{}
}
$script:ConfigurationDataSecretProviders[$Name.ToLowerInvariant()] = [PSCustomObject]@{
Name = $Name
SupportedTypes = @($SupportedTypes | ForEach-Object { $_.ToLowerInvariant() })
Resolver = $Resolver
}
}

View File

@@ -0,0 +1,50 @@
function Resolve-ConfigurationDataParameterSecrets {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[System.Collections.Hashtable]
$ConfigurationData,
[Parameter(Mandatory=$false)]
[hashtable]
$ProviderSettings = @{},
[Parameter(Mandatory=$false)]
[switch]
$UseDummySecrets
)
$ResolvedConfigurationData = $ConfigurationData.Clone()
if($ResolvedConfigurationData.ContainsKey("Parameters")){
$ResolvedConfigurationData.Parameters = $ResolvedConfigurationData.Parameters.Clone()
foreach($Parameter in @($ResolvedConfigurationData.Parameters.GetEnumerator())){
$Definition = $Parameter.Value
if(-not (Test-ConfigurationDataMap -Value $Definition)){
continue
}
$Definition = $Definition.Clone()
$ResolvedConfigurationData.Parameters[$Parameter.Name] = $Definition
$TypeName = [string](Get-ConfigurationDataMapValue -Map $Definition -Key "Type" -DefaultValue "")
if($TypeName -notin @("credential", "secureString", "string")){
continue
}
$ExpectedType = $TypeName.ToLowerInvariant()
foreach($ValueKey in @("Value", "DefaultValue")){
if((Test-ConfigurationDataMapContainsKey -Map $Definition -Key $ValueKey) -and (Test-ConfigurationDataSecretReference -Value $Definition[$ValueKey])){
if($UseDummySecrets){
$Definition[$ValueKey] = New-ConfigurationDataDummySecretValue -ExpectedType $ExpectedType -Reference $Definition[$ValueKey] -ParameterName $Parameter.Name
}else{
$Definition[$ValueKey] = Resolve-ConfigurationDataSecretReference -Reference $Definition[$ValueKey] -ExpectedType $ExpectedType -ProviderSettings $ProviderSettings
}
}
}
}
}
return $ResolvedConfigurationData
}

View File

@@ -0,0 +1,51 @@
function Resolve-ConfigurationDataProviderSecureString {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
$Value
)
if($Value -is [System.Security.SecureString]){
return $Value
}
if($Value -is [string]){
return ConvertTo-SecureString -String $Value -AsPlainText -Force
}
if(-not (Test-ConfigurationDataMap -Value $Value)){
throw "Provider secure string value must be a SecureString, string, or hashtable."
}
if(Test-ConfigurationDataMapContainsKey -Map $Value -Key "EnvironmentVariable"){
$VariableName = [string](Get-ConfigurationDataMapValue -Map $Value -Key "EnvironmentVariable")
$EnvironmentValue = [Environment]::GetEnvironmentVariable($VariableName)
if([string]::IsNullOrEmpty($EnvironmentValue)){
throw "Environment variable [$VariableName] is not defined or empty."
}
return ConvertTo-SecureString -String $EnvironmentValue -AsPlainText -Force
}
if(Test-ConfigurationDataMapContainsKey -Map $Value -Key "ProtectedValue"){
$ProtectedValue = [string](Get-ConfigurationDataMapValue -Map $Value -Key "ProtectedValue")
$Key = $null
if(Test-ConfigurationDataMapContainsKey -Map $Value -Key "Key"){
$Key = [Convert]::FromBase64String([string](Get-ConfigurationDataMapValue -Map $Value -Key "Key"))
}elseif(Test-ConfigurationDataMapContainsKey -Map $Value -Key "KeyPath"){
$KeyPath = [string](Get-ConfigurationDataMapValue -Map $Value -Key "KeyPath")
if(-not (Test-Path -Path $KeyPath -PathType Leaf)){
throw "Secure string key file [$KeyPath] was not found."
}
$Key = [Convert]::FromBase64String((Get-Content -Path $KeyPath -Raw).Trim())
}else{
throw "Protected provider secure string requires [Key] or [KeyPath]."
}
return ConvertTo-SecureString -String $ProtectedValue -Key $Key
}
throw "Unsupported provider secure string reference."
}

View File

@@ -0,0 +1,28 @@
function Resolve-ConfigurationDataProviderSettingsPath {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Provider,
[Parameter(Mandatory=$false)]
[AllowEmptyString()]
[string]
$SettingsPath
)
$FileName = "ProviderSettings.$Provider.psd1"
if([string]::IsNullOrWhiteSpace($SettingsPath)){
return (Join-Path -Path (Get-Location).Path -ChildPath $FileName)
}
if((Test-Path -Path $SettingsPath -PathType Container) -or
$SettingsPath.EndsWith("\") -or
$SettingsPath.EndsWith("/") -or
[string]::IsNullOrWhiteSpace([System.IO.Path]::GetExtension($SettingsPath))){
return (Join-Path -Path $SettingsPath -ChildPath $FileName)
}
return $SettingsPath
}

View File

@@ -0,0 +1,46 @@
function Resolve-ConfigurationDataReference {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]
$Path,
[Parameter(Mandatory=$false)]
[AllowEmptyString()]
[string]
$Property = "",
[Parameter(Mandatory=$true)]
$Context
)
$ReferenceKey = $Path
if(-not [string]::IsNullOrWhiteSpace($Property)){
$ReferenceKey = "$Path::$Property"
}
if($Context.ReferenceCache.ContainsKey($ReferenceKey)){
return $Context.ReferenceCache[$ReferenceKey]
}
if($Context.ResolvingReferences.ContainsKey($ReferenceKey)){
$Stack = @($Context.ResolvingReferences.Keys) + $ReferenceKey
throw "Circular configuration data reference detected: $($Stack -join ' -> ')."
}
$Context.ResolvingReferences[$ReferenceKey] = $true
try {
$RawValue = Get-ConfigurationDataPathValue -Value $Context.ConfigurationData -Path $Path
$ResolvedValue = Resolve-ConfigurationDataValue -Value $RawValue -Context $Context
if(-not [string]::IsNullOrWhiteSpace($Property)){
$ResolvedValue = Get-ConfigurationDataObjectPropertyValue -Value $ResolvedValue -Property $Property -Path $Path
}
$Context.ReferenceCache[$ReferenceKey] = $ResolvedValue
return $ResolvedValue
}
finally {
$Context.ResolvingReferences.Remove($ReferenceKey)
}
}

View File

@@ -0,0 +1,35 @@
function Resolve-ConfigurationDataSecretReference {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[System.Collections.IDictionary]
$Reference,
[Parameter(Mandatory=$true)]
[ValidateSet("credential", "securestring", "string")]
[string]
$ExpectedType,
[Parameter(Mandatory=$false)]
[hashtable]
$ProviderSettings = @{}
)
$Provider = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Provider")
$ProviderDefinition = Get-ConfigurationDataSecretProvider -Name $Provider
if($null -eq $ProviderDefinition){
throw "Unsupported secret provider [$Provider]."
}
if($ProviderDefinition.SupportedTypes -notcontains $ExpectedType.ToLowerInvariant()){
throw "Secret provider [$($ProviderDefinition.Name)] does not support type [$ExpectedType]."
}
$CurrentProviderSettings = @{}
if($ProviderSettings.ContainsKey($ProviderDefinition.Name)){
$CurrentProviderSettings = $ProviderSettings[$ProviderDefinition.Name]
}
return & $ProviderDefinition.Resolver -Reference $Reference -ExpectedType $ExpectedType -ProviderSettings $CurrentProviderSettings
}

View File

@@ -15,6 +15,9 @@ function Resolve-ConfigurationDataValue {
if($Value -is [System.Collections.Specialized.OrderedDictionary]){
$Resolved = [ordered]@{}
foreach($Entry in $Value.GetEnumerator()){
if($Entry.Name -eq "Sealed"){
continue
}
$Resolved[$Entry.Name] = Resolve-ConfigurationDataValue -Value $Entry.Value -Context $Context
}
return $Resolved
@@ -23,6 +26,9 @@ function Resolve-ConfigurationDataValue {
if($Value -is [System.Collections.Hashtable]){
$Resolved = @{}
foreach($Entry in $Value.GetEnumerator()){
if($Entry.Name -eq "Sealed"){
continue
}
$Resolved[$Entry.Name] = Resolve-ConfigurationDataValue -Value $Entry.Value -Context $Context
}
return $Resolved

View File

@@ -0,0 +1,39 @@
function Test-ConfigurationDataExpressionParentheses {
[CmdletBinding()]
Param(
[AllowNull()]
[string]
$Expression
)
if([string]::IsNullOrWhiteSpace($Expression)){
return $true
}
$Depth = 0
$InString = $false
for($Index = 0; $Index -lt $Expression.Length; $Index++){
$Character = $Expression[$Index]
if($Character -eq "'"){
$InString = -not $InString
continue
}
if($InString){
continue
}
if($Character -eq "("){
$Depth++
}elseif($Character -eq ")"){
$Depth--
if($Depth -lt 0){
return $false
}
}
}
return $Depth -eq 0 -and -not $InString
}

View File

@@ -0,0 +1,11 @@
function Test-ConfigurationDataMissingReferenceError {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[System.Management.Automation.ErrorRecord]
$ErrorRecord
)
$Message = $ErrorRecord.Exception.Message
return $Message -match "^(Parameter|Variable) \[.+\] is not defined\.$"
}

View File

@@ -0,0 +1,14 @@
function Test-ConfigurationDataSecretReference {
[CmdletBinding()]
Param(
[AllowNull()]
$Value
)
if(-not (Test-ConfigurationDataMap -Value $Value)){
return $false
}
return (Test-ConfigurationDataMapContainsKey -Map $Value -Key "Provider") -and
(Test-ConfigurationDataMapContainsKey -Map $Value -Key "Name")
}

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])
}

View File

@@ -0,0 +1,49 @@
function Unlock-ConfigurationDataSecretManagementVault {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)]
[hashtable]
$ProviderSettings = @{}
)
if(-not $ProviderSettings.ContainsKey("SecretManagement")){
return
}
$SecretManagementSettings = $ProviderSettings.SecretManagement
if(-not ($SecretManagementSettings -is [System.Collections.IDictionary])){
return
}
if(-not (Test-ConfigurationDataMapContainsKey -Map $SecretManagementSettings -Key "MasterPassword")){
return
}
$Vault = [string](Get-ConfigurationDataMapValue -Map $SecretManagementSettings -Key "DefaultVault" -DefaultValue "")
if([string]::IsNullOrWhiteSpace($Vault)){
throw "SecretManagement provider settings define [MasterPassword], but [DefaultVault] is not defined."
}
$UnlockSecretVaultCommand = Get-Command -Name Unlock-SecretVault -ErrorAction SilentlyContinue
if($null -eq $UnlockSecretVaultCommand){
throw "Command [Unlock-SecretVault] was not found. Install module [Microsoft.PowerShell.SecretManagement]."
}
$MasterPassword = Resolve-ConfigurationDataProviderSecureString -Value $SecretManagementSettings.MasterPassword
& $UnlockSecretVaultCommand -Name $Vault -Password $MasterPassword
$TestSecretVaultCommand = Get-Command -Name Test-SecretVault -ErrorAction SilentlyContinue
if($null -ne $TestSecretVaultCommand){
$IsUnlocked = $false
try {
$IsUnlocked = [bool](& $TestSecretVaultCommand -Name $Vault -ErrorAction Stop)
}
catch {
throw "SecretManagement vault [$Vault] could not be unlocked. $($_.Exception.Message)"
}
if(-not $IsUnlocked){
throw "SecretManagement vault [$Vault] could not be unlocked. Verify the KeePass database path, KeePass key file, and protected master password in the provider settings."
}
}
}

View File

@@ -0,0 +1,57 @@
$SecretManagementProvider = @{
Name = "SecretManagement"
SupportedTypes = @(
"credential",
"securestring",
"string"
)
Resolver = {
Param(
[Parameter(Mandatory=$true)]
[System.Collections.IDictionary]
$Reference,
[Parameter(Mandatory=$true)]
[ValidateSet("credential", "securestring", "string")]
[string]
$ExpectedType,
[Parameter(Mandatory=$false)]
[hashtable]
$ProviderSettings = @{}
)
$GetSecretCommand = Get-Command -Name Get-Secret -ErrorAction SilentlyContinue
if($null -eq $GetSecretCommand){
throw "SecretManagement provider requires the module [Microsoft.PowerShell.SecretManagement] and command [Get-Secret]."
}
$Vault = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Vault" -DefaultValue "")
$Name = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "Name")
$UserName = [string](Get-ConfigurationDataMapValue -Map $Reference -Key "UserName" -DefaultValue "")
$Options = Get-ConfigurationDataMapValue -Map $Reference -Key "Options" -DefaultValue @{}
if([string]::IsNullOrWhiteSpace($Vault) -and $ProviderSettings.ContainsKey("DefaultVault")){
$Vault = [string]$ProviderSettings.DefaultVault
}
$CommandParameters = @{
Name = $Name
}
if(-not [string]::IsNullOrWhiteSpace($Vault)){
$CommandParameters["Vault"] = $Vault
}
if($Options -is [System.Collections.IDictionary]){
foreach($Key in $Options.Keys){
$CommandParameters[$Key] = $Options[$Key]
}
}
$Secret = & $GetSecretCommand @CommandParameters
return ConvertFrom-ConfigurationDataSecretValue -Secret $Secret -ExpectedType $ExpectedType -Name $Name -UserName $UserName
}
}
Register-ConfigurationDataSecretProvider @SecretManagementProvider

View File

@@ -0,0 +1,119 @@
function Get-DSCConfigurationDataCredentialProvider {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)]
[string]
$Vault,
[Parameter(Mandatory=$false)]
[string]
$SettingsPath,
[Parameter(Mandatory=$false)]
[switch]
$TestVault
)
DynamicParam {
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
if($ProviderNames.Count -eq 0){
$ProviderNames = @("__NoProvidersRegistered__")
}
$ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new()
$ParameterAttribute.Mandatory = $false
$ParameterAttribute.Position = 0
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList (,[string[]]$ProviderNames)
$Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$Attributes.Add($ParameterAttribute)
$Attributes.Add($ValidateSetAttribute)
$RuntimeParameter = [System.Management.Automation.RuntimeDefinedParameter]::new(
"Provider",
[string],
$Attributes
)
$Dictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$Dictionary.Add("Provider", $RuntimeParameter)
return $Dictionary
}
begin {
$Provider = [string]$PSBoundParameters["Provider"]
if([string]::IsNullOrWhiteSpace($Provider)){
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
if($ProviderNames.Count -eq 1){
$Provider = $ProviderNames[0]
}elseif($ProviderNames.Count -eq 0){
throw "No configuration data secret providers are registered."
}else{
throw "Provider is required. Registered providers: $($ProviderNames -join ', ')."
}
}
$ResolvedSettingsPath = Resolve-ConfigurationDataProviderSettingsPath -Provider $Provider -SettingsPath $SettingsPath
}
process {
$Settings = @{}
$SettingsExists = Test-Path -LiteralPath $ResolvedSettingsPath -PathType Leaf
if($SettingsExists){
$Settings = Import-PowerShellDataFile -LiteralPath $ResolvedSettingsPath
}
$ProviderSettings = @{}
if($Settings -is [System.Collections.IDictionary] -and $Settings.ContainsKey($Provider)){
$ProviderSettings = $Settings[$Provider]
}
$DefaultVault = ""
if($ProviderSettings -is [System.Collections.IDictionary] -and $ProviderSettings.ContainsKey("DefaultVault")){
$DefaultVault = [string]$ProviderSettings.DefaultVault
}
if([string]::IsNullOrWhiteSpace($Vault)){
$Vault = $DefaultVault
}
$Vaults = @()
$GetSecretVaultCommand = Get-Command -Name Get-SecretVault -ErrorAction SilentlyContinue
if($null -ne $GetSecretVaultCommand){
if([string]::IsNullOrWhiteSpace($Vault)){
$Vaults = @(& $GetSecretVaultCommand)
}else{
$Vaults = @(& $GetSecretVaultCommand -Name $Vault -ErrorAction SilentlyContinue)
}
}
$TestResult = $null
$TestError = $null
if($TestVault -and -not [string]::IsNullOrWhiteSpace($Vault)){
$TestSecretVaultCommand = Get-Command -Name Test-SecretVault -ErrorAction SilentlyContinue
if($null -eq $TestSecretVaultCommand){
$TestError = "Command [Test-SecretVault] was not found."
}else{
try {
$TestResult = [bool](& $TestSecretVaultCommand -Name $Vault -ErrorAction Stop)
}
catch {
$TestResult = $false
$TestError = $_.Exception.Message
}
}
}
[PSCustomObject]@{
Provider = $Provider
SettingsPath = $ResolvedSettingsPath
SettingsExists = $SettingsExists
DefaultVault = $DefaultVault
RequestedVault = $Vault
ProviderSettings = $ProviderSettings
RegisteredVaults = $Vaults
TestVault = $TestResult
TestError = $TestError
}
}
}

View File

@@ -0,0 +1,243 @@
function Register-DSCConfigurationDataCredentialProvider {
[CmdletBinding(SupportsShouldProcess=$true)]
Param(
[Parameter(Mandatory=$true, Position=1)]
[string]
$Vault,
[Parameter(Mandatory=$false)]
[ValidateSet("Generic", "KeePass")]
[string]
$VaultType = "Generic",
[Parameter(Mandatory=$false)]
[string]
$DatabasePath,
[Parameter(Mandatory=$false)]
[string]
$KeyPath,
[Parameter(Mandatory=$false)]
[string]
$SettingsPath,
[Parameter(Mandatory=$false)]
[string]
$ModuleName,
[Parameter(Mandatory=$false)]
[hashtable]
$VaultParameters = @{},
[Parameter(Mandatory=$false)]
[switch]
$RegisterVault,
[Parameter(Mandatory=$false)]
[switch]
$DefaultVault,
[Parameter(Mandatory=$false)]
[switch]
$AllowClobber,
[Parameter(Mandatory=$false)]
[switch]
$UseMasterPassword,
[Parameter(Mandatory=$false)]
[Alias("MasterKey")]
[System.Security.SecureString]
$MasterPassword,
[Parameter(Mandatory=$false)]
[Alias("MasterKeyPath")]
[string]
$MasterPasswordKeyPath,
[Parameter(Mandatory=$false)]
[switch]
$Force,
[Parameter(Mandatory=$false)]
[switch]
$PassThru
)
DynamicParam {
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
if($ProviderNames.Count -eq 0){
$ProviderNames = @("__NoProvidersRegistered__")
}
$ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new()
$ParameterAttribute.Mandatory = $false
$ParameterAttribute.Position = 0
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList (,[string[]]$ProviderNames)
$Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$Attributes.Add($ParameterAttribute)
$Attributes.Add($ValidateSetAttribute)
$RuntimeParameter = [System.Management.Automation.RuntimeDefinedParameter]::new(
"Provider",
[string],
$Attributes
)
$Dictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$Dictionary.Add("Provider", $RuntimeParameter)
return $Dictionary
}
begin {
$Provider = [string]$PSBoundParameters["Provider"]
if([string]::IsNullOrWhiteSpace($Provider)){
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
if($ProviderNames.Count -eq 1){
$Provider = $ProviderNames[0]
}elseif($ProviderNames.Count -eq 0){
throw "No configuration data secret providers are registered."
}else{
throw "Provider is required. Registered providers: $($ProviderNames -join ', ')."
}
}
$SettingsPath = Resolve-ConfigurationDataProviderSettingsPath -Provider $Provider -SettingsPath $SettingsPath
}
process {
switch($Provider){
"SecretManagement" {
if([string]::IsNullOrWhiteSpace($ModuleName)){
if($VaultType -eq "KeePass"){
$ModuleName = "SecretManagement.KeePass"
}else{
$ModuleName = "Microsoft.PowerShell.SecretStore"
}
}
$ShouldRegisterVault = $RegisterVault -or $PSBoundParameters.ContainsKey("ModuleName")
if($VaultType -eq "KeePass" -and $ShouldRegisterVault){
if([string]::IsNullOrWhiteSpace($DatabasePath) -and -not $VaultParameters.ContainsKey("Path")){
throw "SecretManagement KeePass vault registration requires [DatabasePath] or VaultParameters['Path']."
}
if(-not [string]::IsNullOrWhiteSpace($DatabasePath)){
$VaultParameters["Path"] = $DatabasePath
}
if(-not [string]::IsNullOrWhiteSpace($KeyPath)){
$VaultParameters["KeyPath"] = $KeyPath
}
if($UseMasterPassword){
$VaultParameters["UseMasterPassword"] = $true
}
}
if($ShouldRegisterVault){
if($VaultType -eq "KeePass"){
$KeePassModule = Get-Module -Name SecretManagement.KeePass -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1
if($null -eq $KeePassModule){
throw "SecretManagement KeePass vault registration requires module [SecretManagement.KeePass]."
}
$ModuleReference = $KeePassModule.Name
if(-not [string]::IsNullOrWhiteSpace($KeePassModule.Path)){
$ModuleReference = $KeePassModule.Path
}
$CommandParameters = @{
Name = $Vault
ModuleName = $ModuleReference
VaultParameters = @{
Path = $VaultParameters["Path"]
UseMasterPassword = $false
UseWindowsAccount = $false
KeyPath = $null
ShowFullTitle = $false
ShowRecycleBin = $false
}
}
foreach($Key in @("KeyPath", "UseMasterPassword", "UseWindowsAccount", "ShowFullTitle", "ShowRecycleBin", "SkipValidate")){
if($VaultParameters.ContainsKey($Key)){
$CommandParameters.VaultParameters[$Key] = $VaultParameters[$Key]
}
}
if($DefaultVault){
$CommandParameters["DefaultVault"] = $true
}
if($AllowClobber){
$CommandParameters["AllowClobber"] = $true
}
if($PSCmdlet.ShouldProcess($Vault, "Register KeePass SecretManagement vault [$ModuleReference]")){
Register-SecretVault @CommandParameters
}
}else{
$CommandParameters = @{
Name = $Vault
ModuleName = $ModuleName
}
if($VaultParameters.Count -gt 0){
$CommandParameters["VaultParameters"] = $VaultParameters
}
if($DefaultVault){
$CommandParameters["DefaultVault"] = $true
}
if($AllowClobber){
$CommandParameters["AllowClobber"] = $true
}
if($PSCmdlet.ShouldProcess($Vault, "Register SecretManagement vault [$ModuleName]")){
Register-SecretVault @CommandParameters
}
}
}
$ProviderSettings = [ordered]@{
SecretManagement = [ordered]@{
DefaultVault = $Vault
}
}
if($VaultType -ne "Generic"){
$ProviderSettings.SecretManagement["VaultType"] = $VaultType
}
if($null -ne $MasterPassword){
if([string]::IsNullOrWhiteSpace($MasterPasswordKeyPath)){
throw "MasterPassword requires [MasterPasswordKeyPath]."
}
$Key = New-ConfigurationDataAesKeyFile -Path $MasterPasswordKeyPath -Force:$Force
$ProtectedValue = $MasterPassword | ConvertFrom-SecureString -Key $Key
$ProviderSettings.SecretManagement["MasterPassword"] = [ordered]@{
ProtectedValue = $ProtectedValue
KeyPath = $MasterPasswordKeyPath
}
}
if($PSCmdlet.ShouldProcess($SettingsPath, "Create provider settings for [$Provider]")){
Export-PowerShellDataFile -InputObject $ProviderSettings -Path $SettingsPath -Force:$Force
}
if($PassThru){
return $ProviderSettings
}
}
default {
throw "Provider [$Provider] does not provide an initialization implementation."
}
}
}
}

View File

@@ -3,10 +3,37 @@ function Resolve-DSCConfigurationData {
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[System.Collections.Hashtable]
$ConfigurationData
$ConfigurationData,
[Parameter(Mandatory=$false)]
[hashtable]
$ProviderSettings = @{},
[Parameter(Mandatory=$false)]
[string]
$ProviderSettingsPath,
[Parameter(Mandatory=$false)]
[switch]
$SkipSecrets
)
process {
if(-not [string]::IsNullOrWhiteSpace($ProviderSettingsPath)){
if(-not (Test-Path -Path $ProviderSettingsPath -PathType Leaf)){
throw "Provider settings file [$ProviderSettingsPath] was not found."
}
$ProviderSettings = Import-PowerShellDataFile -Path $ProviderSettingsPath
}
if($SkipSecrets){
$ConfigurationData = Resolve-ConfigurationDataParameterSecrets -ConfigurationData $ConfigurationData -ProviderSettings $ProviderSettings -UseDummySecrets
}else{
Unlock-ConfigurationDataSecretManagementVault -ProviderSettings $ProviderSettings
$ConfigurationData = Resolve-ConfigurationDataParameterSecrets -ConfigurationData $ConfigurationData -ProviderSettings $ProviderSettings
}
$Context = New-ConfigurationDataResolutionContext -ConfigurationData $ConfigurationData
return Resolve-ConfigurationDataValue -Value $ConfigurationData -Context $Context
}

View File

@@ -0,0 +1,258 @@
function Set-DSCConfigurationDataCredentialProvider {
[CmdletBinding(SupportsShouldProcess=$true)]
Param(
[Parameter(Mandatory=$false)]
[string]
$Vault,
[Parameter(Mandatory=$false)]
[AllowEmptyString()]
[ValidateSet("", "Generic", "KeePass")]
[string]
$VaultType = "",
[Parameter(Mandatory=$false)]
[string]
$DatabasePath,
[Parameter(Mandatory=$false)]
[string]
$KeyPath,
[Parameter(Mandatory=$false)]
[switch]
$DisableKeePassKeyFile,
[Parameter(Mandatory=$false)]
[string]
$ModuleName,
[Parameter(Mandatory=$false)]
[hashtable]
$VaultParameters = @{},
[Parameter(Mandatory=$false)]
[switch]
$RegisterVault,
[Parameter(Mandatory=$false)]
[switch]
$DefaultVault,
[Parameter(Mandatory=$false)]
[switch]
$AllowClobber,
[Parameter(Mandatory=$false)]
[Alias("UseMasterKey")]
[switch]
$UseMasterPassword,
[Parameter(Mandatory=$false)]
[Alias("NoMasterKey", "NoMasterPassword")]
[switch]
$DisableMasterPassword,
[Parameter(Mandatory=$false)]
[Alias("MasterKey")]
[System.Security.SecureString]
$MasterPassword,
[Parameter(Mandatory=$false)]
[Alias("MasterKeyPath")]
[string]
$MasterPasswordKeyPath,
[Parameter(Mandatory=$false)]
[switch]
$UseWindowsAccount,
[Parameter(Mandatory=$false)]
[switch]
$ShowFullTitle,
[Parameter(Mandatory=$false)]
[switch]
$ShowRecycleBin,
[Parameter(Mandatory=$false)]
[switch]
$SkipValidate,
[Parameter(Mandatory=$false)]
[string]
$SettingsPath,
[Parameter(Mandatory=$false)]
[switch]
$Force,
[Parameter(Mandatory=$false)]
[switch]
$PassThru
)
DynamicParam {
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
if($ProviderNames.Count -eq 0){
$ProviderNames = @("__NoProvidersRegistered__")
}
$ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new()
$ParameterAttribute.Mandatory = $false
$ParameterAttribute.Position = 0
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList (,[string[]]$ProviderNames)
$Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$Attributes.Add($ParameterAttribute)
$Attributes.Add($ValidateSetAttribute)
$RuntimeParameter = [System.Management.Automation.RuntimeDefinedParameter]::new(
"Provider",
[string],
$Attributes
)
$Dictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$Dictionary.Add("Provider", $RuntimeParameter)
return $Dictionary
}
begin {
$Provider = [string]$PSBoundParameters["Provider"]
if([string]::IsNullOrWhiteSpace($Provider)){
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
if($ProviderNames.Count -eq 1){
$Provider = $ProviderNames[0]
}elseif($ProviderNames.Count -eq 0){
throw "No configuration data secret providers are registered."
}else{
throw "Provider is required. Registered providers: $($ProviderNames -join ', ')."
}
}
$ProviderInfo = Get-DSCConfigurationDataCredentialProvider -Provider $Provider -Vault $Vault -SettingsPath $SettingsPath
if([string]::IsNullOrWhiteSpace($Vault)){
$Vault = [string]$ProviderInfo.DefaultVault
}
if([string]::IsNullOrWhiteSpace($Vault)){
throw "Vault is required. Pass [Vault] or define [DefaultVault] in the provider settings file."
}
}
process {
$ExistingVault = @($ProviderInfo.RegisteredVaults | Select-Object -First 1)
$ExistingVaultParameters = @{}
if($ExistingVault.Count -gt 0 -and $ExistingVault[0].PSObject.Properties["VaultParameters"] -and $ExistingVault[0].VaultParameters -is [System.Collections.IDictionary]){
foreach($Key in $ExistingVault[0].VaultParameters.Keys){
$ExistingVaultParameters[$Key] = $ExistingVault[0].VaultParameters[$Key]
}
}
$EffectiveVaultType = $VaultType
if([string]::IsNullOrWhiteSpace($EffectiveVaultType) -and $ProviderInfo.ProviderSettings -is [System.Collections.IDictionary] -and $ProviderInfo.ProviderSettings.ContainsKey("VaultType")){
$EffectiveVaultType = [string]$ProviderInfo.ProviderSettings.VaultType
}
if([string]::IsNullOrWhiteSpace($EffectiveVaultType) -and $ExistingVault.Count -gt 0 -and $ExistingVault[0].PSObject.Properties["ModuleName"]){
if([string]$ExistingVault[0].ModuleName -match "SecretManagement\.KeePass"){
$EffectiveVaultType = "KeePass"
}
}
if([string]::IsNullOrWhiteSpace($EffectiveVaultType)){
$EffectiveVaultType = "Generic"
}
$EffectiveVaultParameters = @{}
foreach($Key in $ExistingVaultParameters.Keys){
$EffectiveVaultParameters[$Key] = $ExistingVaultParameters[$Key]
}
foreach($Key in $VaultParameters.Keys){
$EffectiveVaultParameters[$Key] = $VaultParameters[$Key]
}
if(-not [string]::IsNullOrWhiteSpace($DatabasePath)){
$EffectiveVaultParameters["Path"] = $DatabasePath
}
if(-not [string]::IsNullOrWhiteSpace($KeyPath)){
$EffectiveVaultParameters["KeyPath"] = $KeyPath
}
if($DisableKeePassKeyFile){
$EffectiveVaultParameters["KeyPath"] = $null
}
if($UseMasterPassword -and $DisableMasterPassword){
throw "Use either [UseMasterPassword] or [DisableMasterPassword], not both."
}
foreach($SwitchName in @("UseMasterPassword", "UseWindowsAccount", "ShowFullTitle", "ShowRecycleBin", "SkipValidate")){
if($PSBoundParameters.ContainsKey($SwitchName)){
$EffectiveVaultParameters[$SwitchName] = [bool]$PSBoundParameters[$SwitchName]
}
}
if($DisableMasterPassword){
$EffectiveVaultParameters["UseMasterPassword"] = $false
}
if($PSCmdlet.ShouldProcess($Vault, "Update DSC configuration data credential provider settings")){
$EffectiveMasterPasswordKeyPath = $MasterPasswordKeyPath
if([string]::IsNullOrWhiteSpace($EffectiveMasterPasswordKeyPath) -and
$ProviderInfo.ProviderSettings -is [System.Collections.IDictionary] -and
$ProviderInfo.ProviderSettings.ContainsKey("MasterPassword") -and
$ProviderInfo.ProviderSettings.MasterPassword -is [System.Collections.IDictionary] -and
$ProviderInfo.ProviderSettings.MasterPassword.ContainsKey("KeyPath")){
$EffectiveMasterPasswordKeyPath = [string]$ProviderInfo.ProviderSettings.MasterPassword.KeyPath
}
$CommandParameters = @{
Provider = $Provider
Vault = $Vault
VaultType = $EffectiveVaultType
SettingsPath = $SettingsPath
VaultParameters = $EffectiveVaultParameters
Force = $Force
PassThru = $true
}
if(-not [string]::IsNullOrWhiteSpace($ModuleName)){
$CommandParameters["ModuleName"] = $ModuleName
}
if($RegisterVault){
$CommandParameters["RegisterVault"] = $true
}
if($DefaultVault){
$CommandParameters["DefaultVault"] = $true
}
if($AllowClobber){
$CommandParameters["AllowClobber"] = $true
}
if($null -ne $MasterPassword){
$CommandParameters["MasterPassword"] = $MasterPassword
}
if(-not [string]::IsNullOrWhiteSpace($EffectiveMasterPasswordKeyPath)){
$CommandParameters["MasterPasswordKeyPath"] = $EffectiveMasterPasswordKeyPath
}
$Result = Register-DSCConfigurationDataCredentialProvider @CommandParameters
}
if($PassThru){
if($null -ne $Result){
return $Result
}
return (Get-DSCConfigurationDataCredentialProvider -Provider $Provider -Vault $Vault -SettingsPath $SettingsPath)
}
}
}

View File

@@ -0,0 +1,100 @@
function Unregister-DSCConfigurationDataCredentialProvider {
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")]
Param(
[Parameter(Mandatory=$true)]
[string]
$Vault,
[Parameter(Mandatory=$false)]
[string]
$SettingsPath,
[Parameter(Mandatory=$false)]
[switch]
$RemoveSettings,
[Parameter(Mandatory=$false)]
[switch]
$UnregisterVault,
[Parameter(Mandatory=$false)]
[switch]
$Force,
[Parameter(Mandatory=$false)]
[switch]
$PassThru
)
DynamicParam {
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
if($ProviderNames.Count -eq 0){
$ProviderNames = @("__NoProvidersRegistered__")
}
$ParameterAttribute = [System.Management.Automation.ParameterAttribute]::new()
$ParameterAttribute.Mandatory = $false
$ParameterAttribute.Position = 0
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList (,[string[]]$ProviderNames)
$Attributes = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$Attributes.Add($ParameterAttribute)
$Attributes.Add($ValidateSetAttribute)
$RuntimeParameter = [System.Management.Automation.RuntimeDefinedParameter]::new(
"Provider",
[string],
$Attributes
)
$Dictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$Dictionary.Add("Provider", $RuntimeParameter)
return $Dictionary
}
begin {
$Provider = [string]$PSBoundParameters["Provider"]
if([string]::IsNullOrWhiteSpace($Provider)){
$ProviderNames = @(Get-ConfigurationDataSecretProviderName)
if($ProviderNames.Count -eq 1){
$Provider = $ProviderNames[0]
}elseif($ProviderNames.Count -eq 0){
throw "No configuration data secret providers are registered."
}else{
throw "Provider is required. Registered providers: $($ProviderNames -join ', ')."
}
}
$SettingsPath = Resolve-ConfigurationDataProviderSettingsPath -Provider $Provider -SettingsPath $SettingsPath
}
process {
$Result = [ordered]@{
Provider = $Provider
Vault = $Vault
SettingsPath = $SettingsPath
RemovedSettings = $false
UnregisteredVault = $false
}
if($RemoveSettings -or (-not $PSBoundParameters.ContainsKey("RemoveSettings"))){
if(Test-Path -Path $SettingsPath -PathType Leaf){
if($PSCmdlet.ShouldProcess($SettingsPath, "Remove provider settings file")){
Remove-Item -Path $SettingsPath -Force:$Force
$Result.RemovedSettings = $true
}
}
}
if($UnregisterVault){
if($PSCmdlet.ShouldProcess($Vault, "Unregister SecretManagement vault")){
Unregister-SecretVault -Name $Vault
$Result.UnregisteredVault = $true
}
}
if($PassThru){
return [PSCustomObject]$Result
}
}
}

342
Readme.md
View File

@@ -1,4 +1,4 @@
# Resolve-DSCConfigurationData
# Resolve-DSCConfigurationData
`Resolve-DSCConfigurationData` resolves parameter, variable, and expression references in DSC configuration data.
@@ -9,6 +9,13 @@ $merged = Merge-DSCConfigurationData -Template $service -Deployment $environment
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged
```
Pipeline flow:
```powershell
$resolved = Merge-DSCConfigurationData -Template $service -Deployment $environment |
Resolve-DSCConfigurationData
```
Expressions use an ARM-like syntax:
```powershell
@@ -47,12 +54,19 @@ 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_-]*$'
# 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 = @{
@@ -75,11 +89,277 @@ 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.
- `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:
```powershell
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:
```powershell
$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.
```powershell
$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:
```powershell
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`:
```powershell
$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:
```powershell
Register-DSCConfigurationDataCredentialProvider `
-Provider SecretManagement `
-Vault LocalStore `
-ModuleName Microsoft.PowerShell.SecretStore `
-RegisterVault `
-DefaultVault `
-SettingsPath 'C:\DSC\Contoso'
```
Provider setup can be removed again:
```powershell
Unregister-DSCConfigurationDataCredentialProvider `
-Provider SecretManagement `
-Vault Contoso `
-SettingsPath 'C:\DSC\Contoso' `
-UnregisterVault
```
Then use it directly:
```powershell
$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.
```powershell
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:
```powershell
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:
```powershell
$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:
```powershell
SetupCredential = @{
Type = 'credential'
Required = $true
Sensitive = $true
Value = @{
Provider = 'SecretManagement'
Vault = 'LocalStore'
Name = 'SharePointSetupCredential'
}
}
```
SecretStore example through SecretManagement:
```powershell
FarmPassphrase = @{
Type = 'secureString'
Required = $true
Sensitive = $true
Value = @{
Provider = 'SecretManagement'
Vault = 'LocalStore'
Name = 'SharePointFarmPassphrase'
}
}
```
Azure Key Vault example through SecretManagement:
```powershell
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:
```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 +373,7 @@ Variables may reference parameters and other variables. Nested variable referenc
}
DomainLabel = @{
Type = 'string'
Value = 'LAN'
Value = 'corp'
}
Landscape = @{
Type = 'string'
@@ -122,9 +402,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
@@ -134,12 +414,16 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```powershell
"[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
```powershell
@@ -149,12 +433,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 +469,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 +485,8 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```
```powershell
"[replace('BGW-LAN', '-', '_')]"
# BGW_LAN
"[replace('Contoso-CORP', '-', '_')]"
# Contoso_CORP
```
```powershell
@@ -211,7 +495,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```
```powershell
"[indexOf('BGW', 1)]"
"[indexOf('CON', 1)]"
# G
```
@@ -228,23 +512,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 +540,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 +560,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```powershell
"[take(split(parameters('DomainFQDN'), '.'), 1)]"
# @('bgw-online')
# @('contoso')
```
```powershell
@@ -307,7 +591,7 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
```
```powershell
"[startsWith(parameters('DomainFQDN'), 'bgw')]"
"[startsWith(parameters('DomainFQDN'), 'contoso')]"
# True
```
@@ -342,6 +626,8 @@ ConfigDbName : SharePoint_LAN_TST_Farm_Config
- `parameters(name)`
- `variables(name)`
- `reference(path)`
- `reference(path, property)`
- `concat(value1, value2, ...)`
- `format(formatString, value1, value2, ...)`
- `coalesce(value1, value2, ...)`

View File

@@ -1,13 +1,17 @@
@{
@{
RootModule = "Resolve-DSCConfigurationData.psm1"
ModuleVersion = "1.0.0"
ModuleVersion = "1.1.0"
GUID = "1d6ba0d4-93d5-4b0f-94c7-bf10a30fe0e8"
Author = "Torsten Brendgen"
Copyright = "(c) Torsten Brendgen. All rights reserved."
Description = "Resolves parameters, variables, and expressions in DSC configuration data."
PowerShellVersion = "5.1"
FunctionsToExport = @(
"Resolve-DSCConfigurationData"
"Get-DSCConfigurationDataCredentialProvider",
"Resolve-DSCConfigurationData",
"Register-DSCConfigurationDataCredentialProvider",
"Set-DSCConfigurationDataCredentialProvider",
"Unregister-DSCConfigurationDataCredentialProvider"
)
CmdletsToExport = @()
VariablesToExport = @()

View File

@@ -1,13 +1,22 @@
$PrivatePath = Join-Path -Path $PSScriptRoot -ChildPath "Private"
$ProviderPath = Join-Path -Path $PSScriptRoot -ChildPath "Providers"
$PublicPath = Join-Path -Path $PSScriptRoot -ChildPath "Public"
$Private = @(Get-ChildItem -Path $PrivatePath -Filter "*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName)
$Providers = @()
if(Test-Path -Path $ProviderPath){
$Providers = @(Get-ChildItem -Path $ProviderPath -Filter "Provider.*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName)
}
$Public = @(Get-ChildItem -Path $PublicPath -Filter "*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName)
foreach($File in @($Private + $Public)){
foreach($File in @($Private + $Providers + $Public)){
. $File.FullName
}
Export-ModuleMember -Function @(
"Resolve-DSCConfigurationData"
"Get-DSCConfigurationDataCredentialProvider",
"Resolve-DSCConfigurationData",
"Register-DSCConfigurationDataCredentialProvider",
"Set-DSCConfigurationDataCredentialProvider",
"Unregister-DSCConfigurationDataCredentialProvider"
)