Using Conditional Breakpoints in PowerShell Scripts

At Microsoft Ignite I participated in a PowerShell Q&A session, where someone asked how to use conditional breakpoints in PowerShell for a script, so that processing would break only when a condition was met. Usually this sort of thing is done with a variable. For example: if $X is greater than 10, then break. At the time, I couldn’t give him the exact steps, because I develop my scripts and tools in such a way that my debugging needs are minimal. But when I got a chance I cracked open PowerShell, so I could explain the process to all of you.

You should know that you can work with breakpoints directly in the PowerShell console. You are not limited to the PowerShell ISE. A breakpoint is a user-defined trigger that pauses pipeline processing and drops you into a debug mode. The cmdlet you use to create this breakpoints is Set-PSBreakpoint. As you look at help, you will see there are a few ways to use this cmdlet. We’re going to work with variables.

The Set-PSBreakpoint cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
The Set-PSBreakpoint cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)

The variable parameter will be the name, without the $. The mode defaults to Write access.
051215 1703 PowerShellP2
PowerShell will trigger the break just before the variable is updated. And finally the action parameter is a scriptblock of PowerShell commands that you want to run when the breakpoint is reached. This is where the conditional magic happens. To demonstrate, let’s do a simple test in the console. First, define the breakpoint.

Set-PSBreakpoint -Variable i

I haven’t told PowerShell to do anything other than watch when $i is updated.

1..5 | foreach { $i = $_*2}

As soon as PowerShell tries to write to $i, it goes into debug mode.

Entering debug mode in Windows PowerShell. (Image Credit: Jeff Hicks)
Entering debug mode in Windows PowerShell. (Image Credit: Jeff Hicks)

Notice the prompt change. I can run just about any command I want here and even look at the value of $i. Type a ‘?’ to see help.

Help information for debugging in Windows PowerShell. (Image Credit: Jeff Hicks)
Help information for debugging in Windows PowerShell. (Image Credit: Jeff Hicks)

When you are ready to continue, type ‘c’ or ‘continue’. Now let’s get conditional. First, I’ll remove all my breakpoints, so I can start over.

Get-PSBreakpoint | Remove-PSBreakpoint

Next, I’ll define my conditional scriptblock, which can be as complex as you need it to be.

$a = {if ($i -gt 5) { Break }}

In my case, I’m telling PowerShell that if $i is greater than 5, then go into debug mode. I’ll set the breakpoint with this action and test it out.

Set-PSBreakpoint -Variable i -Action $a
1..5 | foreach { $i = $_*2}
PowerShell hit the variable breakpoint. (Image Credit: Jeff Hicks)
PowerShell hit the variable breakpoint. (Image Credit: Jeff Hicks)


PowerShell kept running until the condition was met. This was testing directly in the console. Let’s take another step and watch a variable in one of my functions. Again, I’ll remove the existing breakpoints.

Get-PSBreakpoint | Remove-PSBreakpoint

Here’s the function I want to debug.

Function Test-Freespace {
[cmdletbinding()]
Param(
[Parameter(Position=0,ValueFromPipeline)]
[Alias("CN")]
[ValidateNotNullorEmpty()]
[string[]]$Computername = $env:COMPUTERNAME,
[int]$PercentFree = 20
)
Begin {
    Write-Verbose "Starting $($MyInvocation.Mycommand)"
} #begin
Process {
foreach ($computer in $computername) {
  $cdrive = Get-Ciminstance -ClassName Win32_logicaldisk -filter "DeviceID='c:'" -ComputerName $computer
  $cdrive | where {($_.FreeSpace/$_.size)*100 -le $PercentFree} |
  Select PSComputername,Size,Freespace,
  @{Name="PctFree";Expression={[math]::Round(($_.FreeSpace/$_.size)*100,2) }}
}
} #process
End {
    Write-Verbose "Ending $($MyInvocation.Mycommand)"
} #end
}

The function will write disk information to the pipeline if the percentage of free spaced is less than or equal to a threshold, which defaults to 20 percent. I want to add a conditional breakpoint for any computer where the percentage is less than or equal to 50 percent. The variable is what I am using in the function.

$a = {
if ( ($cdrive.FreeSpace/$cdrive.size)*100 -le 50 ) {Break}
}
Set-PSBreakpoint -variable cdrive -Action $a

I’m going to test with a list of servers.

$computers = "chi-dc01","chi-dc02","chi-dc04","chi-hvr2","chi-core01","chi-fp02"
$computers | test-freespace

As soon as the condition is met, I enter debug mode, where I can do all the checking I need.
051215 1703 PowerShellP6
Obviously you need to have access to the command’s source code to know what variable name to use. Let’s remove the breakpoint, and try again with a script.

Get-PSBreakpoint | Remove-PSBreakpoint

My script defines a function to list all non-administrative shares for a list of computers.

#requires -version 4.0
Function Get-NonAdminShare {
[cmdletbinding()]
Param(
[Parameter(Position=0,ValueFromPipeline)]
[Alias("CN")]
[ValidateNotNullorEmpty()]
[string[]]$Computername = $env:COMPUTERNAME
)
Begin {
    Write-Verbose "Starting $($MyInvocation.MyCommand)"
} #begin
Process {
foreach ($computer in $computername) {
    Try {
        $d = Get-WmiObject win32_share -filter "Type <= 1" -ComputerName $computer -ErrorAction stop
        $d
    }
    Catch {
        Throw $_
    }
 }
} #process
End {
    Write-Verbose "Ending $($MyInvocation.MyCommand)"
} #end
} #end function
$computers = "chi-fp02","chi-dc01","chi-dc02","chi-core01","chi-dc04","chi-hvr2"
$computers | Get-NonAdminShare | Select PSComputername,Name,Type,Path,Description

I want to set a breakpoint so that if the count of $d, the non-admin shares, is greater than or equal to 3, then I’ll drop into debug mode.

$a = {If ($d.count -ge 3) { Break}}

There is no specific line in the script that I want to debug, just the variable.

Set-PSBreakpoint -Script C:\scripts\Get-NonAdminShare.ps1 -Variable d -Action $a

I run the script and hit the breakpoint.
051215 1703 PowerShellP7
Excellent. This is just what I expected, and hopefully you realize that this isn’t too complicated. However, if you are using the PowerShell ISE, all you can do is set a breakpoint on a given line.

Debugging in the PowerShell ISE. (Image Credit: Jeff Hicks)
Debugging in the PowerShell ISE. (Image Credit: Jeff Hicks)

You could manually type the same commands I’ve just demonstrated in the PowerShell ISE, but that takes too long. Instead I wrote an add-on for the PowerShell ISE that will let you create a conditional breakpoint.

#requires -version 4.0
#this requires the PowerShell ISE
Function New-ConditionalBreakPoint {
[cmdletbinding()]
Param(
[ValidateNotNullorEmpty()]
[string]$Variable = $psise.CurrentFile.Editor.SelectedText
)
 Add-Type -AssemblyName "microsoft.visualbasic" -ErrorAction Stop
 $Prompt =  "Enter an IF ( condition ) to force a break"
 $Title = "New Conditional Breakpoint"
 $Default = "`$$variable -SomeOperator SomeValue"
 $if = [microsoft.visualbasic.interaction]::InputBox($Prompt,$Title,$Default)
if ($if) {
    $action = [scriptblock]::Create( "If ($if) { Break}")
    Set-PSBreakpoint -Script $psise.CurrentFile.FullPath -Variable $Variable -Action $action
}
else {
    Write-Warning "No IF condition specified."
}
} #end function
#add menu-shortcut
$psise.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("New Conditional Breakpoint",{New-ConditionalBreakpoint},'Ctrl+B')

When you run this function, it will add a menu shortcut under the Add-Ons menu.
051215 1703 PowerShellP9
In my function, I want to set a conditional breakpoint on $computer, so I select the variable name. Don’t include the $ sign.
051215 1703 PowerShellP10
The function displays an input box, where I can create the condition that will go into the if statement. My function assumes you aren’t doing anything other than breaking if the variable meets some condition.
051215 1703 PowerShellP11
In my case, I enter this:

$computer -match "dc"

Here you do need the $ sign with variable name. My function created a breakpoint in the ISE.

get-psbreakpoint cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
get-psbreakpoint cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)


Now I can run the script in the ISE and when I get to one of my domain controllers, I’ll drop into debug mode.
051215 1703 PowerShellP13
As you can see, the script runs normally until my condition is met. From here I can use the debug menu in the ISE to disable or remove the breakpoints.
Conditional breakpoints are actually quite interesting. True, they can help with debugging a script or function, but I also like how they all me to step through my script interactively. If you found this useful, I hope you’ll let me know. I’d also like to know what else keeps you from creating killer PowerShell tools.

Related Articles: