skip to content

A PowerShell script loops over hundreds of inputs calling a cmdlet for each. It must not stop at the first failure, but at the end it must report exactly which ones failed. How do you capture those errors, and why is $Error not a good enough answer?

level: seniorimportance: should knowfreq 36%

answer

  1. do not stop, but do remember
  2. a common parameter, not $Error
  3. give the name without the dollar sign
  4. a plus sign appends across iterations

basics

~20 s

Pair -ErrorAction SilentlyContinue with -ErrorVariable on the call inside the loop, prefixing the variable name with a plus sign so each error record is appended rather than overwriting the last. $Error is session-wide, capped, and mixed with unrelated failures.

solid answer

~50 s

On the call inside the loop I use `-ErrorAction SilentlyContinue -ErrorVariable +failures`. `SilentlyContinue` keeps the loop running without spraying red text; `-ErrorVariable` captures the `ErrorRecord` objects into a variable I name **without** a `$`; and the leading `+` appends across iterations instead of replacing the contents on every call, which is the detail people miss. Afterwards `$failures` is a structured list I can count, log, or feed into a retry — each record carrying the message, the target object and the invocation details. `$Error` is a poor substitute: it is a session-wide history shared with everything else the session has done, it is capped by `$MaximumErrorCount` (256 by default), and it mixes my loop's failures with unrelated ones. It is a triage tool at an interactive prompt, not a report. The alternative I also accept is a per-item `try/catch` with `-ErrorAction Stop`, which gives me the loop variable in scope alongside the error.

code

powershell · 10 lines
powershell
$paths = 'C:\logs\a.log', 'C:\logs\missing.log', 'C:\logs\c.log'

foreach ($path in $paths) {
    Get-Item -Path $path -ErrorAction SilentlyContinue -ErrorVariable +fileErrors | Out-Null
}

'Failures: {0} of {1}' -f $fileErrors.Count, $paths.Count
foreach ($e in $fileErrors) {
    $e.Exception.Message
}

go deeper

for a junior

Know that -ErrorVariable is a common parameter available on cmdlets, and that you pass it a variable name without the dollar sign.

for a middle

Explain the pairing of -ErrorAction SilentlyContinue with -ErrorVariable, and that a leading plus on the name appends rather than overwriting on each call in a loop.

for a senior

Show a working bulk pattern end to end and justify it: how each failure is tied back to its input, and the specific reasons $Error is unfit as a report.

for a principal

Own the operational contract for bulk jobs — partial-failure exit status, a machine-readable failure list, retrying only the failures, and the threshold at which a run should abort instead of continuing.

## The requirement: keep going, but remember Bulk automation has a shape that neither pure fail-fast nor pure ignore-errors satisfies: attempt every input, and finish with a precise, machine-readable list of what failed. PowerShell gives you two idiomatic ways to build that. ## Pattern one: -ErrorAction SilentlyContinue with -ErrorVariable ```powershell foreach ($path in $paths) { Get-Item -Path $path -ErrorAction SilentlyContinue -ErrorVariable +fileErrors | Out-Null } "Failures: {0} of {1}" -f $fileErrors.Count, $paths.Count $fileErrors | ForEach-Object { $_.Exception.Message } ``` Three details make or break this pattern: **The name has no dollar sign.** `-ErrorVariable` takes the *name* of a variable to create, not the variable itself. Writing `-ErrorVariable $fileErrors` passes the current (probably empty) value of that variable as the name, and the errors land somewhere you did not expect. The same convention applies to the sibling common parameters `-OutVariable`, `-WarningVariable` and `-InformationVariable`. **The leading + appends.** Without it, `-ErrorVariable` *replaces* the variable's contents on every single invocation, so after the loop you hold only the errors from the final iteration. `+fileErrors` appends, accumulating across the whole loop. This is the single most common bug in the pattern. **SilentlyContinue, not Ignore.** `Ignore` throws the error away outright — it is not recorded in `$Error`, and there is nothing left to report. If you are capturing errors, `SilentlyContinue` is the value you want: no console noise, full retention. ## Pattern two: per-item try/catch ```powershell $failures = [System.Collections.Generic.List[object]]::new() foreach ($path in $paths) { try { Get-Item -Path $path -ErrorAction Stop | Out-Null } catch { $failures.Add([pscustomobject]@{ Input = $path Reason = $_.Exception.Message Type = $_.Exception.GetType().FullName }) } } ``` This is more code but strictly more control. The loop variable is in scope inside the `catch`, so you record precisely which input failed without relying on the error record to identify it; you can shape the report exactly as your downstream consumer wants; and you can branch on the exception type — retry a transient timeout, skip a permanently missing item. Note what would go wrong without the loop: `-ErrorAction Stop` on a single call that processes the whole collection aborts at the first failure and never touches the rest. Stop belongs *inside* the iteration, not around it. ## Why $Error is the wrong tool here `$Error` is an automatic variable holding a session-wide history of error records, newest first at `$Error[0]`. It is genuinely useful — at an interactive prompt, after something went wrong, `$Error[0] | Format-List -Force` is the fastest way to see everything about the last failure. As the basis of a report it fails on several counts: - **It is shared.** Every error the session produced is in there, including ones from before your loop started and ones from unrelated code. You cannot tell yours apart without extra bookkeeping. - **It is capped.** `$MaximumErrorCount` defaults to 256, so a long bulk run silently discards the oldest records — the earliest failures, usually the ones that explain the pattern. - **It is order-only.** It records sequence, not association with an input, so mapping records back to the items that produced them is guesswork. - **It is mutable state you do not own.** `$Error.Clear()` before a loop makes it slightly more usable and is a common trick, but you are still relying on nothing else in the session writing to it. `-ErrorVariable` gives you exactly the records from exactly the calls you asked about, with no cap and no sharing. ## Reporting and the exit contract Once you hold the failure list, decide what "the job failed" means. Common practice for a scheduled or CI-invoked script is: emit the failures as structured objects (or write them to a file), and end with a non-zero exit code when the list is non-empty, so the scheduler can see the run as failed. A useful refinement is a threshold — if more than some proportion of items fail, the assumption behind the run is probably wrong and it should abort rather than plough on for another hour producing the same error a thousand times. ## A note on $? `$?` is a related but much weaker signal: a boolean reporting whether the *immediately preceding* command succeeded. It is fine for a quick guard directly after a call, but it is overwritten by the very next command, so it cannot accumulate anything and is no substitute for capturing records.

  • What breaks if you write -ErrorVariable failures without the leading plus sign inside a loop?
    Each invocation replaces the variable's contents instead of adding to it, so when the loop ends you hold only the errors from the last iteration — usually an empty list if the final item succeeded. The plus prefix is what makes the capture cumulative.
  • Why would you choose a per-item try/catch over -ErrorVariable for this job?
    Because the loop variable is in scope inside the catch, so you can pair each failure with the exact input that caused it, shape a custom report object, and branch on exception type to retry transient failures while skipping permanent ones. -ErrorVariable gives you records but no such control.
  • Is -ErrorAction Ignore a reasonable substitute for SilentlyContinue in a bulk loop?
    No, not when you intend to report. Ignore discards the error outright and does not record it in $Error, so there is nothing left to count or log. Use Ignore only for noise you have consciously decided never to look at.

saying these in an interview costs you the question

  • Passes -ErrorVariable a name with a dollar sign
  • Omits the plus prefix and keeps only the last error
  • Uses $Error as the run's failure report
  • Puts -ErrorAction Stop on the bulk call itself
  • Confuses $? with an accumulated error list

context