diff --git a/private/functions/Get-LoginPasswordHash.ps1 b/private/functions/Get-LoginPasswordHash.ps1 index 11dc3b7b2b1..343e1f7e871 100644 --- a/private/functions/Get-LoginPasswordHash.ps1 +++ b/private/functions/Get-LoginPasswordHash.ps1 @@ -63,7 +63,9 @@ function Get-LoginPasswordHash { $hashedPass = $server.ConnectionContext.ExecuteScalar($sql) } catch { try { - $hashedPassDt = $server.Databases["master"].ExecuteWithResults($sql) + # Same query as above, so it runs on the connection as well. Going through the master + # database would leave the connection of the caller there. See #10555. + $hashedPassDt = $server.ConnectionContext.ExecuteWithResults($sql) $hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0) } catch { Stop-Function -Message "Failed to retrieve password hash for login $($Login.Name)" -ErrorRecord $_ -Target $Login -Continue diff --git a/private/functions/Get-OfflineSqlFileStructure.ps1 b/private/functions/Get-OfflineSqlFileStructure.ps1 index a29dd02d08a..9bb094f5537 100644 --- a/private/functions/Get-OfflineSqlFileStructure.ps1 +++ b/private/functions/Get-OfflineSqlFileStructure.ps1 @@ -27,7 +27,9 @@ Internal function. Returns dictionary object that contains file structures for S if ($filestream) { $sql = "SELECT COALESCE(SERVERPROPERTY('FilestreamConfiguredLevel'),0) AS fs" - $fscheck = $server.databases['master'].ExecuteWithResults($sql) + # SERVERPROPERTY does not depend on the current database, so this runs on the connection. Going + # through the master database would leave the connection of the caller there. See #10555. + $fscheck = $server.ConnectionContext.ExecuteWithResults($sql) if ($fscheck.tables.fs -eq 0) { return $false } } diff --git a/public/Connect-DbaInstance.ps1 b/public/Connect-DbaInstance.ps1 index eba1d495313..73aaf17997e 100644 --- a/public/Connect-DbaInstance.ps1 +++ b/public/Connect-DbaInstance.ps1 @@ -19,7 +19,9 @@ function Connect-DbaInstance { https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnectionstringbuilder.aspx https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.aspx - To execute SQL commands directly: $server.ConnectionContext.ExecuteReader($sql) or $server.Databases['master'].ExecuteNonQuery($sql) + To execute SQL commands directly: $server.ConnectionContext.ExecuteReader($sql) or $server.ConnectionContext.ExecuteNonQuery($sql) + + Run statements through the connection context rather than through a database object. A database object executes on the same connection and leaves it in that database, which changes the database of every later command that reuses the connection. .PARAMETER SqlInstance The target SQL Server instance or instances. This can be a collection and receive pipeline input to allow the function to be executed against multiple SQL Server instances. diff --git a/public/Export-DbaLogin.ps1 b/public/Export-DbaLogin.ps1 index a285ce4af87..96ba8850c4d 100644 --- a/public/Export-DbaLogin.ps1 +++ b/public/Export-DbaLogin.ps1 @@ -390,7 +390,9 @@ function Export-DbaLogin { try { $hashedPass = $server.ConnectionContext.ExecuteScalar($sql) } catch { - $hashedPassDt = $server.Databases['master'].ExecuteWithResults($sql) + # Same query as above, so it runs on the connection as well. Going through the + # master database would leave the connection of the caller there. See #10555. + $hashedPassDt = $server.ConnectionContext.ExecuteWithResults($sql) $hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0) } diff --git a/public/Get-DbaDbDetachedFileInfo.ps1 b/public/Get-DbaDbDetachedFileInfo.ps1 index c1f76ae1676..655e4cc995b 100644 --- a/public/Get-DbaDbDetachedFileInfo.ps1 +++ b/public/Get-DbaDbDetachedFileInfo.ps1 @@ -130,7 +130,9 @@ function Get-DbaDbDetachedFileInfo { $collationsql = "SELECT name FROM fn_helpcollations() WHERE COLLATIONPROPERTY(name, N'COLLATIONID') = $collationid" try { - $dataset = $server.databases['master'].ExecuteWithResults($collationsql) + # fn_helpcollations is available in every database, so this runs on the connection. Going + # through the master database would leave the connection of the caller there. See #10555. + $dataset = $server.ConnectionContext.ExecuteWithResults($collationsql) $collation = "$($dataset.Tables[0].Rows[0].Item(0))" } catch { $collation = $collationid diff --git a/public/New-DbaLogin.ps1 b/public/New-DbaLogin.ps1 index d542c57deef..90273f46378 100644 --- a/public/New-DbaLogin.ps1 +++ b/public/New-DbaLogin.ps1 @@ -327,7 +327,9 @@ function New-DbaLogin { try { $hashedPass = $sourceServer.ConnectionContext.ExecuteScalar($sql) } catch { - $hashedPassDt = $sourceServer.Databases['master'].ExecuteWithResults($sql) + # Same query as above, so it runs on the connection as well. Going through the + # master database would leave the connection of the caller there. See #10555. + $hashedPassDt = $sourceServer.ConnectionContext.ExecuteWithResults($sql) $hashedPass = $hashedPassDt.Tables[0].Rows[0].Item(0) } diff --git a/public/Remove-DbaAgentJob.ps1 b/public/Remove-DbaAgentJob.ps1 index 3d1c0663493..35748cf1f49 100644 --- a/public/Remove-DbaAgentJob.ps1 +++ b/public/Remove-DbaAgentJob.ps1 @@ -136,8 +136,10 @@ function Remove-DbaAgentJob { $dropSchedule = 0 } Write-Message -Level SomewhatVerbose -Message "Removing job" - $dropJobQuery = ("EXEC dbo.sp_delete_job @job_name = '{0}', @delete_history = {1}, @delete_unused_schedule = {2}" -f $currentJob.Name.Replace("'", "''"), $dropHistory, $dropSchedule) - $server.Databases['msdb'].ExecuteNonQuery($dropJobQuery) + # The procedure is named in full so that it does not need the connection to be in msdb. + # Going through the msdb database would leave the connection of the caller there. See #10555. + $dropJobQuery = ("EXEC msdb.dbo.sp_delete_job @job_name = '{0}', @delete_history = {1}, @delete_unused_schedule = {2}" -f $currentJob.Name.Replace("'", "''"), $dropHistory, $dropSchedule) + $server.ConnectionContext.ExecuteNonQuery($dropJobQuery) $server.JobServer.Jobs.Refresh() Remove-TeppCacheItem -SqlInstance $server -Type job -Name $currentJob.Name [PSCustomObject]@{ diff --git a/public/Set-DbaTempDbConfig.ps1 b/public/Set-DbaTempDbConfig.ps1 index 669f282061b..9671f622c2d 100644 --- a/public/Set-DbaTempDbConfig.ps1 +++ b/public/Set-DbaTempDbConfig.ps1 @@ -358,7 +358,10 @@ ORDER BY file_id; } else { if ($Pscmdlet.ShouldProcess($instance, "Executing query and informing that a restart is required.")) { try { - $server.Databases['master'].ExecuteNonQuery($sql) + # ALTER DATABASE does not depend on the current database, so this runs on the + # connection. Going through the master database would leave the connection of the + # caller there. See #10555. + $server.ConnectionContext.ExecuteNonQuery($sql) Write-Message -Level Verbose -Message "tempdb successfully reconfigured." [PSCustomObject]@{ diff --git a/tests/Get-DbaDbDetachedFileInfo.Tests.ps1 b/tests/Get-DbaDbDetachedFileInfo.Tests.ps1 index 247cc631837..f8e8cba6925 100644 --- a/tests/Get-DbaDbDetachedFileInfo.Tests.ps1 +++ b/tests/Get-DbaDbDetachedFileInfo.Tests.ps1 @@ -64,4 +64,38 @@ Describe $CommandName -Tag IntegrationTests { $results.LogFiles | Should -Not -BeNullOrEmpty } } + + Context "The connection of the caller keeps its database (#10555)" { + BeforeAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + # The collation lookup used to run through the master database, which leaves the connection of + # the caller there. Only a non-pooled connection shows it, because SMO reopens a pooled one at + # its default database. + $callerServer = Connect-DbaInstance -SqlInstance $TestConfig.InstanceSingle -Database msdb -NonPooledConnection + $callerResult = Get-DbaDbDetachedFileInfo -SqlInstance $callerServer -Path $path + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + AfterAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + $null = $callerServer | Disconnect-DbaInstance + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + It "still reads the detached file" { + $callerResult.Name | Should -Be $dbname + } + + It "still resolves the collation" { + $callerResult.Collation | Should -Not -BeNullOrEmpty + } + + It "leaves the connection in the database it was on" { + $callerServer.ConnectionContext.ExecuteScalar("SELECT DB_NAME()") | Should -Be "msdb" + } + } } \ No newline at end of file diff --git a/tests/Remove-DbaAgentJob.Tests.ps1 b/tests/Remove-DbaAgentJob.Tests.ps1 index d2836b6ccf4..88baa3ebe46 100644 --- a/tests/Remove-DbaAgentJob.Tests.ps1 +++ b/tests/Remove-DbaAgentJob.Tests.ps1 @@ -159,4 +159,38 @@ Describe $CommandName -Tag IntegrationTests { (Get-DbaAgentJob -SqlInstance $TestConfig.InstanceSingle -Job dbatoolsci_testjob_validation) | Should -Not -BeNullOrEmpty } } + + Context "The connection of the caller keeps its database (#10555)" { + BeforeAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + $contextJobName = "dbatoolsci_testjob_context" + $null = New-DbaAgentJob -SqlInstance $TestConfig.InstanceSingle -Job $contextJobName + + # sp_delete_job used to be run through the msdb database, which leaves the connection of the + # caller there. Only a non-pooled connection shows it, because SMO reopens a pooled one at its + # default database. + $callerServer = Connect-DbaInstance -SqlInstance $TestConfig.InstanceSingle -NonPooledConnection + $null = Remove-DbaAgentJob -SqlInstance $callerServer -Job $contextJobName + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + AfterAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + $null = $callerServer | Disconnect-DbaInstance + $null = Get-DbaAgentJob -SqlInstance $TestConfig.InstanceSingle -Job $contextJobName | Remove-DbaAgentJob -ErrorAction SilentlyContinue + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + It "still removes the job" { + Get-DbaAgentJob -SqlInstance $TestConfig.InstanceSingle -Job $contextJobName | Should -BeNullOrEmpty + } + + It "leaves the connection in the database it was on" { + $callerServer.ConnectionContext.ExecuteScalar("SELECT DB_NAME()") | Should -Be "master" + } + } } \ No newline at end of file