From 211fd90d0eae810f1dc47ce62d5c0b79506dde30 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 12 Sep 2026 15:25:39 +0200 Subject: [PATCH 1/2] Invoke-DbaDbDecryptObject - Stop eating the caller loop when the export folder cannot be created Stop-Function -Continue runs PowerShell's continue. No loop encloses this call site inside the command, so the continue unwound out of the command and consumed an iteration of whatever loop the caller runs in: a user's foreach silently skipped an element, and Pester's runner corrupted. The escape only bites the non-EnableException path; with EnableException Stop-Function throws before it gets there. The guard itself was dead as well: the failing New-Item was not a terminating error, so a folder that could not be created was ignored and the export failed later. New-Item now stops, so the guard fires as designed. Part of #10638 (do Invoke-DbaDbDecryptObject) Co-Authored-By: Claude Fable 5.1 --- public/Invoke-DbaDbDecryptObject.ps1 | 7 +++-- tests/Invoke-DbaDbDecryptObject.Tests.ps1 | 33 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/public/Invoke-DbaDbDecryptObject.ps1 b/public/Invoke-DbaDbDecryptObject.ps1 index 9c34c16ede8..10c7ae76682 100644 --- a/public/Invoke-DbaDbDecryptObject.ps1 +++ b/public/Invoke-DbaDbDecryptObject.ps1 @@ -209,9 +209,12 @@ function Invoke-DbaDbDecryptObject { if ($ExportDestination -and -not (Test-Path $ExportDestination)) { try { # Create the new destination - New-Item -Path $ExportDestination -ItemType Directory -Force | Out-Null + New-Item -Path $ExportDestination -ItemType Directory -Force -ErrorAction Stop | Out-Null } catch { - Stop-Function -Message "Couldn't create destination folder $ExportDestination" -ErrorRecord $_ -Target $instance -Continue + # No -Continue here: the begin block has no enclosing loop, so the continue would escape the command + # and eat an iteration of whatever loop the caller runs in (#10638). The process block is guarded. + Stop-Function -Message "Couldn't create destination folder $ExportDestination" -ErrorRecord $_ -Target $instance + return } } diff --git a/tests/Invoke-DbaDbDecryptObject.Tests.ps1 b/tests/Invoke-DbaDbDecryptObject.Tests.ps1 index 680472f274d..9e273faaa66 100644 --- a/tests/Invoke-DbaDbDecryptObject.Tests.ps1 +++ b/tests/Invoke-DbaDbDecryptObject.Tests.ps1 @@ -1181,4 +1181,37 @@ Describe $CommandName -Tag IntegrationTests { $contextAfter | Should -Be $contextBefore } } + + Context "When the export destination cannot be created" { + BeforeAll { + # A file where the destination folder should go, so the folder cannot be created underneath it. + $blockingFile = Join-Path -Path $env:TEMP -ChildPath "dbatoolsci_blocking_$(Get-Random).txt" + Set-Content -Path $blockingFile -Value "dbatoolsci" + } + + AfterAll { + Remove-Item -Path $blockingFile -Force -ErrorAction SilentlyContinue + } + + It "Warns without eating an iteration of the caller's loop" { + # The folder check used to run Stop-Function -Continue in the begin block, where no loop encloses + # it - the continue escaped the command and consumed an iteration of this very loop (#10638). It + # also never fired, because the failing New-Item was not a terminating error, so a folder that could + # not be created was silently ignored. The check fires before any connection is made. + $loopCount = 0 + foreach ($i in 1..3) { + $splatBadFolder = @{ + SqlInstance = $TestConfig.InstanceMulti1 + Database = "dbatoolsci_none" + ObjectName = "dbatoolsci_none" + ExportDestination = "$blockingFile\dbatoolsci_sub" + WarningAction = "SilentlyContinue" + } + $null = Invoke-DbaDbDecryptObject @splatBadFolder + $loopCount++ + } + $loopCount | Should -Be 3 + ($WarnVar -join " ") | Should -BeLike "*Couldn't create destination folder*" + } + } } From 73009210f56ff3baca646169684d69d00bad2280 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 12 Sep 2026 16:11:58 +0200 Subject: [PATCH 2/2] Invoke-DbaDbDecryptObject - Trigger the folder guard with a drive that does not exist New-Item -Force on a folder under a file returns quietly on both editions, a missing drive is what makes it throw. (do Invoke-DbaDbDecryptObject) Co-Authored-By: Claude Fable 5.1 --- tests/Invoke-DbaDbDecryptObject.Tests.ps1 | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/Invoke-DbaDbDecryptObject.Tests.ps1 b/tests/Invoke-DbaDbDecryptObject.Tests.ps1 index 9e273faaa66..511ed7e03a2 100644 --- a/tests/Invoke-DbaDbDecryptObject.Tests.ps1 +++ b/tests/Invoke-DbaDbDecryptObject.Tests.ps1 @@ -1184,13 +1184,10 @@ Describe $CommandName -Tag IntegrationTests { Context "When the export destination cannot be created" { BeforeAll { - # A file where the destination folder should go, so the folder cannot be created underneath it. - $blockingFile = Join-Path -Path $env:TEMP -ChildPath "dbatoolsci_blocking_$(Get-Random).txt" - Set-Content -Path $blockingFile -Value "dbatoolsci" - } - - AfterAll { - Remove-Item -Path $blockingFile -Force -ErrorAction SilentlyContinue + # A destination on a drive that does not exist: New-Item -Force on a folder under a file returns + # quietly, a missing drive is what makes it throw on both editions. + $freeDriveLetter = [char[]](90..65) | Where-Object { -not (Test-Path -Path "$([char]$PSItem):\") } | Select-Object -First 1 + $badDestination = "$([char]$freeDriveLetter):\dbatoolsci_nodrive\export" } It "Warns without eating an iteration of the caller's loop" { @@ -1204,14 +1201,14 @@ Describe $CommandName -Tag IntegrationTests { SqlInstance = $TestConfig.InstanceMulti1 Database = "dbatoolsci_none" ObjectName = "dbatoolsci_none" - ExportDestination = "$blockingFile\dbatoolsci_sub" + ExportDestination = $badDestination WarningAction = "SilentlyContinue" } $null = Invoke-DbaDbDecryptObject @splatBadFolder $loopCount++ } $loopCount | Should -Be 3 - ($WarnVar -join " ") | Should -BeLike "*Couldn't create destination folder*" + ($WarnVar -join " ") | Should -BeLike "*create destination folder $badDestination*" } } }