Skip to content

Connect-DbaInstance / Invoke-DbaQuery - Do not close connections of the caller and do not reuse them for the wrong database - #10564

Merged
potatoqualitee merged 2 commits into
developmentfrom
fix-connection-ownership-and-reuse
Aug 13, 2026
Merged

Connect-DbaInstance / Invoke-DbaQuery - Do not close connections of the caller and do not reuse them for the wrong database#10564
potatoqualitee merged 2 commits into
developmentfrom
fix-connection-ownership-and-reuse

Conversation

@andreasjordan

Copy link
Copy Markdown
Collaborator

Replaces #10559 and #10563, which have to ship together. Fixes the first part of #10554.

Both changes are about the connection a caller hands to Invoke-DbaQuery: the command must not close a connection it did not open, and it must not reuse one that is not on the requested database. Separately, each of them makes things worse, which is what this pull request is really about - see "Why they belong together" below.

1. Connect-DbaInstance reports whether it opened a connection

A command that connects and cleans up afterwards had no way to tell whether Connect-DbaInstance opened a connection for it or handed back the object it was given. Invoke-DbaQuery inferred it from its own parameters and closed the connection of the caller, taking the session with it:

$server = Connect-DbaInstance -SqlInstance $instance -NonPooledConnection
$null = $server.ConnectionContext.ExecuteNonQuery("CREATE TABLE #t (id int)")
$null = Invoke-DbaQuery -SqlInstance $server -Database master -Query "SELECT 1"
$server.ConnectionContext.ExecuteScalar("SELECT COUNT(*) FROM #t")
# Invalid object name '#t'.

Connect-DbaInstance now reports this through -IsNewConnectionReference, and Invoke-DbaQuery only disconnects when it opened the connection itself. The value is the $isNewConnection the command already computes internally, so no connection logic changed, only its exposure - at both places that emit a server, including the -SqlConnectionOnly path.

It takes a [ref] and not a variable name because $PSCmdlet.SessionState.PSVariable.Set() never reaches a caller inside of dbatools: both share the module session state, so the value lands in the local scope of Connect-DbaInstance. Set-Variable -Scope 1 does not help either, because from a module function the parent scope is the module scope.

2. Invoke-DbaQuery tests the database the connection is on

To decide whether the connection can be reused, the command compared ConnectionContext.DatabaseName - the database the connection was opened with - against -Database. Connect-DbaInstance asks the same question with ConnectionContext.CurrentDatabase, the database the connection is on right now. The two differ as soon as anything runs a USE, and the query then runs in the wrong database:

$server = Connect-DbaInstance -SqlInstance $instance -Database tempdb -NonPooledConnection
$null = $server.ConnectionContext.ExecuteNonQuery("USE [master]")
Invoke-DbaQuery -SqlInstance $server -Database tempdb -Query "SELECT DB_NAME()"
# master

Why they belong together

#10559 alone breaks Install-DbaFirstResponderKit, which is how this was found - its test file failed in CI and reproduces locally.

The command opens one connection with -Database $Database, and before signing the procedures it runs Get-DbaLogin | Remove-DbaLogin. SMO's Login.Drop() leaves that shared connection in master (New-DbaLogin does the same, Get-DbaLogin does not) - the defect described in #10555, here from SMO's own object scripting rather than from $db.Query(). Every later query for the user database is then reused onto a connection that sits in master, and signing fails with Cannot alter the object 'sp_BlitzFirst', because it does not exist or you do not have permission.

Today that is hidden: the disconnect from part 1 closes the connection in between, and the reconnect lands back on the catalog from the connection string. Measured on the three states:

=== development ===
after Remove-DbaLogin (SMO drops the login)    DB_NAME()=master
after Invoke-DbaQuery -Database master         DB_NAME()=dbatoolsci_frkrepair   <-- repaired by the disconnect
the next query against the database runs in: dbatoolsci_frkrepair

=== part 1 only ===
after Invoke-DbaQuery -Database master         DB_NAME()=master
the next query against the database runs in: master                            <-- broken

=== part 1 and part 2 ===
after Invoke-DbaQuery -Database master         DB_NAME()=master
the next query against the database runs in: dbatoolsci_frkrepair              <-- correct for the right reason

So part 1 removes a crutch and part 2 removes the need for it. Install-DbaFirstResponderKit passes 22 of 22 with both, and the install produces its 2 signed objects again.

Tests

Connect-DbaInstance: the parameter list, and that the reference is $true for a string, $false when a server object is passed back in, $true when the connection context has to be copied, and that the same object comes back when nothing has to change.

Invoke-DbaQuery: the connection of the caller stays open and its temp table survives; connections the command opens itself are still closed, so #6210 does not come back; the connection is still reused while it is on the requested database; and a query runs in the requested database after the connection was moved away from it.

Reverting either production change makes the matching test fail, so both are covered by something that actually tests them.

Locally against SQL Server 2025 and 2022: Connect-DbaInstance 32 passed and 1 skipped (Azure), Invoke-DbaQuery 31 passed, Install-DbaFirstResponderKit 22 passed.

Still worth checking in review

  • Azure SQL Database, where the database of a connection cannot be changed the same way
  • availability group listeners together with -ReadOnly
  • inputs that are not a Server object: SqlConnection, connection strings and registered servers

This text was created by Claude and reviewed by Andreas Jordan.

andreasjordan and others added 2 commits August 11, 2026 16:18
Commands that connect and clean up afterwards had no way to tell whether
Connect-DbaInstance opened a connection for them or handed back the object
the caller passed in. They inferred it from their own parameters, which is
wrong whenever Connect-DbaInstance has no reason to copy anything, and then
they closed a connection that belongs to the caller - taking the session,
its temp tables and its database context with it.

Connect-DbaInstance now writes that information into the variable behind
-IsNewConnectionReference, and Invoke-DbaQuery only closes the connection
when it opened one itself.

A reference is used instead of a variable name because a variable set via
$PSCmdlet.SessionState never reaches a caller inside of dbatools: both share
the module session state, so the value lands in the local scope of
Connect-DbaInstance instead of the scope of the calling command.

See #10554

(do Connect-DbaInstance, Invoke-DbaQuery)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… it was opened with

To decide whether the connection that was passed in can be reused, the
command compared ConnectionContext.DatabaseName with -Database. That
property only holds the database the connection was opened with. As soon as
anything runs a USE on that connection, the two differ, the connection is
reused although it is on another database now, and the query silently runs
in the wrong database:

    $server = Connect-DbaInstance -SqlInstance $instance -Database tempdb -NonPooledConnection
    $null = $server.ConnectionContext.ExecuteNonQuery("USE [master]")
    Invoke-DbaQuery -SqlInstance $server -Database tempdb -Query "SELECT DB_NAME()"
    # master

ConnectionContext.CurrentDatabase is the database the connection is on right
now, and it is also what Connect-DbaInstance compares, so both commands now
ask the same question. That also saves a needless trip through
Connect-DbaInstance when the connection is already on the wanted database.

See #10554

(do Invoke-DbaQuery)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@andreasjordan andreasjordan changed the title Invoke-DbaQuery - Do not close connections of the caller and do not reuse them for the wrong database Connect-DbaInstance / Invoke-DbaQuery - Do not close connections of the caller and do not reuse them for the wrong database Aug 12, 2026
@potatoqualitee

Copy link
Copy Markdown
Member

thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants