Skip to content

[PowerShell] Bare break in error-handling catch blocks escapes into the _caller's_ enclosing loop, silently aborting unrelated work #382

Description

@tejeszacsko

Scope: this report is about the PowerShell side of the repo (Redfish PowerShell/), checked against the current master branch. The pattern described is specific to PowerShell's break semantics and doesn't apply to the Python modules.

Summary

Several cmdlets use a bare break statement inside catch blocks to abort on error, with no enclosing loop in the function itself. In PowerShell, an unlabeled break with no enclosing loop at the current scope searches up the entire call stack for the nearest enclosing loop — including loops in whatever code called the cmdlet. If the caller happens to invoke the cmdlet from inside a loop (a very normal pattern — e.g. looping over several firmware components to update, or several disks to configure), that break silently terminates the caller's loop instead of just this cmdlet's own error handling, with no exception or error surfaced.

Minimal repro (pure PowerShell language behavior, no Dell hardware needed):

Function Inner {
    Try { Throw "simulated failure" }
    Catch { break }   # no enclosing loop in this function
}

Function Outer {
    ForEach ($i in 1..3) {
        Write-Host "iteration $i"
        Inner
        Write-Host "after Inner, iteration $i"   # never printed
    }
    Write-Host "loop finished"   # reached immediately after iteration 1
}

Outer

Output is only iteration 1, then straight to loop finished — iterations 2 and 3 never run, and "after Inner" never prints, even though Inner returned without throwing anything the caller could see.

Concrete impact observed

In Set-DeviceFirmwareSimpleUpdateREDFISH.psm1, the catch block around the firmware-upload Invoke-WebRequest call ends in a bare break. When that request fails — e.g. the iDRAC rejects the payload — this break propagated out of the module and out of our own wrapper function, silently terminating our caller's loop over several required firmware components. Our tool stopped checking the remaining firmware components entirely and reported overall success despite the update having failed, with no error or log line indicating anything had gone wrong.

Full scan of the current master branch

Parsed all 77 .psm1 files under Redfish PowerShell/ with System.Management.Automation.Language.Parser and checked every break statement's AST ancestry for a lexically-enclosing loop within its own function. Found 51 such "unsafe" break statements across 9 individual cmdlet files (not counting the same underlying functions duplicated again inside the large combined IdracRedfishSupport.psm1 convenience file):

File Unsafe break count Affected function(s)
Set-DeviceFirmwareSimpleUpdateREDFISH 10 download_image_payload, reboot_server, install_image_payload_query_job_status_reboot_server
Invoke-SupportAssistCollectionLocalREDFISH 10 get_support_assist_license_agreement, accept_support_assist_license_agreement, register_support_assist_feature, execute_support_assist_collection
Set-ScheduleRepositoryUpdateREDFISH 8 get_update_schedule_details, get_idrac_time, clear_update_schedule_details, set_scheduled_repository_update
Set-DeviceFirmwareRollbackREDFISH 6 get_firmware_versions, rollback_image_query_job_status_reboot_server
Set-DeviceFirmwareSimpleUpdateTransferProtocolREDFISH 6 get_firmware_versions, reboot_server, install_image_payload_query_job_status_reboot_server
Invoke-SystemEraseREDFISH 5 get_system_erase_components, execute_system_erase, power_on_server, finish_bios_reset_to_default
Invoke-ExportImportSslCertificateREDFISH 3 export_import_ssl_cert, delete_ssl_cert, restore_factory_defaults
Invoke-RemoteDiagsREDFISH 2 execute_remote_diags, export_remote_diags_results
Set-ExportImportServerConfigurationProfileNetworkShareREDFISH 1 Set-ExportImportServerConfigurationProfileNetworkShareREDFISH

The same functions are duplicated (with the same break statements) inside IdracRedfishSupport.psm1, which bundles many cmdlets into one combined file.

For context on false positives to rule out: several other files also use bare break (e.g. Get-IdracLifecycleLogsREDFISH, Set-NextOneTimeBootVirtualMediaDeviceOemREDFISH), but in those cases the break sits inside an actual while/for loop within the same function, so it's correctly scoped and not a problem — only the ones listed above lack any enclosing loop of their own.

Suggested fix

Where a catch block is meant to abort the current function on error (which appears to be the intent in all cases found), replace the bare break with return (or return $false/an appropriate error/status value). return exits only the current function and cannot escape into a caller's loop, avoiding this class of bug entirely while preserving the "stop processing now" intent.

Environment

  • Windows PowerShell 5.1
  • Modules loaded via Import-Module from a calling script that iterates over multiple items (e.g. multiple firmware components) in a loop

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