diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dfe4952b5..e49c7789da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - SqlSetup - Fixed issue with AddNode where cluster IP information was not being passed to setup.exe ([issue #1171](https://github.com/dsccommunity/SqlServerDsc/issues/1171)). + - Allow installtion of minor version upgrades. ([Issue #2053](https://github.com/dsccommunity/SqlServerDsc/issues/2053)) ### Fixed diff --git a/source/DSCResources/DSC_SqlSetup/DSC_SqlSetup.psm1 b/source/DSCResources/DSC_SqlSetup/DSC_SqlSetup.psm1 index 24056b34f2..5584cc190f 100644 --- a/source/DSCResources/DSC_SqlSetup/DSC_SqlSetup.psm1 +++ b/source/DSCResources/DSC_SqlSetup/DSC_SqlSetup.psm1 @@ -176,6 +176,7 @@ function Get-TargetResource UseEnglish = $UseEnglish ServerName = $ServerName SqlVersion = $null + SqlMinorVersion = $null ProductCoveredBySA = $null } @@ -224,6 +225,16 @@ function Get-TargetResource $getTargetResourceReturnValue.SqlVersion = $SqlVersion + if ($Action -eq 'Upgrade') + { + $sqlMinorVersion = (Connect-SQL -ServerName $ServerName -InstanceName $InstanceName -ErrorAction 'Stop' ).ServerVersion.BuildNumber + $getTargetResourceReturnValue.SqlMinorVersion = $sqlMinorVersion + } + else + { + $getTargetResourceReturnValue.SqlMinorVersion = 0 + } + if ($SourceCredential) { Disconnect-UncPath -RemotePath $SourcePath @@ -325,7 +336,7 @@ function Get-TargetResource $getRegistryPropertyParams = @{ Path = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL$($SqlVersion).$($InstanceName)\Setup" Name = 'IsProductCoveredBySA' - } + } $getTargetResourceReturnValue.ProductCoveredBySA = Get-RegistryPropertyValue @getRegistryPropertyParams } @@ -1660,99 +1671,116 @@ function Set-TargetResource Write-Verbose -Message ($script:localizedData.UsingPath -f $pathToSetupExecutable) - try + $installerSqlVersion = Get-FilePathMajorVersion -Path (Join-Path -Path $sourcePath -ChildPath 'setup.exe') + $instanceSqlVersion = Get-SQLInstanceMajorVersion -InstanceName $InstanceName + if ($installerSqlVersion -eq $instanceSqlVersion -and $Action -eq 'Upgrade') { - <# - This handles when PsDscRunAsCredential is set, or running as the SYSTEM account (when - PsDscRunAsCredential is not set). - #> - - $startProcessParameters = @{ - FilePath = $pathToSetupExecutable - ArgumentList = $arguments - Timeout = $SetupProcessTimeout + try + { + Connect-SQL -InstanceName $InstanceName | Update-SqlDscServer -MediaPath $UpdateSource + } + catch + { + throw $_ } + } + else + { - $processExitCode = Start-SqlSetupProcess @startProcessParameters + try + { + <# + This handles when PsDscRunAsCredential is set, or running as the SYSTEM account (when + PsDscRunAsCredential is not set). + #> - $setupExitMessage = ($script:localizedData.SetupExitMessage -f $processExitCode) + $startProcessParameters = @{ + FilePath = $pathToSetupExecutable + ArgumentList = $arguments + Timeout = $SetupProcessTimeout + } - $setupEndedInError = $false + $processExitCode = Start-SqlSetupProcess @startProcessParameters - if ($processExitCode -eq 3010 -and -not $SuppressReboot) - { - $setupExitMessageRebootRequired = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupSuccessfulRebootRequired)) + $setupExitMessage = ($script:localizedData.SetupExitMessage -f $processExitCode) - Write-Verbose -Message $setupExitMessageRebootRequired + $setupEndedInError = $false - # Setup ended with error code 3010 which means reboot is required. - $global:DSCMachineStatus = 1 - } - elseif ($processExitCode -ne 0) - { - $setupExitMessageError = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupFailed)) - Write-Warning $setupExitMessageError + if ($processExitCode -eq 3010 -and -not $SuppressReboot) + { + $setupExitMessageRebootRequired = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupSuccessfulRebootRequired)) - $setupEndedInError = $true - } - else - { - $setupExitMessageSuccessful = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupSuccessful)) + Write-Verbose -Message $setupExitMessageRebootRequired - Write-Verbose -Message $setupExitMessageSuccessful - } + # Setup ended with error code 3010 which means reboot is required. + $global:DSCMachineStatus = 1 + } + elseif ($processExitCode -ne 0) + { + $setupExitMessageError = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupFailed)) + Write-Warning $setupExitMessageError - if ($ForceReboot -or (Test-PendingRestart)) - { - if (-not ($SuppressReboot)) + $setupEndedInError = $true + } + else { - Write-Verbose -Message $script:localizedData.Reboot + $setupExitMessageSuccessful = ('{0} {1}' -f $setupExitMessage, ($script:localizedData.SetupSuccessful)) - # Rebooting, so no point in refreshing the session. - $forceReloadPowerShellModule = $false + Write-Verbose -Message $setupExitMessageSuccessful + } - $global:DSCMachineStatus = 1 + if ($ForceReboot -or (Test-PendingRestart)) + { + if (-not ($SuppressReboot)) + { + Write-Verbose -Message $script:localizedData.Reboot + + # Rebooting, so no point in refreshing the session. + $forceReloadPowerShellModule = $false + + $global:DSCMachineStatus = 1 + } + else + { + Write-Verbose -Message $script:localizedData.SuppressReboot + $forceReloadPowerShellModule = $true + } } else { - Write-Verbose -Message $script:localizedData.SuppressReboot $forceReloadPowerShellModule = $true } - } - else - { - $forceReloadPowerShellModule = $true - } - if ((-not $setupEndedInError) -and $forceReloadPowerShellModule) - { - <# - Force reload of SQLPS module in case a newer version of - SQL Server was installed that contains a newer version - of the SQLPS module, although if SqlServer module exist - on the target node, that will be used regardless. - This is to make sure we use the latest SQLPS module that - matches the latest assemblies in GAC, mitigating for example - issue #1151. - #> - Import-SqlDscPreferredModule -Force - } + if ((-not $setupEndedInError) -and $forceReloadPowerShellModule) + { + <# + Force reload of SQLPS module in case a newer version of + SQL Server was installed that contains a newer version + of the SQLPS module, although if SqlServer module exist + on the target node, that will be used regardless. + This is to make sure we use the latest SQLPS module that + matches the latest assemblies in GAC, mitigating for example + issue #1151. + #> + Import-SqlDscPreferredModule -Force + } - if (-not (Test-TargetResource @PSBoundParameters)) + if (-not (Test-TargetResource @PSBoundParameters)) + { + $errorMessage = $script:localizedData.TestFailedAfterSet + New-InvalidResultException -Message $errorMessage + } + } + catch { - $errorMessage = $script:localizedData.TestFailedAfterSet - New-InvalidResultException -Message $errorMessage + throw $_ } } - catch - { - throw $_ - } } <# .SYNOPSIS - Tests if the SQL Server features are installed on the node. + Tests if the SQL Server Features are installed on the node. .PARAMETER Action The action to be performed. Default value is 'Install'. @@ -1799,7 +1827,7 @@ function Set-TargetResource False, or omitting the parameter, indicates it's covered under a SQL Server license. Default value is False. - Not used in Test-TargetResource. + Not used in Test-TargetResource. .PARAMETER UpdateEnabled Enabled updates during installation. @@ -2268,6 +2296,7 @@ function Test-TargetResource [Parameter()] [System.String] $SqlVersion + ) if ($Action -eq 'Upgrade' -and $PSBoundParameters.ContainsKey('SqlVersion')) @@ -2360,7 +2389,6 @@ function Test-TargetResource { $installerSqlVersion = Get-FilePathMajorVersion -Path (Join-Path -Path $sourcePath -ChildPath 'setup.exe') $instanceSqlVersion = Get-SQLInstanceMajorVersion -InstanceName $InstanceName - if ($installerSQLVersion -gt $instanceSqlVersion) { Write-Verbose -Message ( @@ -2369,6 +2397,17 @@ function Test-TargetResource $result = $false } + elseif ($instanceSqlVersion -eq $installerSqlVersion) + { + $latestCU = Find-SqlDscLatestCu -MediaPath $UpdateSource -MajorVersion $installerSqlVersion + $updateSourceLatestMinor = Get-FilePathMinorVersion -Path $latestCU + if ($updateSourceLatestMinor -gt $getTargetResourceResult.SqlMinorVersion) + { + Write-Verbose -Message ( + $script:localizedData.DifferentMinorVersion -f $InstanceName, $getTargetResourceResult.SqlMinorVersion, $updateSourceLatestMinor) + $result = $false + } + } } return $result @@ -2717,6 +2756,7 @@ function Get-SqlEngineProperties $sqlUserDatabaseDirectory = $sqlServerObject.DefaultFile $sqlUserDatabaseLogDirectory = $sqlServerObject.DefaultLog $sqlBackupDirectory = $sqlServerObject.BackupDirectory + $sqlMinorVersion = $sqlServerObject.ServerVersion.BuildNumber if ($sqlServerObject.LoginMode -eq 'Mixed') { @@ -2739,6 +2779,7 @@ function Get-SqlEngineProperties SQLUserDBLogDir = $sqlUserDatabaseLogDirectory SQLBackupDir = $sqlBackupDirectory SecurityMode = $securityMode + MinorVersion = $sqlMinorVersion } } diff --git a/source/DSCResources/DSC_SqlSetup/DSC_SqlSetup.schema.mof b/source/DSCResources/DSC_SqlSetup/DSC_SqlSetup.schema.mof index cd96c84940..952c3de66b 100644 --- a/source/DSCResources/DSC_SqlSetup/DSC_SqlSetup.schema.mof +++ b/source/DSCResources/DSC_SqlSetup/DSC_SqlSetup.schema.mof @@ -49,6 +49,7 @@ class DSC_SqlSetup : OMI_BaseResource [Write, Description("The server mode for _SQL Server Analysis Services_ instance. The default is to install in Multidimensional mode. Valid values in a cluster scenario are `'MULTIDIMENSIONAL'` or `'TABULAR'`. Parameter **ASServerMode** is case-sensitive. **All values must be expressed in upper case**."), ValueMap{"MULTIDIMENSIONAL", "TABULAR", "POWERPIVOT"}, Values{"MULTIDIMENSIONAL", "TABULAR", "POWERPIVOT"}] String ASServerMode; [Write, EmbeddedInstance("MSFT_Credential"), Description("Service account for _Integration Services_'s _Windows_ service.")] String ISSvcAccount; [Read, Description("Returns the username for the _Integration Services_'s _Windows_ service.")] String ISSvcAccountUsername; + [Read, Description("The minor version of the Sql server. for example in sql server 2022 cu2 iw will be 4015")] String SqlMinorVersion; [Write, Description("Specifies the startup mode for the _SQL Server Database Engine_'s _Windows_ service."), ValueMap{"Automatic", "Disabled", "Manual"}, Values{"Automatic", "Disabled", "Manual"}] String SqlSvcStartupType; [Write, Description("Specifies the startup mode for the _SQL Server Agent_'s _Windows_ service."), ValueMap{"Automatic", "Disabled", "Manual"}, Values{"Automatic", "Disabled", "Manual"}] String AgtSvcStartupType; [Write, Description("Specifies the startup mode for the _SQL Server Integration Services_'s _Windows_ service."), ValueMap{"Automatic", "Disabled", "Manual"}, Values{"Automatic", "Disabled", "Manual"}] String IsSvcStartupType; diff --git a/source/DSCResources/DSC_SqlSetup/en-US/DSC_SqlSetup.strings.psd1 b/source/DSCResources/DSC_SqlSetup/en-US/DSC_SqlSetup.strings.psd1 index 84d3ae0ef6..7ba65d0fa0 100644 --- a/source/DSCResources/DSC_SqlSetup/en-US/DSC_SqlSetup.strings.psd1 +++ b/source/DSCResources/DSC_SqlSetup/en-US/DSC_SqlSetup.strings.psd1 @@ -64,5 +64,6 @@ ConvertFrom-StringData @' FeatureAlreadyInstalled = The feature '{0}' is already installed so it will not be installed again. FeatureFlag = Using feature flag '{0}' DifferentMajorVersion = The instance '{0}' has the wrong major version. The major version is '{1}', but expected version '{2}'. + DifferentMinorVersion = The instance '{0} has the wrong minor version. The minor version is '{1}', but expected version '{2}'. ParameterSqlVersionNotAllowedForSetupActionUpgrade = The parameter SqlVersion is not allowed to be specified when using the setup action Upgrade. '@ diff --git a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psd1 b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psd1 index e73d52d1b3..8ec1b4b3fb 100644 --- a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psd1 +++ b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psd1 @@ -52,6 +52,7 @@ 'Import-Assembly' 'ConvertTo-ServerInstanceName' 'Get-FilePathMajorVersion' + 'Get-FilePathMinorVersion' 'Test-FeatureFlag' ) diff --git a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 index 12b01ce988..13d57fbe0f 100644 --- a/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 +++ b/source/Modules/SqlServerDsc.Common/SqlServerDsc.Common.psm1 @@ -2270,6 +2270,19 @@ function Get-FilePathMajorVersion (Get-Item -Path $Path).VersionInfo.ProductVersion.Split('.')[0] } +function Get-FilePathMinorVersion +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Path + ) + + (Get-Item -Path $Path).VersionInfo.ProductVersion.Split('.')[2] +} + <# .SYNOPSIS Test if the specific feature flag should be enabled. diff --git a/source/Public/Find-SqlDscLatestCu.ps1 b/source/Public/Find-SqlDscLatestCu.ps1 new file mode 100644 index 0000000000..1e849770f1 --- /dev/null +++ b/source/Public/Find-SqlDscLatestCu.ps1 @@ -0,0 +1,37 @@ +<# + .SYNOPSIS + Find the latest Cumulative Update for SQL Server In folder + .DESCRIPTION + Find the latest Cumulative Update for SQL Server In folder + This function will find the latest Cumulative Update for SQL Server in the supplied path. + .PARAMETER MediaPath + Specifies the path to look CU files. + .OUTPUTS + System.String +#> + +function Find-SqlDscLatestCu { + + param ( + [Parameter(Mandatory = $true)] + [System.String] + $MediaPath, + [Parameter(Mandatory = $true)] + [System.String] + $MajorVersion + ) + $highestMinorVersion = -1 + $selectedExe = $null + $exeFiles = Get-ChildItem -Path $MediaPath -Filter *.exe + foreach ($exeFile in $exeFiles) { + $fileMajorVersion = Get-FilePathMajorVersion -Path $exeFile.FullName + if ($fileMajorVersion -eq $MajorVersion) { + $fileMinorVersion = Get-FilePathMinorVersion -Path $exeFile.FullName + if ($fileMinorVersion -gt $highestMinorVersion) { + $highestMinorVersion = $fileMinorVersion + $selectedExe = $exeFile.FullName + } + } + } + return $selectedExe +} diff --git a/source/Public/Get-SqlDscServerVersion.ps1 b/source/Public/Get-SqlDscServerVersion.ps1 new file mode 100644 index 0000000000..6466cd371e --- /dev/null +++ b/source/Public/Get-SqlDscServerVersion.ps1 @@ -0,0 +1,27 @@ +<# + .SYNOPSIS + Get server version. + .DESCRIPTION + This command gets the version from a SQL Server Database Engine instance. + .PARAMETER ServerObject + Specifies current server connection object. + .EXAMPLE + $serverObject = Connect-SqlDscDatabaseEngine -InstanceName 'MyInstance' + Get-SqlDscServerVersiono -ServerObject $serverObject + Get the version of the SQL Server instance. + .OUTPUTS + `[Microsoft.SqlServer.Management.Common.ServerVersion]` + #> + +function Get-SqlDscServerVersion +{ + [OutputType([Microsoft.SqlServer.Management.Common.ServerVersion])] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject + ) + return $ServerObject.ServerVersion +} diff --git a/source/Public/Update-SqlDscServer.ps1 b/source/Public/Update-SqlDscServer.ps1 new file mode 100644 index 0000000000..c710d07b90 --- /dev/null +++ b/source/Public/Update-SqlDscServer.ps1 @@ -0,0 +1,54 @@ +<# + .SYNOPSIS + Perform minor version updates to the SQL Server instance. + .DESCRIPTION + Perform minor version updates to the SQL Server instance. + This function will update the SQL Server instance to the latest minor version + available in the supplied path. + .PARAMETER MediaPath + Specifies the path to look CU or SP files. + .OUTPUTS + None. +#> +function Update-SqlDscServer +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $MediaPath, + [Parameter(ValueFromPipeline = $true, Mandatory = $true)] + [Microsoft.SqlServer.Management.Smo.Server] + $ServerObject + ) + $sqlMajorVersion = $ServerObject | Get-SqlDscServerVersion | Select-Object -ExpandProperty Major + $sqlMinorVersion = $ServerObject | Get-SqlDscServerVersion | Select-Object -ExpandProperty BuildNumber + $selectedExe = Find-SqlDscLatestCu -MediaPath $MediaPath -MajorVersion $sqlMajorVersion + + try + { + $exeMinorVersion = Get-FilePathMinorVersion -Path $selectedExe + } + catch + { + Write-Error 'The executable could not be found.' + New-InvalidOperationException -Message 'The execuatble could not be found' -ErrorRecord $_ + } + + if ($exeMinorVersion -le $sqlMinorVersion) + { + return + } + + $patchSplat = @( + '/Action=Patch' + '/Quiet' + '/IAcceptSQLServerLicenseTerms' + "/InstanceId=$(($ServerObject.ServiceInstanceId -split '\.')[1])" + ) + $process = Start-Process -FilePath $selectedExe -ArgumentList $patchSplat -NoNewWindow -Wait -PassThru + if ($process.ExitCode -ne 0) + { + Write-Error 'The executable encountered an error.' + } +}