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