Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get-SqlDscPreferredModule: Optionally specify which version of the SQL module is imported #1966

Merged
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ 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)).
- 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
Expand Down Expand Up @@ -41,6 +45,17 @@ 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.
- Now throws an exception if the currently loaded SMO module version does not match the expected version set in the SMODefaultModuleVersion environment variable.

### Remove

- SqlServerDsc
- Removed PreferredModule_ModuleFound string in favor for more verbose PreferredModule_ModuleVersionFound.

## [16.4.0] - 2023-08-22

Expand Down
63 changes: 63 additions & 0 deletions source/Private/Get-SMOModuleCalculatedVersion.ps1
Original file line number Diff line number Diff line change
@@ -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-SMOModuleCalculatedVersion -PSModuleInfo (Get-Module -Name 'sqlps')

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
}
}
113 changes: 46 additions & 67 deletions source/Public/Get-SqlDscPreferredModule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
(
Expand Down Expand Up @@ -92,79 +90,60 @@ 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 -Process {
@{
Name = 'Version'
Expression = {
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

if ($_.ContainsKey('PrivateData') -and $_.PrivateData.ContainsKey('PSData') -and $_.PrivateData.PSData.ContainsKey('Prerelease'))
{
if (-not [System.String]::IsNullOrEmpty($_.PrivateData.PSData.Prerelease))
{
$versionToReturn = '{0}-{1}' -f $_.Version, $_.PrivateData.PSData.Prerelease
}
}

$versionToReturn
}
}
PSModuleInfo = $_
CalculatedVersion = $_ | Get-SMOModuleCalculatedVersion
}
)
}

foreach ($preferredModuleName in $Name)
{
$preferredModule = $availableModule |
Where-Object -Property 'Name' -EQ -Value $preferredModuleName
$preferredModules = $availableModules |
Where-Object -FilterScript { $_.PSModuleInfo.Name -eq $preferredModuleName }

if ($preferredModule)
if ($preferredModules)
{
if ($preferredModule.Name -eq 'SQLPS')
if ($env:SMODefaultModuleVersion)
{
# Get the latest version if available.
$preferredModule = $preferredModule |
Sort-Object -Property 'Version' -Descending |
# Get the version specified in $env:SMODefaultModuleVersion if available
$availableModule = $preferredModules |
Where-Object -FilterScript { $_.CalculatedVersion -eq $env:SMODefaultModuleVersion } |
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
{
$availableModuleName = ($preferredModule | Select-Object -First 1).Name
# 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_ModuleVersionFound -f $availableModule.PSModuleInfo.Name, $availableModule.CalculatedVersion)

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
}
40 changes: 26 additions & 14 deletions source/Public/Import-SqlDscPreferredModule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function Import-SqlDscPreferredModule
$getSqlDscPreferredModuleParameters.Refresh = $true
}

$availableModuleName = Get-SqlDscPreferredModule @getSqlDscPreferredModuleParameters
$availableModule = Get-SqlDscPreferredModule @getSqlDscPreferredModuleParameters

if ($Force.IsPresent)
{
Expand All @@ -80,37 +80,49 @@ function Import-SqlDscPreferredModule

if ($PSBoundParameters.ContainsKey('Name'))
{
$removeModule += $Name
$removeModule += Get-Module -Name $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 -ModuleInfo $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
$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
}
Expand All @@ -126,14 +138,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 -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 ($availableModuleName -eq 'SQLPS')
if ($availableModule.Name -eq 'SQLPS')
{
$importedModule = $importedModule | Where-Object -Property 'ModuleType' -EQ -Value 'Manifest'
}
Expand Down
4 changes: 3 additions & 1 deletion source/en-US/SqlServerDsc.strings.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,13 @@ 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.

## 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.
Expand Down
Loading
Loading