diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..877df7e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.git +.sample +.tests \ No newline at end of file diff --git a/Merge-ConfigurationData.ps1 b/Merge-ConfigurationData.ps1 deleted file mode 100644 index 79fddec..0000000 --- a/Merge-ConfigurationData.ps1 +++ /dev/null @@ -1,70 +0,0 @@ -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),$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),$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" - for($i=0;$i -lt $Property.Value.Count; $i++){ - - $SearchItem = $($Property.Value[$i].GetEnumerator() | Where-Object {($_.Value -is [String]) -and ($_.Name -like "*Name")})[0] - if($($Deployment.$($Property.Name) | ? { $_.($SearchItem.Name) -eq $SearchItem.Value })){ - Merge-ConfigurationData -Template $Property.Value[$i] -Deployment $($Deployment.$($Property.Name) | ? { $_.($SearchItem.Name) -eq $SearchItem.Value }) -Output $($Output.$($Property.Name) | ? { $_.($SearchItem.Name) -eq $SearchItem.Value }) | Out-Null - }else{ - Write-Verbose "Pair $($Key.Name) - $($Key.Value) not present" - $Output.$($Property.Name) += $Property.Value[$i] - } - } - }else{ - Write-Verbose "Array is not defined in Deployment" - $Output.$($Property.Name) = $Template.$($Property.Name) - } - }else{ - Write-Verbose "$($Property.Name) is a String or Integer Value" - if($null -eq $Deployment.$($Property.Name)){ - - $Output.Add($Property.Name,$Template.($Property.Name)) - - }elseif($Deployment.$($Property.Name) -ne $Property.Value){ - - } - } - } - return $Output -} \ No newline at end of file diff --git a/Merge-DSCConfigurationData.psd1 b/Merge-DSCConfigurationData.psd1 new file mode 100644 index 0000000..5d0ef2d --- /dev/null +++ b/Merge-DSCConfigurationData.psd1 @@ -0,0 +1,24 @@ +@{ + RootModule = "Merge-DSCConfigurationData.psm1" + ModuleVersion = "1.0.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-DSCConfigurationData" + ) + 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..6b916e2 --- /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-DSCConfigurationData" +) 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-DSCConfigurationData.ps1 b/Public/Merge-DSCConfigurationData.ps1 new file mode 100644 index 0000000..0a7d1c6 --- /dev/null +++ b/Public/Merge-DSCConfigurationData.ps1 @@ -0,0 +1,98 @@ +function Merge-DSCConfigurationData { + [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-DSCConfigurationData -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-DSCConfigurationData -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-DSCConfigurationData -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 +}