close

Windows

Cloud

Microsoft 365

PowerShell

Active Directory

Security

Windows Server

Video

Home

PowerShell

Using Conditional Breakpoints in PowerShell Scripts

Jeff Hicks

|

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.

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.

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.

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.

In my function, I want to set a conditional breakpoint on $computer, so I select the variable name. Don’t include the $ sign.

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.

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.

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:

Article saved!

Access saved content from your profile page. View Saved