From 43a080eda01b2e4b555e79e8b48ec3e130aaab98 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 24 Apr 2023 12:25:29 +0200 Subject: [PATCH] SqlServerDsc: Using dbatools as a preferred module (#1904) --- CHANGELOG.md | 2 + azure-pipelines.yml | 153 +++++++++++++++++- source/Public/Get-SqlDscPreferredModule.ps1 | 14 +- .../Public/Import-SqlDscPreferredModule.ps1 | 14 +- source/WikiSource/Home.md | 24 ++- .../DSC_SqlSetup.Integration.Tests.ps1 | 2 +- tests/Integration/DSC_SqlSetup.config.ps1 | 102 +++++++++--- 7 files changed, 276 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91e3337cc..6aee289a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Now able to use [DbaTools](https://dbatools.io) as a preferred module + (with some restrictions). - Gitversion no longer evaluates bumping major version using the word "major". - Update private commands: - `Assert-SetupActionProperties` was changed to throw diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2152b922a..4de8e375a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,7 +19,7 @@ variables: sourceFolderName: source defaultBranch: main -# cSpell: ignore setvariable updatebuildnumber DSCSQLTEST hqrmtest quickconfig +# cSpell: ignore setvariable updatebuildnumber DSCSQLTEST hqrmtest quickconfig dbatools stages: - stage: Build jobs: @@ -172,6 +172,8 @@ stages: pool: vmImage: $(JOB_VMIMAGE) timeoutInMinutes: 0 + variables: + SMODefaultModuleName: 'SqlServer' steps: - task: DownloadPipelineArtifact@2 displayName: 'Download Build Artifact' @@ -239,6 +241,96 @@ stages: testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' testRunTitle: 'Integration ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))' + - job: Test_Integration_dbatools + displayName: 'Integration (dbatools)' + dependsOn: Test_Integration + strategy: + matrix: + SQL2016_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2016' + SQL2017_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2017' + SQL2019_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2019' + SQL2022_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2022' + pool: + vmImage: $(JOB_VMIMAGE) + timeoutInMinutes: 0 + variables: + SMODefaultModuleName: 'dbatools' + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: configureWinRM + displayName: 'Configure WinRM' + inputs: + targetType: 'inline' + script: 'winrm quickconfig -quiet' + pwsh: false + - powershell: | + Import-Module -Name ./tests/TestHelpers/CommonTestHelper.psm1 + # Make sure to keep SQLPS so that Invoke-SqlCmd is available. + Remove-PowerShellModuleFromCI -Name @('SqlServer') + Remove-Module -Name CommonTestHelper + name: cleanCIWorker + displayName: 'Clean CI worker' + - powershell: | + ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $(TEST_CONFIGURATION) -PesterPath @( + # Run the integration tests in a specific group order. + # Group 1 + 'tests/Integration/DSC_SqlSetup.Integration.Tests.ps1' + # Group 2 + 'tests/Integration/DSC_SqlAgentAlert.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlLogin.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlEndpoint.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlDatabaseMail.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlRSSetup.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlDatabaseDefaultLocation.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlDatabase.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlAlwaysOnService.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlAgentOperator.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlServiceAccount.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlAgentFailsafe.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlTraceFlag.Integration.Tests.ps1' + # Group 3 + 'tests/Integration/DSC_SqlRole.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlRS_Default.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlDatabaseUser.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlReplication.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlAudit.Integration.Tests.ps1' + # Group 4 + 'tests/Integration/DSC_SqlScript.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlDatabasePermission.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlPermission.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlWindowsFirewall.Integration.Tests.ps1' + # Group 5 + 'tests/Integration/DSC_SqlSecureConnection.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlScriptQuery.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlProtocol.Integration.Tests.ps1' + # Group 6 (tests makes changes that could make SQL Server to loose connectivity) + 'tests/Integration/DSC_SqlProtocolTcpIp.Integration.Tests.ps1' + 'tests/Integration/DSC_SqlDatabaseObjectPermission.Integration.Tests.ps1' + ) + name: test + displayName: 'Run Integration Test' + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Integration (dbatools) ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))' + - job: Test_Integration_RS displayName: 'Integration Reporting Services' strategy: @@ -314,6 +406,65 @@ stages: testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' testRunTitle: 'Integration RS ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))' + - job: Test_Integration_RS_dbatools + displayName: 'Integration Reporting Services (dbatools)' + dependsOn: Test_Integration_RS + strategy: + matrix: + SQL2016_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2016' + SQL2017_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2017' + SQL2019_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2019' + SQL2022_WIN2022: + JOB_VMIMAGE: 'windows-2022' + TEST_CONFIGURATION: 'Integration_SQL2022' + variables: + SKIP_DATABASE_ENGINE_DEFAULT_INSTANCE: true + SKIP_ANALYSIS_MULTI_INSTANCE: true + SKIP_ANALYSIS_TABULAR_INSTANCE: true + SMODefaultModuleName: 'dbatools' + pool: + vmImage: $(JOB_VMIMAGE) + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: configureWinRM + displayName: 'Configure WinRM' + inputs: + targetType: 'inline' + script: 'winrm quickconfig -quiet' + pwsh: false + - powershell: | + ./build.ps1 -Tasks test -CodeCoverageThreshold 0 -PesterTag $(TEST_CONFIGURATION) -PesterPath @( + # Run the integration tests in a specific group order. + # Group 1 + 'tests/Integration/DSC_SqlSetup.Integration.Tests.ps1' + # Group 2 + 'tests/Integration/DSC_SqlRSSetup.Integration.Tests.ps1' + # Group 3 + 'tests/Integration/DSC_SqlRS.Integration.Tests.ps1' + ) + name: test + displayName: 'Run Reporting Services Integration Test' + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Integration RS (dbatools) ($(TEST_CONFIGURATION) / $(JOB_VMIMAGE))' + - job: Code_Coverage displayName: 'Publish Code Coverage' dependsOn: Test_Unit diff --git a/source/Public/Get-SqlDscPreferredModule.ps1 b/source/Public/Get-SqlDscPreferredModule.ps1 index e2c0b98ab..bf8b013a6 100644 --- a/source/Public/Get-SqlDscPreferredModule.ps1 +++ b/source/Public/Get-SqlDscPreferredModule.ps1 @@ -51,13 +51,25 @@ function Get-SqlDscPreferredModule ( [Parameter()] [System.String[]] - $Name = @('SqlServer', 'SQLPS'), + $Name, [Parameter()] [System.Management.Automation.SwitchParameter] $Refresh ) + if (-not $PSBoundParameters.ContainsKey('Name')) + { + $Name = if ($env:SMODefaultModuleName) + { + @($env:SMODefaultModuleName, 'SQLPS') + } + else + { + @('SqlServer', 'SQLPS') + } + } + if ($Refresh.IsPresent) { # Only run on Windows that has Machine state. diff --git a/source/Public/Import-SqlDscPreferredModule.ps1 b/source/Public/Import-SqlDscPreferredModule.ps1 index 156c478b5..93dc97c12 100644 --- a/source/Public/Import-SqlDscPreferredModule.ps1 +++ b/source/Public/Import-SqlDscPreferredModule.ps1 @@ -40,13 +40,25 @@ function Import-SqlDscPreferredModule ( [Parameter()] [System.String] - $PreferredModule = 'SqlServer', + $PreferredModule, [Parameter()] [System.Management.Automation.SwitchParameter] $Force ) + if (-not $PSBoundParameters.ContainsKey('PreferredModule')) + { + $PreferredModule = if ($env:SMODefaultModuleName) + { + $env:SMODefaultModuleName + } + else + { + 'SqlServer' + } + } + if ($Force.IsPresent) { Write-Verbose -Message $script:localizedData.PreferredModule_ForceRemoval diff --git a/source/WikiSource/Home.md b/source/WikiSource/Home.md index 20e7702ac..a09231316 100644 --- a/source/WikiSource/Home.md +++ b/source/WikiSource/Home.md @@ -45,7 +45,9 @@ Get-DscResource -Module SqlServerDsc - Familiarity with Powershell DSC Framework - Powershell 5.0 or higher -- SqlServer powershell module (optional) +- PowerShell module containing SMO assemblies (optional) + - For example the SqlServer PowerShell module or the dbatools PowerShell + module. ### Familiarity with Powershell DSC Framework @@ -67,11 +69,23 @@ on Windows 7 SP1, Windows 8.1, Windows Server 2012, and Windows Server 2012 R2. These resource might not work on PowerShell 7.x because they depend on *SQL Server* modules which only works in PowerShell 5.x. -### SqlServer powershell module (optional) +### PowerShell module containing SMO assemblies (optional) -Optionally the PowerShell Module [*SqlServer*](https://www.powershellgallery.com/packages/SqlServer) -can be installed which then will be used instead of the PowerShell module -*SQLPS* that is installed with SQL Server. +There are two options, installing the [*SqlServer*](https://www.powershellgallery.com/packages/SqlServer) +*PowerShell* module or the *dbatools PowerShell* module. + +If the *SqlServer* module is present it will be used instead of *SQLPS* +automatically. + +To use the [*dbatools*](https://www.powershellgallery.com/packages/dbatools) +module as a replacement for *SQLPS* the environment variable `SMODefaultModuleName` +must be set to the value `dbatools`. This environment variable can be set +machine-wide, or at minimum set for each user that runs DSC resources, on +the target node. Make sure you comply with any license terms that is part +of dbatools. + +>**NOTE:** It is also possible to use any module as a preferred module if +>its name is set as the value of the environment variable `SMODefaultModuleName`. ## Change log diff --git a/tests/Integration/DSC_SqlSetup.Integration.Tests.ps1 b/tests/Integration/DSC_SqlSetup.Integration.Tests.ps1 index fb633bdf6..5e27ad3a2 100644 --- a/tests/Integration/DSC_SqlSetup.Integration.Tests.ps1 +++ b/tests/Integration/DSC_SqlSetup.Integration.Tests.ps1 @@ -215,7 +215,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('Integration_SQL2016', } Context ('When using configuration <_>') -ForEach @( - "$($script:dscResourceName)_InstallSqlServerModule_Config" + "$($script:dscResourceName)_InstallSMOModule_Config" ) { BeforeAll { $configurationName = $_ diff --git a/tests/Integration/DSC_SqlSetup.config.ps1 b/tests/Integration/DSC_SqlSetup.config.ps1 index 80f2fe738..05d35b55b 100644 --- a/tests/Integration/DSC_SqlSetup.config.ps1 +++ b/tests/Integration/DSC_SqlSetup.config.ps1 @@ -1,7 +1,6 @@ -#region HEADER -# Integration Test Config Template Version: 1.2.0 -#endregion - +<# + cSpell: ignore MSAS SNAC DREPLAY CTLR dbatools DSCSQLTEST DSCTABULAR DSCMULTI Hadr SQLAGENT SQLSERVERAGENT +#> $configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') if (Test-Path -Path $configFile) { @@ -38,6 +37,7 @@ else SupportedFeatures = 'SQLENGINE,REPLICATION' SqlServerModuleVersion = '22.0.59' + DbatoolsModuleVersion = '2.0.0-preview7' } } @@ -55,6 +55,7 @@ else SupportedFeatures = 'SQLENGINE,REPLICATION,CONN,BC,SDK' SqlServerModuleVersion = '21.1.18256' + DbatoolsModuleVersion = '2.0.0-preview7' } } @@ -68,6 +69,7 @@ else SupportedFeatures = 'SQLENGINE,REPLICATION,CONN,BC,SDK' SqlServerModuleVersion = '21.1.18256' + DbatoolsModuleVersion = '2.0.0-preview7' } } @@ -81,6 +83,7 @@ else SupportedFeatures = 'SQLENGINE,REPLICATION,CONN,BC,SDK' SqlServerModuleVersion = '21.1.18256' + DbatoolsModuleVersion = '2.0.0-preview7' } } } @@ -89,13 +92,31 @@ else $env:IsoDriveLetter = $mockIsoMediaDriveLetter $env:IsoImagePath = Join-Path -Path $env:TEMP -ChildPath $versionSpecificData.IsoImageName + if ($env:SMODefaultModuleName -and $env:SMODefaultModuleName -eq 'SqlServer') + { + $SMOModuleName = $env:SMODefaultModuleName + $SMOModuleVersion = $versionSpecificData.SqlServerModuleVersion + } + elseif ($env:SMODefaultModuleName -and $env:SMODefaultModuleName -eq 'dbatools') + { + $SMOModuleName = $env:SMODefaultModuleName + $SMOModuleVersion = $versionSpecificData.DbatoolsModuleVersion + } + else + { + $SMOModuleName = 'SqlServer' + $SMOModuleVersion = $versionSpecificData.SqlServerModuleVersion + } + $ConfigurationData = @{ AllNodes = @( @{ NodeName = 'localhost' - SqlServerModuleVersion = $versionSpecificData.SqlServerModuleVersion - SqlServerModuleVersionIsPrerelease = $true + SMOModuleName = $SMOModuleName + + SMOModuleVersion = $SMOModuleVersion + SMOModuleVersionIsPrerelease = $true SqlServerInstanceIdPrefix = $versionSpecificData.SqlServerInstanceIdPrefix AnalysisServiceInstanceIdPrefix = $versionSpecificData.AnalysisServiceInstanceIdPrefix @@ -306,7 +327,7 @@ Configuration DSC_SqlSetup_CreateDependencies_Config that it does not conflict with the SqlServerStubs module that is used by unit tests. #> -Configuration DSC_SqlSetup_InstallSqlServerModule_Config +Configuration DSC_SqlSetup_InstallSMOModule_Config { Import-DscResource -ModuleName 'PSDscResources' -ModuleVersion '2.12.0.0' @@ -369,7 +390,6 @@ Configuration DSC_SqlSetup_InstallSqlServerModule_Config GetScript = { $moduleVersion = $null - $sqlServerModule = $null # Fetch the newest PowerShellGet version. $powerShellGetModule = Get-Module -Name 'PowerShellGet' -ListAvailable | @@ -387,7 +407,20 @@ Configuration DSC_SqlSetup_InstallSqlServerModule_Config } } - Script 'InstallSqlServerModule' + # Only set the environment variable for the LCM user only if the pipeline has it configured. + if ($env:SMODefaultModuleName) + { + Environment 'SetSMODefaultModuleName' + { + Name = 'SMODefaultModuleName' + Value = $env:SMODefaultModuleName + Ensure = 'Present' + Path = $false + Target = @('Process', 'Machine') + } + } + + Script 'InstallSMOModule' { DependsOn = @( '[Script]InstallPowerShellGet' @@ -398,25 +431,42 @@ Configuration DSC_SqlSetup_InstallSqlServerModule_Config Set-PSRepository -Name 'PSGallery' -InstallationPolicy 'Trusted' # Uninstall any existing SqlServer module, to make we only have the one we need. - Get-Module -Name 'SqlServer' -ListAvailable | Uninstall-Module -ErrorAction 'Stop' + Get-Module -Name $using:Node.SMOModuleName -ListAvailable | Uninstall-Module -ErrorAction 'Stop' # Make sure we use TLS 1.2. [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 $installModuleParameters = @{ - Name = 'SqlServer' + Name = $using:Node.SMOModuleName Scope = 'AllUsers' Force = $true - RequiredVersion = $Using:Node.SqlServerModuleVersion - AllowPrerelease = $Using:Node.SqlServerModuleVersionIsPrerelease + RequiredVersion = $Using:Node.SMOModuleVersion + AllowPrerelease = $Using:Node.SMOModuleVersionIsPrerelease AllowClobber = $true # Needed to handle existens of module SQLPS. PassThru = $true } # Install the required SqlServer module version. - $installedModule = Install-Module @installModuleParameters + $installedModule = Install-Module @installModuleParameters | + Where-Object -FilterScript { + <# + Need to filter out the right module since if dependencies are + also installed they will also be in the returned array. + #> + $_.Name -eq $using:Node.SMOModuleName + } - Write-Verbose -Message ('Installed SqlServer module version {0}' -f $installedModule.Version) + Write-Verbose -Message ('Installed {0} module version {1}' -f $using:Node.SMOModuleName, $installedModule.Version) + + Write-Verbose -Message ('Current set preferred module name (SMODefaultModuleName): {0}' -f ($env:SMODefaultModuleName | Out-String)) + + if ($using:Node.SMOModuleName -eq 'dbatools') + { + Set-DbatoolsConfig -Name Import.EncryptionMessageCheck -Value $false -PassThru | + Register-DbatoolsConfig -Verbose + + Write-Verbose -Message 'Disabled dbatools setting Import.EncryptionMessageCheck' + } } TestScript = { @@ -427,43 +477,43 @@ Configuration DSC_SqlSetup_InstallSqlServerModule_Config #> $getScriptResult = & ([ScriptBlock]::Create($GetScript)) - if ($getScriptResult.Result -eq $Using:Node.SqlServerModuleVersion) + if ($getScriptResult.Result -eq $Using:Node.SMOModuleVersion) { - Write-Verbose -Message ('The node already contain the module SqlServer with version {0}.' -f $Using:Node.SqlServerModuleVersion) + Write-Verbose -Message ('The node already contain the module {0} with version {1}.' -f $using:Node.SMOModuleName, $Using:Node.SMOModuleVersion) return $true } - Write-Verbose -Message ('The module SqlServer with version {0} is not installed.' -f $Using:Node.SqlServerModuleVersion) + Write-Verbose -Message ('The module {0} with version {1} is not installed.' -f $using:Node.SMOModuleName, $Using:Node.SMOModuleVersion) return $false } GetScript = { $moduleVersion = $null - $sqlServerModule = $null + $smoModule = $null # Forcibly import the required modules that is required for using prerelease modules. Import-Module -Name 'PackageManagement' -MinimumVersion '1.4.8.1' -Force Import-Module -Name 'PowerShellGet' -MinimumVersion '2.2.5' -Force - $sqlServerModule = Get-Module -Name 'SqlServer' -ListAvailable | + $smoModule = Get-Module -Name $using:Node.SMOModuleName -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1 - if ($sqlServerModule) + if ($smoModule) { - $moduleVersion = $sqlServerModule.Version.ToString() + $moduleVersion = $smoModule.Version.ToString() - if ($sqlServerModule.PrivateData.PSData.Keys -contains 'Prerelease') + if ($smoModule.PrivateData.PSData.Keys -contains 'Prerelease') { - if (-not [System.String]::IsNullOrEmpty($sqlServerModule.PrivateData.PSData.Prerelease)) + if (-not [System.String]::IsNullOrEmpty($smoModule.PrivateData.PSData.Prerelease)) { - $moduleVersion = '{0}-{1}' -f $moduleVersion, $sqlServerModule.PrivateData.PSData.Prerelease + $moduleVersion = '{0}-{1}' -f $moduleVersion, $smoModule.PrivateData.PSData.Prerelease } } - Write-Verbose -Message ('Found SqlServer module v{0}.' -f $moduleVersion) -Verbose + Write-Verbose -Message ('Found {0} module v{1}.' -f $using:Node.SMOModuleName, $moduleVersion) -Verbose } return @{