Compare commits
9 Commits
a96fb5bd07
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b67c74ac0 | ||
|
|
45e71c093c | ||
|
|
3fa52d7ada | ||
|
|
4b7ebe59ef | ||
|
|
943894b8fe | ||
|
|
9b36693444 | ||
|
|
115b8be385 | ||
|
|
1cd225c7a0 | ||
| 88b842c8e9 |
@@ -1,512 +0,0 @@
|
|||||||
$script:ModuleRoot = Split-Path -Path $PSScriptRoot -Parent
|
|
||||||
Import-Module (Join-Path -Path $script:ModuleRoot -ChildPath "Resolve-DSCConfigurationData.psd1") -Force
|
|
||||||
|
|
||||||
Describe "Resolve-DSCConfigurationData" {
|
|
||||||
It "resolves parameters using Value before DefaultValue" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Name = @{
|
|
||||||
DefaultValue = "Default"
|
|
||||||
Value = "Deployment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Value = "[parameters('Name')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Value | Should Be "Deployment"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "allows parameter values listed in AllowedValues" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Stage = @{
|
|
||||||
Value = "Test"
|
|
||||||
AllowedValues = @(
|
|
||||||
"Install",
|
|
||||||
"Test",
|
|
||||||
"Release"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Stage = "[parameters('Stage')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Stage | Should Be "Test"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "validates DefaultValue against AllowedValues when Value is not set" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Stage = @{
|
|
||||||
DefaultValue = "Release"
|
|
||||||
AllowedValues = @(
|
|
||||||
"Install",
|
|
||||||
"Test",
|
|
||||||
"Release"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Stage = "[parameters('Stage')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Stage | Should Be "Release"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "throws when a parameter value is not listed in AllowedValues" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Stage = @{
|
|
||||||
Value = "Production"
|
|
||||||
AllowedValues = @(
|
|
||||||
"Install",
|
|
||||||
"Test",
|
|
||||||
"Release"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Stage = "[parameters('Stage')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
|
|
||||||
}
|
|
||||||
|
|
||||||
It "validates array parameter items against AllowedValues" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
ServerRoles = @{
|
|
||||||
Type = "array"
|
|
||||||
Value = @(
|
|
||||||
"WebFrontEnd",
|
|
||||||
"Application"
|
|
||||||
)
|
|
||||||
AllowedValues = @(
|
|
||||||
"WebFrontEnd",
|
|
||||||
"Application",
|
|
||||||
"Search"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Roles = "[parameters('ServerRoles')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Roles[0] | Should Be "WebFrontEnd"
|
|
||||||
$Result.Resources.Roles[1] | Should Be "Application"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "throws when an array parameter item is not listed in AllowedValues" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
ServerRoles = @{
|
|
||||||
Type = "array"
|
|
||||||
Value = @(
|
|
||||||
"WebFrontEnd",
|
|
||||||
"InvalidRole"
|
|
||||||
)
|
|
||||||
AllowedValues = @(
|
|
||||||
"WebFrontEnd",
|
|
||||||
"Application",
|
|
||||||
"Search"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
|
|
||||||
}
|
|
||||||
|
|
||||||
It "validates required parameters" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
DatabasePrefix = @{
|
|
||||||
Type = "string"
|
|
||||||
Required = $true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
|
|
||||||
}
|
|
||||||
|
|
||||||
It "validates parameter length constraints" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
DatabasePrefix = @{
|
|
||||||
Value = "S"
|
|
||||||
MinLength = 2
|
|
||||||
MaxLength = 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
|
|
||||||
}
|
|
||||||
|
|
||||||
It "validates parameter patterns" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
DatabasePrefix = @{
|
|
||||||
Value = "Share Point"
|
|
||||||
Pattern = "^[A-Za-z][A-Za-z0-9_-]*$"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
|
|
||||||
}
|
|
||||||
|
|
||||||
It "validates supported parameter types" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Name = @{
|
|
||||||
Type = "string"
|
|
||||||
Value = "SharePoint"
|
|
||||||
}
|
|
||||||
Port = @{
|
|
||||||
Type = "int"
|
|
||||||
Value = 1433
|
|
||||||
}
|
|
||||||
Enabled = @{
|
|
||||||
Type = "bool"
|
|
||||||
Value = $true
|
|
||||||
}
|
|
||||||
Roles = @{
|
|
||||||
Type = "array"
|
|
||||||
Value = @(
|
|
||||||
"WebFrontEnd",
|
|
||||||
"Application"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SecretReference = @{
|
|
||||||
Type = "secureString"
|
|
||||||
Value = @{
|
|
||||||
SecretName = "SharePointFarmPassphrase"
|
|
||||||
}
|
|
||||||
Sensitive = $true
|
|
||||||
}
|
|
||||||
CredentialReference = @{
|
|
||||||
Type = "credential"
|
|
||||||
Value = @{
|
|
||||||
CredentialName = "SharePointSetup"
|
|
||||||
}
|
|
||||||
Sensitive = $true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Name = "[parameters('Name')]"
|
|
||||||
Port = "[parameters('Port')]"
|
|
||||||
Enabled = "[parameters('Enabled')]"
|
|
||||||
RoleCount = "[length(parameters('Roles'))]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Name | Should Be "SharePoint"
|
|
||||||
$Result.Resources.Port | Should Be 1433
|
|
||||||
$Result.Resources.Enabled | Should Be $true
|
|
||||||
$Result.Resources.RoleCount | Should Be 2
|
|
||||||
}
|
|
||||||
|
|
||||||
It "throws when parameter type validation fails" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Port = @{
|
|
||||||
Type = "int"
|
|
||||||
Value = "1433"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
|
|
||||||
}
|
|
||||||
|
|
||||||
It "validates numeric ranges" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Port = @{
|
|
||||||
Type = "int"
|
|
||||||
Value = 1433
|
|
||||||
MinValue = 1
|
|
||||||
MaxValue = 65535
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Port = "[parameters('Port')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Port | Should Be 1433
|
|
||||||
}
|
|
||||||
|
|
||||||
It "throws when numeric range validation fails" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Port = @{
|
|
||||||
Type = "int"
|
|
||||||
Value = 70000
|
|
||||||
MinValue = 1
|
|
||||||
MaxValue = 65535
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
|
|
||||||
}
|
|
||||||
|
|
||||||
It "supports localized deprecated parameter messages" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
OldDatabasePrefix = @{
|
|
||||||
Value = "SharePoint"
|
|
||||||
Deprecated = @{
|
|
||||||
Message = @{
|
|
||||||
"de-DE" = "Der Parameter [OldDatabasePrefix] ist veraltet. Verwende [DatabasePrefix]."
|
|
||||||
"en-US" = "Parameter [OldDatabasePrefix] is deprecated. Use [DatabasePrefix]."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Value = "[parameters('OldDatabasePrefix')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Warnings = @()
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData -WarningVariable Warnings 3>$null
|
|
||||||
|
|
||||||
$Result.Resources.Value | Should Be "SharePoint"
|
|
||||||
$Warnings.Count | Should Be 1
|
|
||||||
([string]$Warnings[0]) | Should Match "OldDatabasePrefix"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "resolves variables that reference parameters" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Prefix = @{
|
|
||||||
Value = "SharePoint"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Variables = @{
|
|
||||||
ConfigDbName = "[concat(parameters('Prefix'), '_Farm_Config')]"
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
DatabaseName = "[variables('ConfigDbName')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Variables.ConfigDbName | Should Be "SharePoint_Farm_Config"
|
|
||||||
$Result.Resources.DatabaseName | Should Be "SharePoint_Farm_Config"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "resolves nested functions" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
Prefix = @{
|
|
||||||
Value = "sharepoint"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Name = "[concat(toUpper(parameters('Prefix')), '_Services')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Name | Should Be "SHAREPOINT_Services"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "resolves string helper functions" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Resources = @{
|
|
||||||
Lower = "[toLower('BGW-LAN')]"
|
|
||||||
Upper = "[toUpper('bgw-lan')]"
|
|
||||||
First = "[firstIndexOf('a', 'b', 'c')]"
|
|
||||||
Last = "[lastIndexOf('a', 'b', 'c')]"
|
|
||||||
Index = "[indexOf('BGW', 1)]"
|
|
||||||
Substring = "[substring('SharePoint', 5, 5)]"
|
|
||||||
Replace = "[replace('BGW-LAN', '-', '_')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Lower | Should Be "bgw-lan"
|
|
||||||
$Result.Resources.Upper | Should Be "BGW-LAN"
|
|
||||||
$Result.Resources.First | Should Be "a"
|
|
||||||
$Result.Resources.Last | Should Be "c"
|
|
||||||
$Result.Resources.Index | Should Be "G"
|
|
||||||
$Result.Resources.Substring | Should Be "Point"
|
|
||||||
$Result.Resources.Replace | Should Be "BGW_LAN"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "resolves formatting, fallback, and conditional functions" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
DatabasePrefix = @{
|
|
||||||
Value = "SharePoint"
|
|
||||||
}
|
|
||||||
DomainLabel = @{
|
|
||||||
Value = "LAN"
|
|
||||||
}
|
|
||||||
Stage = @{
|
|
||||||
Value = "Test"
|
|
||||||
}
|
|
||||||
OptionalValue = @{
|
|
||||||
Value = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Formatted = "[format('{0}_{1}_{2}', parameters('DatabasePrefix'), parameters('DomainLabel'), parameters('Stage'))]"
|
|
||||||
Fallback = "[coalesce(parameters('OptionalValue'), 'DefaultValue')]"
|
|
||||||
Conditional = "[if(equals(parameters('Stage'), 'Test'), 'TST', 'PRD')]"
|
|
||||||
NotEquals = "[notEquals(parameters('Stage'), 'Prod')]"
|
|
||||||
AndValue = "[and(equals(parameters('Stage'), 'Test'), not(empty(parameters('DatabasePrefix'))))]"
|
|
||||||
OrValue = "[or(equals(parameters('Stage'), 'Prod'), equals(parameters('Stage'), 'Test'))]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Formatted | Should Be "SharePoint_LAN_Test"
|
|
||||||
$Result.Resources.Fallback | Should Be "DefaultValue"
|
|
||||||
$Result.Resources.Conditional | Should Be "TST"
|
|
||||||
$Result.Resources.NotEquals | Should Be $true
|
|
||||||
$Result.Resources.AndValue | Should Be $true
|
|
||||||
$Result.Resources.OrValue | Should Be $true
|
|
||||||
}
|
|
||||||
|
|
||||||
It "resolves collection and string inspection functions" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
DomainFQDN = @{
|
|
||||||
Value = "bgw-online.de"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Split = "[split(parameters('DomainFQDN'), '.')]"
|
|
||||||
Joined = "[join(split(parameters('DomainFQDN'), '.'), '_')]"
|
|
||||||
JoinedNotEmpty = "[joinNotEmpty('_', 'SharePoint', '', 'LAN', 'Test', '', 'Services')]"
|
|
||||||
Take = "[join(take(split(parameters('DomainFQDN'), '.'), 1), '_')]"
|
|
||||||
Skip = "[join(skip(split(parameters('DomainFQDN'), '.'), 1), '_')]"
|
|
||||||
First = "[first(split(parameters('DomainFQDN'), '.'))]"
|
|
||||||
Last = "[last(split(parameters('DomainFQDN'), '.'))]"
|
|
||||||
Unique = "[join(unique(split('SP.SP.SQL', '.')), '_')]"
|
|
||||||
Sort = "[join(sort(split('SQL.SP.APP', '.')), '_')]"
|
|
||||||
ContainsString = "[contains(parameters('DomainFQDN'), 'online')]"
|
|
||||||
ContainsArray = "[contains(split(parameters('DomainFQDN'), '.'), 'de')]"
|
|
||||||
StartsWith = "[startsWith(parameters('DomainFQDN'), 'bgw')]"
|
|
||||||
EndsWith = "[endsWith(parameters('DomainFQDN'), 'DE')]"
|
|
||||||
Length = "[length(split(parameters('DomainFQDN'), '.'))]"
|
|
||||||
Empty = "[empty('')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Split[0] | Should Be "bgw-online"
|
|
||||||
$Result.Resources.Split[1] | Should Be "de"
|
|
||||||
$Result.Resources.Joined | Should Be "bgw-online_de"
|
|
||||||
$Result.Resources.JoinedNotEmpty | Should Be "SharePoint_LAN_Test_Services"
|
|
||||||
$Result.Resources.Take | Should Be "bgw-online"
|
|
||||||
$Result.Resources.Skip | Should Be "de"
|
|
||||||
$Result.Resources.First | Should Be "bgw-online"
|
|
||||||
$Result.Resources.Last | Should Be "de"
|
|
||||||
$Result.Resources.Unique | Should Be "SP_SQL"
|
|
||||||
$Result.Resources.Sort | Should Be "APP_SP_SQL"
|
|
||||||
$Result.Resources.ContainsString | Should Be $true
|
|
||||||
$Result.Resources.ContainsArray | Should Be $true
|
|
||||||
$Result.Resources.StartsWith | Should Be $true
|
|
||||||
$Result.Resources.EndsWith | Should Be $true
|
|
||||||
$Result.Resources.Length | Should Be 2
|
|
||||||
$Result.Resources.Empty | Should Be $true
|
|
||||||
}
|
|
||||||
|
|
||||||
It "resolves trim and padding functions" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Resources = @{
|
|
||||||
Trim = "[trim(' SharePoint ')]"
|
|
||||||
TrimStart = "[trimStart(' SharePoint')]"
|
|
||||||
TrimEnd = "[trimEnd('SharePoint ')]"
|
|
||||||
PadLeft = "[padLeft('1', 2, '0')]"
|
|
||||||
PadRight = "[padRight('SP', 4, '0')]"
|
|
||||||
DefaultIfEmpty = "[defaultIfEmpty('', 'SharePoint')]"
|
|
||||||
Sanitized = "[sanitizeName(' SharePoint LAN/Test DB ')]"
|
|
||||||
Normalized = "[normalizeSeparator('__SharePoint___LAN_Test__', '_')]"
|
|
||||||
Prefix = "[prefixIfNotEmpty('LAN', 'BGW-')]"
|
|
||||||
EmptyPrefix = "[prefixIfNotEmpty('', 'BGW-')]"
|
|
||||||
Suffix = "[suffixIfNotEmpty('SharePoint', '_DB')]"
|
|
||||||
EmptySuffix = "[suffixIfNotEmpty('', '_DB')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.Resources.Trim | Should Be "SharePoint"
|
|
||||||
$Result.Resources.TrimStart | Should Be "SharePoint"
|
|
||||||
$Result.Resources.TrimEnd | Should Be "SharePoint"
|
|
||||||
$Result.Resources.PadLeft | Should Be "01"
|
|
||||||
$Result.Resources.PadRight | Should Be "SP00"
|
|
||||||
$Result.Resources.DefaultIfEmpty | Should Be "SharePoint"
|
|
||||||
$Result.Resources.Sanitized | Should Be "SharePoint_LAN_Test_DB"
|
|
||||||
$Result.Resources.Normalized | Should Be "SharePoint_LAN_Test"
|
|
||||||
$Result.Resources.Prefix | Should Be "BGW-LAN"
|
|
||||||
$Result.Resources.EmptyPrefix | Should Be ""
|
|
||||||
$Result.Resources.Suffix | Should Be "SharePoint_DB"
|
|
||||||
$Result.Resources.EmptySuffix | Should Be ""
|
|
||||||
}
|
|
||||||
|
|
||||||
It "resolves values inside arrays recursively" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Parameters = @{
|
|
||||||
NodeName = @{
|
|
||||||
Value = "SP01"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AllNodes = @(
|
|
||||||
@{
|
|
||||||
NodeName = "[parameters('NodeName')]"
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
$Result = Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData
|
|
||||||
|
|
||||||
$Result.AllNodes[0].NodeName | Should Be "SP01"
|
|
||||||
}
|
|
||||||
|
|
||||||
It "throws for circular variables" {
|
|
||||||
$ConfigurationData = @{
|
|
||||||
Variables = @{
|
|
||||||
A = "[variables('B')]"
|
|
||||||
B = "[variables('A')]"
|
|
||||||
}
|
|
||||||
Resources = @{
|
|
||||||
Value = "[variables('A')]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ Resolve-DSCConfigurationData -ConfigurationData $ConfigurationData } | Should Throw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -42,6 +42,10 @@ function Assert-ConfigurationDataParameter {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if((Test-ConfigurationDataValueIsEmpty -Value $Value) -and (-not $Required)){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Assert-ConfigurationDataParameterType -Name $Name -Definition $Definition -Value $Value
|
Assert-ConfigurationDataParameterType -Name $Name -Definition $Definition -Value $Value
|
||||||
Assert-ConfigurationDataParameterAllowedValue -Name $Name -Definition $Definition -Value $Value
|
Assert-ConfigurationDataParameterAllowedValue -Name $Name -Definition $Definition -Value $Value
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ function Assert-ConfigurationDataParameterType {
|
|||||||
|
|
||||||
switch($TypeName){
|
switch($TypeName){
|
||||||
"string" {
|
"string" {
|
||||||
|
if(Test-ConfigurationDataSecretReference -Value $Value){
|
||||||
|
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "string" -Value $Value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if($Value -isnot [string]){
|
if($Value -isnot [string]){
|
||||||
throw "Parameter [$Name] expects type [string], but received [$($Value.GetType().Name)]."
|
throw "Parameter [$Name] expects type [string], but received [$($Value.GetType().Name)]."
|
||||||
}
|
}
|
||||||
@@ -48,11 +53,15 @@ function Assert-ConfigurationDataParameterType {
|
|||||||
if(($Value -isnot [string]) -and ($Value -isnot [System.Security.SecureString]) -and (-not (Test-ConfigurationDataMap -Value $Value))){
|
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)]."
|
throw "Parameter [$Name] expects type [secureString], but received [$($Value.GetType().Name)]."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "securestring" -Value $Value
|
||||||
}
|
}
|
||||||
"credential" {
|
"credential" {
|
||||||
if(($Value -isnot [System.Management.Automation.PSCredential]) -and (-not (Test-ConfigurationDataMap -Value $Value))){
|
if(($Value -isnot [System.Management.Automation.PSCredential]) -and (-not (Test-ConfigurationDataMap -Value $Value))){
|
||||||
throw "Parameter [$Name] expects type [credential], but received [$($Value.GetType().Name)]."
|
throw "Parameter [$Name] expects type [credential], but received [$($Value.GetType().Name)]."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert-ConfigurationDataSecretReference -Name $Name -TypeName "credential" -Value $Value
|
||||||
}
|
}
|
||||||
default {
|
default {
|
||||||
throw "Parameter [$Name] uses unsupported type [$TypeName]."
|
throw "Parameter [$Name] uses unsupported type [$TypeName]."
|
||||||
|
|||||||
47
Private/Assert-ConfigurationDataSecretReference.ps1
Normal file
47
Private/Assert-ConfigurationDataSecretReference.ps1
Normal 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]."
|
||||||
|
}
|
||||||
|
}
|
||||||
101
Private/ConvertFrom-ConfigurationDataSecretValue.ps1
Normal file
101
Private/ConvertFrom-ConfigurationDataSecretValue.ps1
Normal 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
|
||||||
|
}
|
||||||
19
Private/ConvertTo-ConfigurationDataCredential.ps1
Normal file
19
Private/ConvertTo-ConfigurationDataCredential.ps1
Normal 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)
|
||||||
|
}
|
||||||
63
Private/ConvertTo-PowerShellDataFileText.ps1
Normal file
63
Private/ConvertTo-PowerShellDataFileText.ps1
Normal 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("'", "''"))'"
|
||||||
|
}
|
||||||
28
Private/Export-PowerShellDataFile.ps1
Normal file
28
Private/Export-PowerShellDataFile.ps1
Normal 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
|
||||||
|
}
|
||||||
@@ -6,8 +6,15 @@ function Get-ConfigurationDataMapValue {
|
|||||||
|
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]
|
[string]
|
||||||
$Key
|
$Key,
|
||||||
|
|
||||||
|
[AllowNull()]
|
||||||
|
$DefaultValue = $null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(-not (Test-ConfigurationDataMapContainsKey -Map $Map -Key $Key)){
|
||||||
|
return $DefaultValue
|
||||||
|
}
|
||||||
|
|
||||||
return $Map[$Key]
|
return $Map[$Key]
|
||||||
}
|
}
|
||||||
|
|||||||
39
Private/Get-ConfigurationDataObjectPropertyValue.ps1
Normal file
39
Private/Get-ConfigurationDataObjectPropertyValue.ps1
Normal 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
|
||||||
|
}
|
||||||
61
Private/Get-ConfigurationDataPathValue.ps1
Normal file
61
Private/Get-ConfigurationDataPathValue.ps1
Normal 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
|
||||||
|
}
|
||||||
19
Private/Get-ConfigurationDataSecretProvider.ps1
Normal file
19
Private/Get-ConfigurationDataSecretProvider.ps1
Normal 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]
|
||||||
|
}
|
||||||
14
Private/Get-ConfigurationDataSecretProviderName.ps1
Normal file
14
Private/Get-ConfigurationDataSecretProviderName.ps1
Normal 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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -23,12 +23,40 @@ function Invoke-ConfigurationDataExpression {
|
|||||||
return [bool]::Parse($Expression)
|
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]*)\((.*)\)$"){
|
if($Expression -notmatch "^([A-Za-z][A-Za-z0-9]*)\((.*)\)$"){
|
||||||
throw "Invalid configuration data expression [$Expression]."
|
throw "Invalid configuration data expression [$Expression]."
|
||||||
}
|
}
|
||||||
|
|
||||||
$FunctionName = $Matches[1]
|
$FunctionName = $Matches[1]
|
||||||
$ArgumentText = $Matches[2]
|
$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 = @()
|
$Arguments = @()
|
||||||
foreach($Argument in @(Split-ConfigurationDataExpressionArguments -ArgumentText $ArgumentText)){
|
foreach($Argument in @(Split-ConfigurationDataExpressionArguments -ArgumentText $ArgumentText)){
|
||||||
$Arguments += ,(Invoke-ConfigurationDataExpressionArgument -Argument $Argument -Context $Context)
|
$Arguments += ,(Invoke-ConfigurationDataExpressionArgument -Argument $Argument -Context $Context)
|
||||||
|
|||||||
@@ -24,6 +24,17 @@ function Invoke-ConfigurationDataExpressionFunction {
|
|||||||
Assert-ConfigurationDataExpressionArgumentCount -Name $Name -Arguments $Arguments -Count 1
|
Assert-ConfigurationDataExpressionArgumentCount -Name $Name -Arguments $Arguments -Count 1
|
||||||
return Resolve-ConfigurationDataVariable -Name ([string]$Arguments[0]) -Context $Context
|
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" {
|
"concat" {
|
||||||
return (@($Arguments) | ForEach-Object { [string]$_ }) -join ""
|
return (@($Arguments) | ForEach-Object { [string]$_ }) -join ""
|
||||||
}
|
}
|
||||||
|
|||||||
31
Private/New-ConfigurationDataAesKeyFile.ps1
Normal file
31
Private/New-ConfigurationDataAesKeyFile.ps1
Normal 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
|
||||||
|
}
|
||||||
68
Private/New-ConfigurationDataDummySecretValue.ps1
Normal file
68
Private/New-ConfigurationDataDummySecretValue.ps1
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,13 @@ function New-ConfigurationDataResolutionContext {
|
|||||||
)
|
)
|
||||||
|
|
||||||
$Context = [PSCustomObject]@{
|
$Context = [PSCustomObject]@{
|
||||||
|
ConfigurationData = $ConfigurationData
|
||||||
Parameters = @{}
|
Parameters = @{}
|
||||||
Variables = @{}
|
Variables = @{}
|
||||||
VariableDefinitions = @{}
|
VariableDefinitions = @{}
|
||||||
ResolvingVariables = @{}
|
ResolvingVariables = @{}
|
||||||
|
ReferenceCache = @{}
|
||||||
|
ResolvingReferences = @{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($ConfigurationData.ContainsKey("Parameters") -and $null -ne $ConfigurationData.Parameters){
|
if($ConfigurationData.ContainsKey("Parameters") -and $null -ne $ConfigurationData.Parameters){
|
||||||
|
|||||||
40
Private/Register-ConfigurationDataSecretProvider.ps1
Normal file
40
Private/Register-ConfigurationDataSecretProvider.ps1
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Private/Resolve-ConfigurationDataParameterSecrets.ps1
Normal file
50
Private/Resolve-ConfigurationDataParameterSecrets.ps1
Normal 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
|
||||||
|
}
|
||||||
51
Private/Resolve-ConfigurationDataProviderSecureString.ps1
Normal file
51
Private/Resolve-ConfigurationDataProviderSecureString.ps1
Normal 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."
|
||||||
|
}
|
||||||
28
Private/Resolve-ConfigurationDataProviderSettingsPath.ps1
Normal file
28
Private/Resolve-ConfigurationDataProviderSettingsPath.ps1
Normal 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
|
||||||
|
}
|
||||||
46
Private/Resolve-ConfigurationDataReference.ps1
Normal file
46
Private/Resolve-ConfigurationDataReference.ps1
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Private/Resolve-ConfigurationDataSecretReference.ps1
Normal file
35
Private/Resolve-ConfigurationDataSecretReference.ps1
Normal 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
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ function Resolve-ConfigurationDataValue {
|
|||||||
if($Value -is [System.Collections.Specialized.OrderedDictionary]){
|
if($Value -is [System.Collections.Specialized.OrderedDictionary]){
|
||||||
$Resolved = [ordered]@{}
|
$Resolved = [ordered]@{}
|
||||||
foreach($Entry in $Value.GetEnumerator()){
|
foreach($Entry in $Value.GetEnumerator()){
|
||||||
|
if($Entry.Name -eq "Sealed"){
|
||||||
|
continue
|
||||||
|
}
|
||||||
$Resolved[$Entry.Name] = Resolve-ConfigurationDataValue -Value $Entry.Value -Context $Context
|
$Resolved[$Entry.Name] = Resolve-ConfigurationDataValue -Value $Entry.Value -Context $Context
|
||||||
}
|
}
|
||||||
return $Resolved
|
return $Resolved
|
||||||
@@ -23,6 +26,9 @@ function Resolve-ConfigurationDataValue {
|
|||||||
if($Value -is [System.Collections.Hashtable]){
|
if($Value -is [System.Collections.Hashtable]){
|
||||||
$Resolved = @{}
|
$Resolved = @{}
|
||||||
foreach($Entry in $Value.GetEnumerator()){
|
foreach($Entry in $Value.GetEnumerator()){
|
||||||
|
if($Entry.Name -eq "Sealed"){
|
||||||
|
continue
|
||||||
|
}
|
||||||
$Resolved[$Entry.Name] = Resolve-ConfigurationDataValue -Value $Entry.Value -Context $Context
|
$Resolved[$Entry.Name] = Resolve-ConfigurationDataValue -Value $Entry.Value -Context $Context
|
||||||
}
|
}
|
||||||
return $Resolved
|
return $Resolved
|
||||||
|
|||||||
39
Private/Test-ConfigurationDataExpressionParentheses.ps1
Normal file
39
Private/Test-ConfigurationDataExpressionParentheses.ps1
Normal 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
|
||||||
|
}
|
||||||
11
Private/Test-ConfigurationDataMissingReferenceError.ps1
Normal file
11
Private/Test-ConfigurationDataMissingReferenceError.ps1
Normal 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\.$"
|
||||||
|
}
|
||||||
14
Private/Test-ConfigurationDataSecretReference.ps1
Normal file
14
Private/Test-ConfigurationDataSecretReference.ps1
Normal 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")
|
||||||
|
}
|
||||||
49
Private/Unlock-ConfigurationDataSecretManagementVault.ps1
Normal file
49
Private/Unlock-ConfigurationDataSecretManagementVault.ps1
Normal 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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
Providers/Provider.SecretManagement.ps1
Normal file
57
Providers/Provider.SecretManagement.ps1
Normal 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
|
||||||
119
Public/Get-DSCConfigurationDataCredentialProvider.ps1
Normal file
119
Public/Get-DSCConfigurationDataCredentialProvider.ps1
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
243
Public/Register-DSCConfigurationDataCredentialProvider.ps1
Normal file
243
Public/Register-DSCConfigurationDataCredentialProvider.ps1
Normal 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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,37 @@ function Resolve-DSCConfigurationData {
|
|||||||
Param(
|
Param(
|
||||||
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
|
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
|
||||||
[System.Collections.Hashtable]
|
[System.Collections.Hashtable]
|
||||||
$ConfigurationData
|
$ConfigurationData,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[hashtable]
|
||||||
|
$ProviderSettings = @{},
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[string]
|
||||||
|
$ProviderSettingsPath,
|
||||||
|
|
||||||
|
[Parameter(Mandatory=$false)]
|
||||||
|
[switch]
|
||||||
|
$SkipSecrets
|
||||||
)
|
)
|
||||||
|
|
||||||
process {
|
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
|
$Context = New-ConfigurationDataResolutionContext -ConfigurationData $ConfigurationData
|
||||||
return Resolve-ConfigurationDataValue -Value $ConfigurationData -Context $Context
|
return Resolve-ConfigurationDataValue -Value $ConfigurationData -Context $Context
|
||||||
}
|
}
|
||||||
|
|||||||
258
Public/Set-DSCConfigurationDataCredentialProvider.ps1
Normal file
258
Public/Set-DSCConfigurationDataCredentialProvider.ps1
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
Public/Unregister-DSCConfigurationDataCredentialProvider.ps1
Normal file
100
Public/Unregister-DSCConfigurationDataCredentialProvider.ps1
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
229
Readme.md
229
Readme.md
@@ -9,6 +9,13 @@ $merged = Merge-DSCConfigurationData -Template $service -Deployment $environment
|
|||||||
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged
|
$resolved = Resolve-DSCConfigurationData -ConfigurationData $merged
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Pipeline flow:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$resolved = Merge-DSCConfigurationData -Template $service -Deployment $environment |
|
||||||
|
Resolve-DSCConfigurationData
|
||||||
|
```
|
||||||
|
|
||||||
Expressions use an ARM-like syntax:
|
Expressions use an ARM-like syntax:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
@@ -57,6 +64,9 @@ This block shows all currently supported parameter properties.
|
|||||||
# Metadata for later reporting/output tooling. The resolver does not mask values yet.
|
# Metadata for later reporting/output tooling. The resolver does not mask values yet.
|
||||||
Sensitive = $false
|
Sensitive = $false
|
||||||
|
|
||||||
|
# Prevents child templates from changing this parameter definition during merge.
|
||||||
|
Sealed = $false
|
||||||
|
|
||||||
# Emits a warning when the parameter is present.
|
# Emits a warning when the parameter is present.
|
||||||
Deprecated = @{
|
Deprecated = @{
|
||||||
Message = @{
|
Message = @{
|
||||||
@@ -82,6 +92,7 @@ Notes:
|
|||||||
- `Type`, `Required`, `AllowedValues`, `MinLength`, `MaxLength`, `MinValue`, `MaxValue`, 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.
|
- `AllowedValues` validates scalar values directly and array values item by item.
|
||||||
- `Sensitive` is currently metadata only.
|
- `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.
|
- `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_-]*$'`.
|
- Optional string parameters can allow empty values with a pattern like `'^$|^[A-Za-z][A-Za-z0-9_-]*$'`.
|
||||||
|
|
||||||
@@ -101,18 +112,222 @@ Supported parameter types:
|
|||||||
FarmPassphrase = @{
|
FarmPassphrase = @{
|
||||||
Type = 'secureString'
|
Type = 'secureString'
|
||||||
Required = $true
|
Required = $true
|
||||||
Sensitive = $true
|
Sensitive = $true
|
||||||
Value = @{
|
Value = @{
|
||||||
SecretName = 'SharePointFarmPassphrase'
|
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 = @{
|
SetupCredential = @{
|
||||||
Type = 'credential'
|
Type = 'credential'
|
||||||
Required = $true
|
Required = $true
|
||||||
Sensitive = $true
|
Sensitive = $true
|
||||||
Value = @{
|
Value = @{
|
||||||
CredentialName = 'SharePointSetup'
|
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'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -199,12 +414,16 @@ ConfigDbName : SharePoint_corp_TST_Farm_Config
|
|||||||
```powershell
|
```powershell
|
||||||
"[parameters('DatabasePrefix')]"
|
"[parameters('DatabasePrefix')]"
|
||||||
"[variables('ServiceDbPrefix')]"
|
"[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`.
|
`parameters(name)` returns the effective parameter value. `Value` is used before `DefaultValue`.
|
||||||
|
|
||||||
`variables(name)` resolves another variable. Variables may reference other variables.
|
`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
|
### String Composition
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
@@ -407,6 +626,8 @@ ConfigDbName : SharePoint_corp_TST_Farm_Config
|
|||||||
|
|
||||||
- `parameters(name)`
|
- `parameters(name)`
|
||||||
- `variables(name)`
|
- `variables(name)`
|
||||||
|
- `reference(path)`
|
||||||
|
- `reference(path, property)`
|
||||||
- `concat(value1, value2, ...)`
|
- `concat(value1, value2, ...)`
|
||||||
- `format(formatString, value1, value2, ...)`
|
- `format(formatString, value1, value2, ...)`
|
||||||
- `coalesce(value1, value2, ...)`
|
- `coalesce(value1, value2, ...)`
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
@{
|
@{
|
||||||
RootModule = "Resolve-DSCConfigurationData.psm1"
|
RootModule = "Resolve-DSCConfigurationData.psm1"
|
||||||
ModuleVersion = "1.0.0"
|
ModuleVersion = "1.1.0"
|
||||||
GUID = "1d6ba0d4-93d5-4b0f-94c7-bf10a30fe0e8"
|
GUID = "1d6ba0d4-93d5-4b0f-94c7-bf10a30fe0e8"
|
||||||
Author = "Torsten Brendgen"
|
Author = "Torsten Brendgen"
|
||||||
Copyright = "(c) Torsten Brendgen. All rights reserved."
|
Copyright = "(c) Torsten Brendgen. All rights reserved."
|
||||||
Description = "Resolves parameters, variables, and expressions in DSC configuration data."
|
Description = "Resolves parameters, variables, and expressions in DSC configuration data."
|
||||||
PowerShellVersion = "5.1"
|
PowerShellVersion = "5.1"
|
||||||
FunctionsToExport = @(
|
FunctionsToExport = @(
|
||||||
"Resolve-DSCConfigurationData"
|
"Get-DSCConfigurationDataCredentialProvider",
|
||||||
|
"Resolve-DSCConfigurationData",
|
||||||
|
"Register-DSCConfigurationDataCredentialProvider",
|
||||||
|
"Set-DSCConfigurationDataCredentialProvider",
|
||||||
|
"Unregister-DSCConfigurationDataCredentialProvider"
|
||||||
)
|
)
|
||||||
CmdletsToExport = @()
|
CmdletsToExport = @()
|
||||||
VariablesToExport = @()
|
VariablesToExport = @()
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
$PrivatePath = Join-Path -Path $PSScriptRoot -ChildPath "Private"
|
$PrivatePath = Join-Path -Path $PSScriptRoot -ChildPath "Private"
|
||||||
|
$ProviderPath = Join-Path -Path $PSScriptRoot -ChildPath "Providers"
|
||||||
$PublicPath = Join-Path -Path $PSScriptRoot -ChildPath "Public"
|
$PublicPath = Join-Path -Path $PSScriptRoot -ChildPath "Public"
|
||||||
|
|
||||||
$Private = @(Get-ChildItem -Path $PrivatePath -Filter "*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName)
|
$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)
|
$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
|
. $File.FullName
|
||||||
}
|
}
|
||||||
|
|
||||||
Export-ModuleMember -Function @(
|
Export-ModuleMember -Function @(
|
||||||
"Resolve-DSCConfigurationData"
|
"Get-DSCConfigurationDataCredentialProvider",
|
||||||
|
"Resolve-DSCConfigurationData",
|
||||||
|
"Register-DSCConfigurationDataCredentialProvider",
|
||||||
|
"Set-DSCConfigurationDataCredentialProvider",
|
||||||
|
"Unregister-DSCConfigurationDataCredentialProvider"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user