Skip to content

Get-DbaBackupInformation - Stop eating the caller loop when a backup header cannot be read - #10701

Open
andreasjordan wants to merge 4 commits into
developmentfrom
fix-get-dbabackupinformation-continue-escape
Open

Get-DbaBackupInformation - Stop eating the caller loop when a backup header cannot be read#10701
andreasjordan wants to merge 4 commits into
developmentfrom
fix-get-dbabackupinformation-continue-escape

Conversation

@andreasjordan

Copy link
Copy Markdown
Collaborator

Part of #10638, the sweep for Stop-Function -Continue outside an enclosing loop. Same shape as #10637.

Problem

Get-DbaBackupInformation reports a failed header read from the catch around Read-DbaBackupHeader, which runs with -EnableException so the catch is live with Stop-Function -Continue (public/Get-DbaBackupInformation.ps1:352). No loop encloses that site inside the command, so without -EnableException the continue unwinds out of the command and consumes an iteration of whatever loop the caller runs in. Proven in the lab with the plain-script recipe: three calls in a foreach left the loop counter at 0 on development and at 3 with the fix.

What changed

The -Continue is dropped and an explicit return ends the block for that input, as in #10637. Nothing else changes.

Tests

New context When a file is not a backup: a text file on the shared path the instance can read, three calls, asserting the loop count and the Failure on warning. Through the testing-dbatools harness: green on both editions with the fix; the plain-script proof above is the red-on-old.

created by Claude and reviewed by Andreas Jordan

🤖 Generated with Claude Code

andreasjordan and others added 2 commits September 12, 2026 15:25
…header cannot be read

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.

Part of #10638

(do Get-DbaBackupInformation)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…to Read-DbaBackupHeader

The S3 CI test expects "S3 paths cannot be enumerated using T-SQL" for an
s3:// folder, but an S3 URL forces the non-xp_dirtree branch, where the folder
was appended to the file list and Read-DbaBackupHeader rejected it. That
rejection used a broken string literal, "Path ("$p") should be ...", which
handed $p to Stop-Function as a positional argument and turned the warning into
a binding error. On development the escaping continue of the header-read catch
ended the CI test before its assertions, so it passed vacuously; the first
commit of this branch made it fail honestly.

An s3:// path without an extension is now reported through Stop-Function
-Continue inside the path loop, and Read-DbaBackupHeader's literal is repaired.
One regression test each.

Part of #10638

(do Get-DbaBackupInformation, Read-DbaBackupHeader)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@andreasjordan

Copy link
Copy Markdown
Collaborator Author

The S3 workflow failure is a real finding of this PR, not a flake. It is green on development and on every other branch today, and it was green there only because the escaping continue that this PR removes ended the CI test before its assertions ran.

What the failing test does. gh-s3actions.ps1 calls Get-DbaBackupInformation -Path s3://.../enumtest/ and expects the warning "S3 paths cannot be enumerated using T-SQL". That warning lives in Get-XpDirTreeRestoreFile, but an S3 URL forces $NoXpDirTree = $true, so that helper is never called for a plain path. The folder string is appended to the file list as if it were a file and handed to Read-DbaBackupHeader, whose begin block rejects folders with Stop-Function -Message "Path ("$p") should be a file, not a folder". That literal is broken: it is three tokens, so $p becomes a positional argument and the rejection turns into a parameter-binding exception. Get-DbaBackupInformation catches it and warns "Failure on ... | A positional parameter cannot be found that accepts argument ...".

On development that catch ran Stop-Function -Continue with no enclosing loop. The continue unwound out of the command and out of the It block, so the two assertions never executed and Pester recorded a pass. With the escape fixed, the assertions run and show that the warning the test expects was never produced on this path.

Fix, pushed as a second commit:

  • Get-DbaBackupInformation: an s3:// path without an extension is now reported as "S3 paths cannot be enumerated using T-SQL ..." through Stop-Function -Continue inside the foreach ($f in $path) loop (loop-enclosed, so the continue is legitimate) instead of being passed on as a file. With -EnableException it throws.
  • Read-DbaBackupHeader: the string literal is repaired to "Path ($p) should be a file, not a folder", so a folder gets the intended warning instead of a binding error. That is a second command in this PR, kept here because it is what produced the garbage message and it is one line.
  • One regression test for each: an S3 folder against a lab instance (no S3 access needed, the path is rejected before any connection to storage) and a folder passed to Read-DbaBackupHeader. Both red on the PR's first commit, green now, on both editions.

Lab results: Get-DbaBackupInformation 20/20 and Read-DbaBackupHeader 2/2 on both PowerShell 7 and Windows PowerShell 5.1. With the two source files swapped back to the first commit, the two new tests are the only failures, and the S3 one reproduces the CI message locally ("A positional parameter cannot be found that accepts argument ...").

Created by Claude and reviewed by Andreas Jordan.

@potatoqualitee potatoqualitee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public/Get-DbaBackupInformation.ps1:358-359 still drops valid later pipeline inputs after one unreadable backup. Stop-Function without -Continue sets the command-scope interrupt flag; although the explicit return ends only the current process invocation, the next piped Path reaches line 247, sees that persistent flag via Test-FunctionInterrupt, and returns without processing it. For example, piping an invalid file followed by a valid backup returns no information for the valid backup. The new test calls the command three separate times, so each call gets a fresh function scope and misses this path.

Please handle the header-read failure without leaving the command-wide interrupt flag set (or restructure per-input processing so continuation is local), and add a regression test that pipes an invalid path followed by a valid backup and asserts the valid backup is returned.

andreasjordan and others added 2 commits September 13, 2026 11:33
…opping the paths piped in after it

A plain Stop-Function set the function-scope interrupt flag, so after one unreadable file Test-FunctionInterrupt dropped every later piped path. Throw under -EnableException, otherwise warn, and return from that process invocation only. Adds one-pipeline and -EnableException tests.

(do Get-DbaBackupInformation)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…fixture and Stop-Function produce

The fixture backup file can hold two backup sets, and Stop-Function rethrows the inner RESTORE HEADERONLY error under -EnableException.

(do Get-DbaBackupInformation)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@andreasjordan

Copy link
Copy Markdown
Collaborator Author

Confirmed and fixed in a752106: the plain Stop-Function set the function-scope interrupt variable, so after one unreadable file Test-FunctionInterrupt dropped every path piped in after it.

What changed: the header-read catch now throws under -EnableException and otherwise warns (with the error record) and returns from that process invocation only, so the flag is never set for this per-input failure. -Continue is not an option here: no loop encloses the catch, and the escaping continue was the original #10638 defect.

Tests: two new tests in the "file is not a backup" context. A text file followed by a real backup in one pipeline still returns the backup and warns for the text file; the -EnableException form throws (the inner RESTORE HEADERONLY error, which is what Stop-Function -ErrorRecord rethrows). Through the testing-dbatools harness: the file is green on both editions (22/22); against the reviewed head, the pipeline test fails with zero results.

One observation outside this PR's scope: the array form -Path $bad, $good still returns nothing, because all files of one call go into a single Read-DbaBackupHeader call that fails as a whole. That is the pre-existing behavior and unchanged here.

created by Claude and reviewed by Andreas Jordan

@potatoqualitee potatoqualitee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current head resolves my previous pipeline-input finding. No material defects remain. Reviewed at head a752106.

# continue here (#10638). So throw under -EnableException, otherwise warn, and return from
# this process invocation only.
if ($EnableException) {
Stop-Function -Message "Failure on $($server.Name)" -ErrorRecord $PSItem -Target $server.Name -EnableException $true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dupe error message yeah?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants