Hey y’all. I’ve been getting verrrry deep into the world of Asp.net Model View Controller and working on some big updates to ClientFaux, but I saw this tweet and it spoke to me:
Why? Because until recently, I was notorious for leaving Write-Debug statements everywhere. I mean, just take a look at my local git folder.

My code was just littered with these after practically every logical operation…just in case I needed to pause my code here at some point in the future. Actually, someone could look at my code in the past and every Verbose or Debug cmd was basically a place that I got stuck while writing that cmdlet or script. I mean, using the tools is not wrong, but it always felt like there should be better ways to do it.
Recently, I have learned of a much better way and I want to share it with everybody.
Why not use Write-Debug?
Write-Debug is wrong and if you use it you should feel bad
I’m just kidding! You know, to be honest, something really gets under my skin about those super preachy posts like you always find on medium that say things like ‘You’re using strings wrong’, or “You’re all morons for not using WINS” or something snarky like that.
It’s like, I might have agreed with them or found the info useful, but the delivery is so irksome that I am forced to wage war against them by means of a passive aggressive campaign of refusing to like their Tweets any more as my retribution.
That being said, here’s why I think we should avoid Write-Debug. It ain’t wrong, but you might like the alternative better.
Pester will annoy you
If you’re using Pester, you might like to use -CodeCoverage to help you identify which logical units of your code may not have test coverage. Well, Pester will view each use of Write-Debug
as a separate command and will prompt you in your code coverage reports to write a test for each. A relatively simple function like this one:
Function My-ShoddyFactorialFunction {param($baseNumber) Write-Debug "Starting with base number of $baseNumber" $temp = $baseNumber ForEach($i in ($baseNumber-1)..1){ Write-Debug "multiplying $temp by $($i)" $temp = $temp * $i } Write-Debug "multiplying $temp by $($i)" return $temp }
When this short script is run through CodeCoverage
, Pester will call out each Write-Debug
as a separate entities that need to be tested. We both know that there’s no reason to write a Pester test for something like this, but if you work with sticklers for pristine CodeCoverage reports then you’ll have to look out for this.
Not guaranteed to be present on every PowerShell host
Did you know that not every PowerShell host supports Write-Debug
? Since it is an interactive cmdlet, consoles that operate headlessly don’t support it. This means that Azure Automation for one does not support the cmdlet, so it will basically be ignored, at best.
As developers of PowerShell scripts and tools, we’re accustomed to having the fully fledged PowerShell console available to us, but our code may not always execute in the same type of environment.
For instance, once I was working on a project for a customer with very long PowerShell Run Script steps embedded into System Center Orchestrator. I wrote some functions for them, one of which involved creating and deleting ServiceNow Tickets.
I was very big at that time on creating ‘Full and Proper’ advanced cmdlets and “Doing it the right way™” so I went totally overboard with $ConfirmImpact
and PSCmdletShouldProcess usage. The code worked great in my local IDE so we deployed it to production and our runbooks started failing.
Why? Well the host in which Orchestrator runs PowerShell Scripts runs headless, and when it tried to run my cmdlets, it threw this error.
Exception calling "Invoke" with "0" argument(s):
"A command that prompts the user failed because the host program or thecommand type does not support user interaction. The host was attempting to request confirmation with the following message : some error123
This lesson taught me the point that I shouldn’t always count on all input streams and forms of user interaction being available in my code.
Not a great user experience anyway
Back to our first function, imagine if I wanted to debug the value of the output right before we exited. To do so, look at how many times I have to hit ‘Continue’!
This sucks. And it really sucks when you’re doing code reviews.
Write-Debug make Peer Reviews super suck
If you’re fortunate enough to work on a team of Powershell slingers, you almost definitely have (and if you don’t, start on Tuesday!) a repository to check in and review code.
And if you’re doing this the right way, no one has access to push untested code until it goes through review in the form of a pull request.
So what happens when you need to test ‘why’ something happens in your coworkers code? If you were me, you would have to litter your colleagues (hopefully) clean code with tons of debug statements. These you have to remember to roll back or you get annoying messages from git when you try to change branches again.
I was changing my peers code while reviewing it. It was bad and I feel bad.
So what should I do instead?
It turns out that there has been an answer to this problem just hiding in my consoles for years and I’ve mentally ignored them this whole time.
If you’ve never used a breakpoint before, prepare to be amazed! Whether you use the ISE, Visual Studio, or VS Code, breakpoints are a great tool that let you set an ephemeral debug point without editing the original file!
They essentially function just the same as a Write-Debug
statement, but you can add and remove them without editing the original code, and are deeply integrated into our favorite editors to unlock all kinds of goodness.
How to use them
If you’re in the PowerShell ISE (obligatory WHAT YEAR IS THIS.png) , simply highlight a line on which you’d love to pause your code, then hit F9
. Then run the code and PowerShell will automatically stop in a debug command line.

The code will execute like it normally would until it reaches the breakpoint line at which point…

The same goes for Visual Studio Code, which is even better, as it includes a point-in-time listing of all variable values as well!
It doesn’t stop here! You can also hover over variables to see their value in real time!
This was a huge game changer for me, as I used to type the names of variables over and over and over into the shell to see their current values. Now, I just hover, like you see below. Note the little boxes which appear over the cursor!
But the awesomeness doesn’t stop there!
When you’re paused at a breakpoint, you can also proceed through your code line by line. The same keys work in either VS Code, VS or ISE.
Key | Function |
---|---|
F5 | Continue running when paused |
F9 | Set a breakpoint on this line |
F10 | Step Over – run this line and stop |
F11 | Step Into – go INTO the functions called on this line |
Shift+F11 | Step Out – move your paused breakpoint out to the calling function |
These commands will change your debugging life.
In the demo below, I show how Step-Over works, which runs the current line but doesn’t jump into the definition of any functions within it, like Step-Into does.
Now, let’s go back to our initial example and set a breakpoint to test the value on that last line.
See how easy that was? This is why I believe that once you learn of the power of ultra instinct–er, once you learn about Breakpoints, you’ll simply never need Write-Debug again!

Still confused about the difference between Step Over, Step Into and Step Out? I don’t blame you, checkout this great answer from StackOverflow which does a good job shining light on the distinction.
Alright, fine, you’ve convinced me to start using the debugger!
Sounds like you and I had very similar debugging styles until now… “WRITE ALL THE THINGS!”
LikeLike
I wasn’t joking I have been so so guilty of leaving those debug statements everywhere. And especially when I was reviewing my teammates code and needed to know what value should be expected at a certain point in time oh, I accidentally submitted those debug statements one time!
LikeLike
This is all very nice, fortunately I know all of this from my programming days.
Tou pointed out a feature that should be a no brainer but most people don’t use it.
On the other hand write-debug has it purpose if tou use it correctly. Usually I have it only once in my logging function. There is no need to have a lot of write debug calls if tou do it like that. And when you use it it should be in a part of the code where you really need it not at every variable assignment.
Like all things in life moderation is key.
Thanks for the great post!
LikeLike
For information, to avoid hitting ‘Continue’ for every Write-Debug line, you can use the following at the beginning of your script or function:
If ($PSBoundParameters[‘Debug’]) {$DebugPreference = ‘Continue’}
LikeLike
If you doj this, it makes it harder to actually stop at a debug point though. We have to be careful, because doing this is more akin to using Write-Debug as a surrogate for Write-Verbose or Write-Host. Continuing by default makes it much harder to use its debugging capabilities.
LikeLike
I must admit, I vastly prefer using PSFramework’s Write-PSFMessage instead, rather than drop messages entirely.
But then, it’s a somewhat different use-case, since I use it for forensics in the field (and a second level of verbosity), rather than debugging in the editor.
I’ll confess to some lunacy:
I don’t debug in the editor, ever. All code is run in a regular console, as that’s where it will live in production (usually). Set-PSBreakpoint / Get-PSBreakpoint / Remove-PSBreakpoint are good friends of me though, filling the same role in another tooling context. What’s in the code is meant to stay in the code.
LikeLike
Can you share a link to PSFramework, it’s news to me (and maybe others!)
Dude why not use an ide? Do you also write on machine code too? ☺
LikeLike
This is interesting… It also has logging… so I could drop my logging function
LikeLike