From f799c4adcc5fdd0e60ce667a377cf94da89ef898 Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 21 Apr 2026 12:25:33 +0200 Subject: [PATCH 1/7] adding samples --- .gitignore | 2 + sample/Deployment.psd1 | 111 +++++++++++++++++++++++++++++ sample/Template.psd1 | 157 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 .gitignore create mode 100644 sample/Deployment.psd1 create mode 100644 sample/Template.psd1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..552c980 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.git +.sample \ No newline at end of file diff --git a/sample/Deployment.psd1 b/sample/Deployment.psd1 new file mode 100644 index 0000000..3c65743 --- /dev/null +++ b/sample/Deployment.psd1 @@ -0,0 +1,111 @@ +@{ + Templates = @( + @{ + TemplateName = "HZD Template" + TemplatePath = "./Configurations/HZD.psd1" + }, + @{ + TemplateName = "SQL Server Template" + TemplatePath = "./Configurations/Services/Template-SQLServer.psd1" + }, + @{ + TemplateName = "Umgebungs Template" + TemplatePath = "./Configurations/Umgebungen/Template-Test.psd1" + } + ) + + AllNodes = @( + @{ + NodeName = "JURZMAZSSQL901" + } + ) + + NonNodeData =@{ + Services = @{ + SQLServer = @{ + Instances = @( + @{ + Name = 'MSSQLSERVER' + + ServiceAccounts = @{ + SQLEngineAccount = @{ + ServiceAccountName = "gmsaE2Af901" + AccountType = "Group" + Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" + ManagedPasswordPrincipals = @( + @{ + GroupName = "sicE2AfE2ADB901" + Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" + } + ) + } + SQLAgentAccount = @{ + ServiceAccountName = "gmsaE2Aa901" + AccountType = "Group" + Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" + ManagedPasswordPrincipals = @( + @{ + GroupName = "sicE2AfE2ADB901" + Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" + } + ) + } + } + AlwaysOn = @{ + StartIndex = "B" + AlwaysOnGroups = @( + @{ + Name = "TestAG" + FailoverMode ="Automatic" + SeedingMode = "Automatic" + Listener = @{ + Provision = $true + IpAddress = "'10.96.x.x/255.255.255.0','10.96.x.x/255.255.255.0'" + Port = 1433 + } + + } + ) + } + + ConfigurationOptions= @() + + AdditionalScripts = @() + }, + @{ + Name = 'MSSQLSERVERTEST' + + ServiceAccounts = @{ + SQLEngineAccount = @{ + ServiceAccountName = "gmsaE2Af901" + AccountType = "Group" + Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" + ManagedPasswordPrincipals = @( + @{ + GroupName = "sicE2AfE2ADB901" + Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" + } + ) + } + SQLAgentAccount = @{ + ServiceAccountName = "gmsaE2Aa901" + AccountType = "Group" + Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" + ManagedPasswordPrincipals = @( + @{ + GroupName = "sicE2AfE2ADB901" + Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" + } + ) + } + } + + ConfigurationOptions= @() + + AdditionalScripts = @() + } + ) + } + } + } +} \ No newline at end of file diff --git a/sample/Template.psd1 b/sample/Template.psd1 new file mode 100644 index 0000000..938a3be --- /dev/null +++ b/sample/Template.psd1 @@ -0,0 +1,157 @@ +@{ + NonNodeData =@{ + Services = @{ + SQLServer = @{ + General = @{ + Version = "" + Release = "2022" + SourcePath = "\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\Binaries" + DestinationPath = "C:\HZD\SQL_Install" + } + Basic = @{ + Registry = @( + @{ + Key = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" + ValueName = "DisableLoopbackCheck" + ValueData = 1 + ValueType = "Dword" + }, + @{ + Ensure = "Present" + Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\SQLServerAgent" + ValueName = "MsxEncryptChannelOptions" + ValueData = 1 + ValueType = "Dword" + } + + @{ + Ensure = 'Present' + Key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\SQLServerAgent' + ValueName = 'ErrorLogFile' + ValueData = 'D:\MSSQL16.MSSQLSERVER\MSSQL\Log\SQLAGENT.OUT' + ValueType = 'String' + }, + @{ + Ensure = 'Present' + Key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\SQLServerAgent' + ValueName = 'WorkingDirectory' + ValueData = 'D:\MSSQL16.MSSQLSERVER\MSSQL\JOBS' + ValueType = 'String' + } + ) + WindowsFeatures = @( + @{ + Name = "RSAT-AD-PowerShell" + } + ) + Modules = @( + @{ + Name = "SQLServer" + AllowClobber = $true + } + ) + } + Instances = @( + @{ + Name = '*' + Features = 'SQLENGINE,FULLTEXT' + SQLSysAdminAccounts = 'sicDBServer_ServerAdmins' + Directories = @{ + InstallSharedDir = 'D:\Microsoft SQL Server' + InstallSharedWOWDir = 'D:\Microsoft SQL Server (x86)' + InstanceDir = 'D:\Microsoft SQL Server' + InstallSQLDataDir = 'D:' + SQLSysAdminAccounts = 'sicDBServer_ServerAdmins' + SQLBackupDir = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\Backups' + SQLUserDBDir = '' + SQLUserDBLogDir = '' + SQLTempDBDir = '' + SQLTempDBLogDir = '' + } + + Drives = @{ + } + + MountPoints = @{ + } + + Memory = @{ + UseDynamic = $true + minValue = 1024 + maxValue = 8192 + } + + ServiceAccounts = @{ + } + + AlwaysOn = @{ + } + + ConfigurationOptions= @( + @{ + OptionName = 'show advanced options' + OptionValue = '1' + }, + @{ + OptionName = 'cost threshold for parallelism' + OptionValue = '50' + }, + @{ + OptionName = 'blocked process threshold (s)' + OptionValue = '30' + }, + @{ + OptionName = 'backup compression default' + OptionValue = '1' + } + + ) + + AdditionalScripts = @( + + @{ + ScriptName = 'AdminDB' + ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\AdminDB' + DependsOn = '[SqlSetup]Instance-MSSQLSERVER' + }, + + @{ + ScriptName = 'RenameSA' + ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\RenameSA' + DependsOn = '[SqlScript]AdminDB' + + }, + @{ + ScriptName = 'Maintenance_Solution' + ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\Maintenance_Solutions' + DependsOn = '[SqlScript]RenameSA' + }, + + @{ + ScriptName = 'WhoIsActive' + ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\WhoIsActive_Monitoring' + DependsOn = '[SqlScript]Maintenance_Solution' + }, + + + @{ + ScriptName = 'Alerts' + ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\Alerts_Email' + DependsOn = '[SqlScript]WhoIsActive' + }, + + + @{ + ScriptName = 'TempDB' + ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\TempDB_Size' + DependsOn = '[SqlScript]Alerts' + + } + + ) + } + ) + } + } + } +} \ No newline at end of file -- 2.39.5 From 7c9b854aec6163792354402fbb6d545c68403474 Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 21 Apr 2026 12:51:25 +0200 Subject: [PATCH 2/7] adding samples --- .gitignore | 3 +- Merge-ConfigurationData.ps1 | 281 ++++++++++++++++++++++++++++++++++-- sample/Deployment.psd1 | 111 -------------- sample/Template.psd1 | 157 -------------------- 4 files changed, 271 insertions(+), 281 deletions(-) delete mode 100644 sample/Deployment.psd1 delete mode 100644 sample/Template.psd1 diff --git a/.gitignore b/.gitignore index 552c980..942936b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .git -.sample \ No newline at end of file +.sample +.output \ No newline at end of file diff --git a/Merge-ConfigurationData.ps1 b/Merge-ConfigurationData.ps1 index 79fddec..e5f1a8d 100644 --- a/Merge-ConfigurationData.ps1 +++ b/Merge-ConfigurationData.ps1 @@ -24,7 +24,7 @@ $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)) + $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" @@ -33,7 +33,7 @@ $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)) + $Output.Add($($Property.Name),(Copy-ConfigurationDataValue -Value $Template.$($Property.Name))) } }elseif($Property.Value -is [System.Array]){ Write-Verbose "$($Property.Name) is ein Array" @@ -41,25 +41,56 @@ 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 + + $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{ - Write-Verbose "Pair $($Key.Name) - $($Key.Value) not present" - $Output.$($Property.Name) += $Property.Value[$i] + $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) = $Template.$($Property.Name) + $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,$Template.($Property.Name)) + $Output.Add($Property.Name,(Copy-ConfigurationDataValue -Value $Template.($Property.Name))) }elseif($Deployment.$($Property.Name) -ne $Property.Value){ @@ -67,4 +98,230 @@ } } return $Output -} \ No newline at end of file +} + +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 +} + +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 ", ") +} diff --git a/sample/Deployment.psd1 b/sample/Deployment.psd1 deleted file mode 100644 index 3c65743..0000000 --- a/sample/Deployment.psd1 +++ /dev/null @@ -1,111 +0,0 @@ -@{ - Templates = @( - @{ - TemplateName = "HZD Template" - TemplatePath = "./Configurations/HZD.psd1" - }, - @{ - TemplateName = "SQL Server Template" - TemplatePath = "./Configurations/Services/Template-SQLServer.psd1" - }, - @{ - TemplateName = "Umgebungs Template" - TemplatePath = "./Configurations/Umgebungen/Template-Test.psd1" - } - ) - - AllNodes = @( - @{ - NodeName = "JURZMAZSSQL901" - } - ) - - NonNodeData =@{ - Services = @{ - SQLServer = @{ - Instances = @( - @{ - Name = 'MSSQLSERVER' - - ServiceAccounts = @{ - SQLEngineAccount = @{ - ServiceAccountName = "gmsaE2Af901" - AccountType = "Group" - Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" - ManagedPasswordPrincipals = @( - @{ - GroupName = "sicE2AfE2ADB901" - Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" - } - ) - } - SQLAgentAccount = @{ - ServiceAccountName = "gmsaE2Aa901" - AccountType = "Group" - Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" - ManagedPasswordPrincipals = @( - @{ - GroupName = "sicE2AfE2ADB901" - Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" - } - ) - } - } - AlwaysOn = @{ - StartIndex = "B" - AlwaysOnGroups = @( - @{ - Name = "TestAG" - FailoverMode ="Automatic" - SeedingMode = "Automatic" - Listener = @{ - Provision = $true - IpAddress = "'10.96.x.x/255.255.255.0','10.96.x.x/255.255.255.0'" - Port = 1433 - } - - } - ) - } - - ConfigurationOptions= @() - - AdditionalScripts = @() - }, - @{ - Name = 'MSSQLSERVERTEST' - - ServiceAccounts = @{ - SQLEngineAccount = @{ - ServiceAccountName = "gmsaE2Af901" - AccountType = "Group" - Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" - ManagedPasswordPrincipals = @( - @{ - GroupName = "sicE2AfE2ADB901" - Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" - } - ) - } - SQLAgentAccount = @{ - ServiceAccountName = "gmsaE2Aa901" - AccountType = "Group" - Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" - ManagedPasswordPrincipals = @( - @{ - GroupName = "sicE2AfE2ADB901" - Path = "OU=DBA,OU=Dienste_Verfahren,OU=Administration,DC=justiz,DC=hessen,DC=de" - } - ) - } - } - - ConfigurationOptions= @() - - AdditionalScripts = @() - } - ) - } - } - } -} \ No newline at end of file diff --git a/sample/Template.psd1 b/sample/Template.psd1 deleted file mode 100644 index 938a3be..0000000 --- a/sample/Template.psd1 +++ /dev/null @@ -1,157 +0,0 @@ -@{ - NonNodeData =@{ - Services = @{ - SQLServer = @{ - General = @{ - Version = "" - Release = "2022" - SourcePath = "\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\Binaries" - DestinationPath = "C:\HZD\SQL_Install" - } - Basic = @{ - Registry = @( - @{ - Key = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa" - ValueName = "DisableLoopbackCheck" - ValueData = 1 - ValueType = "Dword" - }, - @{ - Ensure = "Present" - Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\SQLServerAgent" - ValueName = "MsxEncryptChannelOptions" - ValueData = 1 - ValueType = "Dword" - } - - @{ - Ensure = 'Present' - Key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\SQLServerAgent' - ValueName = 'ErrorLogFile' - ValueData = 'D:\MSSQL16.MSSQLSERVER\MSSQL\Log\SQLAGENT.OUT' - ValueType = 'String' - }, - @{ - Ensure = 'Present' - Key = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\SQLServerAgent' - ValueName = 'WorkingDirectory' - ValueData = 'D:\MSSQL16.MSSQLSERVER\MSSQL\JOBS' - ValueType = 'String' - } - ) - WindowsFeatures = @( - @{ - Name = "RSAT-AD-PowerShell" - } - ) - Modules = @( - @{ - Name = "SQLServer" - AllowClobber = $true - } - ) - } - Instances = @( - @{ - Name = '*' - Features = 'SQLENGINE,FULLTEXT' - SQLSysAdminAccounts = 'sicDBServer_ServerAdmins' - Directories = @{ - InstallSharedDir = 'D:\Microsoft SQL Server' - InstallSharedWOWDir = 'D:\Microsoft SQL Server (x86)' - InstanceDir = 'D:\Microsoft SQL Server' - InstallSQLDataDir = 'D:' - SQLSysAdminAccounts = 'sicDBServer_ServerAdmins' - SQLBackupDir = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\Backups' - SQLUserDBDir = '' - SQLUserDBLogDir = '' - SQLTempDBDir = '' - SQLTempDBLogDir = '' - } - - Drives = @{ - } - - MountPoints = @{ - } - - Memory = @{ - UseDynamic = $true - minValue = 1024 - maxValue = 8192 - } - - ServiceAccounts = @{ - } - - AlwaysOn = @{ - } - - ConfigurationOptions= @( - @{ - OptionName = 'show advanced options' - OptionValue = '1' - }, - @{ - OptionName = 'cost threshold for parallelism' - OptionValue = '50' - }, - @{ - OptionName = 'blocked process threshold (s)' - OptionValue = '30' - }, - @{ - OptionName = 'backup compression default' - OptionValue = '1' - } - - ) - - AdditionalScripts = @( - - @{ - ScriptName = 'AdminDB' - ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\AdminDB' - DependsOn = '[SqlSetup]Instance-MSSQLSERVER' - }, - - @{ - ScriptName = 'RenameSA' - ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\RenameSA' - DependsOn = '[SqlScript]AdminDB' - - }, - @{ - ScriptName = 'Maintenance_Solution' - ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\Maintenance_Solutions' - DependsOn = '[SqlScript]RenameSA' - }, - - @{ - ScriptName = 'WhoIsActive' - ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\WhoIsActive_Monitoring' - DependsOn = '[SqlScript]Maintenance_Solution' - }, - - - @{ - ScriptName = 'Alerts' - ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\Alerts_Email' - DependsOn = '[SqlScript]WhoIsActive' - }, - - - @{ - ScriptName = 'TempDB' - ScriptPath = '\\JURZMAZSFIL02.justiz.hessen.de\DbaDSCSources\SQL_Scripts\TempDB_Size' - DependsOn = '[SqlScript]Alerts' - - } - - ) - } - ) - } - } - } -} \ No newline at end of file -- 2.39.5 From e10ab48bb42835004bb30a9dd61d41b5e4afcbfd Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 21 Apr 2026 13:02:51 +0200 Subject: [PATCH 3/7] 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" + } +} -- 2.39.5 From aa5d5eea28547ca33286afc95413e185beb8962a Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 21 Apr 2026 13:07:57 +0200 Subject: [PATCH 4/7] adding Pester Tests --- .gitignore | 2 +- Tests/Merge-ConfigurationData.Tests.ps1 | 132 ------------------------ 2 files changed, 1 insertion(+), 133 deletions(-) delete mode 100644 Tests/Merge-ConfigurationData.Tests.ps1 diff --git a/.gitignore b/.gitignore index 942936b..877df7e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .git .sample -.output \ No newline at end of file +.tests \ No newline at end of file diff --git a/Tests/Merge-ConfigurationData.Tests.ps1 b/Tests/Merge-ConfigurationData.Tests.ps1 deleted file mode 100644 index 1d73b2a..0000000 --- a/Tests/Merge-ConfigurationData.Tests.ps1 +++ /dev/null @@ -1,132 +0,0 @@ -$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" - } -} -- 2.39.5 From 59d884c0619d08c6afd51ad5d23b62b0bd2f689d Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 21 Apr 2026 14:03:44 +0200 Subject: [PATCH 5/7] Rename Function to Merge-DSCConfigurationData --- Merge-DSCConfigurationData.psd1 | 2 +- Merge-DSCConfigurationData.psm1 | 2 +- ...nfigurationData.ps1 => Merge-DSCConfigurationData.ps1} | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) rename Public/{Merge-ConfigurationData.ps1 => Merge-DSCConfigurationData.ps1} (90%) diff --git a/Merge-DSCConfigurationData.psd1 b/Merge-DSCConfigurationData.psd1 index 53b5a0f..1892346 100644 --- a/Merge-DSCConfigurationData.psd1 +++ b/Merge-DSCConfigurationData.psd1 @@ -7,7 +7,7 @@ Description = "Merges DSC configuration data from templates and deployment data." PowerShellVersion = "5.1" FunctionsToExport = @( - "Merge-ConfigurationData" + "Merge-DSCConfigurationData" ) CmdletsToExport = @() VariablesToExport = @() diff --git a/Merge-DSCConfigurationData.psm1 b/Merge-DSCConfigurationData.psm1 index 46660ff..6b916e2 100644 --- a/Merge-DSCConfigurationData.psm1 +++ b/Merge-DSCConfigurationData.psm1 @@ -9,5 +9,5 @@ foreach($File in @($Private + $Public)){ } Export-ModuleMember -Function @( - "Merge-ConfigurationData" + "Merge-DSCConfigurationData" ) diff --git a/Public/Merge-ConfigurationData.ps1 b/Public/Merge-DSCConfigurationData.ps1 similarity index 90% rename from Public/Merge-ConfigurationData.ps1 rename to Public/Merge-DSCConfigurationData.ps1 index 38afef3..0a7d1c6 100644 --- a/Public/Merge-ConfigurationData.ps1 +++ b/Public/Merge-DSCConfigurationData.ps1 @@ -1,4 +1,4 @@ -function Merge-ConfigurationData { +function Merge-DSCConfigurationData { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] @@ -21,7 +21,7 @@ function Merge-ConfigurationData { 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) + $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))) @@ -30,7 +30,7 @@ function Merge-ConfigurationData { 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) + $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))) @@ -71,7 +71,7 @@ function Merge-ConfigurationData { 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 + Merge-DSCConfigurationData -Template $TemplateItem.Value -Deployment $DeploymentItem -Output $OutputItem | Out-Null } }else{ if($TemplateItem.IsWildcard){ -- 2.39.5 From c267d78cfbf634fcf34639bf657ec402d4b57ef8 Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 21 Apr 2026 15:28:25 +0200 Subject: [PATCH 6/7] =?UTF-8?q?Removed=20Legacy=20Kompatibilit=C3=A4ts-Loa?= =?UTF-8?q?der.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Merge-ConfigurationData.ps1 | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 Merge-ConfigurationData.ps1 diff --git a/Merge-ConfigurationData.ps1 b/Merge-ConfigurationData.ps1 deleted file mode 100644 index f04aad9..0000000 --- a/Merge-ConfigurationData.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -$ModuleRoot = $PSScriptRoot - -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." -} - -$PrivatePath = Join-Path -Path $ModuleRoot -ChildPath "Private" -$PublicPath = Join-Path -Path $ModuleRoot -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 -} -- 2.39.5 From 0afc9dd1e258af6e81e0088bc12fa4942fd5ad64 Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Tue, 21 Apr 2026 15:52:02 +0200 Subject: [PATCH 7/7] Version 1.0.0 --- Merge-DSCConfigurationData.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Merge-DSCConfigurationData.psd1 b/Merge-DSCConfigurationData.psd1 index 1892346..5d0ef2d 100644 --- a/Merge-DSCConfigurationData.psd1 +++ b/Merge-DSCConfigurationData.psd1 @@ -1,6 +1,6 @@ @{ RootModule = "Merge-DSCConfigurationData.psm1" - ModuleVersion = "0.1.0" + ModuleVersion = "1.0.0" GUID = "c1c7e70d-9049-4eaa-a3c9-44a424c35ef5" Author = "Torsten Brendgen" Copyright = "(c) Torsten Brendgen. All rights reserved." -- 2.39.5