【问题标题】:Is there a way to enter the debugger on an error?有没有办法在出错时进入调试器?
【发布时间】:2014-01-21 15:18:20
【问题描述】:

有没有办法进入 PowerShell 调试器以响应错误? ErrorAction 参数有几个值,但我没有看到像 Debug 这样的东西。我想要打开调试器,就像我设置了断点一样打开调试器,但仅在发生错误时(例如,Write-Error)。

编辑 我应该澄清一下:我主要是一名 C# 开发人员,对 PowerShell 有点陌生,我期望的是类似于 Visual Studio 调试器为您提供的“未处理异常”的行为。似乎 PowerShell 命令引发异常更为常见,而自定义脚本似乎主要使用 Write-Error。我不认为我特别关心区分两者,但我想同时处理两者。

Trevor Sullivan 在下面的回答中提到您可以使用Set-PSBreakpoint -Command Write-Error -Action { break; };,这似乎可以很好地解决这些情况。不过,我发现在许多情况下,它实际上是一个引发我想中断的异常的命令。如果您设置$ErrorActionPreference = "stop",Roman Kuzmin 的答案似乎有效,但是,我遇到的问题是我无法单步执行该程序,它似乎突破了该位置并结束了脚本。如果$ErrorActionPreference = "continue" 对我不起作用。一般来说,陷阱似乎有一个类似的问题,即它们会脱离任何嵌套范围,这是不希望的。

【问题讨论】:

  • 好问题 - 刚刚发布了一个答案,并附有示例。

标签: debugging powershell powershell-3.0


【解决方案1】:

当然。您可以使用 Set-PSBreakpoint cmdlet 在 PowerShell 中创建条件断点。考虑以下代码。将其保存为脚本文件,然后执行。有在线 cmets 可以帮助您了解发生了什么。

请记住,有三种不同类型的断点:

  • 变量
  • 命令

命令断点

此代码示例使用命令断点类型,因为我告诉它只在Get-WmiObject 命令上设置断点。您可以交替指定特定的行号或变量断点类型。您使用-Action 参数来指定您希望设置断点的条件。您必须在 -Action ScriptBlock 中的某处使用 break 关键字,以指示调试器暂停执行 PowerShell 脚本。

# 1. Reset $Error to $null
$WmiError = $null;

# 2. Clean up any existing breakpoints
Get-PSBreakpoint | Remove-PSBreakpoint;

# 3. Set breakpoint, but only on Get-WmiObject commands, when the $WmiError variable is not $null
Set-PSBreakpoint -Command Get-WmiObject -Action { if ($WmiError) { break; } };

# 4. Failed Get-WmiObject command
Get-WmiObject -Class Win32_NonExistentClass -ErrorVariable WmiError;

# 5. Successful Get-WmiObject command
#    PowerShell breaks here, because:
#     - It's a Get-WmiObject command
#     - The $WmiError variable is not null
Get-WmiObject -Class Win32_BIOS;

由于您提到使用Write-Error,您可以在出现Write-Error 的行上设置PSBreakpoint。以下是如何做到这一点的示例:

Set-PSBreakpoint -Command Write-Error -Action { break; };

很简单,对吧?

变量断点

此示例使用变量PSBreakpoint 类型,但仅当变量的内容被修改时。您可以使用-Mode参数来确定在什么条件下命中变量断点:

  • 阅读
  • 读写

代码:

# 1. Clean up any existing breakpoints
Get-PSBreakpoint | Remove-PSBreakpoint;

# 2. Set a PSBreakpoint of type "variable" on a variable named "Data," but only when it has changed
Set-PSBreakpoint -Action { Write-Host -ForegroundColor Green -Object ('The $Data variable has changed! Value is: {0}' -f $Data); break; } -Variable Data -Mode Write;

# 3. No break on this line, because we are not changing the variable
Write-Host -Object $Data;

# 4. Execution is paused on this line, because we change the variable
$Data = 1;

行断点

现在我们已经查看了 variablecommand PSBreakpoint 类型,最后要探索的断点类型是 line断点。如果您要复制/粘贴下面的代码,保存并执行它,您会看到代码在 Write-Host 行(恰好是第 9 行)上中断,但仅在 Name 属性时$Service 变量等于 WinRM。这就是-Action 参数的ScriptBlock 中的条件语句所定义的内容。

# 1. Clean up any existing breakpoints
Get-PSBreakpoint | Remove-PSBreakpoint;

# 2. Set a PSBreakpoint of type "line" on line #8, but only if the $Service variable's Name property equals 'winrm'
Set-PSBreakpoint -Action { if ($Service.Name -eq 'winrm') { break; } } -Line 9 -Script $MyInvocation.MyCommand.Path;

# 3. Get a list of Windows Services and iterate over them
foreach ($Service in (Get-WmiObject -Class Win32_Service)) {
    Write-Host -Object ('Service name is: {0}' -f $Service.Name);
}

【讨论】:

  • 看似陈旧,但感谢您的清晰解释。
【解决方案2】:

您可以创建一个函数,在变量上设置断点,然后更改变量的值。

function Debug-Here {
    if(!(Get-PSBreakpoint -Variable DebugHereCount)) {
        $SCRIPT:DebugHere= Set-PSBreakpoint -Variable DebugHereCount
    }
    $DebugHereCount++
}

然后从脚本顶部的陷阱中调用函数以捕获所有终止错误。

trap { Debug-Here }

或者从try-catch 语句中调用它。

Get-Item DoesNotExist.txt -ErrorAction Stop

try {
    $x = 0
    $y = 1/$x
}
catch {
    Debug-Here
}

进入调试模式后,检查$Error[0] 以查看触发断点的原因。

【讨论】:

  • 这很棒。我想实现一个带有 scriptblock 参数的 BreakIf 函数。该脚本确定是否采用断点。使用行断点,BreakIf 脚本必须知道自身内的哪个行号来设置 bp。这给了我另一个想法:使用 Get-PSCallStack 在调用者中查找行号以设置中断。这样,中断就发生在 BreakIf 之后,而不是在 BreakIf 内
【解决方案3】:

是的,有一种简单的方法可以在出现错误时进入调试器。看起来像 每次发生错误时,变量StackTrace 都会更新。所以我 使用这个技巧:在我的个人资料中,我有这两个功能(开关):

<#
.Synopsis
    Sets $StackTrace breakpoint
.Link
    rbps
#>
function sbps {
    $null = Set-PSBreakpoint -Variable StackTrace -Mode Write
}

<#
.Synopsis
    Removes $StackTrace breakpoint
.Link
    sbps
#>
function rbps {
    Get-PSBreakpoint -Variable StackTrace | Remove-PSBreakpoint
}

当第一个被调用时,在错误时闯入调试器实际上是 启用。写入错误StackTrace 并触发断点。

第二个函数关闭错误时进入调试器。

在大多数情况下,这种方法对我很有效。


更新

为了在没有配置文件功能的情况下使用这种技术,需要一个帮助脚本,例如 Debug-Error.ps1 可以使用。理想情况下,它应该位于路径中,以便命令 Debug-ErrorDebug-Error -Off 始终可用。

另见blog post


Connect 上的一些相关票证:

【讨论】:

  • 如果 PowerShell ISE 正确集成它,它仍然会好得多。例如,在遇到断点后,您必须手动运行 Step Into 才能真正发生错误:此断点会及时返回到引发错误之前!然而,这可能是我们目前所拥有的一切,所以谢谢你——至少它让 PowerShell 稍微不那么令人难以忍受。
【解决方案4】:

有点做作,但适用于写错误或抛出语句:

$error.clear()
$global:errcnt=0
Set-PSBreakpoint -Command * -action {if ($error.count -ne $global:errcnt) {$global:errcnt=$error.count;break}}

【讨论】:

  • 不错,但是在错误发生后导致break,这意味着当前行通常在try/catch块中(因此超出范围)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-13
  • 1970-01-01
  • 2018-03-28
  • 2021-03-28
  • 2011-01-14
  • 2010-12-10
  • 2014-05-14
相关资源
最近更新 更多