From 0429887221574004b2ba5124954db80d6ef7cb58 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Tue, 1 Sep 2026 15:42:13 +0200 Subject: [PATCH] Connect-DbaInstance - Keep the tab completion cache of instance names an array The cache starts as $null, and "$null += name" makes it a string. From the second instance on, every name was concatenated onto that string, -notcontains compared the whole string, and every later connection appended its name again, so the completer offered one long blob instead of names. A new private function rebuilds the cache as an array of unique lower-cased names, and the three writers (Connect-DbaInstance, Add-DbaInstanceList, the dynamicparams script) call it instead of +=. (do Connect-DbaInstance, Add-DbaInstanceList) --- private/dynamicparams/sqlinstance.ps1 | 9 +---- .../tabcompletion/Add-DbaTeppInstanceName.ps1 | 39 +++++++++++++++++++ public/Add-DbaInstanceList.ps1 | 4 +- public/Connect-DbaInstance.ps1 | 4 +- tests/Add-DbaInstanceList.Tests.ps1 | 15 ++++++- tests/Connect-DbaInstance.Tests.ps1 | 13 +++++++ 6 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 private/functions/tabcompletion/Add-DbaTeppInstanceName.ps1 diff --git a/private/dynamicparams/sqlinstance.ps1 b/private/dynamicparams/sqlinstance.ps1 index a1a64844c449..09ab0cb31ae7 100644 --- a/private/dynamicparams/sqlinstance.ps1 +++ b/private/dynamicparams/sqlinstance.ps1 @@ -5,18 +5,13 @@ if (-not [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] # Load user-defined instances from config (set via Add-DbaInstanceList) foreach ($instance in (Get-DbatoolsConfigValue -FullName "TabExpansion.KnownInstances" -Fallback @())) { - if ($instance -and [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] -notcontains $instance) { - [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] += $instance - } + Add-DbaTeppInstanceName -Name $instance } # Load from environment variable (comma-separated list, e.g. set in PowerShell profile) if ($env:DBATOOLS_KNOWN_INSTANCES) { foreach ($instance in ($env:DBATOOLS_KNOWN_INSTANCES -split ",")) { - $lower = $instance.Trim().ToLowerInvariant() - if ($lower -and [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] -notcontains $lower) { - [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] += $lower - } + Add-DbaTeppInstanceName -Name $instance } } #endregion Initialize Cache diff --git a/private/functions/tabcompletion/Add-DbaTeppInstanceName.ps1 b/private/functions/tabcompletion/Add-DbaTeppInstanceName.ps1 new file mode 100644 index 000000000000..0e6fa27d163b --- /dev/null +++ b/private/functions/tabcompletion/Add-DbaTeppInstanceName.ps1 @@ -0,0 +1,39 @@ +function Add-DbaTeppInstanceName { + <# + .SYNOPSIS + Adds instance names to the tab completion cache for -SqlInstance, keeping the cache an array. + + .DESCRIPTION + The cache [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] starts as $null. + Appending to it with += turned it into a string, after which += concatenated every further name + onto that string and -notcontains compared the whole string, so every later connection appended + its name again and the completer offered one long blob. This function rebuilds the cache as an + array every time, lower cases the names like the completer expects, and skips duplicates. + + .PARAMETER Name + The instance names to add. Empty entries are ignored. + + .EXAMPLE + PS C:\> Add-DbaTeppInstanceName -Name $instance.FullSmoName + + Adds the instance to the completion cache of the current session. + #> + [CmdletBinding()] + param ( + [AllowEmptyString()] + [AllowNull()] + [string[]]$Name + ) + + $knownInstances = @([Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] | Where-Object { $PSItem }) + foreach ($item in $Name) { + if (-not $item) { + continue + } + $lower = $item.Trim().ToLowerInvariant() + if ($lower -and $knownInstances -notcontains $lower) { + $knownInstances += $lower + } + } + [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] = $knownInstances +} diff --git a/public/Add-DbaInstanceList.ps1 b/public/Add-DbaInstanceList.ps1 index 0a336ee8749e..7b55a6c2f457 100644 --- a/public/Add-DbaInstanceList.ps1 +++ b/public/Add-DbaInstanceList.ps1 @@ -84,9 +84,7 @@ function Add-DbaInstanceList { } # Update the TEPP cache immediately for this session - if ([Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] -notcontains $lower) { - [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] += $lower - } + Add-DbaTeppInstanceName -Name $lower } } diff --git a/public/Connect-DbaInstance.ps1 b/public/Connect-DbaInstance.ps1 index adc11018022c..3990b791159d 100644 --- a/public/Connect-DbaInstance.ps1 +++ b/public/Connect-DbaInstance.ps1 @@ -1534,9 +1534,7 @@ SELECT SERVERPROPERTY('ProductVersion') [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::SetInstance($instance.FullSmoName.ToLowerInvariant(), $teppConnectionContext, ($server.ConnectionContext.FixedServerRoles -match "SysAdmin")) # Update cache for instance names - if ([Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] -notcontains $instance.FullSmoName.ToLowerInvariant()) { - [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] += $instance.FullSmoName.ToLowerInvariant() - } + Add-DbaTeppInstanceName -Name $instance.FullSmoName # Update lots of registered stuff # Default for [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::TeppSyncDisabled is $true, so will not run by default diff --git a/tests/Add-DbaInstanceList.Tests.ps1 b/tests/Add-DbaInstanceList.Tests.ps1 index 2b773cd59838..980f6b4dafc2 100644 --- a/tests/Add-DbaInstanceList.Tests.ps1 +++ b/tests/Add-DbaInstanceList.Tests.ps1 @@ -23,10 +23,11 @@ Describe $CommandName -Tag UnitTests { Describe $CommandName -Tag IntegrationTests { BeforeAll { $instanceName = "dbatoolsci_testinstance_$(Get-Random)" + $secondInstanceName = "dbatoolsci_testinstance2_$(Get-Random)" } AfterAll { - $null = Remove-DbaInstanceList -SqlInstance $instanceName -ErrorAction SilentlyContinue + $null = Remove-DbaInstanceList -SqlInstance $instanceName, $secondInstanceName -ErrorAction SilentlyContinue } Context "adds instances to the list" { @@ -49,5 +50,17 @@ Describe $CommandName -Tag IntegrationTests { $result = Get-DbaInstanceList ($result | Where-Object { $PSItem -eq $instanceName.ToLowerInvariant() }).Count | Should -Be 1 } + + It "keeps the TEPP cache an array when more than one instance is known" { + # The cache starts as $null, and $null += "x" made it a string. Every further name was then + # concatenated onto that string and the completer offered one long blob instead of names. + Add-DbaInstanceList -SqlInstance $secondInstanceName + Add-DbaInstanceList -SqlInstance $instanceName + $cache = [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] + $cache -is [array] | Should -BeTrue + $cache | Should -Contain $instanceName.ToLowerInvariant() + $cache | Should -Contain $secondInstanceName.ToLowerInvariant() + @($cache | Where-Object { $PSItem -eq $instanceName.ToLowerInvariant() }).Count | Should -Be 1 + } } } diff --git a/tests/Connect-DbaInstance.Tests.ps1 b/tests/Connect-DbaInstance.Tests.ps1 index 1f6db1262e73..e52e45413ff1 100644 --- a/tests/Connect-DbaInstance.Tests.ps1 +++ b/tests/Connect-DbaInstance.Tests.ps1 @@ -1037,4 +1037,17 @@ SELECT SERVERPROPERTY('ProductVersion') $tsqlVersionMajor | Should -Be $smoVersionMajor } } + + Context "tab completion cache of instance names" { + It "keeps every connected instance as its own entry" { + # The cache starts as $null, and $null += "x" made it a string. Every further name was then + # concatenated onto that string and the completer offered one long blob instead of names. + $null = Connect-DbaInstance -SqlInstance $TestConfig.InstanceMulti1 + $null = Connect-DbaInstance -SqlInstance $TestConfig.InstanceMulti2 + $cache = [Dataplat.Dbatools.TabExpansion.TabExpansionHost]::Cache["sqlinstance"] + $cache -is [array] | Should -BeTrue + $cache | Should -Contain $TestConfig.InstanceMulti1.ToLowerInvariant() + $cache | Should -Contain $TestConfig.InstanceMulti2.ToLowerInvariant() + } + } }