Last Update: Sep 04, 2024 | Published: Jun 18, 2015
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 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.
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.
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 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:scriptsGet-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.
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.
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: