Single Stepping a PowerShell Pipeline

As I was building up a moderately complicated pipeline in PowerShell, I was having some trouble and really wished there was a way to single step the pipeline so I could see the state of each item as it was processed. I wasn’t sure this was possible, but it is and I thought others might find it helpful. The magic is in the super powerful Set-PSBreakpoint cmdlet.

Because Set-PSBreakpoint can create breakpoints on not only lines and variable reads/writes, but also commands, I thought I’d try setting a breakpoint in the PowerShell console on one of the commands in the pipeline and it worked great! In the example below I will set a breakpoint on the Get-ChildItem cmdlet and when the breakpoint is hit, use the command line debugger to step through parts of the pipeline (the ‘s’ command is step into)

  1. PS C:Junk> Set-PSBreakpoint -Command Get-ChildItem
  2.   ID Script   Line Command        Variable    Action
  3.   — ——   —- ——-        ——–    ——
  4.    0               Get-ChildItem
  5. PS C:Junk> Get-ChildItem *.exe | ForEach-Object { $_.Name }
  6. Entering debug mode. Use h or ? for help.
  7. Hit Command breakpoint on ‘Get-ChildItem’
  8. At line:1 char:1
  9. + Get-ChildItem *.exe | ForEach-Object { $_.Name }
  10. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  11. [DBG]: PS C:Junk>> s
  12. At line:1 char:38
  13. + Get-ChildItem *.exe | ForEach-Object { $_.Name }
  14. +                                      ~
  15. [DBG]: PS C:Junk>> s
  16. At line:1 char:40
  17. + Get-ChildItem *.exe | ForEach-Object { $_.Name }
  18. +                                        ~~~~~~~
  19. [DBG]: PS C:Junk>> s
  20. Foo.exe
  21. At line:1 char:48
  22. + Get-ChildItem *.exe | ForEach-Object { $_.Name }
  23. +                                                ~
  24. [DBG]: PS C:Junk>>

I love how the tildes show you exactly where you are in the pipeline. That makes it super easy to debug the pipeline. A little hint when single stepping pipelines is if you are setting a command breakpoint on something like Get-ChildItem, you’re going to hit it all the time. What I found works best is writing the commands in the ISE and pasting them into the console window where you want to debug.

For those of you that use the ISE most of the time, you have one problem. Here’s the same commands run in the ISE.

  1. PS C:Junk> Set-PSBreakpoint -Command Get-ChildItem
  2.   ID Script   Line Command        Variable    Action
  3.   — ——   —- ——-        ——–    ——
  4.    0               Get-ChildItem
  5. PS C:junk> Get-ChildItem *.exe | ForEach-Object { $_.Name }
  6. Hit Command breakpoint on ‘Get-ChildItem’
  7. Stopped at: Get-ChildItem *.exe | ForEach-Object { $_.Name }
  8. [DBG]: PS C:junk>> s
  9. Stopped at: Get-ChildItem *.exe | ForEach-Object { $_.Name }
  10. [DBG]: PS C:junk>> s
  11. Stopped at: Get-ChildItem *.exe | ForEach-Object { $_.Name }
  12. [DBG]: PS C:junk>>

For whatever reason, the ISE does not show the tildes where you are in the pipeline. Fortunately, it’s not hard to find where you are because the $PSDebugContext variable’s InvocationInfo field has all the information about everything going on at that point in the debugger.

  1. [DBG]: PS C:junk>> $PSDebugContext.InvocationInfo
  2. MyCommand             :
  3. BoundParameters       : {}
  4. UnboundArguments      : {}
  5. ScriptLineNumber      : 1
  6. OffsetInLine          : 40
  7. HistoryId             : 3
  8. ScriptName            :
  9. Line                  : Get-ChildItem *.exe | ForEach-Object { $_.Name }
  10. PositionMessage       : At line:1 char:40
  11.                         + Get-ChildItem *.exe | ForEach-Object { $_.Name }
  12.                         +                                        ~~~~~~~
  13. PSScriptRoot          :
  14. PSCommandPath         :
  15. InvocationName        :
  16. PipelineLength        : 0
  17. PipelinePosition      : 0
  18. ExpectingInput        : False
  19. CommandOrigin         : Internal
  20. DisplayScriptPosition :

 

As you can see, the PositionMessage is the important data. In my ISE profile, I added the following function to make it easy to see where I’m at when single stepping a pipeline.

  1. function cpo
  2. {
  3.     if (test-path variable:/PSDebugContext)
  4.     {
  5.         $PSDebugContext.InvocationInfo.PositionMessage
  6.     }
  7. }

Single stepping the pipeline isn’t super ninja magic like some of those PowerShell people do but this sure helped me figure out what was going on in my troublesome pipeline.

Stay Informed

Sign up for the latest blogs, events, and insights.

We deliver solutions that accelerate the value of Azure.
Ready to experience the full power of Microsoft Azure?

Atmosera is thrilled to announce that we have been named GitHub AI Partner of the Year.

X