From e10ab48bb42835004bb30a9dd61d41b5e4afcbfd Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 21 Apr 2026 13:02:51 +0200 Subject: [PATCH] Creating Module --- Merge-ConfigurationData.ps1 | 330 +----------------- Merge-DSCConfigurationData.psd1 | 24 ++ Merge-DSCConfigurationData.psm1 | 13 + Private/Copy-ConfigurationDataValue.ps1 | 37 ++ Private/Format-ConfigurationDataMergeKey.ps1 | 16 + ...et-ConfigurationDataArrayMergeKeyNames.ps1 | 30 ++ .../Get-ConfigurationDataArraySearchItem.ps1 | 24 ++ Private/Get-ConfigurationDataItemValue.ps1 | 20 ++ .../Test-ConfigurationDataItemContainsKey.ps1 | 20 ++ Private/Test-ConfigurationDataItemHasKeys.ps1 | 18 + .../Test-ConfigurationDataItemKeyMatch.ps1 | 32 ++ .../Test-ConfigurationDataItemWildcard.ps1 | 20 ++ Public/Merge-ConfigurationData.ps1 | 98 ++++++ Tests/Merge-ConfigurationData.Tests.ps1 | 132 +++++++ 14 files changed, 493 insertions(+), 321 deletions(-) create mode 100644 Merge-DSCConfigurationData.psd1 create mode 100644 Merge-DSCConfigurationData.psm1 create mode 100644 Private/Copy-ConfigurationDataValue.ps1 create mode 100644 Private/Format-ConfigurationDataMergeKey.ps1 create mode 100644 Private/Get-ConfigurationDataArrayMergeKeyNames.ps1 create mode 100644 Private/Get-ConfigurationDataArraySearchItem.ps1 create mode 100644 Private/Get-ConfigurationDataItemValue.ps1 create mode 100644 Private/Test-ConfigurationDataItemContainsKey.ps1 create mode 100644 Private/Test-ConfigurationDataItemHasKeys.ps1 create mode 100644 Private/Test-ConfigurationDataItemKeyMatch.ps1 create mode 100644 Private/Test-ConfigurationDataItemWildcard.ps1 create mode 100644 Public/Merge-ConfigurationData.ps1 create mode 100644 Tests/Merge-ConfigurationData.Tests.ps1 diff --git a/Merge-ConfigurationData.ps1 b/Merge-ConfigurationData.ps1 index e5f1a8d..f04aad9 100644 --- a/Merge-ConfigurationData.ps1 +++ b/Merge-ConfigurationData.ps1 @@ -1,327 +1,15 @@ -function Merge-ConfigurationData { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true)] - [System.Collections.Hashtable] - $Template, - [Parameter(Mandatory=$true)] - [System.Collections.Hashtable] - $Deployment, - [Parameter(Mandatory=$false)] - [System.Collections.Hashtable] - $Output - ) - - if($Output -eq $null){ - $Output = $Deployment - } +$ModuleRoot = $PSScriptRoot - foreach($Property in $Template.GetEnumerator()){ - if($Property.Value -is [System.Collections.Hashtable]){ - Write-Verbose "Key [$($Property.Name)] is a Hashtable" - if($null -ne $Deployment.$($Property.Name)){ - Write-Verbose "Key [$($Property.Name)] is present in Deployment Data" - $Output.($Property.Name) = Merge-ConfigurationData -Template $Template.$($Property.Name) -Deployment $Deployment.$($Property.Name) -Output $Output.$($Property.Name) - }else{ - Write-Verbose "Key [$($Property.Name)] is not present in Deployment Data" - $Output.Add($($Property.Name),(Copy-ConfigurationDataValue -Value $Template.$($Property.Name))) - } - }elseif($Property.Value -is [System.Collections.Specialized.OrderedDictionary]){ - Write-Verbose "Key [$($Property.Name)] is a Ordered Dictionary" - if($null -ne $Deployment.$($Property.Name)){ - Write-Verbose "Key [$($Property.Name)] is present in Deployment Data" - $Output.($Property.Name) = Merge-ConfigurationData -Template $Template.$($Property.Name) -Deployment $Deployment.$($Property.Name) -Output $Output.$($Property.Name) - }else{ - Write-Verbose "Key [$($Property.Name)] is not present in Deployment Data" - $Output.Add($($Property.Name),(Copy-ConfigurationDataValue -Value $Template.$($Property.Name))) - } - }elseif($Property.Value -is [System.Array]){ - Write-Verbose "$($Property.Name) is ein Array" - Write-Verbose "Total Items in Template Array [$($Property.Value.Count)]" - Write-Verbose "Total Items in Deployment Array [$($Deployment.$($Property.Name).Count)]" - if($null -ne $Deployment.$($Property.Name)){ - Write-Verbose "Array is defined in Deployment" - - $TemplateItems = for($i=0;$i -lt $Property.Value.Count; $i++){ - $SearchKeyNames = @(Get-ConfigurationDataArrayMergeKeyNames -ArrayName $Property.Name -Item $Property.Value[$i]) - [PSCustomObject]@{ - Value = $Property.Value[$i] - SearchKeyNames = $SearchKeyNames - IsWildcard = Test-ConfigurationDataItemWildcard -Item $Property.Value[$i] -KeyNames $SearchKeyNames - } - } - - $TemplateItems = @($TemplateItems | Where-Object { -not $_.IsWildcard }) + @($TemplateItems | Where-Object { $_.IsWildcard }) - - foreach($TemplateItem in $TemplateItems){ - $SearchKeyNames = @($TemplateItem.SearchKeyNames) - - if($SearchKeyNames.Count -eq 0){ - Write-Verbose "No matching name key found for item in [$($Property.Name)]" - $Output.$($Property.Name) += ,(Copy-ConfigurationDataValue -Value $TemplateItem.Value) - continue - } - - if($TemplateItem.IsWildcard){ - $MatchingDeploymentItems = @($Deployment.$($Property.Name) | Where-Object { Test-ConfigurationDataItemHasKeys -Item $_ -KeyNames $SearchKeyNames }) - }else{ - $MatchingDeploymentItems = @($Deployment.$($Property.Name) | Where-Object { Test-ConfigurationDataItemKeyMatch -Left $_ -Right $TemplateItem.Value -KeyNames $SearchKeyNames }) - } - - if($MatchingDeploymentItems.Count -gt 0){ - foreach($DeploymentItem in $MatchingDeploymentItems){ - $OutputItem = @($Output.$($Property.Name) | Where-Object { Test-ConfigurationDataItemKeyMatch -Left $_ -Right $DeploymentItem -KeyNames $SearchKeyNames })[0] - Merge-ConfigurationData -Template $TemplateItem.Value -Deployment $DeploymentItem -Output $OutputItem | Out-Null - } - }else{ - if($TemplateItem.IsWildcard){ - Write-Verbose "Wildcard item [$(Format-ConfigurationDataMergeKey -Item $TemplateItem.Value -KeyNames $SearchKeyNames)] in [$($Property.Name)] matched no deployment items" - }else{ - Write-Verbose "Pair [$(Format-ConfigurationDataMergeKey -Item $TemplateItem.Value -KeyNames $SearchKeyNames)] not present" - $Output.$($Property.Name) += ,(Copy-ConfigurationDataValue -Value $TemplateItem.Value) - } - } - } - }else{ - Write-Verbose "Array is not defined in Deployment" - $Output.$($Property.Name) = Copy-ConfigurationDataValue -Value $Template.$($Property.Name) - } - }else{ - Write-Verbose "$($Property.Name) is a String or Integer Value" - if($null -eq $Deployment.$($Property.Name)){ - - $Output.Add($Property.Name,(Copy-ConfigurationDataValue -Value $Template.($Property.Name))) - - }elseif($Deployment.$($Property.Name) -ne $Property.Value){ - - } - } - } - return $Output +if([String]::IsNullOrWhiteSpace($ModuleRoot)){ + throw "This compatibility loader must be dot-sourced from a file path. Use Import-Module './Merge-DSCConfigurationData.psd1' for module usage." } -function Copy-ConfigurationDataValue { - [CmdletBinding()] - Param( - [AllowNull()] - $Value - ) +$PrivatePath = Join-Path -Path $ModuleRoot -ChildPath "Private" +$PublicPath = Join-Path -Path $ModuleRoot -ChildPath "Public" - if($null -eq $Value){ - return $null - } +$Private = @(Get-ChildItem -Path $PrivatePath -Filter "*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName) +$Public = @(Get-ChildItem -Path $PublicPath -Filter "*.ps1" -File -ErrorAction Stop | Sort-Object -Property FullName) - if($Value -is [System.Collections.Specialized.OrderedDictionary]){ - $Copy = [ordered]@{} - foreach($Entry in $Value.GetEnumerator()){ - $Copy[$Entry.Name] = Copy-ConfigurationDataValue -Value $Entry.Value - } - return $Copy - } - - if($Value -is [System.Collections.Hashtable]){ - $Copy = @{} - foreach($Entry in $Value.GetEnumerator()){ - $Copy[$Entry.Name] = Copy-ConfigurationDataValue -Value $Entry.Value - } - return $Copy - } - - if($Value -is [System.Array]){ - $Copy = @() - foreach($Item in $Value){ - $Copy += ,(Copy-ConfigurationDataValue -Value $Item) - } - return ,$Copy - } - - return $Value -} - -function Get-ConfigurationDataArraySearchItem { - [CmdletBinding()] - Param( - [AllowNull()] - $Item - ) - - if($null -eq $Item){ - return $null - } - - if(-not ( - ($Item -is [System.Collections.Hashtable]) -or - ($Item -is [System.Collections.Specialized.OrderedDictionary]) - )){ - return $null - } - - return @( - $Item.GetEnumerator() | - Where-Object { ($_.Value -is [String]) -and ($_.Name -like "*Name") } | - Select-Object -First 1 - )[0] -} - -function Get-ConfigurationDataArrayMergeKeyNames { - [CmdletBinding()] - Param( - [Parameter(Mandatory=$true)] - [String] - $ArrayName, - [AllowNull()] - $Item - ) - - $MergeKeyMap = @{ - Instances = @('Name') - ConfigurationOptions = @('OptionName') - AdditionalScripts = @('ScriptName') - Templates = @('TemplateName') - AllNodes = @('NodeName') - Registry = @('Key','ValueName') - } - - if($MergeKeyMap.ContainsKey($ArrayName)){ - return $MergeKeyMap[$ArrayName] - } - - $SearchItem = Get-ConfigurationDataArraySearchItem -Item $Item - if($null -ne $SearchItem){ - return @($SearchItem.Name) - } - - return @() -} - -function Test-ConfigurationDataItemContainsKey { - [CmdletBinding()] - Param( - [AllowNull()] - $Item, - [Parameter(Mandatory=$true)] - [String] - $KeyName - ) - - if($null -eq $Item){ - return $false - } - - if($Item -is [System.Collections.IDictionary]){ - return $Item.Contains($KeyName) - } - - return $null -ne $Item.PSObject.Properties[$KeyName] -} - -function Get-ConfigurationDataItemValue { - [CmdletBinding()] - Param( - [AllowNull()] - $Item, - [Parameter(Mandatory=$true)] - [String] - $KeyName - ) - - if(-not (Test-ConfigurationDataItemContainsKey -Item $Item -KeyName $KeyName)){ - return $null - } - - if($Item -is [System.Collections.IDictionary]){ - return $Item[$KeyName] - } - - return $Item.$KeyName -} - -function Test-ConfigurationDataItemHasKeys { - [CmdletBinding()] - Param( - [AllowNull()] - $Item, - [Parameter(Mandatory=$true)] - [String[]] - $KeyNames - ) - - foreach($KeyName in $KeyNames){ - if(-not (Test-ConfigurationDataItemContainsKey -Item $Item -KeyName $KeyName)){ - return $false - } - } - - return $true -} - -function Test-ConfigurationDataItemKeyMatch { - [CmdletBinding()] - Param( - [AllowNull()] - $Left, - [AllowNull()] - $Right, - [Parameter(Mandatory=$true)] - [String[]] - $KeyNames - ) - - if($KeyNames.Count -eq 0){ - return $false - } - - foreach($KeyName in $KeyNames){ - if(-not (Test-ConfigurationDataItemContainsKey -Item $Left -KeyName $KeyName)){ - return $false - } - - if(-not (Test-ConfigurationDataItemContainsKey -Item $Right -KeyName $KeyName)){ - return $false - } - - if((Get-ConfigurationDataItemValue -Item $Left -KeyName $KeyName) -ne (Get-ConfigurationDataItemValue -Item $Right -KeyName $KeyName)){ - return $false - } - } - - return $true -} - -function Test-ConfigurationDataItemWildcard { - [CmdletBinding()] - Param( - [AllowNull()] - $Item, - [Parameter(Mandatory=$true)] - [String[]] - $KeyNames - ) - - if($KeyNames.Count -ne 1){ - return $false - } - - if(-not (Test-ConfigurationDataItemContainsKey -Item $Item -KeyName $KeyNames[0])){ - return $false - } - - return (Get-ConfigurationDataItemValue -Item $Item -KeyName $KeyNames[0]) -eq "*" -} - -function Format-ConfigurationDataMergeKey { - [CmdletBinding()] - Param( - [AllowNull()] - $Item, - [Parameter(Mandatory=$true)] - [String[]] - $KeyNames - ) - - $Pairs = foreach($KeyName in $KeyNames){ - "$KeyName=$(Get-ConfigurationDataItemValue -Item $Item -KeyName $KeyName)" - } - - return ($Pairs -join ", ") +foreach($File in @($Private + $Public)){ + . $File.FullName } diff --git a/Merge-DSCConfigurationData.psd1 b/Merge-DSCConfigurationData.psd1 new file mode 100644 index 0000000..53b5a0f --- /dev/null +++ b/Merge-DSCConfigurationData.psd1 @@ -0,0 +1,24 @@ +@{ + RootModule = "Merge-DSCConfigurationData.psm1" + ModuleVersion = "0.1.0" + GUID = "c1c7e70d-9049-4eaa-a3c9-44a424c35ef5" + Author = "Torsten Brendgen" + Copyright = "(c) Torsten Brendgen. All rights reserved." + Description = "Merges DSC configuration data from templates and deployment data." + PowerShellVersion = "5.1" + FunctionsToExport = @( + "Merge-ConfigurationData" + ) + CmdletsToExport = @() + VariablesToExport = @() + AliasesToExport = @() + PrivateData = @{ + PSData = @{ + Tags = @( + "DSC", + "ConfigurationData" + ) + ReleaseNotes = "Initial module layout." + } + } +} diff --git a/Merge-DSCConfigurationData.psm1 b/Merge-DSCConfigurationData.psm1 new file mode 100644 index 0000000..46660ff --- /dev/null +++ b/Merge-DSCConfigurationData.psm1 @@ -0,0 +1,13 @@ +$PrivatePath = Join-Path -Path $PSScriptRoot -ChildPath "Private" +$PublicPath = Join-Path -Path $PSScriptRoot -ChildPath "Public" + +$Private = @(Get-ChildItem -Path $PrivatePath -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)){ + . $File.FullName +} + +Export-ModuleMember -Function @( + "Merge-ConfigurationData" +) diff --git a/Private/Copy-ConfigurationDataValue.ps1 b/Private/Copy-ConfigurationDataValue.ps1 new file mode 100644 index 0000000..30bcefd --- /dev/null +++ b/Private/Copy-ConfigurationDataValue.ps1 @@ -0,0 +1,37 @@ +function Copy-ConfigurationDataValue { + [CmdletBinding()] + Param( + [AllowNull()] + $Value + ) + + if($null -eq $Value){ + return $null + } + + if($Value -is [System.Collections.Specialized.OrderedDictionary]){ + $Copy = [ordered]@{} + foreach($Entry in $Value.GetEnumerator()){ + $Copy[$Entry.Name] = Copy-ConfigurationDataValue -Value $Entry.Value + } + return $Copy + } + + if($Value -is [System.Collections.Hashtable]){ + $Copy = @{} + foreach($Entry in $Value.GetEnumerator()){ + $Copy[$Entry.Name] = Copy-ConfigurationDataValue -Value $Entry.Value + } + return $Copy + } + + if($Value -is [System.Array]){ + $Copy = @() + foreach($Item in $Value){ + $Copy += ,(Copy-ConfigurationDataValue -Value $Item) + } + return ,$Copy + } + + return $Value +} diff --git a/Private/Format-ConfigurationDataMergeKey.ps1 b/Private/Format-ConfigurationDataMergeKey.ps1 new file mode 100644 index 0000000..1715810 --- /dev/null +++ b/Private/Format-ConfigurationDataMergeKey.ps1 @@ -0,0 +1,16 @@ +function Format-ConfigurationDataMergeKey { + [CmdletBinding()] + Param( + [AllowNull()] + $Item, + [Parameter(Mandatory=$true)] + [String[]] + $KeyNames + ) + + $Pairs = foreach($KeyName in $KeyNames){ + "$KeyName=$(Get-ConfigurationDataItemValue -Item $Item -KeyName $KeyName)" + } + + return ($Pairs -join ", ") +} diff --git a/Private/Get-ConfigurationDataArrayMergeKeyNames.ps1 b/Private/Get-ConfigurationDataArrayMergeKeyNames.ps1 new file mode 100644 index 0000000..8c512eb --- /dev/null +++ b/Private/Get-ConfigurationDataArrayMergeKeyNames.ps1 @@ -0,0 +1,30 @@ +function Get-ConfigurationDataArrayMergeKeyNames { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [String] + $ArrayName, + [AllowNull()] + $Item + ) + + $MergeKeyMap = @{ + Instances = @("Name") + ConfigurationOptions = @("OptionName") + AdditionalScripts = @("ScriptName") + Templates = @("TemplateName") + AllNodes = @("NodeName") + Registry = @("Key","ValueName") + } + + if($MergeKeyMap.ContainsKey($ArrayName)){ + return $MergeKeyMap[$ArrayName] + } + + $SearchItem = Get-ConfigurationDataArraySearchItem -Item $Item + if($null -ne $SearchItem){ + return @($SearchItem.Name) + } + + return @() +} diff --git a/Private/Get-ConfigurationDataArraySearchItem.ps1 b/Private/Get-ConfigurationDataArraySearchItem.ps1 new file mode 100644 index 0000000..a1ed7cc --- /dev/null +++ b/Private/Get-ConfigurationDataArraySearchItem.ps1 @@ -0,0 +1,24 @@ +function Get-ConfigurationDataArraySearchItem { + [CmdletBinding()] + Param( + [AllowNull()] + $Item + ) + + if($null -eq $Item){ + return $null + } + + if(-not ( + ($Item -is [System.Collections.Hashtable]) -or + ($Item -is [System.Collections.Specialized.OrderedDictionary]) + )){ + return $null + } + + return @( + $Item.GetEnumerator() | + Where-Object { ($_.Value -is [String]) -and ($_.Name -like "*Name") } | + Select-Object -First 1 + )[0] +} diff --git a/Private/Get-ConfigurationDataItemValue.ps1 b/Private/Get-ConfigurationDataItemValue.ps1 new file mode 100644 index 0000000..e1bb764 --- /dev/null +++ b/Private/Get-ConfigurationDataItemValue.ps1 @@ -0,0 +1,20 @@ +function Get-ConfigurationDataItemValue { + [CmdletBinding()] + Param( + [AllowNull()] + $Item, + [Parameter(Mandatory=$true)] + [String] + $KeyName + ) + + if(-not (Test-ConfigurationDataItemContainsKey -Item $Item -KeyName $KeyName)){ + return $null + } + + if($Item -is [System.Collections.IDictionary]){ + return $Item[$KeyName] + } + + return $Item.$KeyName +} diff --git a/Private/Test-ConfigurationDataItemContainsKey.ps1 b/Private/Test-ConfigurationDataItemContainsKey.ps1 new file mode 100644 index 0000000..952689c --- /dev/null +++ b/Private/Test-ConfigurationDataItemContainsKey.ps1 @@ -0,0 +1,20 @@ +function Test-ConfigurationDataItemContainsKey { + [CmdletBinding()] + Param( + [AllowNull()] + $Item, + [Parameter(Mandatory=$true)] + [String] + $KeyName + ) + + if($null -eq $Item){ + return $false + } + + if($Item -is [System.Collections.IDictionary]){ + return $Item.Contains($KeyName) + } + + return $null -ne $Item.PSObject.Properties[$KeyName] +} diff --git a/Private/Test-ConfigurationDataItemHasKeys.ps1 b/Private/Test-ConfigurationDataItemHasKeys.ps1 new file mode 100644 index 0000000..f140d80 --- /dev/null +++ b/Private/Test-ConfigurationDataItemHasKeys.ps1 @@ -0,0 +1,18 @@ +function Test-ConfigurationDataItemHasKeys { + [CmdletBinding()] + Param( + [AllowNull()] + $Item, + [Parameter(Mandatory=$true)] + [String[]] + $KeyNames + ) + + foreach($KeyName in $KeyNames){ + if(-not (Test-ConfigurationDataItemContainsKey -Item $Item -KeyName $KeyName)){ + return $false + } + } + + return $true +} diff --git a/Private/Test-ConfigurationDataItemKeyMatch.ps1 b/Private/Test-ConfigurationDataItemKeyMatch.ps1 new file mode 100644 index 0000000..1b1479d --- /dev/null +++ b/Private/Test-ConfigurationDataItemKeyMatch.ps1 @@ -0,0 +1,32 @@ +function Test-ConfigurationDataItemKeyMatch { + [CmdletBinding()] + Param( + [AllowNull()] + $Left, + [AllowNull()] + $Right, + [Parameter(Mandatory=$true)] + [String[]] + $KeyNames + ) + + if($KeyNames.Count -eq 0){ + return $false + } + + foreach($KeyName in $KeyNames){ + if(-not (Test-ConfigurationDataItemContainsKey -Item $Left -KeyName $KeyName)){ + return $false + } + + if(-not (Test-ConfigurationDataItemContainsKey -Item $Right -KeyName $KeyName)){ + return $false + } + + if((Get-ConfigurationDataItemValue -Item $Left -KeyName $KeyName) -ne (Get-ConfigurationDataItemValue -Item $Right -KeyName $KeyName)){ + return $false + } + } + + return $true +} diff --git a/Private/Test-ConfigurationDataItemWildcard.ps1 b/Private/Test-ConfigurationDataItemWildcard.ps1 new file mode 100644 index 0000000..4676f49 --- /dev/null +++ b/Private/Test-ConfigurationDataItemWildcard.ps1 @@ -0,0 +1,20 @@ +function Test-ConfigurationDataItemWildcard { + [CmdletBinding()] + Param( + [AllowNull()] + $Item, + [Parameter(Mandatory=$true)] + [String[]] + $KeyNames + ) + + if($KeyNames.Count -ne 1){ + return $false + } + + if(-not (Test-ConfigurationDataItemContainsKey -Item $Item -KeyName $KeyNames[0])){ + return $false + } + + return (Get-ConfigurationDataItemValue -Item $Item -KeyName $KeyNames[0]) -eq "*" +} diff --git a/Public/Merge-ConfigurationData.ps1 b/Public/Merge-ConfigurationData.ps1 new file mode 100644 index 0000000..38afef3 --- /dev/null +++ b/Public/Merge-ConfigurationData.ps1 @@ -0,0 +1,98 @@ +function Merge-ConfigurationData { + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true)] + [System.Collections.Hashtable] + $Template, + [Parameter(Mandatory=$true)] + [System.Collections.Hashtable] + $Deployment, + [Parameter(Mandatory=$false)] + [System.Collections.Hashtable] + $Output + ) + + if($Output -eq $null){ + $Output = $Deployment + } + + foreach($Property in $Template.GetEnumerator()){ + if($Property.Value -is [System.Collections.Hashtable]){ + Write-Verbose "Key [$($Property.Name)] is a Hashtable" + if($null -ne $Deployment.$($Property.Name)){ + Write-Verbose "Key [$($Property.Name)] is present in Deployment Data" + $Output.($Property.Name) = Merge-ConfigurationData -Template $Template.$($Property.Name) -Deployment $Deployment.$($Property.Name) -Output $Output.$($Property.Name) + }else{ + Write-Verbose "Key [$($Property.Name)] is not present in Deployment Data" + $Output.Add($($Property.Name),(Copy-ConfigurationDataValue -Value $Template.$($Property.Name))) + } + }elseif($Property.Value -is [System.Collections.Specialized.OrderedDictionary]){ + Write-Verbose "Key [$($Property.Name)] is a Ordered Dictionary" + if($null -ne $Deployment.$($Property.Name)){ + Write-Verbose "Key [$($Property.Name)] is present in Deployment Data" + $Output.($Property.Name) = Merge-ConfigurationData -Template $Template.$($Property.Name) -Deployment $Deployment.$($Property.Name) -Output $Output.$($Property.Name) + }else{ + Write-Verbose "Key [$($Property.Name)] is not present in Deployment Data" + $Output.Add($($Property.Name),(Copy-ConfigurationDataValue -Value $Template.$($Property.Name))) + } + }elseif($Property.Value -is [System.Array]){ + Write-Verbose "$($Property.Name) is ein Array" + Write-Verbose "Total Items in Template Array [$($Property.Value.Count)]" + Write-Verbose "Total Items in Deployment Array [$($Deployment.$($Property.Name).Count)]" + if($null -ne $Deployment.$($Property.Name)){ + Write-Verbose "Array is defined in Deployment" + + $TemplateItems = for($i=0;$i -lt $Property.Value.Count; $i++){ + $SearchKeyNames = @(Get-ConfigurationDataArrayMergeKeyNames -ArrayName $Property.Name -Item $Property.Value[$i]) + [PSCustomObject]@{ + Value = $Property.Value[$i] + SearchKeyNames = $SearchKeyNames + IsWildcard = Test-ConfigurationDataItemWildcard -Item $Property.Value[$i] -KeyNames $SearchKeyNames + } + } + + $TemplateItems = @($TemplateItems | Where-Object { -not $_.IsWildcard }) + @($TemplateItems | Where-Object { $_.IsWildcard }) + + foreach($TemplateItem in $TemplateItems){ + $SearchKeyNames = @($TemplateItem.SearchKeyNames) + + if($SearchKeyNames.Count -eq 0){ + Write-Verbose "No matching name key found for item in [$($Property.Name)]" + $Output.$($Property.Name) += ,(Copy-ConfigurationDataValue -Value $TemplateItem.Value) + continue + } + + if($TemplateItem.IsWildcard){ + $MatchingDeploymentItems = @($Deployment.$($Property.Name) | Where-Object { Test-ConfigurationDataItemHasKeys -Item $_ -KeyNames $SearchKeyNames }) + }else{ + $MatchingDeploymentItems = @($Deployment.$($Property.Name) | Where-Object { Test-ConfigurationDataItemKeyMatch -Left $_ -Right $TemplateItem.Value -KeyNames $SearchKeyNames }) + } + + if($MatchingDeploymentItems.Count -gt 0){ + foreach($DeploymentItem in $MatchingDeploymentItems){ + $OutputItem = @($Output.$($Property.Name) | Where-Object { Test-ConfigurationDataItemKeyMatch -Left $_ -Right $DeploymentItem -KeyNames $SearchKeyNames })[0] + Merge-ConfigurationData -Template $TemplateItem.Value -Deployment $DeploymentItem -Output $OutputItem | Out-Null + } + }else{ + if($TemplateItem.IsWildcard){ + Write-Verbose "Wildcard item [$(Format-ConfigurationDataMergeKey -Item $TemplateItem.Value -KeyNames $SearchKeyNames)] in [$($Property.Name)] matched no deployment items" + }else{ + Write-Verbose "Pair [$(Format-ConfigurationDataMergeKey -Item $TemplateItem.Value -KeyNames $SearchKeyNames)] not present" + $Output.$($Property.Name) += ,(Copy-ConfigurationDataValue -Value $TemplateItem.Value) + } + } + } + }else{ + Write-Verbose "Array is not defined in Deployment" + $Output.$($Property.Name) = Copy-ConfigurationDataValue -Value $Template.$($Property.Name) + } + }else{ + Write-Verbose "$($Property.Name) is a String or Integer Value" + if($null -eq $Deployment.$($Property.Name)){ + $Output.Add($Property.Name,(Copy-ConfigurationDataValue -Value $Template.($Property.Name))) + }elseif($Deployment.$($Property.Name) -ne $Property.Value){ + } + } + } + return $Output +} diff --git a/Tests/Merge-ConfigurationData.Tests.ps1 b/Tests/Merge-ConfigurationData.Tests.ps1 new file mode 100644 index 0000000..1d73b2a --- /dev/null +++ b/Tests/Merge-ConfigurationData.Tests.ps1 @@ -0,0 +1,132 @@ +$script:ModuleRoot = Split-Path -Parent $PSScriptRoot +$script:SampleRoot = Join-Path -Path $script:ModuleRoot -ChildPath ".sample" + +Import-Module (Join-Path -Path $script:ModuleRoot -ChildPath "Merge-DSCConfigurationData.psd1") -Force + +Describe "Merge-ConfigurationData" { + It "merges wildcard instance templates into all deployment instances" { + $Template = Import-PowerShellDataFile (Join-Path -Path $script:SampleRoot -ChildPath "Template.psd1") + $Deployment = Import-PowerShellDataFile (Join-Path -Path $script:SampleRoot -ChildPath "Deployment.psd1") + + $Result = Merge-ConfigurationData -Template $Template -Deployment $Deployment + $Instances = @($Result.NonNodeData.Services.SQLServer.Instances) + + $Instances.Count | Should Be 2 + ($Instances.Name -contains "*") | Should Be $false + + foreach($Name in @("MSSQLSERVER", "MSSQLSERVERTEST")){ + $Instance = $Instances | Where-Object { $_.Name -eq $Name } + + $Instance | Should Not BeNullOrEmpty + $Instance.Features | Should Be "SQLENGINE,FULLTEXT" + $Instance.ConfigurationOptions.Count | Should Be 4 + $Instance.AdditionalScripts.Count | Should Be 6 + $Instance.Memory | Should Not BeNullOrEmpty + $Instance.Directories | Should Not BeNullOrEmpty + } + } + + It "does not merge an exact instance template into another instance name" { + $Template = @{ + NonNodeData = @{ + Services = @{ + SQLServer = @{ + Instances = @( + @{ + Name = "MSSQLSERVER" + Marker = "exact-only" + } + ) + } + } + } + } + + $Deployment = @{ + NonNodeData = @{ + Services = @{ + SQLServer = @{ + Instances = @( + @{ Name = "MSSQLSERVER" }, + @{ Name = "MSSQLSERVERTEST" } + ) + } + } + } + } + + $Result = Merge-ConfigurationData -Template $Template -Deployment $Deployment + $Instances = @($Result.NonNodeData.Services.SQLServer.Instances) + + ($Instances | Where-Object { $_.Name -eq "MSSQLSERVER" }).Marker | Should Be "exact-only" + ($Instances | Where-Object { $_.Name -eq "MSSQLSERVERTEST" }).Marker | Should BeNullOrEmpty + } + + It "uses the name-key fallback for unknown arrays" { + $Template = @{ + Databases = @( + @{ + DatabaseName = "AppDb" + RecoveryModel = "Full" + } + ) + } + + $Deployment = @{ + Databases = @( + @{ + DatabaseName = "AppDb" + Owner = "dbo" + }, + @{ + DatabaseName = "LogDb" + Owner = "dbo" + } + ) + } + + $Result = Merge-ConfigurationData -Template $Template -Deployment $Deployment + $Databases = @($Result.Databases) + + ($Databases | Where-Object { $_.DatabaseName -eq "AppDb" }).RecoveryModel | Should Be "Full" + ($Databases | Where-Object { $_.DatabaseName -eq "LogDb" }).RecoveryModel | Should BeNullOrEmpty + } + + It "uses Key and ValueName together for registry array merges" { + $Template = @{ + Basic = @{ + Registry = @( + @{ + Key = "K1" + ValueName = "Enabled" + Default = "Template-K1" + }, + @{ + Key = "K2" + ValueName = "Enabled" + Default = "Template-K2" + } + ) + } + } + + $Deployment = @{ + Basic = @{ + Registry = @( + @{ + Key = "K1" + ValueName = "Enabled" + ValueData = "Deployment-K1" + } + ) + } + } + + $Result = Merge-ConfigurationData -Template $Template -Deployment $Deployment + $Registry = @($Result.Basic.Registry) + + ($Registry | Where-Object { $_.Key -eq "K1" }).Default | Should Be "Template-K1" + ($Registry | Where-Object { $_.Key -eq "K1" }).ValueData | Should Be "Deployment-K1" + ($Registry | Where-Object { $_.Key -eq "K2" }).Default | Should Be "Template-K2" + } +}