pf-errorhandling.ps1

function Get-ErrorStackMessage {
    $currentErrors = $global:Error | 
        Where-Object Exception -IsNot [System.Management.Automation.ActionPreferenceStopException]
    # [System.Management.Automation.RemoteException]
    $rootScriptName = Get-ScriptPath -rootScript
    [string[]]$errInfo = @("Root Script '$rootScriptName'")
    $errInfo += '======='

    foreach ($err in $currentErrors) {
        if ($null -ne $err.Exception) {
            $errInfo += $err.Exception.GetType().Fullname
            $errInfo += $err.Exception.Message
        }
        $errInfo += $err.InvocationInfo
        $errInfo += $err.ScriptStackTrace
        $errInfo += $err.PSMessageDetails 
        $errInfo += '---------'
    }

    $errInfoMessage = $errInfo -join "`n"
    return $errInfoMessage
}

function Export-Error([switch]$open) {
    if ($global:Error.Count -eq 0 ) { return }

    $errInfoMessage = Get-ErrorStackMessage
    Write-Warning $errInfoMessage
    
    $rootScriptName = Get-ScriptPath -rootScript
    $filename = Split-Path $rootScriptName -Leaf | Update-Path_ReplaceFileSpecialChars
    $filename = Initialize-ExportFile "Error_$filename.xml"
    $errInfoMessage >> $filename
    
    $show = $true # $open -or ( Test-Powershell_ISE )
    if ( $show ) {
        Show-File $filename
    }
    Export-Context -filenamePrefix "Error_" -open:$show
}

function Get-MsiError ($logFile) {
   if (test-path $logFile ) {
      $msiLogLines =  Get-Content $logFile 
      $errorMark = $msiLogLines | Select-String -SimpleMatch 'Return value 3.'
      
      if ($errorMark) {
          $errLineNumber = $errorMark[0].LineNumber - 1
          
          $actionStartHeader = 'Executing op: ActionStart('
          $actionStart = $msiLogLines | Select-String -SimpleMatch $actionStartHeader
          if ($actionStart) {
              [Array]::Reverse($actionStart)
              $lastActionStarted = $actionStart | skip-until { $_.LineNumber -lt $errLineNumber } | 
                Select-Object -first 1

              $ShortInfo = @("=== START EXTRACT OF '$logFile' ===")
              if ( $lastActionStarted ) {
                $ShortInfo += $msiLogLines[$lastActionStarted[0].LineNumber .. $errLineNumber]
              }
              else {
                $ShortInfo += $msiLogLines[ ($errLineNumber - 50 ) .. $errLineNumber]
              }
              $ShortInfo += "=== END EXTRACT OF '$logFile' ==="
              $shortLogFile = $logFile | Add-FilenameSuffix '.min'
              $Encoding = Get-FileEncoding -FullName $logFile
              Set-Content -Path $shortLogFile -Value $ShortInfo -Encoding $Encoding
          }
          
          if (-not ( Test-Powershell_RemoteSession ) ) {
              notepad $logFile
              if (Test-Path $shortLogFile) {
                 notepad $shortLogFile
              }
          }

          return $ShortInfo
      }
   }
}
function Get-MsiError:::Example{
    $logFile = 'C:\logs\App.msi.log'
    Get-MsiError -logFile $logFile
}

function Test-Dev_Mode {
    if ( Test-Powershell_ISE ) { return $true; }
    if ( Test-Powershell_VsCode ) { return $true; }
    if ( Get-PSBreakpoint ) { return $true; }
}

function Stop-OnException ([Switch]$Disable, [string[]]$ignoreMatch = @()) {
    $bp = Get-PSBreakpoint -Variable StackTrace
    if ($Disable) {
        if ($bp) {
            $bp | Remove-PSBreakpoint
        } 
        return
    }
    
    if (-not $bp) {
        $bp = Set-PSBreakpoint -Variable StackTrace -Mode Write -Action { 
            $defaultIgnoreMatches = @(
                '*at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference*'
                '*at System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecord errorRecord\)*'
                '*LoadModuleManifest*'
            ) 
            $toIgnore = $ignoreMatch + $defaultIgnoreMatches 

            function ConvertFrom_CallStack_To_String {
                $callStack = Get-PSCallStack
                $result = $callStack | ForEach-Object { [PSCustomObject]@{
                    Location = $_.ScriptName + ":" + $_.ScriptLineNumber
                    FunctionName = $_.FunctionName
                    Line = $_.Position
                    }
                }
                $result | Out-String #Required in order to force it to convert as string that renders the info
            }
            
            if ($_) {
                $shouldIgnore = $toIgnore | Where-Object { $StackTrace -like $_ } | Select-Object -First 1
                if (-not $shouldIgnore) {
                    write-host $stacktrace
                    write-host "Stop-OnException" -ForegroundColor Red
                    # $errInfoMessage = Get-ErrorStackMessage
                    # Write-Warning $errInfoMessage
                    $stack = ConvertFrom_CallStack_To_String
                    write-host $stack

                    write-host "Stop-OnException Ready" -ForegroundColor Red
                    break
                }
            }
        }

    }
    else {
        $bp | Enable-PSBreakpoint
    }
}

function Disable-BreakPoint_StackTrace{
    Param (
        [ScriptBlock]$script
    )
    Get-PSBreakpoint -Variable StackTrace | Disable-PSBreakpoint
    if ( $script ) {
        try {
            . $script
        }
        finally {
            Enable-BreakPoint_StackTrace
        }
    }
}
function Disable-BreakPoint_StackTrace:::Example {
    Disable-BreakPoint_StackTrace -script { return 'a' } | assert 'a'
}

function Enable-BreakPoint_StackTrace {
    Get-PSBreakpoint -Variable StackTrace | Enable-PSBreakpoint
}