From 46cbf45b4a18b5a61c9af9f095c94cd0de800eea Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:30 +0200 Subject: [PATCH 01/17] Copy-DbaAgentJob - Stop writing a $null into the output of every copied job Query() returns $null for a batch without a result set, and the call had no $null = in front of it. The script of every copied job went through it, so each job put a $null in front of its result. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaAgentJob) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaAgentJob.ps1 | 2 +- tests/Copy-DbaAgentJob.Tests.ps1 | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/Copy-DbaAgentJob.ps1 b/public/Copy-DbaAgentJob.ps1 index 5017c5ae008..8c7bc24fb2b 100644 --- a/public/Copy-DbaAgentJob.ps1 +++ b/public/Copy-DbaAgentJob.ps1 @@ -533,7 +533,7 @@ WHERE j.job_id = @jobId" } Write-Message -Message $sql -Level Debug - $destServer.Query($sql) + $null = $destServer.Query($sql) $destServer.JobServer.Jobs.Refresh() $destServer.JobServer.Jobs[$destJobName].IsEnabled = $sourceServer.JobServer.Jobs[$serverJob.name].IsEnabled diff --git a/tests/Copy-DbaAgentJob.Tests.ps1 b/tests/Copy-DbaAgentJob.Tests.ps1 index e8fc895f637..a8eac5809c8 100644 --- a/tests/Copy-DbaAgentJob.Tests.ps1 +++ b/tests/Copy-DbaAgentJob.Tests.ps1 @@ -75,6 +75,8 @@ Describe $CommandName -Tag IntegrationTests { } It "returns one success" { + # Counts the output itself, $results.Status.Count would skip a $null written by the command + @($results).Count | Should -Be 1 $results.Name | Should -Be "dbatoolsci_copyjob" $results.Status | Should -Be "Successful" } From 55a8b4afa821855ebc22b1cb2227ec1fd1d2ac1e Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:31 +0200 Subject: [PATCH 02/17] Copy-DbaAgentJobCategory - Stop writing a $null into the output of every copied category Query() returns $null for a batch without a result set, and the call had no $null = in front of it. All five sites - job, operator and alert categories, each created new or recreated with -Force - put a $null in front of the result. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaAgentJobCategory) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaAgentJobCategory.ps1 | 10 +++++----- tests/Copy-DbaAgentJobCategory.Tests.ps1 | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/public/Copy-DbaAgentJobCategory.ps1 b/public/Copy-DbaAgentJobCategory.ps1 index 925123ef7d5..4de751ba675 100644 --- a/public/Copy-DbaAgentJobCategory.ps1 +++ b/public/Copy-DbaAgentJobCategory.ps1 @@ -189,7 +189,7 @@ function Copy-DbaAgentJobCategory { Write-Message -Level Verbose -Message "Copying Job category $categoryName" $sql = $jobCategory.Script() | Out-String Write-Message -Level Debug -Message "SQL Statement: $sql" - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyJobCategoryStatus.Status = "Successful" $copyJobCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject } catch { @@ -255,7 +255,7 @@ function Copy-DbaAgentJobCategory { Write-Message -Level Verbose -Message "Copying Operator category $categoryName" $sql = $operatorCategory.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) } catch { $copyOperatorCategoryStatus.Status = "Failed" $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject @@ -270,7 +270,7 @@ function Copy-DbaAgentJobCategory { Write-Message -Level Verbose -Message "Copying Operator category $categoryName" $sql = $operatorCategory.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyOperatorCategoryStatus.Status = "Successful" $copyOperatorCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject @@ -343,7 +343,7 @@ function Copy-DbaAgentJobCategory { Write-Message -Level Verbose -Message "Copying Alert category $categoryName" $sql = $alertcategory.Script() | Out-String Write-Message -Level Debug -Message "SQL Statement: $sql" - $destServer.Query($sql) + $null = $destServer.Query($sql) } catch { $copyAlertCategoryStatus.Status = "Failed" $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject @@ -358,7 +358,7 @@ function Copy-DbaAgentJobCategory { Write-Message -Level Verbose -Message "Copying Alert category $categoryName" $sql = $alertCategory.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyAlertCategoryStatus.Status = "Successful" $copyAlertCategoryStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject diff --git a/tests/Copy-DbaAgentJobCategory.Tests.ps1 b/tests/Copy-DbaAgentJobCategory.Tests.ps1 index 2b4d727a854..ab08ed1b19f 100644 --- a/tests/Copy-DbaAgentJobCategory.Tests.ps1 +++ b/tests/Copy-DbaAgentJobCategory.Tests.ps1 @@ -83,6 +83,8 @@ Describe $CommandName -Tag IntegrationTests { } $results = Copy-DbaAgentJobCategory @splatCopyCategory + # Counts the output itself, $results.Status.Count would skip a $null written by the command + @($results).Count | Should -Be 1 $results.Name | Should -Be "dbatoolsci test category" $results.Status | Should -Be "Successful" } @@ -111,6 +113,7 @@ Describe $CommandName -Tag IntegrationTests { AlertCategory = "dbatoolsci alert category" } $results = Copy-DbaAgentJobCategory @splatCopyAlertCategory + @($results).Count | Should -Be 1 $results.Name | Should -Be "dbatoolsci alert category" $results.Status | Should -Be "Successful" (Get-DbaAgentAlertCategory -SqlInstance $TestConfig.InstanceCopy2 -Category $alertCategoryNames).Name | Should -Be "dbatoolsci alert category" From 24c941fe339fe072b05293a55ae7619f93c1f355 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:31 +0200 Subject: [PATCH 03/17] Copy-DbaAgentJobStep - Stop writing a $null into the output for every copied step Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every step of a synchronized job put a $null in front of the result of the job. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaAgentJobStep) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaAgentJobStep.ps1 | 2 +- tests/Copy-DbaAgentJobStep.Tests.ps1 | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/Copy-DbaAgentJobStep.ps1 b/public/Copy-DbaAgentJobStep.ps1 index ce94bdcd772..90b2352bc7e 100644 --- a/public/Copy-DbaAgentJobStep.ps1 +++ b/public/Copy-DbaAgentJobStep.ps1 @@ -213,7 +213,7 @@ function Copy-DbaAgentJobStep { # Replace @job_id with @job_name since the destination job has a different GUID $sql = $sql -replace "@job_id=N'[0-9a-fA-F-]+'", "@job_name=N'$($jobName -replace "'", "''")'" Write-Message -Message $sql -Level Debug - $destServer.Query($sql) + $null = $destServer.Query($sql) } $destJob.JobSteps.Refresh() diff --git a/tests/Copy-DbaAgentJobStep.Tests.ps1 b/tests/Copy-DbaAgentJobStep.Tests.ps1 index 850a9505047..4043f9f9469 100644 --- a/tests/Copy-DbaAgentJobStep.Tests.ps1 +++ b/tests/Copy-DbaAgentJobStep.Tests.ps1 @@ -100,6 +100,8 @@ Describe $CommandName -Tag IntegrationTests { } It "Returns success status" { + # Counts the output itself, $results.Status.Count would skip a $null written by the command + @($results).Count | Should -Be 1 $results.Name | Should -Be $sourceJobName $results.Status | Should -Be "Successful" $results.Notes | Should -BeLike "Synchronized * job step(s)" From eab9cdf6f57df3dd73a23cbbe2a17d927d3dc34a Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:32 +0200 Subject: [PATCH 04/17] Copy-DbaAgentOperator - Stop writing a $null into the output of every copied operator Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every copied operator put a $null in front of its result. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaAgentOperator) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaAgentOperator.ps1 | 2 +- tests/Copy-DbaAgentOperator.Tests.ps1 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/Copy-DbaAgentOperator.ps1 b/public/Copy-DbaAgentOperator.ps1 index 1a589d41624..a8e9abe3a11 100644 --- a/public/Copy-DbaAgentOperator.ps1 +++ b/public/Copy-DbaAgentOperator.ps1 @@ -183,7 +183,7 @@ function Copy-DbaAgentOperator { Write-Message -Level Verbose -Message "Copying Operator $operatorName" $sql = $sOperator.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) $destServer.JobServer.Operators.Refresh() $copyOperatorStatus.Status = "Successful" diff --git a/tests/Copy-DbaAgentOperator.Tests.ps1 b/tests/Copy-DbaAgentOperator.Tests.ps1 index b3f9cbb586e..06d885e7b27 100644 --- a/tests/Copy-DbaAgentOperator.Tests.ps1 +++ b/tests/Copy-DbaAgentOperator.Tests.ps1 @@ -73,7 +73,8 @@ Describe $CommandName -Tag IntegrationTests { Operator = @($operatorName1, $operatorName2) } $results = Copy-DbaAgentOperator @splatCopyOperators - $results.Status.Count | Should -Be 2 + # Counts the output itself, $results.Status.Count would skip a $null written by the command + @($results).Count | Should -Be 2 $results.Status | Should -Be @("Successful", "Successful") } From 03f1132472495035481f21772d33cea6b20df189 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:33 +0200 Subject: [PATCH 05/17] Copy-DbaAgentProxy - Stop writing a $null into the output of every copied proxy Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every copied proxy put a $null in front of its result. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaAgentProxy) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaAgentProxy.ps1 | 2 +- tests/Copy-DbaAgentProxy.Tests.ps1 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/Copy-DbaAgentProxy.ps1 b/public/Copy-DbaAgentProxy.ps1 index aa10d9a8ef7..e49b9d7b45a 100644 --- a/public/Copy-DbaAgentProxy.ps1 +++ b/public/Copy-DbaAgentProxy.ps1 @@ -213,7 +213,7 @@ function Copy-DbaAgentProxy { Write-Message -Level Verbose -Message "Copying server proxy account $proxyName" $sql = $account.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) # Will fixing this misspelled status cause problems downstream? $copyAgentProxyAccountStatus.Status = "Successful" diff --git a/tests/Copy-DbaAgentProxy.Tests.ps1 b/tests/Copy-DbaAgentProxy.Tests.ps1 index 55b020c47c2..0c2cb3d54cf 100644 --- a/tests/Copy-DbaAgentProxy.Tests.ps1 +++ b/tests/Copy-DbaAgentProxy.Tests.ps1 @@ -78,7 +78,8 @@ Describe $CommandName -Tag IntegrationTests { } It "Should return one successful result" { - $copyResults.Status.Count | Should -Be 1 + # Counts the output itself, $copyResults.Status.Count would skip a $null written by the command + @($copyResults).Count | Should -Be 1 $copyResults.Status | Should -Be "Successful" } From 03540e3dc1745be5f7476c55560d0d01d134c40b Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:33 +0200 Subject: [PATCH 06/17] Copy-DbaAgentSchedule - Stop writing a $null into the output of every copied schedule Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every copied schedule put a $null in front of its result. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaAgentSchedule) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaAgentSchedule.ps1 | 2 +- tests/Copy-DbaAgentSchedule.Tests.ps1 | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/Copy-DbaAgentSchedule.ps1 b/public/Copy-DbaAgentSchedule.ps1 index 49a966279c6..932ad2f7cca 100644 --- a/public/Copy-DbaAgentSchedule.ps1 +++ b/public/Copy-DbaAgentSchedule.ps1 @@ -198,7 +198,7 @@ function Copy-DbaAgentSchedule { $sql = $currentschedule.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) $copySharedScheduleStatus.Status = "Successful" $copySharedScheduleStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject diff --git a/tests/Copy-DbaAgentSchedule.Tests.ps1 b/tests/Copy-DbaAgentSchedule.Tests.ps1 index 1e5d3b2b302..391567f5f67 100644 --- a/tests/Copy-DbaAgentSchedule.Tests.ps1 +++ b/tests/Copy-DbaAgentSchedule.Tests.ps1 @@ -127,6 +127,8 @@ Describe $CommandName -Tag IntegrationTests { } It "Reports the schedule as copied" { + # Counts the output itself, $forceResults.Status.Count would skip a $null written by the command + $forceResults.Count | Should -Be 1 $forceResults.Status | Should -Be "Successful" } From 592830dd1f18ba9f436dd8bf7e44beaae3eb6549 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:34 +0200 Subject: [PATCH 07/17] Copy-DbaBackupDevice - Stop writing a $null into the output of every copied device Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every copied backup device put a $null in front of its result. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. The copy in the test is limited to the device of the test, so the count does not depend on other devices of the instance. Part of #10794 (do Copy-DbaBackupDevice) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaBackupDevice.ps1 | 2 +- tests/Copy-DbaBackupDevice.Tests.ps1 | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/public/Copy-DbaBackupDevice.ps1 b/public/Copy-DbaBackupDevice.ps1 index dd7aab14ea6..8ae6f15b97e 100644 --- a/public/Copy-DbaBackupDevice.ps1 +++ b/public/Copy-DbaBackupDevice.ps1 @@ -226,7 +226,7 @@ function Copy-DbaBackupDevice { if ($Pscmdlet.ShouldProcess($destinstance, "Adding backup device $deviceName")) { Write-Message -Level Verbose -Message "Adding backup device $deviceName on $destinstance" try { - $destServer.Query($sql) + $null = $destServer.Query($sql) $destServer.BackupDevices.Refresh() $copyBackupDeviceStatus.Status = "Successful" diff --git a/tests/Copy-DbaBackupDevice.Tests.ps1 b/tests/Copy-DbaBackupDevice.Tests.ps1 index 7b792ab7abc..91773a7e5a8 100644 --- a/tests/Copy-DbaBackupDevice.Tests.ps1 +++ b/tests/Copy-DbaBackupDevice.Tests.ps1 @@ -72,11 +72,20 @@ Describe $CommandName -Tag IntegrationTests { Context "When copying backup device between instances" { It "Should copy the backup device successfully or warn about local copy" { - $results = Copy-DbaBackupDevice -Source $TestConfig.InstanceCopy1 -Destination $TestConfig.InstanceCopy2 -WarningVariable WarnVar -WarningAction SilentlyContinue 3> $null + $splatCopyDevice = @{ + Source = $TestConfig.InstanceCopy1 + Destination = $TestConfig.InstanceCopy2 + BackupDevice = $deviceName + WarningVariable = "WarnVar" + WarningAction = "SilentlyContinue" + } + $results = Copy-DbaBackupDevice @splatCopyDevice 3> $null if ($WarnVar) { $WarnVar | Should -Match "backup device to destination" } else { + # Counts the output itself, $results.Status.Count would skip a $null written by the command + @($results).Count | Should -Be 1 $results.Status | Should -Be "Successful" } } From 1eaecd88ddd5b3328ccc86e33a90610c7bc6a46d Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:35 +0200 Subject: [PATCH 08/17] Copy-DbaCustomError - Stop writing a $null into the output of every copied message Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every copied message - one per language - put a $null in front of its result. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaCustomError) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaCustomError.ps1 | 2 +- tests/Copy-DbaCustomError.Tests.ps1 | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/Copy-DbaCustomError.ps1 b/public/Copy-DbaCustomError.ps1 index b307f5823b1..dfa106b225d 100644 --- a/public/Copy-DbaCustomError.ps1 +++ b/public/Copy-DbaCustomError.ps1 @@ -189,7 +189,7 @@ function Copy-DbaCustomError { Write-Message -Level Verbose -Message "Copying custom error $customErrorId $language" $sql = $currentCustomError.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyCustomErrorStatus.Status = "Successful" $copyCustomErrorStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject } catch { diff --git a/tests/Copy-DbaCustomError.Tests.ps1 b/tests/Copy-DbaCustomError.Tests.ps1 index ca5e928b69e..3c26a9dba2e 100644 --- a/tests/Copy-DbaCustomError.Tests.ps1 +++ b/tests/Copy-DbaCustomError.Tests.ps1 @@ -66,6 +66,8 @@ Describe $CommandName -Tag IntegrationTests { CustomError = 60000 } $copyResults = Copy-DbaCustomError @splatCopyError + # Counts the output itself, $copyResults.Status.Count would skip a $null written by the command + @($copyResults).Count | Should -Be 2 $copyResults.Name[0] | Should -Be "60000:'us_english'" $copyResults.Name[1] | Should -Match "60000\:'Fran" $copyResults.Status | Should -Be @("Successful", "Successful") From 8fa4d8cef90b05e15e20444175b8e413624e5416 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:36 +0200 Subject: [PATCH 09/17] Copy-DbaDataCollector - Stop writing a $null into the output of every copied collection set Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every migrated collection set put a $null in front of its result. The second site, the server reconfiguration, cannot run: -NoServerReconfig is always set to $true just before it. No test: copying collection sets needs a configured Data Collector on both instances, which neither CI nor the lab has. Part of #10794 (do Copy-DbaDataCollector) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaDataCollector.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/Copy-DbaDataCollector.ps1 b/public/Copy-DbaDataCollector.ps1 index fecf58d70a8..207d05fa5df 100644 --- a/public/Copy-DbaDataCollector.ps1 +++ b/public/Copy-DbaDataCollector.ps1 @@ -173,7 +173,7 @@ function Copy-DbaDataCollector { if ($Pscmdlet.ShouldProcess($destinstance, "Attempting to modify Data Collector configuration")) { try { $sql = "Unknown at this time" - $destServer.Query($sql) + $null = $destServer.Query($sql) $destStore.Alter() } catch { $copyServerConfigStatus.Status = "Failed" @@ -243,7 +243,7 @@ function Copy-DbaDataCollector { $sql = $sql -replace [Regex]::Escape("'$source'"), "'$destinstance'" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Migrating collection set $collectionName" - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyCollectionSetStatus.Status = "Successful" $copyCollectionSetStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject } catch { From 204c4c0664e624ea1f450ab477ec1678dfbd2791 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:37 +0200 Subject: [PATCH 10/17] Copy-DbaDbAssembly - Stop writing a $null into the output of every copied assembly Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Setting TRUSTWORTHY and dropping an existing assembly with -Force each put a $null in front of the result. The create call passed the database name as the second argument of Database.Query, which is AllTables, not a database. That returned the empty table collection and so wrote nothing, but only by accident; the argument is gone. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaDbAssembly) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaDbAssembly.ps1 | 6 +++--- tests/Copy-DbaDbAssembly.Tests.ps1 | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/public/Copy-DbaDbAssembly.ps1 b/public/Copy-DbaDbAssembly.ps1 index aca488d86d9..e9c4d894151 100644 --- a/public/Copy-DbaDbAssembly.ps1 +++ b/public/Copy-DbaDbAssembly.ps1 @@ -201,7 +201,7 @@ function Copy-DbaDbAssembly { $sql = "ALTER DATABASE $dbName SET TRUSTWORTHY ON" try { Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) } catch { $copyDbAssemblyStatus.Status = "Failed to set security level to external" $copyDbAssemblyStatus.Notes = "$PSItem" @@ -231,7 +231,7 @@ function Copy-DbaDbAssembly { throw "$assemblyName has dependencies but this command does not yet support dependent objects" } - $destDb.Query("DROP ASSEMBLY $assemblyName") + $null = $destDb.Query("DROP ASSEMBLY $assemblyName") } catch { $copyDbAssemblyStatus.Status = "Failed" $copyDbAssemblyStatus.Notes = "$PSItem" @@ -248,7 +248,7 @@ function Copy-DbaDbAssembly { Write-Message -Level Verbose -Message "Copying assembly $assemblyName from database." $sql = $currentAssembly.Script() Write-Message -Level Debug -Message ($sql -join ' ') - $destDb.Query($sql, $dbName) + $null = $destDb.Query($sql) $copyDbAssemblyStatus.Status = "Successful" $copyDbAssemblyStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject diff --git a/tests/Copy-DbaDbAssembly.Tests.ps1 b/tests/Copy-DbaDbAssembly.Tests.ps1 index 2bb78df9fec..28e2a1ceb55 100644 --- a/tests/Copy-DbaDbAssembly.Tests.ps1 +++ b/tests/Copy-DbaDbAssembly.Tests.ps1 @@ -65,6 +65,8 @@ Describe "$commandname Integration Tests" -Tag "IntegrationTests" { $results.Notes | Should -Be "Already exists on destination" $results = Copy-DbaDbAssembly -Source $TestConfig.InstanceCopy1 -Destination $TestConfig.InstanceCopy2 -Assembly dbclrassembly.resolveDNS -Force + # Counts the output itself, $results.Status.Count would skip a $null written by the command + @($results).Count | Should -Be 1 $results.Name | Should -Be resolveDns $results.Status | Should -Be Successful $results.Type | Should -Be "Database Assembly" From b66bf806fc750f2ecfff9ccce42993dc2601a25f Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:37:44 +0200 Subject: [PATCH 11/17] Copy-DbaInstanceAudit - Output skipped audits, drop their specifications with -Force and stop writing $null Three defects of the same command: - Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every copied audit put a $null in front of its result. - An audit that exists on the destination got the status Skipped, but the object was never written, so the caller got nothing for it. - -Force is meant to drop the server audit specifications that use the audit before it replaces the audit, but the loop went over $spec and tested and dropped $auditSpecification, a variable that does not exist. No specification was ever dropped; they stayed on the destination while the audit was replaced underneath them. The specifications are collected first, then disabled and dropped. The command had no integration test. The new one copies an audit, copies it again to get the skipped result and replaces it with -Force while an enabled specification uses it on the destination. Part of #10794 (do Copy-DbaInstanceAudit) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaInstanceAudit.ps1 | 13 ++-- tests/Copy-DbaInstanceAudit.Tests.ps1 | 102 ++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/public/Copy-DbaInstanceAudit.ps1 b/public/Copy-DbaInstanceAudit.ps1 index 7e67ea8bd38..e9075443a82 100644 --- a/public/Copy-DbaInstanceAudit.ps1 +++ b/public/Copy-DbaInstanceAudit.ps1 @@ -169,6 +169,7 @@ function Copy-DbaInstanceAudit { if ($Pscmdlet.ShouldProcess($destinstance, "Server audit $auditName exists at destination. Use -Force to drop and migrate.")) { $copyAuditStatus.Status = "Skipped" $copyAuditStatus.Notes = "Already exists on destination" + $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject Write-Message -Level Verbose -Message "Server audit $auditName exists at destination. Use -Force to drop and migrate." } continue @@ -176,10 +177,12 @@ function Copy-DbaInstanceAudit { if ($Pscmdlet.ShouldProcess($destinstance, "Dropping server audit $auditName")) { try { Write-Message -Level Verbose -Message "Dropping server audit $auditName." - foreach ($spec in $destServer.ServerAuditSpecifications) { - if ($auditSpecification.Auditname -eq $auditName) { - $auditSpecification.Drop() - } + # Collected first, a drop inside the enumeration would modify the collection + $destAuditSpecs = @($destServer.ServerAuditSpecifications | Where-Object AuditName -eq $auditName) + foreach ($spec in $destAuditSpecs) { + Write-Message -Level Verbose -Message "Dropping server audit specification $($spec.Name) that uses $auditName." + $spec.Disable() + $spec.Drop() } $destServer.audits[$auditName].Disable() @@ -231,7 +234,7 @@ function Copy-DbaInstanceAudit { Write-Message -Level Verbose -Message "File path $($currentAudit.Filepath) exists on $destinstance." Write-Message -Level Verbose -Message "Copying server audit $auditName." $sql = $currentAudit.Script() | Out-String - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyAuditStatus.Status = "Successful" $copyAuditStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject } catch { diff --git a/tests/Copy-DbaInstanceAudit.Tests.ps1 b/tests/Copy-DbaInstanceAudit.Tests.ps1 index 2e595e9c615..0c993f070b3 100644 --- a/tests/Copy-DbaInstanceAudit.Tests.ps1 +++ b/tests/Copy-DbaInstanceAudit.Tests.ps1 @@ -24,4 +24,106 @@ Describe $CommandName -Tag UnitTests { Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty } } +} + +Describe $CommandName -Tag IntegrationTests { + BeforeAll { + # We want to run all commands in the BeforeAll block with EnableException to ensure that the test fails if the setup fails. + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + # The audit is never enabled, so it writes no file into the temp folder + $auditName = "dbatoolsci_audit_$(Get-Random)" + $auditSpecName = "dbatoolsci_auditspec_$(Get-Random)" + $sourceServer = Connect-DbaInstance -SqlInstance $TestConfig.InstanceCopy1 + $destServer = Connect-DbaInstance -SqlInstance $TestConfig.InstanceCopy2 + $null = $sourceServer.Query("CREATE SERVER AUDIT [$auditName] TO FILE (FILEPATH = N'$($TestConfig.Temp)') WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE)") + + # We want to run all commands outside of the BeforeAll block without EnableException to be able to test for specific warnings. + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + AfterAll { + # 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 + + $cleanupSql = @" +IF EXISTS (SELECT 1 FROM sys.server_audit_specifications WHERE name = N'$auditSpecName') +BEGIN + ALTER SERVER AUDIT SPECIFICATION [$auditSpecName] WITH (STATE = OFF); + DROP SERVER AUDIT SPECIFICATION [$auditSpecName]; +END +IF EXISTS (SELECT 1 FROM sys.server_audits WHERE name = N'$auditName') + DROP SERVER AUDIT [$auditName]; +"@ + $null = $sourceServer.Query($cleanupSql) + $null = $destServer.Query($cleanupSql) + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + Context "When copying a server audit" { + BeforeAll { + $splatCopyAudit = @{ + Source = $TestConfig.InstanceCopy1 + Destination = $TestConfig.InstanceCopy2 + Audit = $auditName + } + $results = Copy-DbaInstanceAudit @splatCopyAudit + } + + It "Returns one successful result" { + # Counts the output itself, $results.Status.Count would skip a $null written by the command + @($results).Count | Should -Be 1 + $results.Name | Should -Be $auditName + $results.Status | Should -Be "Successful" + } + + It "Creates the audit on the destination" { + $destServer.Audits.Refresh() + $destServer.Audits[$auditName] | Should -Not -BeNullOrEmpty + } + } + + Context "When the audit exists on the destination" { + It "Reports the audit as skipped" { + $splatCopyExisting = @{ + Source = $TestConfig.InstanceCopy1 + Destination = $TestConfig.InstanceCopy2 + Audit = $auditName + } + $results = Copy-DbaInstanceAudit @splatCopyExisting + @($results).Count | Should -Be 1 + $results.Status | Should -Be "Skipped" + $results.Notes | Should -Be "Already exists on destination" + } + } + + Context "When the audit exists on the destination with a specification and -Force is used" { + BeforeAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + # An enabled specification that uses the audit has to be dropped before the audit can be replaced + $null = $destServer.Query("CREATE SERVER AUDIT SPECIFICATION [$auditSpecName] FOR SERVER AUDIT [$auditName] ADD (FAILED_LOGIN_GROUP) WITH (STATE = ON)") + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + + $splatCopyForce = @{ + Source = $TestConfig.InstanceCopy1 + Destination = $TestConfig.InstanceCopy2 + Audit = $auditName + Force = $true + } + $forceResults = Copy-DbaInstanceAudit @splatCopyForce + } + + It "Replaces the audit" { + @($forceResults).Count | Should -Be 1 + $forceResults.Status | Should -Be "Successful" + } + + It "Drops the specification that used the audit" { + $destServer.ServerAuditSpecifications.Refresh() + $destServer.ServerAuditSpecifications[$auditSpecName] | Should -BeNullOrEmpty + } + } } \ No newline at end of file From 649e45ac9e1e2e699165bff65b851a3ba49c83b2 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:38 +0200 Subject: [PATCH 12/17] Copy-DbaInstanceAuditSpecification - Stop writing a $null into the output of every copied specification Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Every copied specification put a $null in front of its result. The command had no integration test. The new one copies a specification whose audit exists on both instances and counts the output. Part of #10794 (do Copy-DbaInstanceAuditSpecification) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaInstanceAuditSpecification.ps1 | 2 +- ...py-DbaInstanceAuditSpecification.Tests.ps1 | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/public/Copy-DbaInstanceAuditSpecification.ps1 b/public/Copy-DbaInstanceAuditSpecification.ps1 index cfbf6d729a6..df5df7b1725 100644 --- a/public/Copy-DbaInstanceAuditSpecification.ps1 +++ b/public/Copy-DbaInstanceAuditSpecification.ps1 @@ -200,7 +200,7 @@ function Copy-DbaInstanceAuditSpecification { Write-Message -Level Verbose -Message "Copying server audit $auditSpecName" $sql = $auditSpec.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyAuditSpecStatus.Status = "Successful" $copyAuditSpecStatus | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject } catch { diff --git a/tests/Copy-DbaInstanceAuditSpecification.Tests.ps1 b/tests/Copy-DbaInstanceAuditSpecification.Tests.ps1 index bd484d3aa78..052c9a1c0b0 100644 --- a/tests/Copy-DbaInstanceAuditSpecification.Tests.ps1 +++ b/tests/Copy-DbaInstanceAuditSpecification.Tests.ps1 @@ -23,4 +23,63 @@ Describe $CommandName -Tag UnitTests { Compare-Object -ReferenceObject $expectedParameters -DifferenceObject $hasParameters | Should -BeNullOrEmpty } } +} + +Describe $CommandName -Tag IntegrationTests { + BeforeAll { + # We want to run all commands in the BeforeAll block with EnableException to ensure that the test fails if the setup fails. + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + # The specification needs its audit on both instances. The audit is never enabled, so it writes no file into the temp folder. + $auditName = "dbatoolsci_audit_$(Get-Random)" + $auditSpecName = "dbatoolsci_auditspec_$(Get-Random)" + $sourceServer = Connect-DbaInstance -SqlInstance $TestConfig.InstanceCopy1 + $destServer = Connect-DbaInstance -SqlInstance $TestConfig.InstanceCopy2 + $createAuditSql = "CREATE SERVER AUDIT [$auditName] TO FILE (FILEPATH = N'$($TestConfig.Temp)') WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE)" + $null = $sourceServer.Query($createAuditSql) + $null = $destServer.Query($createAuditSql) + $null = $sourceServer.Query("CREATE SERVER AUDIT SPECIFICATION [$auditSpecName] FOR SERVER AUDIT [$auditName] ADD (FAILED_LOGIN_GROUP)") + + # We want to run all commands outside of the BeforeAll block without EnableException to be able to test for specific warnings. + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + AfterAll { + # 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 + + $cleanupSql = @" +IF EXISTS (SELECT 1 FROM sys.server_audit_specifications WHERE name = N'$auditSpecName') + DROP SERVER AUDIT SPECIFICATION [$auditSpecName]; +IF EXISTS (SELECT 1 FROM sys.server_audits WHERE name = N'$auditName') + DROP SERVER AUDIT [$auditName]; +"@ + $null = $sourceServer.Query($cleanupSql) + $null = $destServer.Query($cleanupSql) + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + Context "When copying a server audit specification" { + BeforeAll { + $splatCopySpec = @{ + Source = $TestConfig.InstanceCopy1 + Destination = $TestConfig.InstanceCopy2 + AuditSpecification = $auditSpecName + } + $results = Copy-DbaInstanceAuditSpecification @splatCopySpec + } + + It "Returns one successful result" { + # Counts the output itself, $results.Status.Count would skip a $null written by the command + @($results).Count | Should -Be 1 + $results.Name | Should -Be $auditSpecName + $results.Status | Should -Be "Successful" + } + + It "Creates the specification on the destination" { + $destServer.ServerAuditSpecifications.Refresh() + $destServer.ServerAuditSpecifications[$auditSpecName].AuditName | Should -Be $auditName + } + } } \ No newline at end of file From e0d7d0fd536decd0e3c1b1d9b2549cbdc6f9860a Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:39 +0200 Subject: [PATCH 13/17] Copy-DbaLinkedServer - Stop writing a $null into the output for linked servers with another network name Query() returns $null for a batch without a result set, and the call had no $null = in front of it. A SQL Server linked server whose name differs from its data source gets its network name set with sp_setnetname after the copy, and that call put a $null in front of the result. The existing test copies a linked server whose name is its data source, so it never got there. A new context copies one whose network name was changed with sp_setnetname, counts the output and checks the network name on the destination. Part of #10794 (do Copy-DbaLinkedServer) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaLinkedServer.ps1 | 2 +- tests/Copy-DbaLinkedServer.Tests.ps1 | 47 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/public/Copy-DbaLinkedServer.ps1 b/public/Copy-DbaLinkedServer.ps1 index e7808b53981..b0c0db4ac0d 100644 --- a/public/Copy-DbaLinkedServer.ps1 +++ b/public/Copy-DbaLinkedServer.ps1 @@ -281,7 +281,7 @@ function Copy-DbaLinkedServer { if ($copyLinkedServer.ProductName -eq 'SQL Server' -and $copyLinkedServer.Name -ne $copyLinkedServer.DataSource) { $sql2 = "EXEC sp_setnetname '$($copyLinkedServer.Name)', '$($copyLinkedServer.DataSource)'; " - $destServer.Query($sql2) + $null = $destServer.Query($sql2) } $destServer.LinkedServers.Refresh() diff --git a/tests/Copy-DbaLinkedServer.Tests.ps1 b/tests/Copy-DbaLinkedServer.Tests.ps1 index 1fe640bf2e8..99e4b603cca 100644 --- a/tests/Copy-DbaLinkedServer.Tests.ps1 +++ b/tests/Copy-DbaLinkedServer.Tests.ps1 @@ -93,4 +93,51 @@ Describe $CommandName -Tag IntegrationTests { $results.Status | Should -BeExactly "Skipped" } } + + Context "When copying a SQL Server linked server whose network name differs from its name" { + BeforeAll { + # The copy runs sp_setnetname on the destination only for such a linked server + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + $netNameSql = @" +EXEC master.dbo.sp_addlinkedserver @server = N'dbatoolsci_netname', @srvproduct = N'SQL Server'; +EXEC master.dbo.sp_setnetname @server = N'dbatoolsci_netname', @netname = N'localhost'; +"@ + $null = $server1.Query($netNameSql) + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + + $splatCopyNetName = @{ + Source = $TestConfig.InstanceCopy1 + Destination = $TestConfig.InstanceCopy2 + LinkedServer = "dbatoolsci_netname" + WarningAction = "SilentlyContinue" + } + $netNameResults = Copy-DbaLinkedServer @splatCopyNetName + } + + AfterAll { + $PSDefaultParameterValues["*-Dba*:EnableException"] = $true + + $dropNetNameSql = @" +IF EXISTS (SELECT 1 FROM sys.servers WHERE name = N'dbatoolsci_netname') + EXEC master.dbo.sp_dropserver @server = N'dbatoolsci_netname', @droplogins = 'droplogins'; +"@ + $null = $server1.Query($dropNetNameSql) + $null = $server2.Query($dropNetNameSql) + + $PSDefaultParameterValues.Remove("*-Dba*:EnableException") + } + + It "Returns one successful result" { + # Counts the output itself, $netNameResults.Status.Count would skip a $null written by the command + @($netNameResults).Count | Should -Be 1 + $netNameResults.Status | Should -BeExactly "Successful" + } + + It "Keeps the network name on the destination" { + $server2.LinkedServers.Refresh() + $server2.LinkedServers["dbatoolsci_netname"].DataSource | Should -BeExactly "localhost" + } + } } \ No newline at end of file From 7f80f41f2da0ffa6fbdd3c7675a733ee70f5123c Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:39 +0200 Subject: [PATCH 14/17] Copy-DbaResourceGovernor - Copy pools with -ExcludeResourcePool and -Force, and stop writing $null Three defects of the same command: - Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Sixteen sites - the classifier function, the reconfigurations, every pool and every workload group - put $null between the results. - -ExcludeResourcePool assigned the filtered pools to $pool instead of $pools, so no pool was copied at all. The filter now also leaves out the system pools internal and default, as the default path does. - -Force dropped the workload groups of an existing pool while it enumerated their collection, which failed with "Collection was modified", so replacing a pool that has workload groups always reported Failed. The groups are collected first. The test counts the output against the status values, because their number depends on the edition, and a new case copies with -ExcludeResourcePool and -Force over the pools of the first copy. Part of #10794 (do Copy-DbaResourceGovernor) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaResourceGovernor.ps1 | 37 ++++++++++++------------ tests/Copy-DbaResourceGovernor.Tests.ps1 | 17 +++++++++++ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/public/Copy-DbaResourceGovernor.ps1 b/public/Copy-DbaResourceGovernor.ps1 index 28174a0c614..913a62ad0fc 100644 --- a/public/Copy-DbaResourceGovernor.ps1 +++ b/public/Copy-DbaResourceGovernor.ps1 @@ -173,12 +173,12 @@ function Copy-DbaResourceGovernor { Write-Message -Level Verbose -Message "Creating function." $script = $sourceClassifierFunction.Script() | Where-Object { $_ -notmatch '^SET QUOTED_IDENTIFIER' -and $_ -notmatch '^SET ANSI_NULLS' } - $destServer.Query($script) + $null = $destServer.Query($script) $sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = $fullyQualifiedFunctionName);" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Mapping Resource Governor classifier function." - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyResourceGovClassifierFunc.Status = "Successful" $copyResourceGovClassifierFunc.Notes = "The new classifier function has been created" @@ -187,7 +187,7 @@ function Copy-DbaResourceGovernor { $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Reconfiguring Resource Governor." - $destServer.Query($sql) + $null = $destServer.Query($sql) } else { if ($Force -eq $false) { $copyResourceGovClassifierFunc.Status = "Skipped" @@ -198,12 +198,12 @@ function Copy-DbaResourceGovernor { $sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = NULL);" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Disabling the Resource Governor." - $destServer.Query($sql) + $null = $destServer.Query($sql) $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Reconfiguring Resource Governor." - $destServer.Query($sql) + $null = $destServer.Query($sql) Write-Message -Level Verbose -Message "Dropping the destination classifier function." $destServer = Connect-DbaInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential @@ -212,17 +212,17 @@ function Copy-DbaResourceGovernor { Write-Message -Level Verbose -Message "Re-creating the Resource Governor classifier function." $script = $sourceClassifierFunction.Script() | Where-Object { $_ -notmatch '^SET QUOTED_IDENTIFIER' -and $_ -notmatch '^SET ANSI_NULLS' } - $destServer.Query($script) + $null = $destServer.Query($script) $sql = "ALTER RESOURCE GOVERNOR WITH (CLASSIFIER_FUNCTION = $fullyQualifiedFunctionName);" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Mapping Resource Governor classifier function." - $destServer.Query($sql) + $null = $destServer.Query($sql) $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Reconfiguring Resource Governor." - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyResourceGovClassifierFunc.Status = "Successful" $copyResourceGovClassifierFunc.Notes = "The old classifier function has been overwritten." @@ -237,7 +237,7 @@ function Copy-DbaResourceGovernor { $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Reconfiguring Resource Governor." - $destServer.Query($sql) + $null = $destServer.Query($sql) Write-Message -Level Verbose -Message "Issue reconfiguring Resource Governor on $destinstance | $PSItem" } } @@ -247,7 +247,7 @@ function Copy-DbaResourceGovernor { if ($ResourcePool) { $pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object Name -In $ResourcePool } elseif ($ExcludeResourcePool) { - $pool = $sourceServer.ResourceGovernor.ResourcePools | Where-Object Name -NotIn $ExcludeResourcePool + $pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object { $_.Name -notin "internal", "default" -and $_.Name -notin $ExcludeResourcePool } } else { $pools = $sourceServer.ResourceGovernor.ResourcePools | Where-Object { $_.Name -notin "internal", "default" } } @@ -282,7 +282,8 @@ function Copy-DbaResourceGovernor { try { $destServer = Connect-DbaInstance -SqlInstance $destinstance -SqlCredential $DestinationSqlCredential $destPool = $destServer.ResourceGovernor.ResourcePools[$poolName] - $workloadGroups = $destPool.WorkloadGroups + # Collected first, a drop inside the enumeration would modify the collection + $workloadGroups = @($destPool.WorkloadGroups) foreach ($workloadGroup in $workloadGroups) { $workloadGroup.Drop() } @@ -298,7 +299,7 @@ function Copy-DbaResourceGovernor { $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Reconfiguring Resource Governor." - $destServer.Query($sql) + $null = $destServer.Query($sql) continue } } @@ -310,7 +311,7 @@ function Copy-DbaResourceGovernor { $sql = $pool.Script() | Out-String Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Copying pool $poolName." - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyResourceGovPool.Status = "Successful" $copyResourceGovPool | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject @@ -332,7 +333,7 @@ function Copy-DbaResourceGovernor { $sql = $workloadGroup.Script() | Out-String Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Copying $workgroupName." - $destServer.Query($sql) + $null = $destServer.Query($sql) $copyResourceGovWorkGroup.Status = "Successful" $copyResourceGovWorkGroup | Select-DefaultView -Property DateTime, SourceServer, DestinationServer, Name, Type, Status, Notes -TypeName MigrationObject @@ -340,7 +341,7 @@ function Copy-DbaResourceGovernor { $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Reconfiguring Resource Governor." - $destServer.Query($sql) + $null = $destServer.Query($sql) } } catch { if ($copyResourceGovWorkGroup) { @@ -363,15 +364,15 @@ function Copy-DbaResourceGovernor { try { if (!$sourceServer.ResourceGovernor.Enabled) { $sql = "ALTER RESOURCE GOVERNOR DISABLE" - $destServer.Query($sql) + $null = $destServer.Query($sql) $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE;" Write-Message -Level Debug -Message $sql Write-Message -Level Verbose -Message "Reconfiguring Resource Governor." - $destServer.Query($sql) + $null = $destServer.Query($sql) } else { $sql = "ALTER RESOURCE GOVERNOR RECONFIGURE" - $destServer.Query($sql) + $null = $destServer.Query($sql) } } catch { $altermsg = $_.Exception diff --git a/tests/Copy-DbaResourceGovernor.Tests.ps1 b/tests/Copy-DbaResourceGovernor.Tests.ps1 index 090f650ee05..518bce6cf93 100644 --- a/tests/Copy-DbaResourceGovernor.Tests.ps1 +++ b/tests/Copy-DbaResourceGovernor.Tests.ps1 @@ -90,9 +90,26 @@ Describe $CommandName -Tag IntegrationTests { $results = Copy-DbaResourceGovernor @splatCopyRG $results.Status | Select-Object -Unique | Should -BeExactly "Successful" $results.Status.Count | Should -BeGreaterThan 3 + # Every output object is a result row, $results.Status.Count skips a $null written by the command + @($results).Count | Should -Be $results.Status.Count $results.Name | Should -Contain "dbatoolsci_prod" } + It "Copies all pools but the excluded ones" { + $splatCopyExclude = @{ + Source = $TestConfig.InstanceCopy1 + Destination = $TestConfig.InstanceCopy2 + ExcludeResourcePool = "dbatoolsci_offhoursprocessing" + Force = $true + WarningAction = "SilentlyContinue" + } + + $results = Copy-DbaResourceGovernor @splatCopyExclude + $poolResults = $results | Where-Object Type -eq "Resource Governor Pool" + $poolResults.Name | Should -Be "dbatoolsci_prod" + $poolResults.Status | Should -Be "Successful" + } + It "Returns the proper classifier function" { $results = Get-DbaRgClassifierFunction -SqlInstance $TestConfig.InstanceCopy2 $results.Name | Should -BeExactly "dbatoolsci_fnRG" From c69c4ae1c138111619d939c8e6bf15435896b275 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:40 +0200 Subject: [PATCH 15/17] Copy-DbaServerRole - Stop writing a $null into the output of every copied role Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Creating the role and granting each of its permissions put a $null in front of its result. The test counts the output itself now: $results.Status.Count skips a $null, which is how the leak went unnoticed. Part of #10794 (do Copy-DbaServerRole) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaServerRole.ps1 | 4 ++-- tests/Copy-DbaServerRole.Tests.ps1 | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/Copy-DbaServerRole.ps1 b/public/Copy-DbaServerRole.ps1 index 8506bd4dc03..7a090fbdb79 100644 --- a/public/Copy-DbaServerRole.ps1 +++ b/public/Copy-DbaServerRole.ps1 @@ -193,7 +193,7 @@ function Copy-DbaServerRole { Write-Message -Level Verbose -Message "Copying server role $roleName" $sql = $currentRole.Script() | Out-String Write-Message -Level Debug -Message $sql - $destServer.Query($sql) + $null = $destServer.Query($sql) $destServer.Roles.Refresh() $splatPermissions = @{ @@ -206,7 +206,7 @@ function Copy-DbaServerRole { $permSql = $perm.GrantStatement if ($permSql) { Write-Message -Level Debug -Message "Granting permission: $permSql" - $destServer.Query($permSql) + $null = $destServer.Query($permSql) } } catch { Write-Message -Level Warning -Message "Could not grant permission for role $roleName on $destinstance | $PSItem" diff --git a/tests/Copy-DbaServerRole.Tests.ps1 b/tests/Copy-DbaServerRole.Tests.ps1 index e1b646cdbbf..3a35d5ee75d 100644 --- a/tests/Copy-DbaServerRole.Tests.ps1 +++ b/tests/Copy-DbaServerRole.Tests.ps1 @@ -67,6 +67,8 @@ Describe $CommandName -Tag IntegrationTests { ServerRole = $testRoleName } $copyResults = Copy-DbaServerRole @splatCopyRole + # Counts the output itself, $copyResults.Status.Count would skip a $null written by the command + @($copyResults).Count | Should -Be 1 $copyResults.Name | Should -Be $testRoleName $copyResults.Status | Should -Be "Successful" } From 9b3b84c23cde9a64f199e0641677257885416390 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:41 +0200 Subject: [PATCH 16/17] Copy-DbaStartupProcedure - Stop writing a $null into the output with -Force Query() returns $null for a batch without a result set, and the call had no $null = in front of it. Dropping a startup procedure that exists on the destination put a $null in front of its result. The test had no -Force case. The new one replaces the procedure that the first context copied and counts the output. Part of #10794 (do Copy-DbaStartupProcedure) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaStartupProcedure.ps1 | 2 +- tests/Copy-DbaStartupProcedure.Tests.ps1 | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/public/Copy-DbaStartupProcedure.ps1 b/public/Copy-DbaStartupProcedure.ps1 index 0567e5b19e7..090e4ceff77 100644 --- a/public/Copy-DbaStartupProcedure.ps1 +++ b/public/Copy-DbaStartupProcedure.ps1 @@ -172,7 +172,7 @@ function Copy-DbaStartupProcedure { if ($Pscmdlet.ShouldProcess($destInstance, "Dropping startup procedure $currentProcFullName and recreating")) { try { Write-Message -Level Verbose -Message "Dropping startup procedure $currentProcFullName" - $destServer.Query("DROP PROCEDURE [$($currentProcSchema)].[$($currentProcName)]") + $null = $destServer.Query("DROP PROCEDURE [$($currentProcSchema)].[$($currentProcName)]") } catch { $copyStartupProcStatus.Status = "Failed" $copyStartupProcStatus.Notes = (Get-ErrorMessage -Record $_) diff --git a/tests/Copy-DbaStartupProcedure.Tests.ps1 b/tests/Copy-DbaStartupProcedure.Tests.ps1 index f8f0ba4337d..c1ea4f49e9b 100644 --- a/tests/Copy-DbaStartupProcedure.Tests.ps1 +++ b/tests/Copy-DbaStartupProcedure.Tests.ps1 @@ -76,4 +76,24 @@ Describe $CommandName -Tag IntegrationTests { $copiedProc.Status | Should -Be "Successful" } } + + Context "When the startup procedure exists on the destination and -Force is used" { + BeforeAll { + # The previous context copied the procedure, so -Force drops it on the destination first + $splatCopyForce = @{ + Source = $TestConfig.InstanceCopy1 + Destination = $TestConfig.InstanceCopy2 + Procedure = $procName + Force = $true + } + $forceResults = Copy-DbaStartupProcedure @splatCopyForce + } + + It "Should return one successful result" { + # Counts the output itself, $forceResults.Status.Count would skip a $null written by the command + @($forceResults).Count | Should -Be 1 + $forceResults.Name | Should -Be $procName + $forceResults.Status | Should -Be "Successful" + } + } } \ No newline at end of file From 32a9d40504547d07c8e79d248de2f6f374107795 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 17 Oct 2026 21:11:41 +0200 Subject: [PATCH 17/17] Copy-DbaSystemDbUserObject - Stop writing a $null for every statement of the classic mode Query() returns $null for a batch without a result set, and the call had no $null = in front of it. The classic mode runs every statement of the transfer script through it, so it returned one $null per successful statement, while its documentation says it returns no output. The -Classic test asserted that the output is not empty, which only passed because of those $null values. It now asserts that there is no output. Part of #10794 (do Copy-DbaSystemDbUserObject) Co-Authored-By: Claude Opus 5.5 --- public/Copy-DbaSystemDbUserObject.ps1 | 2 +- tests/Copy-DbaSystemDbUserObject.Tests.ps1 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/Copy-DbaSystemDbUserObject.ps1 b/public/Copy-DbaSystemDbUserObject.ps1 index 78d4ec6cbf5..54938a16d4f 100644 --- a/public/Copy-DbaSystemDbUserObject.ps1 +++ b/public/Copy-DbaSystemDbUserObject.ps1 @@ -405,7 +405,7 @@ function Copy-DbaSystemDbUserObject { Write-Message -Level Debug -Message "$sql" if ($PSCmdlet.ShouldProcess($destInstance, $sql)) { try { - $destServer.Query($sql, $systemDb) + $null = $destServer.Query($sql, $systemDb) } catch { # Don't care - long story having to do with duplicate stuff # here to avoid an empty catch diff --git a/tests/Copy-DbaSystemDbUserObject.Tests.ps1 b/tests/Copy-DbaSystemDbUserObject.Tests.ps1 index b232f3aa907..e190511642b 100644 --- a/tests/Copy-DbaSystemDbUserObject.Tests.ps1 +++ b/tests/Copy-DbaSystemDbUserObject.Tests.ps1 @@ -86,7 +86,8 @@ AS It "Should execute successfully with -Classic parameter" { $results = Copy-DbaSystemDbUserObject -Source $TestConfig.InstanceSingle -Destination $TestConfig.InstanceSingle -Classic - $results | Should -Not -BeNullOrEmpty + # The Classic mode returns no output, the $null of every executed statement used to end up here + @($results).Count | Should -Be 0 } It "Should execute successfully with -Force parameter" {