diff --git a/public/Disable-DbaAgHadr.ps1 b/public/Disable-DbaAgHadr.ps1 index 3161e8c83d0..1ee1777e139 100644 --- a/public/Disable-DbaAgHadr.ps1 +++ b/public/Disable-DbaAgHadr.ps1 @@ -126,12 +126,31 @@ function Disable-DbaAgHadr { } } if (Test-Bound 'Force') { - if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting Engine and Agent service for $instance on $computerFullName")) { + if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting the Engine service, and the Agent service if it is running, for $instance on $computerFullName")) { try { - $null = Stop-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine - $null = Start-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine + # The Agent depends on the engine, so it is stopped with it, but it is only started again if + # it was running before: a stopped Agent used to come back running from this restart. + $splatServices = @{ + ComputerName = $computerFullName + InstanceName = $instanceName + EnableException = $true + } + $agentWasRunning = (Get-DbaService @splatServices -Type Agent).State -eq "Running" + # A refused stop or start only shows as Status "Failed" on the returned objects, so they have + # to be checked. Without that, an Agent that was slow to stop left the engine restart refused + # and the Agent stopped, while the command still reported success. + $restartResults = @(Stop-DbaService @splatServices -Type "Agent", "Engine") + if ($agentWasRunning) { + $restartResults += Start-DbaService @splatServices -Type "Agent", "Engine" + } else { + $restartResults += Start-DbaService @splatServices -Type "Engine" + } + $restartFailures = @($restartResults | Where-Object Status -notlike "Successful*") + if ($restartFailures.Count -gt 0) { + throw (($restartFailures | ForEach-Object { "$($PSItem.ServiceName): $($PSItem.Message)" }) -join " | ") + } } catch { - Stop-Function -Message "Issue restarting $instance" -Target $instance -Continue + Stop-Function -Message "Issue restarting $instance" -ErrorRecord $_ -Target $instance -Continue } } } diff --git a/public/Enable-DbaAgHadr.ps1 b/public/Enable-DbaAgHadr.ps1 index d135df7d09d..f58861847bc 100644 --- a/public/Enable-DbaAgHadr.ps1 +++ b/public/Enable-DbaAgHadr.ps1 @@ -129,12 +129,31 @@ function Enable-DbaAgHadr { } if (Test-Bound -ParameterName Force) { - if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting Engine and Agent service for $instance on $computerFullName")) { + if ($PSCmdlet.ShouldProcess($instance, "Force provided, restarting the Engine service, and the Agent service if it is running, for $instance on $computerFullName")) { try { - $null = Stop-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine - $null = Start-DbaService -ComputerName $computerFullName -InstanceName $instanceName -Type Agent, Engine + # The Agent depends on the engine, so it is stopped with it, but it is only started again if it + # was running before: a stopped Agent used to come back running from this restart. + $splatServices = @{ + ComputerName = $computerFullName + InstanceName = $instanceName + EnableException = $true + } + $agentWasRunning = (Get-DbaService @splatServices -Type Agent).State -eq "Running" + # A refused stop or start only shows as Status "Failed" on the returned objects, so they have to + # be checked. Without that, an Agent that was slow to stop left the engine restart refused and + # the Agent stopped, while the command still reported success. + $restartResults = @(Stop-DbaService @splatServices -Type "Agent", "Engine") + if ($agentWasRunning) { + $restartResults += Start-DbaService @splatServices -Type "Agent", "Engine" + } else { + $restartResults += Start-DbaService @splatServices -Type "Engine" + } + $restartFailures = @($restartResults | Where-Object Status -notlike "Successful*") + if ($restartFailures.Count -gt 0) { + throw (($restartFailures | ForEach-Object { "$($PSItem.ServiceName): $($PSItem.Message)" }) -join " | ") + } } catch { - Stop-Function -Message "Issue restarting $instance" -Target $instance -Continue + Stop-Function -Message "Issue restarting $instance" -ErrorRecord $_ -Target $instance -Continue } } } diff --git a/tests/Disable-DbaAgHadr.Tests.ps1 b/tests/Disable-DbaAgHadr.Tests.ps1 index e1a94d5301d..b628b003f5e 100644 --- a/tests/Disable-DbaAgHadr.Tests.ps1 +++ b/tests/Disable-DbaAgHadr.Tests.ps1 @@ -36,6 +36,12 @@ Describe $CommandName -Tag IntegrationTests { # Re-enable HADR for future tests $null = Enable-DbaAgHadr -SqlInstance $TestConfig.InstanceHadr -Force + # The Agent test stops the Agent, so make sure it is running again whatever happened in between. Collected first, + # because Start-DbaService with nothing piped in falls back to the local machine. + $stoppedAgent = Get-DbaService -SqlInstance $TestConfig.InstanceHadr -Type Agent | Where-Object State -ne "Running" + if ($stoppedAgent) { + $null = $stoppedAgent | Start-DbaService + } $PSDefaultParameterValues.Remove("*-Dba*:EnableException") } @@ -45,5 +51,14 @@ Describe $CommandName -Tag IntegrationTests { $disableResults = Disable-DbaAgHadr -SqlInstance $TestConfig.InstanceHadr -Force $disableResults.IsHadrEnabled | Should -BeFalse } + + It "Leaves a stopped Agent stopped when -Force restarts the engine" { + # The forced restart used to start the Agent with the engine whether it had been running before or not. + $null = Get-DbaService -SqlInstance $TestConfig.InstanceHadr -Type Agent | Stop-DbaService + $disableResults = Disable-DbaAgHadr -SqlInstance $TestConfig.InstanceHadr -Force + $disableResults.IsHadrEnabled | Should -BeFalse + (Get-DbaService -SqlInstance $TestConfig.InstanceHadr -Type Agent).State | Should -Be "Stopped" + $null = Get-DbaService -SqlInstance $TestConfig.InstanceHadr -Type Agent | Start-DbaService + } } } \ No newline at end of file diff --git a/tests/Enable-DbaAgHadr.Tests.ps1 b/tests/Enable-DbaAgHadr.Tests.ps1 index d998b1b0c47..cd91861780e 100644 --- a/tests/Enable-DbaAgHadr.Tests.ps1 +++ b/tests/Enable-DbaAgHadr.Tests.ps1 @@ -37,6 +37,13 @@ Describe $CommandName -Tag IntegrationTests { # We want to run all commands in the AfterAll block with EnableException to ensure that the test fails if the cleanup fails. $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + # The Agent test stops the Agent, so make sure it is running again whatever happened in between. Collected first, + # because Start-DbaService with nothing piped in falls back to the local machine. + $stoppedAgent = Get-DbaService -SqlInstance $TestConfig.InstanceHadr -Type Agent | Where-Object State -ne "Running" + if ($stoppedAgent) { + $null = $stoppedAgent | Start-DbaService + } + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") } @@ -45,5 +52,14 @@ Describe $CommandName -Tag IntegrationTests { $results = Enable-DbaAgHadr -SqlInstance $TestConfig.InstanceHadr -Force $results.IsHadrEnabled | Should -BeTrue } + + It "Leaves a stopped Agent stopped when -Force restarts the engine" { + # The forced restart used to start the Agent with the engine whether it had been running before or not. + $null = Get-DbaService -SqlInstance $TestConfig.InstanceHadr -Type Agent | Stop-DbaService + $results = Enable-DbaAgHadr -SqlInstance $TestConfig.InstanceHadr -Force + $results.IsHadrEnabled | Should -BeTrue + (Get-DbaService -SqlInstance $TestConfig.InstanceHadr -Type Agent).State | Should -Be "Stopped" + $null = Get-DbaService -SqlInstance $TestConfig.InstanceHadr -Type Agent | Start-DbaService + } } } \ No newline at end of file diff --git a/tests/appveyor.SQL2019.ps1 b/tests/appveyor.SQL2019.ps1 index 1e367175ccf..8d43b36266f 100644 --- a/tests/appveyor.SQL2019.ps1 +++ b/tests/appveyor.SQL2019.ps1 @@ -18,7 +18,26 @@ Write-Host -Object "$indent Configuring $instance" -ForegroundColor DarkGreen $null = Set-DbaSpConfigure -SqlInstance $sqlinstance -Name RemoteDacConnectionsEnabled -Value $true -EnableException $null = Set-DbaSpConfigure -SqlInstance $sqlinstance -Name ExtensibleKeyManagementEnabled -Value $true -EnableException Invoke-DbaQuery -SqlInstance $sqlinstance -Query "CREATE CRYPTOGRAPHIC PROVIDER dbatoolsci_AKV FROM FILE = 'C:\github\appveyor-lab\keytests\ekm\Microsoft.AzureKeyVaultService.EKM.dll'" -EnableException -$null = Enable-DbaAgHadr -SqlInstance $sqlinstance -Force -EnableException -Confirm:$false +# Enable-DbaAgHadr -Force stops and starts Agent and Engine. When the Agent is slow to stop, the engine restart is +# refused and the Agent can be left stopped; the command reports that as an error, so settle both services, start +# them again and try once more. +try { + $null = Enable-DbaAgHadr -SqlInstance $sqlinstance -Force -EnableException -Confirm:$false +} catch { + Write-Host -Object "$indent Enabling HADR on $instance failed ($($PSItem.Exception.Message)), starting the services and retrying" -ForegroundColor DarkYellow + foreach ($serviceName in ("MSSQL$" + $instance), ("SQLAgent$" + $instance)) { + $service = Get-Service -Name $serviceName + if ($service.Status -eq "StopPending") { + $service.WaitForStatus("Stopped", "00:03:00") + } + $service.Refresh() + if ($service.Status -ne "Running") { + Start-Service -Name $serviceName + $service.WaitForStatus("Running", "00:03:00") + } + } + $null = Enable-DbaAgHadr -SqlInstance $sqlinstance -Force -EnableException -Confirm:$false +} Invoke-DbaQuery -SqlInstance $sqlinstance -Query "CREATE MASTER KEY ENCRYPTION BY PASSWORD = ''" -EnableException Invoke-DbaQuery -SqlInstance $sqlinstance -Query "CREATE CERTIFICATE dbatoolsci_AGCert WITH SUBJECT = 'AG Certificate'" -EnableException diff --git a/tests/appveyor.SQL2022.ps1 b/tests/appveyor.SQL2022.ps1 index 42e88115d58..c606f800dc1 100644 --- a/tests/appveyor.SQL2022.ps1 +++ b/tests/appveyor.SQL2022.ps1 @@ -18,7 +18,26 @@ Write-Host -Object "$indent Configuring $instance" -ForegroundColor DarkGreen $null = Set-DbaSpConfigure -SqlInstance $sqlinstance -Name RemoteDacConnectionsEnabled -Value $true -EnableException $null = Set-DbaSpConfigure -SqlInstance $sqlinstance -Name ExtensibleKeyManagementEnabled -Value $true -EnableException Invoke-DbaQuery -SqlInstance $sqlinstance -Query "CREATE CRYPTOGRAPHIC PROVIDER dbatoolsci_AKV FROM FILE = 'C:\github\appveyor-lab\keytests\ekm\Microsoft.AzureKeyVaultService.EKM.dll'" -EnableException -$null = Enable-DbaAgHadr -SqlInstance $sqlinstance -Force -EnableException -Confirm:$false +# Enable-DbaAgHadr -Force stops and starts Agent and Engine. When the Agent is slow to stop, the engine restart is +# refused and the Agent can be left stopped; the command reports that as an error, so settle both services, start +# them again and try once more. +try { + $null = Enable-DbaAgHadr -SqlInstance $sqlinstance -Force -EnableException -Confirm:$false +} catch { + Write-Host -Object "$indent Enabling HADR on $instance failed ($($PSItem.Exception.Message)), starting the services and retrying" -ForegroundColor DarkYellow + foreach ($serviceName in ("MSSQL$" + $instance), ("SQLAgent$" + $instance)) { + $service = Get-Service -Name $serviceName + if ($service.Status -eq "StopPending") { + $service.WaitForStatus("Stopped", "00:03:00") + } + $service.Refresh() + if ($service.Status -ne "Running") { + Start-Service -Name $serviceName + $service.WaitForStatus("Running", "00:03:00") + } + } + $null = Enable-DbaAgHadr -SqlInstance $sqlinstance -Force -EnableException -Confirm:$false +} Invoke-DbaQuery -SqlInstance $sqlinstance -Query "CREATE MASTER KEY ENCRYPTION BY PASSWORD = ''" -EnableException Invoke-DbaQuery -SqlInstance $sqlinstance -Query "CREATE CERTIFICATE dbatoolsci_AGCert WITH SUBJECT = 'AG Certificate'" -EnableException