diff --git a/dbatools.psm1 b/dbatools.psm1 index 4d574fe70c..4d6d6e9ee9 100644 --- a/dbatools.psm1 +++ b/dbatools.psm1 @@ -1084,4 +1084,7 @@ $onRemoveScript = { Get-Runspace | Where-Object Name -like dbatools* | ForEach-Object -Process { $_.Dispose() } } $ExecutionContext.SessionState.Module.OnRemove += $onRemoveScript -Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action $onRemoveScript \ No newline at end of file +Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action $onRemoveScript + +# Create collection for servers +$script:connectionhash = @{ } \ No newline at end of file diff --git a/functions/Connect-DbaInstance.ps1 b/functions/Connect-DbaInstance.ps1 index 38cfa1eb2a..8c258a9e07 100644 --- a/functions/Connect-DbaInstance.ps1 +++ b/functions/Connect-DbaInstance.ps1 @@ -283,7 +283,7 @@ function Connect-DbaInstance { PS C:\> $server = Connect-DbaInstance -SqlInstance srv1 -DedicatedAdminConnection PS C:\> $dbaProcess = Get-DbaProcess -SqlInstance $server -ExcludeSystemSpids PS C:\> $killedProcess = $dbaProcess | Out-GridView -OutputMode Multiple | Stop-DbaProcess - PS C:\> $server.ConnectionContext.Disconnect() + PS C:\> $server | Disconnect-DbaInstance Creates a dedicated admin connection (DAC) to the default instance on server srv1. Receives all non-system processes from the instance using the DAC. @@ -963,6 +963,7 @@ function Connect-DbaInstance { } if ($SqlConnectionOnly) { + $null = Add-ConnectionHashValue -Key $server.ConnectionContext.ConnectionString -Value $server.ConnectionContext.SqlConnectionObject Write-Message -Level Debug -Message "We return only SqlConnection in server.ConnectionContext.SqlConnectionObject" $server.ConnectionContext.SqlConnectionObject continue @@ -1066,6 +1067,7 @@ function Connect-DbaInstance { } } + $null = Add-ConnectionHashValue -Key $server.ConnectionContext.ConnectionString -Value $server Write-Message -Level Debug -Message "We are finished with this instance" continue } @@ -1236,6 +1238,7 @@ function Connect-DbaInstance { if ($currentdb) { Add-Member -InputObject $server -NotePropertyName Databases -NotePropertyValue @{ $currentdb.Name = $currentdb } -Force }#> + $null = Add-ConnectionHashValue -Key $server.ConnectionContext.ConnectionString -Value $server $server continue } catch { @@ -1251,6 +1254,7 @@ function Connect-DbaInstance { $instance.InputObject.ConnectionContext.Connect() } if ($SqlConnectionOnly) { + $null = Add-ConnectionHashValue -Key $instance.InputObject.ConnectionContext.ConnectionString -Value $instance.InputObject.ConnectionContext.SqlConnectionObject $instance.InputObject.ConnectionContext.SqlConnectionObject continue } else { @@ -1292,6 +1296,8 @@ function Connect-DbaInstance { if ($AzureUnsupported -and $server.DatabaseEngineType -eq "SqlAzureDatabase") { Stop-Function -Target $instance -Message "Azure SQL Database not supported" -Continue } + $null = Add-ConnectionHashValue -Key $server.ConnectionContext.ConnectionString -Value $server.ConnectionContext.SqlConnectionObject + Write-Message -Level Debug -Message "We return server.ConnectionContext.SqlConnectionObject" $server.ConnectionContext.SqlConnectionObject continue @@ -1321,6 +1327,7 @@ function Connect-DbaInstance { if ([Sqlcollaborative.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] -notcontains $instance.FullSmoName.ToLowerInvariant()) { [Sqlcollaborative.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] += $instance.FullSmoName.ToLowerInvariant() } + $null = Add-ConnectionHashValue -Key $server.ConnectionContext.ConnectionString -Value $server Write-Message -Level Debug -Message "We return server with server.Name = '$($server.Name)'" $server continue @@ -1544,6 +1551,7 @@ function Connect-DbaInstance { } if ($SqlConnectionOnly) { + $null = Add-ConnectionHashValue -Key $server.ConnectionContext.ConnectionString -Value $server.ConnectionContext.SqlConnectionObject Write-Message -Level Debug -Message "SqlConnectionOnly, so returning server.ConnectionContext.SqlConnectionObject" $server.ConnectionContext.SqlConnectionObject continue @@ -1588,6 +1596,7 @@ function Connect-DbaInstance { Stop-Function -Target $instance -Message "Azure SQL Database not supported" -Continue } + $null = Add-ConnectionHashValue -Key $server.ConnectionContext.ConnectionString -Value $server Write-Message -Level Debug -Message "We return server with server.Name = '$($server.Name)'" $server continue diff --git a/functions/Disconnect-DbaInstance.ps1 b/functions/Disconnect-DbaInstance.ps1 new file mode 100644 index 0000000000..c0a76d1b60 --- /dev/null +++ b/functions/Disconnect-DbaInstance.ps1 @@ -0,0 +1,111 @@ +function Disconnect-DbaInstance { + <# + .SYNOPSIS + Disconnects or closes a connection to a SQL Server instance + + .DESCRIPTION + Disconnects or closes a connection to a SQL Server instance + + To clear all of your connection pools, use Clear-DbaConnectionPool + + .PARAMETER InputObject + The server object to disconnect from, usually piped in from Get-DbaConnectedInstance + + .PARAMETER WhatIf + Shows what would happen if the command were to run. No actions are actually performed. + + .PARAMETER Confirm + Prompts you for confirmation before executing any changing operations within the command. + + .PARAMETER EnableException + By default, when something goes wrong we try to catch it, interpret it and give you a friendly warning message. + This avoids overwhelming you with "sea of red" exceptions, but is inconvenient because it basically disables advanced scripting. + Using this switch turns this "nice by default" feature off and enables you to catch exceptions with your own try/catch. + + .NOTES + Tags: Connection + Author: Chrissy LeMaire (@cl), netnerds.net + + Website: https://dbatools.io + Copyright: (c) 2021 by dbatools, licensed under MIT + License: MIT https://opensource.org/licenses/MIT + + .LINK + https://dbatools.io/Disconnect-DbaInstance + + .EXAMPLE + PS C:\> Get-DbaConnectedInstance | Disconnect-DbaInstance + + Disconnects all connected instances + + .EXAMPLE + PS C:\> Get-DbaConnectedInstance | Out-GridView -Passthru | Disconnect-DbaInstance + + Disconnects selected SQL Server instances + + .EXAMPLE + PS C:\> $server = Connect-DbaInstance -SqlInstance sql01 + PS C:\> $server | Disconnect-DbaInstance + + Disconnects the $server connection + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = "Low")] + param ( + [parameter(ValueFromPipeline)] + [psobject[]]$InputObject, + [switch]$EnableException + ) + process { + # to avoid enumeration problems when piped + $objects += $InputObject + } + end { + foreach ($object in $objects) { + if ($object.ConnectionObject) { + $servers = $object.ConnectionObject + } else { + $servers = $object + } + foreach ($server in $servers) { + try { + if ($server.ConnectionContext) { + if ($Pscmdlet.ShouldProcess($server.Name, "Disconnecting SQL Connection")) { + $null = $server.ConnectionContext.Disconnect() + if ($script:connectionhash[$server.ConnectionContext.ConnectionString]) { + Write-Message -Level Verbose -Message "removing from connection hash" + $null = $script:connectionhash.Remove($server.ConnectionContext.ConnectionString) + } + [pscustomobject]@{ + SqlInstance = $server.Name + ConnectionString = (Hide-ConnectionString -ConnectionString $server.ConnectionContext.ConnectionString) + ConnectionType = $server.GetType().FullName + State = "Disconnected" + } | Select-DefaultView -Property SqlInstance, ConnectionType, State + } + } + if ($server.GetType().Name -eq "SqlConnection") { + if ($Pscmdlet.ShouldProcess($server.DataSource, "Closing SQL Connection")) { + if ($server.State -eq "Open") { + $null = $server.Close() + } + + if ($script:connectionhash[$server.ConnectionString]) { + Write-Message -Level Verbose -Message "removing from connection hash" + $null = $script:connectionhash.Remove($server.ConnectionString) + } + + [pscustomobject]@{ + SqlInstance = $server.DataSource + ConnectionString = (Hide-ConnectionString -ConnectionString $server.ConnectionString) + ConnectionType = $server.GetType().FullName + State = $server.State + } | Select-DefaultView -Property SqlInstance, ConnectionType, State + } + } + } catch { + Stop-Function -Message "Failed to disconnect $object" -ErrorRecord $PSItem -Continue + } + } + } + } +} \ No newline at end of file diff --git a/functions/Get-DbaConnectedInstance.ps1 b/functions/Get-DbaConnectedInstance.ps1 new file mode 100644 index 0000000000..d0318908bd --- /dev/null +++ b/functions/Get-DbaConnectedInstance.ps1 @@ -0,0 +1,55 @@ +function Get-DbaConnectedInstance { + <# + .SYNOPSIS + Get a list of all connected instances + + .DESCRIPTION + Get a list of all connected instances + + .NOTES + Tags: Connection + Author: Chrissy LeMaire (@cl), netnerds.net + + Website: https://dbatools.io + Copyright: (c) 2021 by dbatools, licensed under MIT + License: MIT https://opensource.org/licenses/MIT + + .LINK + https://dbatools.io/Get-DbaConnectedInstance + + .EXAMPLE + PS C:\> Get-DbaConnectedInstance + + Gets all connected SQL Server instances + + .EXAMPLE + PS C:\> Get-DbaConnectedInstance | Select * + + Gets all connected SQL Server instances and shows the associated connectionstrings as well + + #> + [CmdletBinding()] + param () + process { + foreach ($key in $script:connectionhash.Keys) { + if ($script:connectionhash[$key].DataSource) { + $instance = $script:connectionhash[$key] | Select-Object -First 1 -ExpandProperty DataSource + } else { + $instance = $script:connectionhash[$key] | Select-Object -First 1 -ExpandProperty Name + } + $value = $script:connectionhash[$key] | Select-Object -First 1 + if ($value.ConnectionContext.NonPooledConnection -or $value.NonPooledConnection) { + $pooling = $false + } else { + $pooling = $true + } + [pscustomobject]@{ + SqlInstance = $instance + ConnectionObject = $script:connectionhash[$key] + ConnectionType = $value.GetType().FullName + Pooled = $pooling + ConnectionString = (Hide-ConnectionString -ConnectionString $key) + } | Select-DefaultView -Property SqlInstance, ConnectionType, ConnectionObject, Pooled + } + } +} \ No newline at end of file diff --git a/internal/functions/Add-ConnectionHashValue.ps1 b/internal/functions/Add-ConnectionHashValue.ps1 new file mode 100644 index 0000000000..601230dbca --- /dev/null +++ b/internal/functions/Add-ConnectionHashValue.ps1 @@ -0,0 +1,18 @@ +function Add-ConnectionHashValue { + param( + [Parameter(Mandatory)] + $Key, + [Parameter(Mandatory)] + $Value + ) + Write-Message -Level Debug -Message "Adding to connection hash" + + if ($Value.ConnectionContext.NonPooledConnection -or $Value.NonPooledConnection) { + if (-not $script:connectionhash[$Key]) { + $script:connectionhash[$Key] = @( ) + } + $script:connectionhash[$Key] += $Value + } else { + $script:connectionhash[$Key] = $Value + } +} \ No newline at end of file diff --git a/tests/Disconnect-DbaInstance.Tests.ps1 b/tests/Disconnect-DbaInstance.Tests.ps1 new file mode 100644 index 0000000000..b2c11c0ce7 --- /dev/null +++ b/tests/Disconnect-DbaInstance.Tests.ps1 @@ -0,0 +1,27 @@ +$CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") +Write-Host -Object "Running $PSCommandpath" -ForegroundColor Cyan +. "$PSScriptRoot\constants.ps1" + +Describe "$CommandName Unit Tests" -Tag "UnitTests" { + Context "Validate parameters" { + [array]$params = ([Management.Automation.CommandMetaData]$ExecutionContext.SessionState.InvokeCommand.GetCommand($CommandName, 'Function')).Parameters.Keys + [object[]]$knownParameters = 'InputObject', 'EnableException' + + It "Should only contain our specific parameters" { + Compare-Object -ReferenceObject $knownParameters -DifferenceObject $params | Should -BeNullOrEmpty + } + } +} + +Describe "$commandname Integration Tests" -Tag "IntegrationTests" { + BeforeAll { + $null = Get-DbaDatabase -SqlInstance $script:instance1 + $null = Connect-DbaInstance -SqlInstance $env:COMPUTERNAME -SqlConnectionOnly + } + Context "disconnets a server" { + It "disconnects and returns some results" { + $results = Get-DbaConnectedInstance | Disconnect-DbaInstance + $results.Count | Should -BeGreaterThan 1 + } + } +} \ No newline at end of file diff --git a/tests/Get-DbaConnectedInstance.Tests.ps1 b/tests/Get-DbaConnectedInstance.Tests.ps1 new file mode 100644 index 0000000000..fc2792ce85 --- /dev/null +++ b/tests/Get-DbaConnectedInstance.Tests.ps1 @@ -0,0 +1,24 @@ +$CommandName = $MyInvocation.MyCommand.Name.Replace(".Tests.ps1", "") +Write-Host -Object "Running $PSCommandpath" -ForegroundColor Cyan +. "$PSScriptRoot\constants.ps1" + +Describe "$CommandName Unit Tests" -Tag "UnitTests" { + Context "Validate parameters" { + # fake tests, no parameters to validate + It "Should only contain our specific parameters" { + $null | Should -BeNullOrEmpty + } + } +} + +Describe "$commandname Integration Tests" -Tag "IntegrationTests" { + BeforeAll { + $null = Get-DbaDatabase -SqlInstance $script:instance1 + } + Context "gets connected objects" { + It "returns some results" { + $results = Get-DbaConnectedInstance + $results.Count | Should -BeGreaterThan 0 + } + } +} \ No newline at end of file