Public/Runner/Invoke-Safely.ps1
|
function Invoke-Safely { <# .SYNOPSIS Wraps a scriptblock in a try/catch and returns a Result. .DESCRIPTION The scriptblock runs with ErrorActionPreference = 'Stop' so that non-terminating cmdlet errors (Write-Error) are also captured — the most common pitfall when wrapping native PS commands. .PARAMETER Action The code to run. Its return value / pipeline output becomes Ok's value. If it produces multiple objects the first one is used; consider wrapping in @() if you need an array. .PARAMETER ErrorMapper Optional transform applied to the caught Exception before Err() wraps it. Defaults to the identity (raw Exception object). .EXAMPLE $result = Invoke-Safely { Get-Content 'missing.txt' } # Returns Err(System.Management.Automation.ItemNotFoundException) .EXAMPLE $result = Invoke-Safely { Get-Content 'missing.txt' } -ErrorMapper { param($e) "File read failed: $($e.Message)" } # Returns Err("File read failed: ...") #> [CmdletBinding()] [OutputType([Result])] param( [Parameter(Mandatory)] [scriptblock]$Action, [Parameter()] [scriptblock]$ErrorMapper = { param($e) $e } ) # Save and restore so callers see no side-effects on the preference variable. $savedEAP = $ErrorActionPreference try { # 'Stop' converts non-terminating errors into terminating ones so our # catch block captures them — this is the single most important fix for # production use of try/catch around PS cmdlets. $ErrorActionPreference = 'Stop' $value = & $Action return [Result]::Ok($value) } catch { try { $mapped = & $ErrorMapper $_.Exception } catch { $mapped = $_.Exception } return [Result]::Err($mapped) } finally { $ErrorActionPreference = $savedEAP } } |