Skip to content

Commit

Permalink
Import-SqlDscPreferredModule: Better handle preferred module (#1921)
Browse files Browse the repository at this point in the history
- `Import-SqlDscPreferredModule`
  - Better handle preferred module and re-uses logic in `Get-SqlDscPreferredModule`.
  • Loading branch information
johlju authored Apr 26, 2023
1 parent 84fc080 commit be2f562
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 59 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
([issue #1918](https://github.com/dsccommunity/SqlServerDsc/issues/1918)).
- Correctly outputs query in verbose message when parameter `RedactText`
is not passed.
- `Import-SqlDscPreferredModule`
- Better handle preferred module and re-uses logic in `Get-SqlDscPreferredModule`.

## [16.2.0] - 2023-04-10

Expand Down
3 changes: 1 addition & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,6 @@ stages:
pool:
vmImage: $(JOB_VMIMAGE)
timeoutInMinutes: 0
variables:
SMODefaultModuleName: 'SqlServer'
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download Build Artifact'
Expand Down Expand Up @@ -363,6 +361,7 @@ stages:
SKIP_DATABASE_ENGINE_DEFAULT_INSTANCE: true
SKIP_ANALYSIS_MULTI_INSTANCE: true
SKIP_ANALYSIS_TABULAR_INSTANCE: true
SMODefaultModuleName: 'SqlServer'
pool:
vmImage: $(JOB_VMIMAGE)
timeoutInMinutes: 0
Expand Down
10 changes: 7 additions & 3 deletions source/Public/Get-SqlDscPreferredModule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
.DESCRIPTION
Get the first available (preferred) module that is installed.
If the environment variable `SMODefaultModuleName` is set to a module name
that name will be used as the preferred module name instead of the default
module 'SqlServer'.
.PARAMETER Name
Specifies the list of the (preferred) modules to search for, in order.
Defaults to 'SqlServer' and then 'SQLPS'.
Expand Down Expand Up @@ -60,13 +64,13 @@ function Get-SqlDscPreferredModule

if (-not $PSBoundParameters.ContainsKey('Name'))
{
$Name = if ($env:SMODefaultModuleName)
if ($env:SMODefaultModuleName)
{
@($env:SMODefaultModuleName, 'SQLPS')
$Name = @($env:SMODefaultModuleName, 'SQLPS')
}
else
{
@('SqlServer', 'SQLPS')
$Name = @('SqlServer', 'SQLPS')
}
}

Expand Down
104 changes: 60 additions & 44 deletions source/Public/Import-SqlDscPreferredModule.ps1
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
<#
.SYNOPSIS
Imports the module SqlServer (preferred) or SQLPS in a standardized way.
Imports a (preferred) module in a standardized way.
.DESCRIPTION
Imports the module SqlServer (preferred) or SQLPS in a standardized way.
Imports a (preferred) module in a standardized way. If the parameter `Name`
is not specified the command will imports the default module SqlServer
if it exist, otherwise SQLPS.
If the environment variable `SMODefaultModuleName` is set to a module name
that name will be used as the preferred module name instead of the default
module 'SqlServer'.
The module is always imported globally.
.PARAMETER PreferredModule
Specifies the name of the preferred module. Defaults to 'SqlServer'.
.PARAMETER Name
Specifies the name of a preferred module.
.PARAMETER Force
Forces the removal of the previous SQL module, to load the same or newer
version fresh. This is meant to make sure the newest version is used, with
the latest assemblies.
Forces the removal of the previous module, to load the same or newer version
fresh. This is meant to make sure the newest version is used, with the latest
assemblies.
.EXAMPLE
Import-SqlDscPreferredModule
Expand All @@ -23,12 +30,12 @@
.EXAMPLE
Import-SqlDscPreferredModule -Force
Removes any already loaded module of the default preferred module (SqlServer)
and the module SQLPS, then it will forcibly import the default preferred
module if it exist, otherwise it will try to import the module SQLPS.
Will forcibly import the default preferred module if it exist, otherwise
it will try to import the module SQLPS. Prior to importing it will remove
an already loaded module.
.EXAMPLE
Import-SqlDscPreferredModule -PreferredModule 'OtherSqlModule'
Import-SqlDscPreferredModule -Name 'OtherSqlModule'
Imports the specified preferred module OtherSqlModule if it exist, otherwise
it will try to import the module SQLPS.
Expand All @@ -39,58 +46,67 @@ function Import-SqlDscPreferredModule
param
(
[Parameter()]
[Alias('PreferredModule')]
[ValidateNotNullOrEmpty()]
[System.String]
$PreferredModule,
$Name,

[Parameter()]
[System.Management.Automation.SwitchParameter]
$Force
)

if (-not $PSBoundParameters.ContainsKey('PreferredModule'))
$getSqlDscPreferredModuleParameters = @{
Refresh = $true
}

if ($PSBoundParameters.ContainsKey('Name'))
{
$PreferredModule = if ($env:SMODefaultModuleName)
{
$env:SMODefaultModuleName
}
else
{
'SqlServer'
}
$getSqlDscPreferredModuleParameters.Name = @($Name, 'SQLPS')
}

$availableModuleName = Get-SqlDscPreferredModule @getSqlDscPreferredModuleParameters

if ($Force.IsPresent)
{
Write-Verbose -Message $script:localizedData.PreferredModule_ForceRemoval

Remove-Module -Name @(
$PreferredModule,
'SQLPS',
'SQLASCmdlets' # cSpell: disable-line
) -Force -ErrorAction 'SilentlyContinue'
}
else
{
<#
Check if either of the modules are already loaded into the session.
Prefer to use the first one (in order found).
NOTE: There should actually only be either SqlServer or SQLPS loaded,
otherwise there can be problems with wrong assemblies being loaded.
#>
$loadedModuleName = (Get-Module -Name @($PreferredModule, 'SQLPS') | Select-Object -First 1).Name

if ($loadedModuleName)
$removeModule = @()

if ($PSBoundParameters.ContainsKey('Name'))
{
Write-Verbose -Message ($script:localizedData.PreferredModule_AlreadyImported -f $loadedModuleName)
$removeModule += $Name
}

return
# Available module could be
if ($availableModuleName)
{
$removeModule += $availableModuleName
}
}

$availableModuleName = Get-SqlDscPreferredModule -Name @($PreferredModule, 'SQLPS') -Refresh
if ($removeModule -contains 'SQLPS')
{
$removeModule += 'SQLASCmdlets' # cSpell: disable-line
}

Remove-Module -Name $removeModule -Force -ErrorAction 'SilentlyContinue'
}

if ($availableModuleName)
{
if (-not $Force.IsPresent)
{
# Check if the preferred module is already loaded into the session.
$loadedModuleName = (Get-Module -Name $availableModuleName | Select-Object -First 1).Name

if ($loadedModuleName)
{
Write-Verbose -Message ($script:localizedData.PreferredModule_AlreadyImported -f $loadedModuleName)

return
}
}

try
{
Write-Debug -Message ($script:localizedData.PreferredModule_PushingLocation)
Expand All @@ -108,7 +124,7 @@ function Import-SqlDscPreferredModule
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 -ne $PreferredModule)
if ($availableModuleName -eq 'SQLPS')
{
$importedModule = $importedModule | Where-Object -Property 'ModuleType' -EQ -Value 'Manifest'
}
Expand All @@ -126,7 +142,7 @@ function Import-SqlDscPreferredModule
{
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
($script:localizedData.PreferredModule_FailedFinding -f $PreferredModule),
($script:localizedData.PreferredModule_FailedFinding),
'ISDPM0001', # cspell: disable-line
[System.Management.Automation.ErrorCategory]::ObjectNotFound,
'PreferredModule'
Expand Down
2 changes: 1 addition & 1 deletion source/en-US/SqlServerDsc.strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ ConvertFrom-StringData @'
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.
PreferredModule_PoppingLocation = Popping location back to what it was before importing SQLPS module.
PreferredModule_FailedFinding = Failed to find a dependent module. Unable to run SQL Server commands or use SQL Server types. Please install the {0} or SQLPS then try to import SqlServerDsc again.
PreferredModule_FailedFinding = Failed to find a dependent module. Unable to run SQL Server commands or use SQL Server types. Please install one of the preferred SMO modules or the SQLPS module, then try to import SqlServerDsc again.
## Invoke-SqlDscQuery
Query_Invoke_ShouldProcessVerboseDescription = Executing a Transact-SQL query on the instance '{0}'.
Expand Down
20 changes: 20 additions & 0 deletions tests/Unit/Public/Get-SqlDscPreferredModule.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -423,4 +423,24 @@ Describe 'Get-SqlDscPreferredModule' -Tag 'Public' {
Should -Invoke -CommandName Set-PSModulePath -Exactly -Times 1 -Scope It
}
}

Context 'When the environment variable SMODefaultModuleName is assigned a module name' {
BeforeAll {
$env:SMODefaultModuleName = 'OtherModule'

Mock -CommandName Get-Module -MockWith {
return @{
Name = $env:SMODefaultModuleName
}
}
}

AfterAll {
Remove-Item -Path 'env:SMODefaultModuleName'
}

It 'Should return the correct module name' {
Get-SqlDscPreferredModule | Should -Be $env:SMODefaultModuleName
}
}
}
69 changes: 60 additions & 9 deletions tests/Unit/Public/Import-SqlDscPreferredModule.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' {
@{
MockParameterSetName = '__AllParameterSets'
# cSpell: disable-next
MockExpectedParameters = '[[-PreferredModule] <string>] [-Force] [<CommonParameters>]'
MockExpectedParameters = '[[-Name] <string>] [-Force] [<CommonParameters>]'
}
) {
$result = (Get-Command -Name 'Import-SqlDscPreferredModule').ParameterSets |
Expand Down Expand Up @@ -140,7 +140,11 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' {

Context 'When module SqlServer is already loaded into the session' {
BeforeAll {
Mock -CommandName Import-Module -MockWith $mockImportModule
Mock -CommandName Import-Module
Mock -CommandName Get-SqlDscPreferredModule -MockWith {
return 'SqlServer'
}

Mock -CommandName Get-Module -MockWith {
return @{
Name = 'SqlServer'
Expand All @@ -157,7 +161,11 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' {

Context 'When module SQLPS is already loaded into the session' {
BeforeAll {
Mock -CommandName Import-Module -MockWith $mockImportModule
Mock -CommandName Import-Module
Mock -CommandName Get-SqlDscPreferredModule -MockWith {
return 'SQLPS'
}

Mock -CommandName Get-Module -MockWith {
return @{
Name = 'SQLPS'
Expand Down Expand Up @@ -193,6 +201,27 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' {
}
}

Context 'When the specific module exists, but not loaded into the session' {
BeforeAll {
Mock -CommandName Import-Module -MockWith $mockImportModule
Mock -CommandName Get-Module
Mock -CommandName Get-SqlDscPreferredModule -MockWith {
return 'OtherModule'
}

$mockExpectedModuleNameToImport = 'OtherModule'
}

It 'Should import the SqlServer module without throwing' {
{ Import-SqlDscPreferredModule -Name 'OtherModule' } | Should -Not -Throw

Should -Invoke -CommandName Get-SqlDscPreferredModule -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
Should -Invoke -CommandName Import-Module -Exactly -Times 1 -Scope It
}
}

Context 'When only module SQLPS exists, but not loaded into the session, and using -Force' {
BeforeAll {
Mock -CommandName Import-Module -MockWith $mockImportModule
Expand Down Expand Up @@ -220,22 +249,44 @@ Describe 'Import-SqlDscPreferredModule' -Tag 'Public' {
Mock -CommandName Import-Module
Mock -CommandName Get-Module
Mock -CommandName Get-SqlDscPreferredModule

$mockExpectedModuleNameToImport = $sqlPsExpectedModulePath
}

It 'Should throw the correct error message' {
$mockLocalizedString = InModuleScope -ScriptBlock {
$script:localizedData.PowerShellSqlModuleNotFound -f 'SqlServer'
$mockErrorMessage = InModuleScope -ScriptBlock {
$script:localizedData.PreferredModule_FailedFinding
}

{ Import-SqlDscPreferredModule } | Should -Throw -ExpectedMessage $mockLocalizedString
{ Import-SqlDscPreferredModule } | Should -Throw -ExpectedMessage $mockErrorMessage

Should -Invoke -CommandName Get-Module -Exactly -Times 1 -Scope It
Should -Invoke -CommandName Get-Module -Exactly -Times 0 -Scope It
Should -Invoke -CommandName Get-SqlDscPreferredModule -Exactly -Times 1 -Scope It
Should -Invoke -CommandName Push-Location -Exactly -Times 0 -Scope It
Should -Invoke -CommandName Pop-Location -Exactly -Times 0 -Scope It
Should -Invoke -CommandName Import-Module -Exactly -Times 0 -Scope It
}
}

Context 'When forcibly importing a specific preferred module but only SQLPS is available' {
BeforeAll {
Mock -CommandName Import-Module -MockWith $mockImportModule
Mock -CommandName Get-Module
Mock -CommandName Remove-Module
Mock -CommandName Get-SqlDscPreferredModule -MockWith {
return 'SQLPS'
}

$mockExpectedModuleNameToImport = 'SQLPS'
}

It 'Should import the SqlServer 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 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
Should -Invoke -CommandName Import-Module -Exactly -Times 1 -Scope It
}
}
}

0 comments on commit be2f562

Please sign in to comment.