One of the more tantalizing potentials in StudioShell is using it with the Visual Studio debugger. This post will describe some of the features that are currently available.
The debugger features are accessible at the path dte:/debugger. A quick call to get-childitems shows how the functionality is broken down:
§ DTE:\debugger>ls
Container: PSDTE::dte:\debugger
Name
---------- ----
d+ < Breakpoints
d < DebuggedProcesses
d < LocalProcesses
There are two major subcategories of debugger features exposed through StudioShell: breakpoints and processes.
Breakpoints
StudioShell lets you add, remove, and modify breakpoints from script using the standard item cmdlets. Here is a simple example that adds a breakpoint to line 25 of Program.cs (the ` line-continuation is used for readability):
new-item -path dte:/debugger/Breakpoints `
-file program.cs -line 25
There is also a condition parameter that causes a debug break for a specific condition, such as when the local variable “v” is less than 65:
new-item -path dte:/debugger/Breakpoints `
-file program.cs -line 25 `
-condition "v < 65"
That’s really the tip of the iceberg – breakpoints are very dynamic beasts. In fact, I had no idea the extent to which you can customize breaking into the debugger until I started researching the Debugger node of the dte: drive. To see all of your options, type:
get-help -path dte:/debugger/breakpoints new-item
You can remove breakpoints using the remove-item cmdlet; this example removes all breakpoints in the Program.cs file:
ls dte:/debugger/breakpoints `
| where{ $_.file -match 'program.cs' } `
| remove-item
You can also disable and enable breakpoints using the clear-item and invoke-item cmdlets, respectively:
$bkpts = ls dte:/debugger/breakpoints;
# disable all breakpoints with a hitcount > 1
$bkpts | ?{ $_.currenthitcount -gt 1 } | clear-item;
# enable all breakpoints in the Program.Main function
$bkpts | ?{ $_.functionName -match 'Program.Main' } `
| invoke-item;
Debugged processes
When you start the debugger, the debuggedprocesses node of the dte: drive springs to life. This node contains a list of all processes currently hooked by the debugger, so naturally its contents vary depending on what startup projects you have specified in your solution:
§ DTE:\debugger\DebuggedProcesses>ls
Location: PSDTE::dte:\debugger\DebuggedProcesses
Available Operations: d <
ID Name
---------- -- ----
d < 4988 DemoSolution.vshost.exe
The DebuggedProcesses node doesn’t support many operations; it is simply a reflection of the current debugger state. In fact, the only things you can do at this node is get-item (to fetch a lightweight process object) and set-location to enter a debugged process and start snooping around the threads:
§ DTE:\debugger\DebuggedProcesses>cd .\DemoSolution.vshost.exe
§ DTE:\debugger\DebuggedProcesses\DemoSolution.vshost.exe>ls
Location: PSDTE::dte:\debugger\DebuggedProcesses\DemoSolution.vshost.exe
Threads for Process: DemoSolution.vshost.exe
Available Operations: d <
Frozen Alive Thread ID Name
---------- ------ ----- --------- ----
d < * 3560 3560
d < * 1980 1980
d < * 464 vshost.RunParkingWindow
d < * 5464 .NET SystemEvents
d < * 5300 5300
Threads
Like processes, thread operations are limited to exploration: get-item will fetch a lightweight thread object for the specified thread, and set-location will drill into the stack for each thread:
§ ...\DemoSolution.vshost.exe>cd 5300
§ ...\DemoSolution.vshost.exe\5300>ls
Location: PSDTE::dte:\debugger\DebuggedProcesses\DemoSolution.vshost.exe\5300
Frames for Thread:
Available Operations: d <
Language Location Name
---------- -------- -------- ----
d < C# DemoSolution.Program.Main Current
d < [Native to Managed Transition] Frame1
d < [Managed to Native Transition] Frame2
d < System.AppDomain.ExecuteAssembly Frame3
d < Microsoft.VisualStudio.HostingProcess... Frame4
d < System.Threading.ThreadHelper.ThreadS... Frame5
d < System.Threading.ExecutionContext.Run Frame6
d < System.Threading.ExecutionContext.Run Frame7
d < System.Threading.ThreadHelper.ThreadS... Frame8
d < [Native to Managed Transition] Frame9
Stack Frames
Stack frames are named by their current index, with the topmost frame always named “Current”, the next frame named “Frame1”, and so on. Each frame is a container, so you can set-location again into a specific stack frame to probe further:
§ ...\DemoSolution.vshost.exe\5300>cd current
§ ...\DemoSolution.vshost.exe\5300\current>ls
Container: PSDTE::dte:\debugger\DebuggedProcesses\DemoSolution.vshost.exe\5300\current
Name
---------- ----
d < Arguments
d < Locals
Which brings us to the meaty part…
Locals and Arguments
Once you navigate into a specific stack frames, you can access the local variables and arguments at that frame:
§ ...\5300\current>ls locals
Location: PSDTE::dte:\debugger\DebuggedProcesses\DemoSolution.vshost.exe\5300\current\locals
Available Operations: d <
Value Type Name
---------- ----- ---- ----
< {string[0]} string[] args
< 77 int v
< "This is a v... string value
Once you’re this deep in the path hierarchy, you’re able to do some pretty neat things. These locals values can be modified from the console, just like they can be modified from the locals or immediate window:
§ ...\5300\current>set-itemproperty -path v `
-name value -value 125
§ ...\5300\current>ls
Location: PSDTE::dte:\debugger\DebuggedProcesses\DemoSolution.vshost.exe\5300\current\locals
Available Operations: d <
Value Type Name
---------- ----- ---- ----
< {string[0]} string[] args
< 125 int v
< "This is a v... string value
Of course, being accessible in PowerShell means you gain so much more, such as the ability to search the stack for specific variables and values:
1 § ...\5300>$v = ls | ` #get all stack frames
2 join-path -child 'locals' | ` #create paths to local variables on each stack frame
3 ls | ` #list all locals...
4 where { $_.name -eq 'v' }#.. named "v"
5 § ...\5300>$v | select pspath,value
6
7 PSPath Value
8 ----- -----
9 ...\Current\locals\v 123
10 ...\Frame1\locals\v 124
11 ...\Frame2\locals\v 125
This looks complex, but it simple when you break it down.
- On line 1 we use get-childitems at the thread node to get a list of all stack frames.
- Line 2 appends the node “locals” to each stack frame path, resulting in a list of StudioShell paths for the local variables at each stack frame.
- Line 3 performs a get-childitems on each of these paths, resulting in a list of all locals across all stack frames.
- Line 4 selects those local variables with a name of “v”.
- Line 5 creates a simple report of the local variable path and its value.
The result is the history of the values of variables named “v” at every stack location. This isn’t something you can easily get from the IDE alone.
Looking Ahead
Hopefully this gives you a few ideas. I’m still exploring this space in StudioShell, but I find myself giddy with ideas.
One that’s got me drooling is the notion of replacing the breakpoint condition string with a scriptblock, enabling me to break at specific code locations in response to WMI events for instance. Not sure how I will accomplish this yet – the condition doesn’t appear to be pluggable, but I’m sure there is something that can be done.