about_Debuggers

简短说明

介绍 PowerShell 调试器。

长说明

调试是在运行脚本时检查脚本的过程,旨在识别和更正脚本指令中的错误。 PowerShell 调试器可帮助检查和识别脚本、函数、命令、PowerShell Desired State Configuration (DSC) 配置或表达式中的错误和效率低下情况。

从 PowerShell 5.0 开始,PowerShell 调试器已更新为调试远程计算机上的控制台或 Windows PowerShell 集成脚本环境 (ISE) 中运行的脚本、函数、命令、配置或表达式。

注意

Windows PowerShell ISE 仅支持 Windows PowerShell。 对于 PowerShell 6 及更高版本,必须将 Visual Studio Code 与 PowerShell 的扩展配合使用。 有关详细信息,请参阅使用 Visual Studio Code 进行调试

调试器 cmdlet

PowerShell 调试器包括以下 cmdlet 集:

  • Set-PSBreakpoint:在行、变量和命令上设置断点。
  • Get-PSBreakpoint:获取当前会话中的断点。
  • Disable-PSBreakpoint:关闭当前会话中的断点。
  • Enable-PSBreakpoint:在当前会话中重新启用断点。
  • Remove-PSBreakpoint:从当前会话中删除断点。
  • Get-PSCallStack:显示当前调用堆栈。

启动和停止调试器

若要启动调试器,请设置一个或多个断点,然后运行要调试的脚本、命令或函数。

到达断点时,执行将停止,并将控制切换到调试器。

若要停止调试器,请运行脚本、命令或函数,直到它完成。 或者,键入 stopt

调试器命令

在 PowerShell 控制台中使用调试器时,请使用以下命令来控制执行。 在 Windows PowerShell ISE 中,使用“调试”菜单中的命令。

注意

有关如何在其他主机应用程序中使用调试器的信息,请参阅主机应用程序文档。

  • sStepInto:执行下一个语句,然后停止。

  • vStepOver:执行下一个语句,但跳过函数和调用。 将执行跳过的语句,但不会单步遍历。

  • Ctrl+Break:(在 ISE 中全部中断)在 PowerShell 控制台或 Windows PowerShell ISE 中进入正在运行的脚本。 请注意,在 Windows PowerShell 2.0、3.0 和 4.0 中按 Ctrl+Break 键会关闭程序。 中断所有操作同时适用于本地和远程交互式运行的脚本。

  • oStepOut:跳出当前函数;如果嵌套,则上升一个级别。 如果位于主体中,它将继续直到结束或到达下一个断点。 将执行跳过的语句,但不会单步遍历。

  • cContinue:继续运行,直到脚本完成或到达下一个断点为止。 将执行跳过的语句,但不会单步遍历。

  • lList:显示正在执行的脚本的一部分。 默认情况下,它显示当前行、5 个之前行和 10 个后续行。 若要继续列出脚本,请按 Enter。

  • l <m>List:显示脚本的 16 行,以 <m> 指定的行号开头。

  • l <m> <n>List:显示脚本的 <n> 行,以 <m> 指定的行号开头。

  • qStopExit:停止执行脚本并退出调试器。 如果通过运行 Debug-Job cmdlet 来调试作业,Exit 命令会分离调试器,并允许作业继续运行。

  • kGet-PsCallStack:显示当前调用堆栈。

  • <Enter>:如果是 Step (s)、StepOver (v) 或 List (l),则重复上一个命令。 否则,表示提交操作。

  • ?h:显示调试器命令帮助。

若要退出调试器,可以使用 Stop (q)。

从 PowerShell 5.0 开始,可以运行 Exit 命令退出通过运行 Debug-JobDebug-Runspace 启动的嵌套调试会话。

使用这些调试器命令可以运行脚本、在关注点处停止、检查变量值和系统状态,以及继续运行脚本,直到发现问题。

注意

如果使用重定向运算符(如 >)单步执行语句,PowerShell 调试器将遍历脚本中的所有剩余语句。

显示脚本变量的值

在调试器中操作时,还可以输入命令、显示变量值、使用 cmdlet 并在命令行中运行脚本。 可以在正在调试的脚本中显示所有变量的当前值,但以下自动变量除外:

$_
$Args
$Input
$MyInvocation
$PSBoundParameters

显示其中任一变量的值时,会获取调试器使用的内部管道的变量值,而不是脚本中变量的值。

若要显示要调试的脚本的这些变量的值,请向脚本添加行以将这些值保存到新变量。 在这些新行之后设置断点。 然后,可以显示新变量的值。

例如,

$scriptArgs = $Args
$scriptname = $MyInvocation.PSCommandPath

调试器环境

到达断点时,你会进入调试器环境。 命令提示符会更改,将以“[DBG]:”开头。 此外,在某些主机应用程序(如 PowerShell 控制台)中,会打开嵌套提示进行调试。 可以通过重复在命令提示符处显示的大于字符 (ASCII 62) 来检测嵌套提示。

有关自定义提示的详细信息,请参阅 about_Prompts

可以使用 $NestedPromptLevel 自动变量找到嵌套级别。 自动变量 $PSDebugContext 在本地作用域内定义。 可以使用 $PSDebugContext 变量的存在状态来确定你是否在调试器中运行。

例如:

if ($PSDebugContext) {"Debugging"} else {"Not Debugging"}

可以在调试中使用 $PSDebugContext 变量的值。

[DBG]: PS>>> $PSDebugContext.InvocationInfo

Name   CommandLineParameters  UnboundArguments  Location
----   ---------------------  ----------------  --------
=      {}                     {}                C:\ps-test\vote.ps1 (1)

调试和作用域

进入调试器不会更改运行所在的作用域,但在脚本中到达断点时,将进入脚本作用域。 脚本作用域是运行调试器的作用域的子级。

若要查找脚本作用域中定义的变量和别名,请使用 Get-AliasGet-Variable cmdlet 的 Scope 参数。

例如,以下命令获取本地(脚本)作用域内的变量:

Get-Variable -scope 0

这是一种仅查看脚本中定义的变量以及调试时定义的变量的有效方法。

在命令行上进行调试

设置变量断点或命令断点时,只能在脚本文件中设置断点。 但是,默认情况下,断点会针对当前会话中运行的任何内容设置。

例如,如果对 $name 变量设置断点,调试器会在运行的任何脚本、命令、函数、脚本 cmdlet 或表达式中中断任何 $name 变量,直到禁用或删除断点。

这样,就可以在更现实的上下文中调试脚本,这些脚本可能会受到会话和用户配置文件中的函数、变量和其他脚本的影响。

行断点特定于脚本文件,因此它们仅在脚本文件中设置。

调试工作流

调试器可用于在 PowerShell 控制台或 Windows PowerShell ISE 中调试 PowerShell 工作流。 使用 PowerShell 调试器调试工作流存在一些限制。

  • 可以在调试器中查看工作流变量,但不支持在调试器中设置工作流变量。
  • 在工作流调试器中停止时,Tab 补全不可用。
  • 工作流调试只适合用于从 PowerShell 脚本同步运行工作流。 如果工作流以作业的形式运行(使用 AsJob 参数),则无法调试工作流。
  • 不会实现其他嵌套调试方案,例如一个工作流调用另一个工作流,或工作流调用脚本的情况。

以下示例演示如何调试工作流。 当调试器单步执行工作流函数时,调试器提示符将更改为 [WFDBG]

PS C:> Set-PSBreakpoint -Script C:\TestWFDemo1.ps1 -Line 8
ID Script           Line Command    Variable     Action
-- ------           ---- -------    --------     ------
0 TestWFDemo1.ps1   8
PS C:> C:\TestWFDemo1.ps1
Entering debug mode. Use h or ? for help.
Hit Line breakpoint on 'C:\TestWFDemo1.ps1:8'
At C:\TestWFDemo1.ps1:8 char:5
+     Write-Output -InputObject "Now writing output:"
# ~~~~~
[WFDBG:localhost]: PS C:>> list
# 3:
4:  workflow SampleWorkflowTest
5:  {
6:      param ($MyOutput)
# 7:
8:*     Write-Output -InputObject "Now writing output:"
9:      Write-Output -Input $MyOutput
# 10:
11:      Write-Output -InputObject "Get PowerShell process:"
12:      Get-Process -Name powershell
# 13:

14:      Write-Output -InputObject "Workflow function complete."
15:  }
# 16:
17:  # Call workflow function
18:  SampleWorkflowTest -MyOutput "Hello"
[WFDBG:localhost]: PS C:>> $MyOutput
Hello
[WFDBG:localhost]: PS C:>> stepOver
Now writing output:
At C:\TestWFDemo1.ps1:9 char:5
+     Write-Output -Input $MyOutput
# +!INCLUDE[]~
[WFDBG:localhost]: PS C:>> list
4:  workflow SampleWorkflowTest
5:  {
6:      param ($MyOutput)
# 7:
8:      Write-Output -InputObject "Now writing output:"
9:*     Write-Output -Input $MyOutput
# 10:
11:      Write-Output -InputObject "Get PowerShell process:"
12:      Get-Process -Name powershell
# 13:
14:      Write-Output -InputObject "Workflow function complete."
15:  }
# 16:
17:  # Call workflow function
18:  SampleWorkflowTest -MyOutput "Hello"
# 19:
[WFDBG:localhost]: PS C:>> stepOver
Hello
At C:\TestWFDemo1.ps1:11 char:5
+     Write-Output -InputObject "Get PowerShell process:"
# +!INCLUDE[]~~~~~~~~~
[WFDBG:localhost]: PS C:>> stepOut
Get PowerShell process:
Handles  NPM(K)    PM(K)    WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----    ----- -----   ------     -- -----------
    433      35   106688   128392   726     2.67   7124 powershell
    499      44   134244   172096   787     2.79   7452 powershell
Workflow function complete.

调试函数

在具有 beginprocessend 部分的函数上设置断点时,调试器将在每个部分的第一行中断。

例如:

function test-cmdlet {
    begin {
        write-output "Begin"
    }
    process {
        write-output "Process"
    }
    end {
        write-output "End"
    }
}

C:\PS> Set-PSBreakpoint -command test-cmdlet

C:\PS> test-cmdlet

Begin
Entering debug mode. Use h or ? for help.

Hit Command breakpoint on 'prompt:test-cmdlet'

test-cmdlet

[DBG]: C:\PS> c
Process
Entering debug mode. Use h or ? for help.

Hit Command breakpoint on 'prompt:test-cmdlet'

test-cmdlet

[DBG]: C:\PS> c
End
Entering debug mode. Use h or ? for help.

Hit Command breakpoint on 'prompt:test-cmdlet'

test-cmdlet

[DBG]: C:\PS>

调试远程脚本

可以运行 Enter-PSSession 来启动交互式远程 PowerShell 会话,可以在该会话中设置断点和调试远程计算机上的脚本文件和命令。 Enter-PSSession 允许重新连接在远程计算机上运行脚本或命令的断开连接会话。 如果正在运行的脚本命中断点,客户端会话会自动启动调试器。 如果运行脚本的断开连接会话已命中断点,Enter-PSSession 重新连接到会话时会自动启动命令行调试器。

以下示例演示其工作原理。 已在脚本的第 6 行、11 行、22 行和 25 行设置断点。 调试器启动时,提示中会发生两处标识变化:

  • 运行会话的计算机的名称
  • 指示你处于调试模式的 DBG 提示符
Enter-PSSession -Cn localhost
[localhost]: PS C:\psscripts> Set-PSBreakpoint .\ttest19.ps1 6,11,22,25

ID Script          Line     Command          Variable          Action
-- ------          ----     -------          --------          ------
0 ttest19.ps1          6
1 ttest19.ps1          11
2 ttest19.ps1          22
3 ttest19.ps1          25

[localhost]: PS C:\psscripts> .\ttest19.ps1
Hit Line breakpoint on 'C:\psscripts\ttest19.ps1:11'

At C:\psscripts\ttest19.ps1:11 char:1
+ $winRMName = "WinRM"
# + ~

[localhost]: [DBG]: PS C:\psscripts>> list

6:      1..5 | foreach { sleep 1; Write-Output "hello2day $_" }
7:  }
# 8:

9:  $count = 10
10:  $psName = "PowerShell"
11:* $winRMName = "WinRM"
12:  $myVar = 102
# 13:

14:  for ($i=0; $i -lt $count; $i++)
15:  {
16:      sleep 1
17:      Write-Output "Loop iteration is: $i"
18:      Write-Output "MyVar is $myVar"
# 19:

20:      hello2day
# 21:


[localhost]: [DBG]: PS C:\psscripts>> stepover
At C:\psscripts\ttest19.ps1:12 char:1
+ $myVar = 102
# + ~

[localhost]: [DBG]: PS C:\psscripts>> quit
[localhost]: PS C:\psscripts> Exit-PSSession
PS C:\psscripts>

示例

此测试脚本检测 PowerShell 的版本,并显示一条与版本相关的消息。 其中会包含函数、函数调用和变量。

以下命令显示测试脚本文件的内容:

PS C:\PS-test>  Get-Content test.ps1

function psversion {
  "PowerShell " + $PSVersionTable.PSVersion
  if ($PSVersionTable.PSVersion.Major -lt 7) {
    "Upgrade to PowerShell 7!"
  }
  else {
    "Have you run a background job today (start-job)?"
  }
}

$scriptName = $MyInvocation.PSCommandPath
psversion
"Done $scriptName."

若要开始,请在脚本中的兴趣点(例如行、命令、变量或函数)处设置断点。

首先,在当前目录中 Test.ps1 脚本的第一行中创建一个行断点。

PS C:\ps-test> Set-PSBreakpoint -line 1 -script test.ps1

该命令返回 System.Management.Automation.LineBreakpoint 对象。

Column     : 0
Line       : 1
Action     :
Enabled    : True
HitCount   : 0
Id         : 0
Script     : C:\ps-test\test.ps1
ScriptName : C:\ps-test\test.ps1

现在启动脚本。

PS C:\ps-test> .\test.ps1

当脚本到达第一个断点时,断点消息会指示调试器处于活动状态。 它会描述断点并预览脚本的第一行(函数声明)。 命令提示符也会更改,以指示调试器具有控制权。

预览行包含脚本名称和预览命令的行号。

Entering debug mode. Use h or ? for help.

Hit Line breakpoint on 'C:\ps-test\test.ps1:1'

test.ps1:1   function psversion {
DBG>

使用 Step 命令 (s) 执行脚本中的第一个语句并预览下一个语句。 下一语句使用 $MyInvocation 自动变量将 $scriptName 变量的值设置为脚本文件的路径和文件名。

DBG> s
test.ps1:11  $scriptName = $MyInvocation.PSCommandPath

此时不会填充 $scriptName 变量,但你可以通过显示变量的值来验证变量值。 在本例中,该值为 $null

DBG> $scriptname
DBG>

使用另一个 Step 命令 (s) 执行当前语句,并在脚本中预览下一个语句。 下一个语句调用 psversion 函数。

DBG> s
test.ps1:12  psversion

此时将填充 $scriptName 变量,但你可以通过显示变量的值来验证变量值。 在本例中,该值设置为脚本路径。

DBG> $scriptName
C:\ps-test\test.ps1

使用另一个 Step 命令执行函数调用。 按 Enter,或键入“s”表示 Step 命令。

DBG> s
test.ps1:2       "PowerShell " + $PSVersionTable.PSVersion

调试消息包含函数中语句的预览。 若要执行此语句并预览函数中的下一个语句,可以使用 Step 命令。 但是,本例使用了 StepOut 命令 (o)。 该命令完成函数的执行(除非到达断点),并执行脚本中的下一个语句。

DBG> o
Windows PowerShell 2.0
Have you run a background job today (start-job)?
test.ps1:13  "Done $scriptName"

由于我们到达了脚本中的最后一条语句,因此 Step、StepOut 和 Continue 命令的效果相同。 本例使用了 StepOut (o)。

Done C:\ps-test\test.ps1
PS C:\ps-test>

StepOut 命令执行最后一个命令。 标准命令提示符指示调试器已退出并向命令处理器返回了控制权。

现在再次运行调试器。 首先,为了删除当前断点,请使用 Get-PsBreakpointRemove-PsBreakpoint cmdlet。 (如果你觉得自己可能会重复使用断点,请使用 Disable-PsBreakpoint cmdlet 而不是 Remove-PsBreakpoint。)

PS C:\ps-test> Get-PSBreakpoint| Remove-PSBreakpoint

可将此命令缩写为:

PS C:\ps-test> gbp | rbp

或者,通过编写函数来运行命令,例如以下函数:

function delbr { gbp | rbp }

现在,在 $scriptname 变量中创建断点。

PS C:\ps-test> Set-PSBreakpoint -variable scriptname -script test.ps1

可以将命令缩写为:

PS C:\ps-test> sbp -v scriptname -s test.ps1

现在启动脚本。 脚本到达变量断点。 默认模式为 Write,因此执行会在更改变量值的语句之前停止。

PS C:\ps-test> .\test.ps1
Hit Variable breakpoint on 'C:\ps-test\test.ps1:$scriptName'
(Write access)

test.ps1:11  $scriptName = $MyInvocation.PSCommandPath
DBG>

显示 $scriptName 变量的当前值(即 $null)。

DBG> $scriptName
DBG>

使用 Step 命令 (s) 执行填充变量的语句。 然后显示 $scriptName 变量的新值。

DBG> $scriptName
C:\ps-test\test.ps1

使用 Step 命令 (s) 预览脚本中的下一个语句。

DBG> s
test.ps1:12  psversion

下一个语句是对 psversion 函数的调用。 若要跳过该函数但仍要执行它,请使用 StepOver 命令 (v)。 如果使用 StepOver 时已进入该函数,则该函数不起作用。 将显示但不执行函数调用。

DBG> v
Windows PowerShell 2.0
Have you run a background job today (start-job)?
test.ps1:13  "Done $scriptName"

StepOver 命令执行函数,并预览脚本中的下一个语句,该语句输出最后一行。

使用 Stop 命令 (t) 退出调试器。 命令提示符将还原为标准命令提示符。

C:\ps-test>

若要删除断点,请使用 Get-PsBreakpointRemove-PsBreakpoint cmdlet。

PS C:\ps-test> Get-PSBreakpoint| Remove-PSBreakpoint

psversion 函数中创建新的命令断点。

PS C:\ps-test> Set-PSBreakpoint -command psversion -script test.ps1

可将此命令缩写为:

PS C:\ps-test> sbp -c psversion -s test.ps1

现在运行该脚本。

PS C:\ps-test> .\test.ps1
Hit Command breakpoint on 'C:\ps-test\test.ps1:psversion'

test.ps1:12  psversion
DBG>

脚本在函数调用处到达断点。 此时尚未调用函数。 这样,你便有机会使用 Set-PSBreakpoint 的 Action 参数来设置断点执行条件,或者执行准备或诊断任务,例如启动日志或者调用诊断或安全脚本。

若要设置操作,请使用 Continue 命令 (c) 退出脚本,并使用 Remove-PsBreakpoint 命令删除当前断点。 (断点是只读的,因此无法向当前断点添加操作。)

DBG> c
Windows PowerShell 2.0
Have you run a background job today (start-job)?
Done C:\ps-test\test.ps1

PS C:\ps-test> Get-PSBreakpoint| Remove-PSBreakpoint
PS C:\ps-test>

现在,使用操作创建新的命令断点。 以下命令设置一个命令断点,该断点包含一个操作,用于在调用函数时记录 $scriptName 变量的值。 由于操作中未使用 break 关键字,因此不会停止执行。 反撇号 (`) 表示行延续字符。

PS C:\ps-test> Set-PSBreakpoint -command psversion -script test.ps1  `
-action { add-content "The value of `$scriptName is $scriptName." `
-path action.log}

还可以添加用于设置断点条件的操作。 在以下命令中,仅当执行策略设置为 RemoteSigned(仍允许运行脚本的最严格策略)时,才会执行命令断点。

PS C:\ps-test> Set-PSBreakpoint -script test.ps1 -command psversion `
-action { if ((Get-ExecutionPolicy) -eq "RemoteSigned") { break }}

操作中的 break 关键字指示调试器执行断点。 还可以使用 continue 关键字指示调试器在不中断的情况下执行。 由于默认关键字为 continue,因此必须指定 break 以停止执行。

现在运行该脚本。

PS C:\ps-test> .\test.ps1
Hit Command breakpoint on 'C:\ps-test\test.ps1:psversion'

test.ps1:12  psversion

由于执行策略设置为 RemoteSigned,因此执行会在函数调用处停止。

此时,你可能需要检查调用堆栈。 使用 Get-PsCallStack cmdlet 或 Get-PsCallStack 调试器命令 (k)。 以下命令获取当前调用堆栈。

DBG> k
2: prompt
1: .\test.ps1: $args=[]
0: prompt: $args=[]

此示例仅演示了使用 PowerShell 调试器的几种方法。

PowerShell 中的其他调试功能

除了 PowerShell 调试器之外,PowerShell 还提供用于调试脚本和函数的其他几项功能。

  • Set-PSDebug cmdlet 提供非常基本的脚本调试功能,包括单步执行和跟踪。

  • 使用 Set-StrictMode cmdlet 可以检测对未初始化变量的引用、对对象不存在的属性的引用,以及无效的函数语法。

  • 将诊断语句添加到脚本,例如用于显示变量值的语句、从命令行读取输入的语句,或报告当前指令的语句。 对于此任务,请使用包含 Write 谓词的 cmdlet,例如 Write-HostWrite-DebugWrite-WarningWrite-Verbose

另请参阅