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 diff --git a/.tests/Merge-DSCConfigurationData.Tests.ps1 b/.tests/Merge-DSCConfigurationData.Tests.ps1 new file mode 100644 index 0000000..3858e57 --- /dev/null +++ b/.tests/Merge-DSCConfigurationData.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-DSCConfigurationData" { + 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-DSCConfigurationData -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-DSCConfigurationData -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-DSCConfigurationData -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-DSCConfigurationData -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" + } +}