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.

John Robbins

Recent Posts

8-Step AWS to Microsoft Azure Migration Strategy

Microsoft Azure and Amazon Web Services (AWS) are two of the most popular cloud platforms.…

2 days ago

How to Navigate Azure Governance

 Cloud management is difficult to do manually, especially if you work with multiple cloud…

1 week ago

Why Azure’s Scalability is Your Key to Business Growth & Efficiency

Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…

3 weeks ago

Unlocking the Power of AI in your Software Development Life Cycle (SDLC)

https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…

1 month ago

The Role of FinOps in Accelerating Business Innovation

FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…

1 month ago

Azure Kubernetes Security Best Practices

Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…

2 months ago