From cebd5de50715ba1e410daa568a3cd5ad1e9791cd Mon Sep 17 00:00:00 2001 From: YaroBear Date: Fri, 25 Aug 2023 13:35:03 -0600 Subject: [PATCH 01/13] Get-DsqlDscPreferredModule returns PSModuleInfo instead of string; enable module version selection via SMODefaultModuleVersion env var --- source/Public/Get-SqlDscPreferredModule.ps1 | 107 +++-- .../Public/Import-SqlDscPreferredModule.ps1 | 22 +- source/en-US/SqlServerDsc.strings.psd1 | 4 +- .../Get-SqlDscPreferredModule.Tests.ps1 | 396 ++++++++++++------ .../Import-SqlDscPreferredModule.Tests.ps1 | 61 ++- 5 files changed, 382 insertions(+), 208 deletions(-) diff --git a/source/Public/Get-SqlDscPreferredModule.ps1 b/source/Public/Get-SqlDscPreferredModule.ps1 index 446232927..99379ee7b 100644 --- a/source/Public/Get-SqlDscPreferredModule.ps1 +++ b/source/Public/Get-SqlDscPreferredModule.ps1 @@ -9,47 +9,45 @@ that name will be used as the preferred module name instead of the default module 'SqlServer'. + If the envrionment variable `SMODefaultModuleVersion` is set, then that + specific version of the preferred module will be searched for. + .PARAMETER Name Specifies the list of the (preferred) modules to search for, in order. Defaults to 'SqlServer' and then 'SQLPS'. .PARAMETER Refresh - Specifies if the session environment variable PSModulePath should be refresh + Specifies if the session environment variable PSModulePath should be refreshed with the paths from other environment variable targets (Machine and User). .EXAMPLE Get-SqlDscPreferredModule - Returns the module name SqlServer if it is installed, otherwise it will - return SQLPS if is is installed. If neither is installed `$null` is - returned. + Returns the SqlServer PSModuleInfo object if it is installed, otherwise it + will return SQLPS PSModuleInfo object if is is installed. If neither is + installed `$null` is returned. .EXAMPLE Get-SqlDscPreferredModule -Refresh - Updated the session environment variable PSModulePath and then returns the - module name SqlServer if it is installed, otherwise it will return SQLPS - if is is installed. If neither is installed `$null` is returned. + Updates the session environment variable PSModulePath and then returns the + SqlServer PSModuleInfo object if it is installed, otherwise it will return SQLPS + PSModuleInfo object if is is installed. If neither is installed `$null` is + returned. .EXAMPLE Get-SqlDscPreferredModule -Name @('MyModule', 'SQLPS') - Returns the module name MyModule if it is installed, otherwise it will - return SQLPS if is is installed. If neither is installed `$null` is - returned. + Returns the MyModule PSModuleInfo object if it is installed, otherwise it will + return SQLPS PSModuleInfo object if is is installed. If neither is installed + `$null` is returned. .NOTES - If the module SQLPS is specified (default value) the path is returned as - the module name. This is because importing 'SQLPS' using simply the name - could make the wrong version to be imported when several different version - of SQL Server is installed on the same node. To make sure the correct - (latest) version is imported the path to the latest version of SQLPS is - returned. The returned path can be passed directly to the parameter Name - of the command Import-Module. + #> function Get-SqlDscPreferredModule { - [OutputType([System.String])] + [OutputType([PSModuleInfo])] [CmdletBinding()] param ( @@ -92,15 +90,13 @@ function Get-SqlDscPreferredModule } } - $availableModuleName = $null + $availableModule = $null - $availableModule = Get-Module -FullyQualifiedName $Name -ListAvailable | - Select-Object -Property @( - 'Name', - 'Path', + $availableModules = Get-Module -Name $Name -ListAvailable | + ForEach-Object { @{ - Name = 'Version' - Expression = { + PSModuleInfo = $_ + CalculatedVersion = .{ if ($_.Name -eq 'SQLPS') { <# @@ -111,60 +107,63 @@ function Get-SqlDscPreferredModule } else { - $versionToReturn = $_.Version + $versionToReturn = $_.Version.ToString() - if ($_.ContainsKey('PrivateData') -and $_.PrivateData.ContainsKey('PSData') -and $_.PrivateData.PSData.ContainsKey('Prerelease')) + if ($_.PrivateData.PSData.Prerelease) { - if (-not [System.String]::IsNullOrEmpty($_.PrivateData.PSData.Prerelease)) - { - $versionToReturn = '{0}-{1}' -f $_.Version, $_.PrivateData.PSData.Prerelease - } + $versionToReturn = '{0}-{1}' -f $_.Version, $_.PrivateData.PSData.Prerelease } $versionToReturn } } } - ) + } foreach ($preferredModuleName in $Name) { - $preferredModule = $availableModule | - Where-Object -Property 'Name' -EQ -Value $preferredModuleName + $preferredModules = $availableModules | + Where-Object { $_.PSModuleInfo.Name -eq $preferredModuleName} - if ($preferredModule) + if ($preferredModules) { - if ($preferredModule.Name -eq 'SQLPS') - { - # Get the latest version if available. - $preferredModule = $preferredModule | - Sort-Object -Property 'Version' -Descending | - Select-Object -First 1 - - <# - For SQLPS the path to the module need to be returned as the - module name to be absolutely sure the latest version is used. - #> - $availableModuleName = Split-Path -Path $preferredModule.Path -Parent - } - else + if ($env:SMODefaultModuleVersion) { - $availableModuleName = ($preferredModule | Select-Object -First 1).Name + # Get the version specified in $env:SMODefaultModuleVersion if available + $availableModule = $preferredModules | + Where-Object { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion} | + Select-Object -First 1 + + Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleVersionFound -f $availableModule.PSModuleInfo.Name, $availableModule.CalculatedVersion) } + else { + # Get the latest version if available + $availableModule = $preferredModules | + Sort-Object -Property 'CalculatedVersion' -Descending | + Select-Object -First 1 - Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleFound -f $availableModuleName) + Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleFound -f $availableModule.PSModuleInfo.Name) + } break } } - if (-not $availableModuleName) + if (-not $availableModule) { - $errorMessage = $script:localizedData.PreferredModule_ModuleNotFound + $errorMessage = $null + + if ($env:SMODefaultModuleVersion) + { + $errorMessage = $script:localizedData.PreferredModule_ModuleVersionNotFound -f $env:SMODefaultModuleVersion + } + else { + $errorMessage = $script:localizedData.PreferredModule_ModuleNotFound + } # cSpell: disable-next Write-Error -Message $errorMessage -Category 'ObjectNotFound' -ErrorId 'GSDPM0001' -TargetObject ($Name -join ', ') } - return $availableModuleName + return $availableModule.PSModuleInfo } diff --git a/source/Public/Import-SqlDscPreferredModule.ps1 b/source/Public/Import-SqlDscPreferredModule.ps1 index 23fa1b69f..eeda5bbd0 100644 --- a/source/Public/Import-SqlDscPreferredModule.ps1 +++ b/source/Public/Import-SqlDscPreferredModule.ps1 @@ -70,7 +70,7 @@ function Import-SqlDscPreferredModule $getSqlDscPreferredModuleParameters.Refresh = $true } - $availableModuleName = Get-SqlDscPreferredModule @getSqlDscPreferredModuleParameters + $availableModule = Get-SqlDscPreferredModule @getSqlDscPreferredModuleParameters if ($Force.IsPresent) { @@ -80,33 +80,31 @@ function Import-SqlDscPreferredModule if ($PSBoundParameters.ContainsKey('Name')) { - $removeModule += $Name + $removeModule += Get-Module $Name } # Available module could be - if ($availableModuleName) + if ($availableModule) { - $removeModule += $availableModuleName + $removeModule += $availableModule } if ($removeModule -contains 'SQLPS') { - $removeModule += 'SQLASCmdlets' # cSpell: disable-line + $removeModule += Get-Module -Name 'SQLASCmdlets' # cSpell: disable-line } - Remove-Module -Name $removeModule -Force -ErrorAction 'SilentlyContinue' + Remove-Module $removeModule -Force -ErrorAction 'SilentlyContinue' } - if ($availableModuleName) + if ($availableModule) { if (-not $Force.IsPresent) { <# Check if the preferred module is already loaded into the session. - If the module name is a path the leaf part must be used, which is - the module name. #> - $loadedModuleName = (Get-Module -Name (Split-Path -Path $availableModuleName -Leaf) | Select-Object -First 1).Name + $loadedModuleName = (Get-Module -Name $availableModule.Name | Select-Object -First 1).Name if ($loadedModuleName) { @@ -126,14 +124,14 @@ function Import-SqlDscPreferredModule SQLPS has unapproved verbs, disable checking to ignore Warnings. Suppressing verbose so all cmdlet is not listed. #> - $importedModule = Import-Module -Name $availableModuleName -DisableNameChecking -Verbose:$false -Force:$Force -Global -PassThru -ErrorAction 'Stop' + $importedModule = Import-Module -Name $availableModule.Name -RequiredVersion $availableModule.Version -DisableNameChecking -Verbose:$false -Force:$Force -Global -PassThru -ErrorAction 'Stop' <# SQLPS returns two entries, one with module type 'Script' and another with module type 'Manifest'. Only return the object with module type 'Manifest'. SqlServer only returns one object (of module type 'Script'), so no need to do anything for SqlServer module. #> - if ($availableModuleName -eq 'SQLPS') + if ($availableModule.Name -eq 'SQLPS') { $importedModule = $importedModule | Where-Object -Property 'ModuleType' -EQ -Value 'Manifest' } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 3f7d23805..dad36d262 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -148,8 +148,10 @@ ConvertFrom-StringData @' TraceFlag_Remove_NoCurrentTraceFlags = There are no current trace flags on instance. Nothing to remove. ## Get-SqlDscPreferredModule - PreferredModule_ModuleFound = Preferred module {0} found. + PreferredModule_ModuleFound = Preferred module '{0}' found. + PreferredModule_ModuleVersionFound = Preferred module '{0}' with version '{1}' found. PreferredModule_ModuleNotFound = No preferred PowerShell module was found. + PreferredModule_ModuleVersionNotFound = No preferred Powershell module with version '{0}' was found. ## Import-SqlDscPreferredModule PreferredModule_ImportedModule = Imported PowerShell module '{0}' with version '{1}' from path '{2}'. diff --git a/tests/Unit/Public/Get-SqlDscPreferredModule.Tests.ps1 b/tests/Unit/Public/Get-SqlDscPreferredModule.Tests.ps1 index a8923aa1a..6fb2f0ec1 100644 --- a/tests/Unit/Public/Get-SqlDscPreferredModule.Tests.ps1 +++ b/tests/Unit/Public/Get-SqlDscPreferredModule.Tests.ps1 @@ -117,121 +117,141 @@ Describe 'Get-SqlDscPreferredModule' -Tag 'Public' { Context 'When only first default preferred module is installed' { BeforeAll { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SqlServer' - } + return $sqlServerModule } } - It 'Should return the correct module name' { - Get-SqlDscPreferredModule | Should -Be 'SqlServer' + It 'Should return the correct module' { + Get-SqlDscPreferredModule | Should -Be $sqlServerModule } } Context 'When only second default preferred module is installed' { BeforeAll { + $sqlpsModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + return $sqlpsModule } } It 'Should return the correct module name' { - Get-SqlDscPreferredModule | Should -Be ('C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS' -replace '\\', [IO.Path]::DirectorySeparatorChar) + Get-SqlDscPreferredModule | Should -Be $sqlpsModule } } Context 'When both default preferred modules are installed' { BeforeAll { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + $sqlpsModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + Mock -CommandName Get-Module -MockWith { return @( - @{ - Name = 'SqlServer' - } - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + $sqlServerModule, + $sqlpsModule ) } } It 'Should return the correct module name' { - Get-SqlDscPreferredModule | Should -Be 'SqlServer' + Get-SqlDscPreferredModule | Should -Be $sqlServerModule } } Context 'When there are several installed versions of all default preferred modules' { BeforeAll { + $sqlServerModule1 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + $sqlServerModule2 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(22, 1, 1) + } + $sqlpsModule1 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + $sqlpsModule2 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + Mock -CommandName Get-Module -MockWith { return @( - @{ - Name = 'SqlServer' - Version = '1.0.0' - } - @{ - Name = 'SqlServer' - Version = '2.0.0' - } - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + $sqlServerModule1, + $sqlServerModule2, + $sqlpsModule1, + $sqlpsModule2 ) } } - It 'Should return the correct module name' { - Get-SqlDscPreferredModule | Should -Be 'SqlServer' + It 'Should return the latest version of the first default preferred module' { + Get-SqlDscPreferredModule | Should -Be $sqlServerModule2 } } Context 'When there are several installed versions of the first default preferred module' { BeforeAll { + $sqlServerModule1 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + $sqlServerModule2 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(22, 1, 1) + } + Mock -CommandName Get-Module -MockWith { return @( - @{ - Name = 'SqlServer' - Version = '1.0.0' - } - @{ - Name = 'SqlServer' - Version = '2.0.0' - } + $sqlServerModule1, + $sqlServerModule2 ) } } - It 'Should return the correct module name' { - Get-SqlDscPreferredModule | Should -Be 'SqlServer' + It 'Should return the latest version of the first default preferred module' { + Get-SqlDscPreferredModule | Should -Be $sqlServerModule2 } } Context 'When there are several installed versions of the second default preferred module' { BeforeAll { + $sqlpsModule1 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + $sqlpsModule2 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + Mock -CommandName Get-Module -MockWith { return @( - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + $sqlpsModule1, + $sqlpsModule2 ) } } - It 'Should return the correct module name' { - Get-SqlDscPreferredModule | Should -Be ('C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS' -replace '\\', [IO.Path]::DirectorySeparatorChar) + It 'Should return the latest version of the second default preferred module' { + Get-SqlDscPreferredModule | Should -Be $sqlpsModule2 } } } @@ -279,126 +299,246 @@ Describe 'Get-SqlDscPreferredModule' -Tag 'Public' { Context 'When only first preferred module is installed' { BeforeAll { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SqlServer' - } + return $sqlServerModule } } It 'Should return the correct module name' { - Get-SqlDscPreferredModule -Name 'SqlServer' | Should -Be 'SqlServer' + Get-SqlDscPreferredModule -Name 'SqlServer' | Should -Be $sqlServerModule } } Context 'When only second preferred module is installed' { BeforeAll { + $sqlpsModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + return $sqlpsModule } } It 'Should return the correct module name' { - Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be ('C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS' -replace '\\', [IO.Path]::DirectorySeparatorChar) + Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be $sqlpsModule } } Context 'When both preferred modules are installed' { BeforeAll { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + $sqlpsModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + Mock -CommandName Get-Module -MockWith { return @( - @{ - Name = 'SqlServer' - } - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + $sqlServerModule, + $sqlpsModule ) } } - It 'Should return the correct module name' { - Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be 'SqlServer' + It 'Should return the first preferred module' { + Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be $sqlServerModule } } Context 'When there are several installed versions of all preferred modules' { BeforeAll { + $sqlServerModule1 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + $sqlServerModule2 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(22, 1, 1) + PrivateData = @{ + PSData = @{ + PreRelease = 'preview1' + } + } + } + $sqlpsModule1 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + $sqlpsModule2 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + Mock -CommandName Get-Module -MockWith { return @( - @{ - Name = 'SqlServer' - Version = '1.0.0' - } - @{ - Name = 'SqlServer' - Version = '2.0.0' - PrivateData = @{ - PSData = @{ - PreRelease = 'preview1' - } - } - } - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + $sqlServerModule1, + $sqlServerModule2, + $sqlpsModule1, + $sqlpsModule2 ) } } - It 'Should return the correct module name' { - Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be 'SqlServer' + It 'Should return the latest first preferred module' { + Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be $sqlServerModule2 + } + + Context 'When the environment variable SMODefaultModuleVersion is assigned a module version' { + Context 'When the version of the module exists' { + BeforeAll { + $env:SMODefaultModuleVersion = '21.1.18068' + } + + AfterAll { + Remove-Item -Path 'env:SMODefaultModuleVersion' + } + + It 'Should return the specified module version' { + Get-SqlDscPreferredModule | Should -Be $sqlServerModule1 + } + } + + Context 'When the version of the module does not exist' { + BeforeAll { + $env:SMODefaultModuleVersion = '1.1.1' + } + + AfterAll { + Remove-Item -Path 'env:SMODefaultModuleVersion' + } + + It 'Should throw the correct error' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.PreferredModule_ModuleVersionNotFound + } + + { Get-SqlDscPreferredModule -ErrorAction 'Stop' } | Should -Throw -ExpectedMessage ($errorMessage -f $env:SMODefaultModuleVersion) + } + } } } Context 'When there are several installed versions of the first preferred module' { BeforeAll { + $sqlServerModule1 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + $sqlServerModule2 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(22, 1, 1) + } + Mock -CommandName Get-Module -MockWith { return @( - @{ - Name = 'SqlServer' - Version = '1.0.0' - } - @{ - Name = 'SqlServer' - Version = '2.0.0' - } + $sqlServerModule1, + $sqlServerModule2 ) } } - It 'Should return the correct module name' { - Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be 'SqlServer' + It 'Should return the latest version of the first preferred module' { + Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be $sqlServerModule2 + } + + Context 'When the environment variable SMODefaultModuleVersion is assigned a module version' { + Context 'When the version of the module exists' { + BeforeAll { + $env:SMODefaultModuleVersion = '21.1.18068' + } + + AfterAll { + Remove-Item -Path 'env:SMODefaultModuleVersion' + } + + It 'Should return the specified module version' { + Get-SqlDscPreferredModule | Should -Be $sqlServerModule1 + } + } + + Context 'When the version of the module does not exist' { + BeforeAll { + $env:SMODefaultModuleVersion = '1.1.1' + } + + AfterAll { + Remove-Item -Path 'env:SMODefaultModuleVersion' + } + + It 'Should throw the correct error' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.PreferredModule_ModuleVersionNotFound + } + + { Get-SqlDscPreferredModule -ErrorAction 'Stop' } | Should -Throw -ExpectedMessage ($errorMessage -f $env:SMODefaultModuleVersion) + } + } } } Context 'When there are several installed versions of the second preferred module' { BeforeAll { + $sqlpsModule1 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + $sqlpsModule2 = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + Mock -CommandName Get-Module -MockWith { return @( - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } - @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + $sqlpsModule1, + $sqlpsModule2 ) } } - It 'Should return the correct module name' { - Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be ('C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS' -replace '\\', [IO.Path]::DirectorySeparatorChar) + It 'Should return the latest version of the second preferred module' { + Get-SqlDscPreferredModule -Name @('SqlServer', 'SQLPS') | Should -Be $sqlpsModule2 + } + + Context 'When the environment variable SMODefaultModuleVersion is assigned a module version' { + Context 'When the version of the module exists' { + BeforeAll { + $env:SMODefaultModuleVersion = '130' + } + + AfterAll { + Remove-Item -Path 'env:SMODefaultModuleVersion' + } + + It 'Should return the specified module version' { + Get-SqlDscPreferredModule | Should -Be $sqlpsModule1 + } + } + + Context 'When the version of the module does not exist' { + BeforeAll { + $env:SMODefaultModuleVersion = '999' + } + + AfterAll { + Remove-Item -Path 'env:SMODefaultModuleVersion' + } + + It 'Should throw the correct error' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.PreferredModule_ModuleVersionNotFound + } + + { Get-SqlDscPreferredModule -ErrorAction 'Stop' } | Should -Throw -ExpectedMessage ($errorMessage -f $env:SMODefaultModuleVersion) + } + } } } } @@ -415,15 +555,18 @@ Describe 'Get-SqlDscPreferredModule' -Tag 'Public' { return 'MockPath' } + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SqlServer' - } + return $sqlServerModule } } It 'Should return the correct module name' { - Get-SqlDscPreferredModule -Refresh | Should -Be 'SqlServer' + Get-SqlDscPreferredModule -Refresh | Should -Be $sqlServerModule Should -Invoke -CommandName Set-PSModulePath -Exactly -Times 1 -Scope It } @@ -433,10 +576,13 @@ Describe 'Get-SqlDscPreferredModule' -Tag 'Public' { BeforeAll { $env:SMODefaultModuleName = 'OtherModule' + $otherModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = $env:SMODefaultModuleName + Version = [Version]::new(1, 1, 1) + } + Mock -CommandName Get-Module -MockWith { - return @{ - Name = $env:SMODefaultModuleName - } + return $otherModule } } @@ -445,7 +591,7 @@ Describe 'Get-SqlDscPreferredModule' -Tag 'Public' { } It 'Should return the correct module name' { - Get-SqlDscPreferredModule | Should -Be $env:SMODefaultModuleName + Get-SqlDscPreferredModule | Should -Be $otherModule } } } diff --git a/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 b/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 index bd2b48e2e..99d38643e 100644 --- a/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 +++ b/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 @@ -141,14 +141,18 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { Context 'When module SqlServer is already loaded into the session' { BeforeAll { Mock -CommandName Import-Module + + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + Mock -CommandName Get-SqlDscPreferredModule -MockWith { - return 'SqlServer' + return $sqlServerModule } Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SqlServer' - } + return $sqlServerModule } } @@ -162,14 +166,17 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { Context 'When module SQLPS is already loaded into the session' { BeforeAll { Mock -CommandName Import-Module + + $sqlpsModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + } + Mock -CommandName Get-SqlDscPreferredModule -MockWith { - return 'SQLPS' + return $sqlpsModule } Mock -CommandName Get-Module -MockWith { - return @{ - Name = 'SQLPS' - } + return $sqlpsModule } } @@ -184,8 +191,14 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { BeforeAll { Mock -CommandName Import-Module -MockWith $mockImportModule Mock -CommandName Get-Module + + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + Mock -CommandName Get-SqlDscPreferredModule -MockWith { - return 'SqlServer' + return $sqlServerModule } $mockExpectedModuleNameToImport = 'SqlServer' @@ -205,8 +218,13 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { BeforeAll { Mock -CommandName Import-Module -MockWith $mockImportModule Mock -CommandName Get-Module + + $otherModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'OtherModule' + } + Mock -CommandName Get-SqlDscPreferredModule -MockWith { - return 'OtherModule' + return $otherModule } $mockExpectedModuleNameToImport = 'OtherModule' @@ -226,14 +244,20 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { BeforeAll { Mock -CommandName Import-Module -MockWith $mockImportModule Mock -CommandName Remove-Module + + $sqlpsModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = $sqlPsExpectedModulePath + } + Mock -CommandName Get-SqlDscPreferredModule -MockWith { - return $sqlPsExpectedModulePath + return $sqlpsModule } - $mockExpectedModuleNameToImport = $sqlPsExpectedModulePath + $mockExpectedModuleNameToImport = 'SQLPS' } - It 'Should import the SqlServer module without throwing' { + It 'Should import the SQLPS module without throwing' { { Import-SqlDscPreferredModule -Force } | Should -Not -Throw Should -Invoke -CommandName Get-SqlDscPreferredModule -ParameterFilter { @@ -274,18 +298,23 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { Mock -CommandName Import-Module -MockWith $mockImportModule Mock -CommandName Get-Module Mock -CommandName Remove-Module + + $sqlpsModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + } + Mock -CommandName Get-SqlDscPreferredModule -MockWith { - return 'SQLPS' + return $sqlpsModule } $mockExpectedModuleNameToImport = 'SQLPS' } - It 'Should import the SqlServer module without throwing' { + It 'Should import the SQLPD module without throwing' { { Import-SqlDscPreferredModule -Name 'OtherModule' -Force } | Should -Not -Throw Should -Invoke -CommandName Get-SqlDscPreferredModule -Exactly -Times 1 -Scope It - Should -Invoke -CommandName Get-Module -Exactly -Times 0 -Scope It + Should -Invoke -CommandName Get-Module -Exactly -Times 1 -Scope It Should -Invoke -CommandName Remove-Module -Exactly -Times 1 -Scope It Should -Invoke -CommandName Push-Location -Exactly -Times 1 -Scope It Should -Invoke -CommandName Pop-Location -Exactly -Times 1 -Scope It From 0db67312ad46463ce62725613be0606873383ec1 Mon Sep 17 00:00:00 2001 From: YaroBear Date: Fri, 25 Aug 2023 16:23:42 -0600 Subject: [PATCH 02/13] pass PSModuleInfo to Install-Module instead --- source/Public/Import-SqlDscPreferredModule.ps1 | 2 +- tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/source/Public/Import-SqlDscPreferredModule.ps1 b/source/Public/Import-SqlDscPreferredModule.ps1 index eeda5bbd0..1b782fd17 100644 --- a/source/Public/Import-SqlDscPreferredModule.ps1 +++ b/source/Public/Import-SqlDscPreferredModule.ps1 @@ -124,7 +124,7 @@ function Import-SqlDscPreferredModule SQLPS has unapproved verbs, disable checking to ignore Warnings. Suppressing verbose so all cmdlet is not listed. #> - $importedModule = Import-Module -Name $availableModule.Name -RequiredVersion $availableModule.Version -DisableNameChecking -Verbose:$false -Force:$Force -Global -PassThru -ErrorAction 'Stop' + $importedModule = Import-Module -ModuleInfo $availableModule -DisableNameChecking -Verbose:$false -Force:$Force -Global -PassThru -ErrorAction 'Stop' <# SQLPS returns two entries, one with module type 'Script' and another with module type 'Manifest'. diff --git a/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 b/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 index 99d38643e..09f587b67 100644 --- a/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 +++ b/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 @@ -89,8 +89,7 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { $mockImportModule = { - # Convert the single value array [String[]] to the expected string value [String]. - $moduleNameToImport = $Name[0] + $moduleNameToImport = $ModuleInfo.Name if ($moduleNameToImport -ne $mockExpectedModuleNameToImport) { From 65f63c2329ae6b8f6c22b4e5d38f659b75e7538d Mon Sep 17 00:00:00 2001 From: YaroBear Date: Fri, 25 Aug 2023 17:37:05 -0600 Subject: [PATCH 03/13] updated changelog to reflect updates to Get/Import-SqlDscPreferredModule --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fba685844..13c9256d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SqlServerDsc - Updated pipeline files to support ModuleFast. + - `Get-SqlDscPreferredModule` + - Optionally specify what version of the the SQL preferred module to be imported using the SMODefaultModuleVersion environment variable ([issue #1965](https://github.com/dsccommunity/SqlServerDsc/issues/1965)). - SqlSetup - Added the parameter `SqlVersion` that can be used to set the SQL Server version to be installed instead of it looking for version in the setup @@ -41,6 +43,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated integration tests to use xPSDesiredStateConfiguration instead of PSDScResources. - SqlWindowsFirewall - Updated integration tests to use xPSDesiredStateConfiguration instead of PSDScResources. +- SqlServerDsc + - `Get-SqlDscPreferredModule` + - Now returns a PSModuleInfo object instead of just the module name. + - `Import-SqlDscPreferredModule` + - Handles PSModuleInfo objects from `Get-SqlDscPreferredModule` instead of strings. ## [16.4.0] - 2023-08-22 From ef4d7112a0f3be8187dd3505fea52e4589fb7eb7 Mon Sep 17 00:00:00 2001 From: YaroBear Date: Mon, 28 Aug 2023 09:57:51 -0600 Subject: [PATCH 04/13] used named parameters --- source/Public/Get-SqlDscPreferredModule.ps1 | 6 +++--- source/Public/Import-SqlDscPreferredModule.ps1 | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/Public/Get-SqlDscPreferredModule.ps1 b/source/Public/Get-SqlDscPreferredModule.ps1 index 99379ee7b..568a826fd 100644 --- a/source/Public/Get-SqlDscPreferredModule.ps1 +++ b/source/Public/Get-SqlDscPreferredModule.ps1 @@ -93,7 +93,7 @@ function Get-SqlDscPreferredModule $availableModule = $null $availableModules = Get-Module -Name $Name -ListAvailable | - ForEach-Object { + ForEach-Object -Process { @{ PSModuleInfo = $_ CalculatedVersion = .{ @@ -123,7 +123,7 @@ function Get-SqlDscPreferredModule foreach ($preferredModuleName in $Name) { $preferredModules = $availableModules | - Where-Object { $_.PSModuleInfo.Name -eq $preferredModuleName} + Where-Object -FilterScript { $_.PSModuleInfo.Name -eq $preferredModuleName} if ($preferredModules) { @@ -131,7 +131,7 @@ function Get-SqlDscPreferredModule { # Get the version specified in $env:SMODefaultModuleVersion if available $availableModule = $preferredModules | - Where-Object { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion} | + Where-Object -FilterScript { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion} | Select-Object -First 1 Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleVersionFound -f $availableModule.PSModuleInfo.Name, $availableModule.CalculatedVersion) diff --git a/source/Public/Import-SqlDscPreferredModule.ps1 b/source/Public/Import-SqlDscPreferredModule.ps1 index 1b782fd17..67f68d31d 100644 --- a/source/Public/Import-SqlDscPreferredModule.ps1 +++ b/source/Public/Import-SqlDscPreferredModule.ps1 @@ -80,7 +80,7 @@ function Import-SqlDscPreferredModule if ($PSBoundParameters.ContainsKey('Name')) { - $removeModule += Get-Module $Name + $removeModule += Get-Module -Name $Name } # Available module could be @@ -94,7 +94,7 @@ function Import-SqlDscPreferredModule $removeModule += Get-Module -Name 'SQLASCmdlets' # cSpell: disable-line } - Remove-Module $removeModule -Force -ErrorAction 'SilentlyContinue' + Remove-Module -ModuleInfo $removeModule -Force -ErrorAction 'SilentlyContinue' } if ($availableModule) From 176d7e811584882632ecdd8a44e39ea99cdbbac7 Mon Sep 17 00:00:00 2001 From: YaroBear Date: Mon, 28 Aug 2023 09:59:27 -0600 Subject: [PATCH 05/13] indent lines; else bracket on newline --- source/Public/Get-SqlDscPreferredModule.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/source/Public/Get-SqlDscPreferredModule.ps1 b/source/Public/Get-SqlDscPreferredModule.ps1 index 568a826fd..41bd1a9ca 100644 --- a/source/Public/Get-SqlDscPreferredModule.ps1 +++ b/source/Public/Get-SqlDscPreferredModule.ps1 @@ -131,16 +131,17 @@ function Get-SqlDscPreferredModule { # Get the version specified in $env:SMODefaultModuleVersion if available $availableModule = $preferredModules | - Where-Object -FilterScript { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion} | - Select-Object -First 1 + Where-Object -FilterScript { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion} | + Select-Object -First 1 Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleVersionFound -f $availableModule.PSModuleInfo.Name, $availableModule.CalculatedVersion) } - else { + else + { # Get the latest version if available $availableModule = $preferredModules | - Sort-Object -Property 'CalculatedVersion' -Descending | - Select-Object -First 1 + Sort-Object -Property 'CalculatedVersion' -Descending | + Select-Object -First 1 Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleFound -f $availableModule.PSModuleInfo.Name) } From 97dbe72f8e22e5fa63a67e896d3bbec20dfa6bfc Mon Sep 17 00:00:00 2001 From: YaroBear Date: Mon, 28 Aug 2023 10:03:39 -0600 Subject: [PATCH 06/13] use ModuleVersionFound string instead; simplify logging; remove ModuleFound string --- source/Public/Get-SqlDscPreferredModule.ps1 | 6 ++---- source/en-US/SqlServerDsc.strings.psd1 | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/source/Public/Get-SqlDscPreferredModule.ps1 b/source/Public/Get-SqlDscPreferredModule.ps1 index 41bd1a9ca..20ca786ae 100644 --- a/source/Public/Get-SqlDscPreferredModule.ps1 +++ b/source/Public/Get-SqlDscPreferredModule.ps1 @@ -133,8 +133,6 @@ function Get-SqlDscPreferredModule $availableModule = $preferredModules | Where-Object -FilterScript { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion} | Select-Object -First 1 - - Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleVersionFound -f $availableModule.PSModuleInfo.Name, $availableModule.CalculatedVersion) } else { @@ -142,10 +140,10 @@ function Get-SqlDscPreferredModule $availableModule = $preferredModules | Sort-Object -Property 'CalculatedVersion' -Descending | Select-Object -First 1 - - Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleFound -f $availableModule.PSModuleInfo.Name) } + Write-Verbose -Message ($script:localizedData.PreferredModule_ModuleVersionFound -f $availableModule.PSModuleInfo.Name, $availableModule.CalculatedVersion) + break } } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index dad36d262..a278fcf5f 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -148,7 +148,6 @@ ConvertFrom-StringData @' TraceFlag_Remove_NoCurrentTraceFlags = There are no current trace flags on instance. Nothing to remove. ## Get-SqlDscPreferredModule - PreferredModule_ModuleFound = Preferred module '{0}' found. PreferredModule_ModuleVersionFound = Preferred module '{0}' with version '{1}' found. PreferredModule_ModuleNotFound = No preferred PowerShell module was found. PreferredModule_ModuleVersionNotFound = No preferred Powershell module with version '{0}' was found. From 0a75f8e2fa2860b249557aa70a42d7378e1eacd7 Mon Sep 17 00:00:00 2001 From: YaroBear Date: Mon, 28 Aug 2023 10:05:45 -0600 Subject: [PATCH 07/13] update changelog:remove for modulefound --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13c9256d0..b3b915e53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Import-SqlDscPreferredModule` - Handles PSModuleInfo objects from `Get-SqlDscPreferredModule` instead of strings. +### Remove + +- SqlServerDsc + - Removed PreferredModule_ModuleFound string in favor for more verbose PreferredModule_ModuleVersionFound + ## [16.4.0] - 2023-08-22 ### Added From b9ca9f34c00fe2342aac9e5701bf128875a894c4 Mon Sep 17 00:00:00 2001 From: YaroBear Date: Mon, 28 Aug 2023 10:06:03 -0600 Subject: [PATCH 08/13] period --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b915e53..04202aa42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,7 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Remove - SqlServerDsc - - Removed PreferredModule_ModuleFound string in favor for more verbose PreferredModule_ModuleVersionFound + - Removed PreferredModule_ModuleFound string in favor for more verbose PreferredModule_ModuleVersionFound. ## [16.4.0] - 2023-08-22 From b67d94c0fa640964c257c60ef256e21e5f91854d Mon Sep 17 00:00:00 2001 From: YaroBear Date: Mon, 28 Aug 2023 17:37:45 -0600 Subject: [PATCH 09/13] move smo version calculation to private function; throw error if the wrong version of smo module was loaded; tests --- CHANGELOG.md | 3 + .../Get-SMOModuleCalculatedVersion.ps1 | 63 ++++++++++ source/Public/Get-SqlDscPreferredModule.ps1 | 22 +--- .../Public/Import-SqlDscPreferredModule.ps1 | 20 ++- source/en-US/SqlServerDsc.strings.psd1 | 1 + .../Get-SMOModuleCalculatedVersion.Tests.ps1 | 117 ++++++++++++++++++ .../Import-SqlDscPreferredModule.Tests.ps1 | 101 ++++++++++++++- 7 files changed, 301 insertions(+), 26 deletions(-) create mode 100644 source/Private/Get-SMOModuleCalculatedVersion.ps1 create mode 100644 tests/Unit/Private/Get-SMOModuleCalculatedVersion.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 04202aa42..7423e7f0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated pipeline files to support ModuleFast. - `Get-SqlDscPreferredModule` - Optionally specify what version of the the SQL preferred module to be imported using the SMODefaultModuleVersion environment variable ([issue #1965](https://github.com/dsccommunity/SqlServerDsc/issues/1965)). + - New private command: + - Get-SMOModuleCalculatedVersion - Returns the version of the SMO module as a string. SQLPS version 120 and 130 do not have the correct version set, so the file path is used to calculate the version. - SqlSetup - Added the parameter `SqlVersion` that can be used to set the SQL Server version to be installed instead of it looking for version in the setup @@ -48,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Now returns a PSModuleInfo object instead of just the module name. - `Import-SqlDscPreferredModule` - Handles PSModuleInfo objects from `Get-SqlDscPreferredModule` instead of strings. + - Now throws an exception if the currently loaded SMO module version does not match the expected version set in the SMODefaultModuleVersion environment variable. ### Remove diff --git a/source/Private/Get-SMOModuleCalculatedVersion.ps1 b/source/Private/Get-SMOModuleCalculatedVersion.ps1 new file mode 100644 index 000000000..c937005bc --- /dev/null +++ b/source/Private/Get-SMOModuleCalculatedVersion.ps1 @@ -0,0 +1,63 @@ +<# + .SYNOPSIS + Returns the calculated version of an SMO PowerShell module. + + .DESCRIPTION + Returns the calculated version of an SMO PowerShell module. + + For SQLServer, the version is calculated using the System.Version + field with '-preview' appended for pre-release versions . For + example: 21.1.1 or 22.0.49-preview + + For SQLPS, the version is calculated using the path of the module. For + example: + C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules + returns 130 + + .PARAMETER PSModuleInfo + Specifies the PSModuleInfo object for which to return the calculated version. + + .EXAMPLE + Get-Module -Name 'sqlps' | Get-SMOModuleCalculatedVersion + + Returns the calculated version as a string. + + .OUTPUTS + [System.String] +#> +function Get-SMOModuleCalculatedVersion +{ + [OutputType([System.String])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Management.Automation.PSModuleInfo] + $PSModuleInfo + ) + + process + { + $version = $null + + if ($PSModuleInfo.Name -eq 'SQLPS') + { + <# + Parse the build version number '120', '130' from the Path. + Older version of SQLPS did not have correct versioning. + #> + $version = (Select-String -InputObject $PSModuleInfo.Path -Pattern '\\([0-9]{3})\\' -List).Matches.Groups[1].Value + } + else + { + $version = $PSModuleInfo.Version.ToString() + + if ($PSModuleInfo.PrivateData.PSData.Prerelease) + { + $version = '{0}-{1}' -f $PSModuleInfo.Version, $PSModuleInfo.PrivateData.PSData.Prerelease + } + } + + return $version + } +} diff --git a/source/Public/Get-SqlDscPreferredModule.ps1 b/source/Public/Get-SqlDscPreferredModule.ps1 index 20ca786ae..136ba249f 100644 --- a/source/Public/Get-SqlDscPreferredModule.ps1 +++ b/source/Public/Get-SqlDscPreferredModule.ps1 @@ -96,27 +96,7 @@ function Get-SqlDscPreferredModule ForEach-Object -Process { @{ PSModuleInfo = $_ - CalculatedVersion = .{ - if ($_.Name -eq 'SQLPS') - { - <# - Parse the build version number '120', '130' from the Path. - Older version of SQLPS did not have correct versioning. - #> - (Select-String -InputObject $_.Path -Pattern '\\([0-9]{3})\\' -List).Matches.Groups[1].Value - } - else - { - $versionToReturn = $_.Version.ToString() - - if ($_.PrivateData.PSData.Prerelease) - { - $versionToReturn = '{0}-{1}' -f $_.Version, $_.PrivateData.PSData.Prerelease - } - - $versionToReturn - } - } + CalculatedVersion = $_ | Get-SMOModuleCalculatedVersion } } diff --git a/source/Public/Import-SqlDscPreferredModule.ps1 b/source/Public/Import-SqlDscPreferredModule.ps1 index 67f68d31d..aa4522535 100644 --- a/source/Public/Import-SqlDscPreferredModule.ps1 +++ b/source/Public/Import-SqlDscPreferredModule.ps1 @@ -104,11 +104,25 @@ function Import-SqlDscPreferredModule <# Check if the preferred module is already loaded into the session. #> - $loadedModuleName = (Get-Module -Name $availableModule.Name | Select-Object -First 1).Name + $loadedModule = Get-Module -Name $availableModule.Name | Select-Object -First 1 - if ($loadedModuleName) + if ($loadedModule) { - Write-Verbose -Message ($script:localizedData.PreferredModule_AlreadyImported -f $loadedModuleName) + $loadedModuleCalculatedVersion = $loadedModule | Get-SMOModuleCalculatedVersion + $availableModuleCalculatedVersion = $availableModule | Get-SMOModuleCalculatedVersion + if ($env:SMODefaultModuleVersion -and $loadedModuleCalculatedVersion -ne $availableModuleCalculatedVersion) + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.PreferredModule_WrongModuleVersionLoaded -f $loadedModule.Name, $loadedModule.Version, $availableModule.Version), + 'ISDPM0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::ObjectNotFound, + 'PreferredModule' + ) + ) + } + + Write-Verbose -Message ($script:localizedData.PreferredModule_AlreadyImported -f $loadedModule.Name) return } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index a278fcf5f..0aff6b53d 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -154,6 +154,7 @@ ConvertFrom-StringData @' ## Import-SqlDscPreferredModule PreferredModule_ImportedModule = Imported PowerShell module '{0}' with version '{1}' from path '{2}'. + PreferredModule_WrongModuleVersionLoaded = PowerShell module '{0}' with version '{1}' is loaded in the current session, but expected version '{2}'. PreferredModule_AlreadyImported = Found PowerShell module {0} already imported in the session. PreferredModule_ForceRemoval = Forcibly removed the SQL PowerShell module from the session to import it fresh again. PreferredModule_PushingLocation = SQLPS module changes CWD to SQLServer:\ when loading, pushing location to pop it when module is loaded. diff --git a/tests/Unit/Private/Get-SMOModuleCalculatedVersion.Tests.ps1 b/tests/Unit/Private/Get-SMOModuleCalculatedVersion.Tests.ps1 new file mode 100644 index 000000000..a9bf56e20 --- /dev/null +++ b/tests/Unit/Private/Get-SMOModuleCalculatedVersion.Tests.ps1 @@ -0,0 +1,117 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'SqlServerDsc' + + $env:SqlServerDscCI = $true + + Import-Module -Name $script:dscModuleName + + # Loading mocked classes + Add-Type -Path (Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '../Stubs') -ChildPath 'SMO.cs') + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force + + Remove-Item -Path 'env:SqlServerDscCI' +} + +Describe 'Get-SMOModuleCalculatedVersion' -Tag 'Private' { + Context 'When passing in SQLServer module' { + It 'Should return the correct version' { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '21.1.18068' + } + + Context 'When module is in pre-release' { + It 'Should return the correct version' { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(22, 0, 49) + PrivateData = @{ + PSData = @{ + PreRelease = 'preview1' + } + } + } + + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '22.0.49-preview1' + } + } + } + + Context 'When passing in SQLPS module' { + It 'Should return the correct version' { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '130' + } + } + + Context 'When passing in any other module' { + It 'Should return the correct version' { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'OtherModule' + Version = [Version]::new(1, 0, 0) + } + + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '1.0.0' + } + + Context 'When module is in pre-release' { + It 'Should return the correct version' { + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'OtherModule' + Version = [Version]::new(1, 0, 0) + PrivateData = @{ + PSData = @{ + PreRelease = 'preview1' + } + } + } + + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '1.0.0-preview1' + } + } + } +} diff --git a/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 b/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 index 09f587b67..169e82808 100644 --- a/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 +++ b/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 @@ -149,17 +149,65 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { Mock -CommandName Get-SqlDscPreferredModule -MockWith { return $sqlServerModule } + } + It 'Should use the already loaded module and not call Import-Module' { Mock -CommandName Get-Module -MockWith { return $sqlServerModule } - } - It 'Should use the already loaded module and not call Import-Module' { { Import-SqlDscPreferredModule } | Should -Not -Throw Should -Invoke -CommandName Import-Module -Exactly -Times 0 -Scope It } + + Context 'When the SMODefaultModuleVersion environment variable is set' { + BeforeAll { + $env:SMODefaultModuleVersion = '21.1.18068' + } + + AfterAll { + Remove-Item -Path 'env:SMODefaultModuleVersion' + } + + Context 'When the wrong module version is currently loaded in the session' { + BeforeAll { + $sqlServerModuleDifferentVersion = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(1, 1, 1) + } + + Mock -CommandName Get-Module -MockWith { + return $sqlServerModuleDifferentVersion + } + } + + It 'Should throw the correct error message' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.PreferredModule_WrongModuleVersionLoaded + } + + { Import-SqlDscPreferredModule } | Should -Throw -ExpectedMessage ($errorMessage -f $sqlServerModuleDifferentVersion.Name, $sqlServerModuleDifferentVersion.Version, $sqlServerModule.Version) + } + } + + Context 'When the correct module version is currently loaded in the session' { + BeforeAll { + $sqlServerModuleSameVersion = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + + Mock -CommandName Get-Module -MockWith { + return $sqlServerModuleSameVersion + } + } + + It 'Should not throw an error' { + { Import-SqlDscPreferredModule } | Should -Not -Throw + } + } + } } Context 'When module SQLPS is already loaded into the session' { @@ -168,6 +216,7 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { $sqlpsModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' } Mock -CommandName Get-SqlDscPreferredModule -MockWith { @@ -184,6 +233,54 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { Should -Invoke -CommandName Import-Module -Exactly -Times 0 -Scope It } + + Context 'When the SMODefaultModuleVersion environment variable is set' { + BeforeAll { + $env:SMODefaultModuleVersion = '130' + } + + AfterAll { + Remove-Item -Path 'env:SMODefaultModuleVersion' + } + + Context 'When the wrong module version is currently loaded in the session' { + BeforeAll { + $sqlpsModuleDifferentVersion = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + + Mock -CommandName Get-Module -MockWith { + return $sqlpsModuleDifferentVersion + } + } + + It 'Should throw the correct error message' { + $errorMessage = InModuleScope -ScriptBlock { + $script:localizedData.PreferredModule_WrongModuleVersionLoaded + } + + { Import-SqlDscPreferredModule } | Should -Throw -ExpectedMessage ($errorMessage -f $sqlpsModuleDifferentVersion.Name, $sqlpsModuleDifferentVersion.Version, $sqlpsModule.Version) + } + } + + Context 'When the correct module version is currently loaded in the session' { + BeforeAll { + $sqlpsModuleSameVersion = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } + + Mock -CommandName Get-Module -MockWith { + return $sqlpsModuleSameVersion + } + } + + It 'Should not throw an error' { + { Import-SqlDscPreferredModule } | Should -Not -Throw + } + } + } } Context 'When module SqlServer exists, but not loaded into the session' { From 098c71a80fe0ce79248c83d482bc21c42aab29d3 Mon Sep 17 00:00:00 2001 From: YaroBear Date: Mon, 28 Aug 2023 17:56:12 -0600 Subject: [PATCH 10/13] remove indent for private command change --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7423e7f0b..d60c39a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated pipeline files to support ModuleFast. - `Get-SqlDscPreferredModule` - Optionally specify what version of the the SQL preferred module to be imported using the SMODefaultModuleVersion environment variable ([issue #1965](https://github.com/dsccommunity/SqlServerDsc/issues/1965)). - - New private command: - - Get-SMOModuleCalculatedVersion - Returns the version of the SMO module as a string. SQLPS version 120 and 130 do not have the correct version set, so the file path is used to calculate the version. +- New private command: + - Get-SMOModuleCalculatedVersion - Returns the version of the SMO module as a string. SQLPS version 120 and 130 do not have the correct version set, so the file path is used to calculate the version. - SqlSetup - Added the parameter `SqlVersion` that can be used to set the SQL Server version to be installed instead of it looking for version in the setup From 636cefe1e04d1d3f5a7c768bd4a27ded699b63a6 Mon Sep 17 00:00:00 2001 From: YaroBear Date: Tue, 29 Aug 2023 10:30:11 -0600 Subject: [PATCH 11/13] resolve issues from scriptanalyzer --- source/Private/Get-SMOModuleCalculatedVersion.ps1 | 2 +- source/Public/Get-SqlDscPreferredModule.ps1 | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/Private/Get-SMOModuleCalculatedVersion.ps1 b/source/Private/Get-SMOModuleCalculatedVersion.ps1 index c937005bc..1af5156cb 100644 --- a/source/Private/Get-SMOModuleCalculatedVersion.ps1 +++ b/source/Private/Get-SMOModuleCalculatedVersion.ps1 @@ -18,7 +18,7 @@ Specifies the PSModuleInfo object for which to return the calculated version. .EXAMPLE - Get-Module -Name 'sqlps' | Get-SMOModuleCalculatedVersion + Get-SMOModuleCalculatedVersion -PSModuleInfo (Get-Module -Name 'sqlps') Returns the calculated version as a string. diff --git a/source/Public/Get-SqlDscPreferredModule.ps1 b/source/Public/Get-SqlDscPreferredModule.ps1 index 136ba249f..08b1bb1ae 100644 --- a/source/Public/Get-SqlDscPreferredModule.ps1 +++ b/source/Public/Get-SqlDscPreferredModule.ps1 @@ -103,7 +103,7 @@ function Get-SqlDscPreferredModule foreach ($preferredModuleName in $Name) { $preferredModules = $availableModules | - Where-Object -FilterScript { $_.PSModuleInfo.Name -eq $preferredModuleName} + Where-Object -FilterScript { $_.PSModuleInfo.Name -eq $preferredModuleName } if ($preferredModules) { @@ -111,7 +111,7 @@ function Get-SqlDscPreferredModule { # Get the version specified in $env:SMODefaultModuleVersion if available $availableModule = $preferredModules | - Where-Object -FilterScript { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion} | + Where-Object -FilterScript { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion } | Select-Object -First 1 } else @@ -136,7 +136,8 @@ function Get-SqlDscPreferredModule { $errorMessage = $script:localizedData.PreferredModule_ModuleVersionNotFound -f $env:SMODefaultModuleVersion } - else { + else + { $errorMessage = $script:localizedData.PreferredModule_ModuleNotFound } From 00451e4f8c7d8d1de3795ed8c41c129d000debd1 Mon Sep 17 00:00:00 2001 From: YaroBear Date: Tue, 29 Aug 2023 10:40:19 -0600 Subject: [PATCH 12/13] wrap tests inmodulescope to use non-exported function --- .../Get-SMOModuleCalculatedVersion.Tests.ps1 | 82 ++++++++++++------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/tests/Unit/Private/Get-SMOModuleCalculatedVersion.Tests.ps1 b/tests/Unit/Private/Get-SMOModuleCalculatedVersion.Tests.ps1 index a9bf56e20..a53b96b8f 100644 --- a/tests/Unit/Private/Get-SMOModuleCalculatedVersion.Tests.ps1 +++ b/tests/Unit/Private/Get-SMOModuleCalculatedVersion.Tests.ps1 @@ -52,65 +52,85 @@ AfterAll { Describe 'Get-SMOModuleCalculatedVersion' -Tag 'Private' { Context 'When passing in SQLServer module' { It 'Should return the correct version' { - $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'SqlServer' - Version = [Version]::new(21, 1, 18068) - } + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 - $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '21.1.18068' + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(21, 1, 18068) + } + + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '21.1.18068' + } } Context 'When module is in pre-release' { It 'Should return the correct version' { - $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'SqlServer' - Version = [Version]::new(22, 0, 49) - PrivateData = @{ - PSData = @{ - PreRelease = 'preview1' + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SqlServer' + Version = [Version]::new(22, 0, 49) + PrivateData = @{ + PSData = @{ + PreRelease = 'preview1' + } } } - } - $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '22.0.49-preview1' + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '22.0.49-preview1' + } } } } Context 'When passing in SQLPS module' { It 'Should return the correct version' { - $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'SQLPS' + Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' + } - $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '130' + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '130' + } } } Context 'When passing in any other module' { It 'Should return the correct version' { - $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'OtherModule' - Version = [Version]::new(1, 0, 0) - } + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'OtherModule' + Version = [Version]::new(1, 0, 0) + } - $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '1.0.0' + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '1.0.0' + } } Context 'When module is in pre-release' { It 'Should return the correct version' { - $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'OtherModule' - Version = [Version]::new(1, 0, 0) - PrivateData = @{ - PSData = @{ - PreRelease = 'preview1' + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $sqlServerModule = New-MockObject -Type 'PSModuleInfo' -Properties @{ + Name = 'OtherModule' + Version = [Version]::new(1, 0, 0) + PrivateData = @{ + PSData = @{ + PreRelease = 'preview1' + } } } - } - $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '1.0.0-preview1' + $sqlServerModule | Get-SMOModuleCalculatedVersion | Should -Be '1.0.0-preview1' + } } } } From 9c6420cb33753086cae2380f9dd5d8d20739df3f Mon Sep 17 00:00:00 2001 From: YaroBear Date: Thu, 31 Aug 2023 11:11:43 -0600 Subject: [PATCH 13/13] set -erroraction 'stop' on get-sqldscpreferredmodule; update, changelog and tests --- CHANGELOG.md | 2 +- .../Public/Import-SqlDscPreferredModule.ps1 | 117 ++++++++---------- source/en-US/SqlServerDsc.strings.psd1 | 3 +- .../Import-SqlDscPreferredModule.Tests.ps1 | 98 +-------------- 4 files changed, 55 insertions(+), 165 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d60c39a52..f00151e13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Now returns a PSModuleInfo object instead of just the module name. - `Import-SqlDscPreferredModule` - Handles PSModuleInfo objects from `Get-SqlDscPreferredModule` instead of strings. - - Now throws an exception if the currently loaded SMO module version does not match the expected version set in the SMODefaultModuleVersion environment variable. + - Sets -ErrorAction 'Stop' on Get-SqlDscPreferredModule to throw an error if no SQL module is found. The script-terminating error is caught and made into a statement-terminating error. ### Remove diff --git a/source/Public/Import-SqlDscPreferredModule.ps1 b/source/Public/Import-SqlDscPreferredModule.ps1 index aa4522535..d2adff63d 100644 --- a/source/Public/Import-SqlDscPreferredModule.ps1 +++ b/source/Public/Import-SqlDscPreferredModule.ps1 @@ -70,7 +70,23 @@ function Import-SqlDscPreferredModule $getSqlDscPreferredModuleParameters.Refresh = $true } - $availableModule = Get-SqlDscPreferredModule @getSqlDscPreferredModuleParameters + $availableModule = $null + + try + { + $availableModule = Get-SqlDscPreferredModule @getSqlDscPreferredModuleParameters -ErrorAction 'Stop' + } + catch + { + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + ($script:localizedData.PreferredModule_FailedFinding), + 'ISDPM0001', # cspell: disable-line + [System.Management.Automation.ErrorCategory]::ObjectNotFound, + 'PreferredModule' + ) + ) + } if ($Force.IsPresent) { @@ -96,78 +112,49 @@ function Import-SqlDscPreferredModule Remove-Module -ModuleInfo $removeModule -Force -ErrorAction 'SilentlyContinue' } - - if ($availableModule) + else { - if (-not $Force.IsPresent) + <# + Check if the preferred module is already loaded into the session. + #> + $loadedModule = Get-Module -Name $availableModule.Name | Select-Object -First 1 + + if ($loadedModule) { - <# - Check if the preferred module is already loaded into the session. - #> - $loadedModule = Get-Module -Name $availableModule.Name | Select-Object -First 1 - - if ($loadedModule) - { - $loadedModuleCalculatedVersion = $loadedModule | Get-SMOModuleCalculatedVersion - $availableModuleCalculatedVersion = $availableModule | Get-SMOModuleCalculatedVersion - if ($env:SMODefaultModuleVersion -and $loadedModuleCalculatedVersion -ne $availableModuleCalculatedVersion) - { - $PSCmdlet.ThrowTerminatingError( - [System.Management.Automation.ErrorRecord]::new( - ($script:localizedData.PreferredModule_WrongModuleVersionLoaded -f $loadedModule.Name, $loadedModule.Version, $availableModule.Version), - 'ISDPM0001', # cspell: disable-line - [System.Management.Automation.ErrorCategory]::ObjectNotFound, - 'PreferredModule' - ) - ) - } - - Write-Verbose -Message ($script:localizedData.PreferredModule_AlreadyImported -f $loadedModule.Name) - - return - } + Write-Verbose -Message ($script:localizedData.PreferredModule_AlreadyImported -f $loadedModule.Name) + + return } + } - try + try + { + Write-Debug -Message ($script:localizedData.PreferredModule_PushingLocation) + + Push-Location + + <# + SQLPS has unapproved verbs, disable checking to ignore Warnings. + Suppressing verbose so all cmdlet is not listed. + #> + $importedModule = Import-Module -ModuleInfo $availableModule -DisableNameChecking -Verbose:$false -Force:$Force -Global -PassThru -ErrorAction 'Stop' + + <# + SQLPS returns two entries, one with module type 'Script' and another with module type 'Manifest'. + Only return the object with module type 'Manifest'. + SqlServer only returns one object (of module type 'Script'), so no need to do anything for SqlServer module. + #> + if ($availableModule.Name -eq 'SQLPS') { - Write-Debug -Message ($script:localizedData.PreferredModule_PushingLocation) - - Push-Location - - <# - SQLPS has unapproved verbs, disable checking to ignore Warnings. - Suppressing verbose so all cmdlet is not listed. - #> - $importedModule = Import-Module -ModuleInfo $availableModule -DisableNameChecking -Verbose:$false -Force:$Force -Global -PassThru -ErrorAction 'Stop' - - <# - SQLPS returns two entries, one with module type 'Script' and another with module type 'Manifest'. - Only return the object with module type 'Manifest'. - SqlServer only returns one object (of module type 'Script'), so no need to do anything for SqlServer module. - #> - if ($availableModule.Name -eq 'SQLPS') - { - $importedModule = $importedModule | Where-Object -Property 'ModuleType' -EQ -Value 'Manifest' - } - - Write-Verbose -Message ($script:localizedData.PreferredModule_ImportedModule -f $importedModule.Name, $importedModule.Version, $importedModule.Path) + $importedModule = $importedModule | Where-Object -Property 'ModuleType' -EQ -Value 'Manifest' } - finally - { - Write-Debug -Message ($script:localizedData.PreferredModule_PoppingLocation) - Pop-Location - } + Write-Verbose -Message ($script:localizedData.PreferredModule_ImportedModule -f $importedModule.Name, $importedModule.Version, $importedModule.Path) } - else + finally { - $PSCmdlet.ThrowTerminatingError( - [System.Management.Automation.ErrorRecord]::new( - ($script:localizedData.PreferredModule_FailedFinding), - 'ISDPM0001', # cspell: disable-line - [System.Management.Automation.ErrorCategory]::ObjectNotFound, - 'PreferredModule' - ) - ) + Write-Debug -Message ($script:localizedData.PreferredModule_PoppingLocation) + + Pop-Location } } diff --git a/source/en-US/SqlServerDsc.strings.psd1 b/source/en-US/SqlServerDsc.strings.psd1 index 0aff6b53d..07e5bf4c5 100644 --- a/source/en-US/SqlServerDsc.strings.psd1 +++ b/source/en-US/SqlServerDsc.strings.psd1 @@ -150,11 +150,10 @@ ConvertFrom-StringData @' ## Get-SqlDscPreferredModule PreferredModule_ModuleVersionFound = Preferred module '{0}' with version '{1}' found. PreferredModule_ModuleNotFound = No preferred PowerShell module was found. - PreferredModule_ModuleVersionNotFound = No preferred Powershell module with version '{0}' was found. +PreferredModule_ModuleVersionNotFound = No preferred Powershell module with version '{0}' was found. ## Import-SqlDscPreferredModule PreferredModule_ImportedModule = Imported PowerShell module '{0}' with version '{1}' from path '{2}'. - PreferredModule_WrongModuleVersionLoaded = PowerShell module '{0}' with version '{1}' is loaded in the current session, but expected version '{2}'. PreferredModule_AlreadyImported = Found PowerShell module {0} already imported in the session. PreferredModule_ForceRemoval = Forcibly removed the SQL PowerShell module from the session to import it fresh again. PreferredModule_PushingLocation = SQLPS module changes CWD to SQLServer:\ when loading, pushing location to pop it when module is loaded. diff --git a/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 b/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 index 169e82808..58face496 100644 --- a/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 +++ b/tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1 @@ -160,54 +160,6 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { Should -Invoke -CommandName Import-Module -Exactly -Times 0 -Scope It } - - Context 'When the SMODefaultModuleVersion environment variable is set' { - BeforeAll { - $env:SMODefaultModuleVersion = '21.1.18068' - } - - AfterAll { - Remove-Item -Path 'env:SMODefaultModuleVersion' - } - - Context 'When the wrong module version is currently loaded in the session' { - BeforeAll { - $sqlServerModuleDifferentVersion = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'SqlServer' - Version = [Version]::new(1, 1, 1) - } - - Mock -CommandName Get-Module -MockWith { - return $sqlServerModuleDifferentVersion - } - } - - It 'Should throw the correct error message' { - $errorMessage = InModuleScope -ScriptBlock { - $script:localizedData.PreferredModule_WrongModuleVersionLoaded - } - - { Import-SqlDscPreferredModule } | Should -Throw -ExpectedMessage ($errorMessage -f $sqlServerModuleDifferentVersion.Name, $sqlServerModuleDifferentVersion.Version, $sqlServerModule.Version) - } - } - - Context 'When the correct module version is currently loaded in the session' { - BeforeAll { - $sqlServerModuleSameVersion = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'SqlServer' - Version = [Version]::new(21, 1, 18068) - } - - Mock -CommandName Get-Module -MockWith { - return $sqlServerModuleSameVersion - } - } - - It 'Should not throw an error' { - { Import-SqlDscPreferredModule } | Should -Not -Throw - } - } - } } Context 'When module SQLPS is already loaded into the session' { @@ -233,54 +185,6 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { Should -Invoke -CommandName Import-Module -Exactly -Times 0 -Scope It } - - Context 'When the SMODefaultModuleVersion environment variable is set' { - BeforeAll { - $env:SMODefaultModuleVersion = '130' - } - - AfterAll { - Remove-Item -Path 'env:SMODefaultModuleVersion' - } - - Context 'When the wrong module version is currently loaded in the session' { - BeforeAll { - $sqlpsModuleDifferentVersion = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\160\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } - - Mock -CommandName Get-Module -MockWith { - return $sqlpsModuleDifferentVersion - } - } - - It 'Should throw the correct error message' { - $errorMessage = InModuleScope -ScriptBlock { - $script:localizedData.PreferredModule_WrongModuleVersionLoaded - } - - { Import-SqlDscPreferredModule } | Should -Throw -ExpectedMessage ($errorMessage -f $sqlpsModuleDifferentVersion.Name, $sqlpsModuleDifferentVersion.Version, $sqlpsModule.Version) - } - } - - Context 'When the correct module version is currently loaded in the session' { - BeforeAll { - $sqlpsModuleSameVersion = New-MockObject -Type 'PSModuleInfo' -Properties @{ - Name = 'SQLPS' - Path = 'C:\Program Files (x86)\Microsoft SQL Server\130\Tools\PowerShell\Modules\SQLPS\Sqlps.ps1' - } - - Mock -CommandName Get-Module -MockWith { - return $sqlpsModuleSameVersion - } - } - - It 'Should not throw an error' { - { Import-SqlDscPreferredModule } | Should -Not -Throw - } - } - } } Context 'When module SqlServer exists, but not loaded into the session' { @@ -371,7 +275,7 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' { BeforeAll { Mock -CommandName Import-Module Mock -CommandName Get-Module - Mock -CommandName Get-SqlDscPreferredModule + Mock -CommandName Get-SqlDscPreferredModule { throw "Could not find the module" } } It 'Should throw the correct error message' {