Skip to content

Stop-Function -Continue without an enclosing loop escapes the command and corrupts the caller #10638

Description

@andreasjordan

Summary

Stop-Function -Continue executes PowerShell's continue. When no loop encloses the call site inside the command's own body, that flow control does not stop at the function boundary: it unwinds into the caller and consumes an iteration of whatever loop the caller happens to be running in. A user restoring or querying in a foreach over instances silently skips elements when a command hits its error path; under Pester it corrupts the test runner itself, which surfaced for years as the undiagnosable Cannot bind argument to parameter 'ErrorRecord' because it is null crash documented above the skipped StopAt tests.

Two demonstrations, both lab-measured:

The escape bites only the non-EnableException path for -Continue (with EnableException, Stop-Function throws first). -SilentlyContinue executes continue in both modes, so it can escape even under EnableException.

Inventory

An AST sweep over all 1,591 -Continue/-SilentlyContinue sites in public/ and private/functions/ (walking each call's parents for a loop or switch, which also catches continue) found 91 sites with no enclosing loop: 31 in begin blocks, 23 in process, 25 in end, and 12 inside nested helper functions where the continue binds dynamically at the helper's caller and needs per-call-site triage.

Top-level sites per file: Get-DbaRandomizedValue (10), Invoke-DbaPfRelog (10), Set-DbaLogin (6), New-DbaLogShippingPrimarySecondary (5), New-DbaLogShippingSecondaryDatabase (4), New-DbaLogShippingSecondaryPrimary (4), New-DbaAvailabilityGroup (4), Import-DbaSpConfigure (4); two each in Test-DbaLastBackup, Get-DbaRandomizedDataset, New-DbaDbDataGeneratorConfig, Add-DbaReplArticle, New-DbaReplSubscription, Convert-DbaMaskingValue, Import-DbaBinaryFile; one each in Invoke-DbaDbLogShipping, Invoke-DbaDbDecryptObject, Set-DbaAgentOperator, Invoke-DbaDbDataGenerator, New-DbaAzAccessToken, Get-DbaRandomizedType, Stop-DbaExternalProcess, Get-DbaDbObjectTrigger, Get-DbaBackupInformation, Export-DbaDacPackage, Expand-DbaDbLogFile, Copy-DbaDatabase, New-DbaLogShippingPrimaryDatabase, Get-LoginPasswordHash, Get-DbaHelp, Get-DbaWaitResource, Convert-DbaIndexToTable. Nested helpers: export-cert (3), Get-MountPointFromDefaultPath (2), Invoke-ProjectDeployment (2), New-SqlTable (2), Get-JobHistory, Get-MountPointFromPath, New-SqlTableWithInferredSchema. One scriptblock-boundary oddity: Import-DbaPfDataCollectorSetTemplate.

Plan

Work through the list one command per PR (fix = stop-and-return, mirroring #10636/#10637, each with a loop-counter regression test proven red on the unfixed command), families batched only where they share one mechanism. Nested-helper sites get call-site triage first, since a helper called from its command's loop is caught there and may be intentional.

Created by Claude and reviewed by Andreas Jordan.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions