From b9eadec4a17118f7d3ea2927fe89f235b9ca0ff4 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Sat, 29 Aug 2026 16:25:09 +0200 Subject: [PATCH] Get-DbaRandomizedValue - Stop eating the caller loop on invalid parameters All ten validation guards in the begin block called Stop-Function -Continue, but no loop encloses them: without -EnableException the continue escaped the command and consumed an iteration of whatever loop the caller was running in. The data masking and generation commands call this per row, and the command's own test suite had even grown a workaround - the every-randomizer-type loop pre-writes its result entry because the escaped continue used to eat its foreach iteration. That comment now documents the history instead of the workaround. Part of the #10638 inventory, same fix shape as #10636-#10639: every guard stops and returns, and the process block's Test-FunctionInterrupt guard handles the rest. The new regression test loops three times over a call without any type parameter and completed zero iterations on the unfixed command. Verified via the lab harness: 10 tests, 0 failed; the unfixed command fails the new test with "Expected 3, but got 0". References #10638 (do Get-DbaRandomizedValue) Co-Authored-By: Claude Fable 5 --- public/Get-DbaRandomizedValue.ps1 | 33 ++++++++++++++++++-------- tests/Get-DbaRandomizedValue.Tests.ps1 | 23 ++++++++++++++---- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/public/Get-DbaRandomizedValue.ps1 b/public/Get-DbaRandomizedValue.ps1 index 104b959ab92..5b13fa805f8 100644 --- a/public/Get-DbaRandomizedValue.ps1 +++ b/public/Get-DbaRandomizedValue.ps1 @@ -214,31 +214,40 @@ function Get-DbaRandomizedValue { $supportedDataTypes = 'bigint', 'bit', 'bool', 'char', 'date', 'datetime', 'datetime2', 'decimal', 'int', 'float', 'guid', 'money', 'numeric', 'nchar', 'ntext', 'nvarchar', 'real', 'smalldatetime', 'smallint', 'text', 'time', 'tinyint', 'uniqueidentifier', 'userdefineddatatype', 'varchar' # Check the variables + # None of these validation stops may use -Continue: 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 - and the data masking and generation commands call this in a loop per row. if (-not $DataType -and -not $RandomizerType -and -not $RandomizerSubType) { - Stop-Function -Message "Please use one of the variables i.e. -DataType, -RandomizerType or -RandomizerSubType" -Continue + Stop-Function -Message "Please use one of the variables i.e. -DataType, -RandomizerType or -RandomizerSubType" + return } elseif ($DataType -and ($RandomizerType -or $RandomizerSubType)) { - Stop-Function -Message "You cannot use -DataType with -RandomizerType or -RandomizerSubType" -Continue + Stop-Function -Message "You cannot use -DataType with -RandomizerType or -RandomizerSubType" + return } elseif (-not $RandomizerSubType -and $RandomizerType) { - Stop-Function -Message "Please enter a sub type" -Continue + Stop-Function -Message "Please enter a sub type" + return } elseif (-not $RandomizerType -and $RandomizerSubType) { $RandomizerType = $uniqueSubType } if ($DataType -and $DataType.ToLowerInvariant() -notin $supportedDataTypes) { - Stop-Function -Message "Unsupported sql data type" -Continue -Target $DataType + Stop-Function -Message "Unsupported sql data type" -Target $DataType + return } # Check the bogus type if ($RandomizerType) { if ($RandomizerType -notin $script:uniquerandomizertypes) { - Stop-Function -Message "Invalid randomizer type" -Continue -Target $RandomizerType + Stop-Function -Message "Invalid randomizer type" -Target $RandomizerType + return } } # Check the sub type if ($RandomizerSubType) { if ($RandomizerSubType -notin $script:uniquerandomizersubtype) { - Stop-Function -Message "Invalid randomizer sub type" -Continue -Target $RandomizerSubType + Stop-Function -Message "Invalid randomizer sub type" -Target $RandomizerSubType + return } # The type and the subtype used to be checked against two independent lists, so Name/ZipCode passed @@ -247,21 +256,25 @@ function Get-DbaRandomizedValue { $randomizerCombination = $script:randomizerTypes.Group | Where-Object { $_.Type -eq $RandomizerType -and $_.SubType -eq $RandomizerSubType } | Select-Object -First 1 if (-not $randomizerCombination) { - Stop-Function -Message "Randomizer type $RandomizerType has no sub type $RandomizerSubType, run Get-DbaRandomizedType to list the valid combinations" -Continue -Target $RandomizerSubType + Stop-Function -Message "Randomizer type $RandomizerType has no sub type $RandomizerSubType, run Get-DbaRandomizedType to list the valid combinations" -Target $RandomizerSubType + return } # Some combinations need input that cannot be made up. The randomizer types say which parameter # that is, so the list stays the single place that knows. if ($randomizerCombination.RequiredParameter -eq "Value" -and -not $Value) { - Stop-Function -Message "Value cannot be empty when using sub type $RandomizerSubType" -Continue -Target $RandomizerSubType + Stop-Function -Message "Value cannot be empty when using sub type $RandomizerSubType" -Target $RandomizerSubType + return } if ($randomizerCombination.RequiredParameter -eq "Format" -and -not $Format) { - Stop-Function -Message "Format cannot be empty when using sub type $RandomizerSubType, use something like ###-###" -Continue -Target $RandomizerSubType + Stop-Function -Message "Format cannot be empty when using sub type $RandomizerSubType, use something like ###-###" -Target $RandomizerSubType + return } if ($randomizerCombination.RequiredParameter -eq "StaticValue") { - Stop-Function -Message "Randomizer type $RandomizerType does not generate a value, it marks a value that the configuration supplies" -Continue -Target $RandomizerType + Stop-Function -Message "Randomizer type $RandomizerType does not generate a value, it marks a value that the configuration supplies" -Target $RandomizerType + return } } diff --git a/tests/Get-DbaRandomizedValue.Tests.ps1 b/tests/Get-DbaRandomizedValue.Tests.ps1 index 75535201dd9..f5470ce02bb 100644 --- a/tests/Get-DbaRandomizedValue.Tests.ps1 +++ b/tests/Get-DbaRandomizedValue.Tests.ps1 @@ -83,6 +83,19 @@ Describe $CommandName -Tag IntegrationTests { It "Accepts the same sub type under the type it belongs to" { Get-DbaRandomizedValue -RandomizerType Address -RandomizerSubType ZipCode | Should -Not -BeNullOrEmpty } + + It "Warns without eating an iteration of the caller's loop" { + # The validation guards 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, so the counter fell short (#10638). + $loopCount = 0 + foreach ($i in 1..3) { + $null = Get-DbaRandomizedValue -WarningAction SilentlyContinue + $loopCount++ + } + $loopCount | Should -Be 3 + $WarnVar | Should -BeLike "*Please use one of the variables*" + } } Context "Every randomizer type" { @@ -96,10 +109,12 @@ Describe $CommandName -Tag IntegrationTests { foreach ($randomizerType in (Get-DbaRandomizedType)) { $typeKey = "$($randomizerType.Type)/$($randomizerType.SubType)" - # Stop-Function -Continue from the begin block skips the rest of this iteration, so this entry - # is written first and only overwritten when the call comes back. Landing on it means a message - # was given. A Stop-Function inside the switch is swallowed by the switch instead, and those - # calls do come back, so the warning is checked as well. + # Landing on "message" means the call warned instead of producing a value - the guard in + # the begin block returns nothing and sets the warning, and a Stop-Function inside the + # switch is swallowed by the switch. Both come back, so the warning is checked below; the + # pre-written entry stays as a safety net. (Before #10638 the begin block guards escaped + # the command via a loop-less -Continue and ate this very foreach iteration, which is why + # the entry is written first.) $typeResults[$typeKey] = "message" $typeWarning = $null