In the last two weeks I’ve gotten many questions about how to automate and program the exception handling the Visual Studio debugger so I thought I’d show how to some cool macros I’ve created to make it easy. If you’re interested in more about making the most of the debugger you can always come to our Devscovery conference in New York City starting on April 14th. (Bethany, Wintellect’s marketing maestro, is going to love me for working in the advertisement there!).

All the questions I got about the debugger exception handling fell into two buckets. The first was that teams have custom exceptions that they want to add to the Exceptions dialog and don’t want to have to add those manually every time the move to a new machine. Since the Exception settings are stored in the .SUO file next to the solution, you also lose those exceptions if you delete the .SUO file. The second question was about programmatically setting just a handful of exceptions to stop when thrown. What was interesting about the second question is that the people asking had the neat idea of having their tests running under the Visual Studio debugger and configured to ignore all exceptions but a handful. That way they’d have those tough error conditions already in the debugger in order to make their debugging easier. I thought that was a very interesting idea.

As with most things in life, there’s some good news and bad news about programmatically manipulating exceptions through the Visual Studio automation model. The good news is that the automation model has everything you need. The bad news is that certain operations, like setting all exceptions to stop when thrown, if done with a macro as so abysmally slow that it’s essentially unusable. I suspect the performance would be better if I wrote an add-in. I’m hoping the VS 2010 will improve the performance of macros so more people will consider writing quick extensions to the IDE.

Let me start by showing you a simple macro from Jim Griesmer that sets a CLR exception to stop when thrown:

Sub BreakWhenThrown(Optional ByVal strException As String = “”)
Dim dbg As Debugger3 = DTE.Debugger
Dim eg As ExceptionSettings = _
dbg.ExceptionGroups.Item(“Common Language Runtime Exceptions”)
eg.SetBreakWhenThrown(True, eg.Item(strException))
End Sub

To execute the above macro, you’ll open the Command window and pass the full name of the exception on the command line like the following:

>Macros.MyMacros.CLRExceptions.BreakWhenThrown System.ArgumentException

The macro itself if relatively straightforward. Once you have the Debugger3 interface, you can get the exceptions under a category by name. As you probably guessed the names of the exception groups maps to exactly what you see in the Exceptions dialog in Visual Studio.

Note that my Exception dialog probably looks different than yours because I turned off Just My Code in the Visual Studio Options dialog (Options, Debugging, General). I highly recommend you do as well because having Just My Code turned on turns off very valuable features such as debugging optimized builds and the awesome .NET Reference Source Code debugging.

The real work in the macro is all in the ExceptionsGroup interface as it has the methods on it to set, create, and remove individual exceptions. To get an individual exception, you access the ExceptionsGroup Items collection by exception name to get the ExceptionSetting.

At the bottom of this blog entry, I included the macro source code for a set of macros that wrap up adding, removing, and configuring CLR exceptions easy with full error handling and reporting. For those of you doing native development, you’ll get the idea what you need to do. The one difference with Win32 Exceptions verses the other categories of exceptions is that you’ll need to specify the exception codes to those exceptions.

In the code I wanted to include macros that would let me set or clear stopping when exceptions were thrown. The problem was that the macro literally took more than 15 minutes to enumerate and set each exception setting. It’s faster to manually bring up the Exceptions dialog and click the check box next to the category. I’ll keep looking to see if I can find a faster way to enable and disable stopping when thrown.

Those of you using my Debugger Settings add in I’m working on adding support for exception settings to it. Keep reading this space for updates. I’m worried about the performance of saving and restoring the exception settings given the horrible performance I’m seeing from the macro so it may turn out I won’t add it.

The team that was running their tests under the debugger and wanted to automate setting various exceptions also was looking for a way to programmatically execute a macro in Visual Studio. They wanted to be able to configure their special exceptions as well as automatically attach or start their application. Fortunately, that’s easy to do with the /command command line option to DEVENV.EXE. That will start the IDE and execute a Visual Studio command or macro.

As always, let me know if you have any questions or have an idea you’d like to see explored.

”””””””””””””””””””””””””””””””””””””””’
‘ CLRExceptions – John Robbins (c) 2009 – [email protected]
‘ Macros that make it easier to manipulate CLR exceptions in the debugger.

‘ Do whatever you want with this code.

‘ Version 1.0 – April 1, 2009
‘   – Initial version.
”””””””””””””””””””””””””””””””””””””””’
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Text
Imports System.Collections.Generic
Imports System.Runtime.InteropServices

Public Module CLRExceptions

‘ Adds a new exception name to the list of CLR exceptions.
Sub CLRExceptionCreate(Optional ByVal strException As String = “”)
CreateDeleteCLRException(strException, True)
End Sub

‘ Removes an exception from the list of CLR exceptions.
Sub CLRExceptionDelete(Optional ByVal strException As String = “”)
CreateDeleteCLRException(strException, False)
End Sub

‘ Sets the specifically named exception to stop when thrown.
Sub CLRExceptionBreakWhenThrown(Optional ByVal strException As String = “”)
SetClrException(strException, True)
End Sub

‘ Sets the specifically named exception to continue when thrown.
Sub CLRExceptionContinueWhenThrown(Optional ByVal strException _
As String = “”)
SetClrException(strException, False)
End Sub

‘ Helper method to create or delete a CLR exception.
Private Sub CreateDeleteCLRException(ByVal strException As String, _
ByVal create As Boolean)
Dim dbg As Debugger3 = DTE.Debugger
Dim cmdWindow As CmdWindow = New CmdWindow()
If (True = String.IsNullOrEmpty(strException)) Then
cmdWindow.WriteLine(“You must pass the exception as the parameter”)
Return
End If

‘ If ExceptionGroups is null, there’s no project loaded.
If (dbg.ExceptionGroups IsNot Nothing) Then

Dim eg As ExceptionSettings = _
dbg.ExceptionGroups.Item(“Common Language Runtime Exceptions”)
Try
If (True = create) Then
eg.NewException(strException, 100)
cmdWindow.WriteLine(“New CLR exception created: ” + _
strException)
Else
eg.Remove(strException)
cmdWindow.WriteLine(“CLR exception deleted: ” + _
strException)
End If
Catch ex As COMException
cmdWindow.WriteLine(ex.Message)
End Try
Else
cmdWindow.WriteLine(“You must open a solution to set exceptions”)
End If
End Sub

‘ Helper method to have a CLR exception stop or continue when thrown.
Private Sub SetClrException(ByVal strException As String, _
ByVal enabled As Boolean)
Dim dbg As Debugger3 = DTE.Debugger
Dim cmdWindow As CmdWindow = New CmdWindow()

If (True = String.IsNullOrEmpty(strException)) Then
cmdWindow.WriteLine(“Missing exception parameter”)
Return
End If

Dim eg As ExceptionSettings = _
dbg.ExceptionGroups.Item(“Common Language Runtime Exceptions”)
‘ If ExceptionGroups is null, there’s no project loaded.
If (dbg.ExceptionGroups IsNot Nothing) Then
Try
eg.SetBreakWhenThrown(enabled, eg.Item(strException))
Dim msg As String = “continue”
If (True = enabled) Then
msg = “break”
End If
cmdWindow.WriteLine(“Exception ‘” + strException + _
“‘ will ” + msg + ” when thrown”)
Catch ex As COMException
cmdWindow.WriteLine(“Problem setting exception: ” + ex.Message)
End Try
Else
cmdWindow.WriteLine(“You must open a solution to set exceptions”)
End If
End Sub
EndModule

‘ Drop this into a module called Utilites
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics
Imports System.Globalization
Imports System.Text.RegularExpressions
Imports System.Collections.Generic

Public Module Utilities

‘ Stuff a new GUID into the current cursor location.
Sub InsertGuid()
Dim objTextSelection As TextSelection
objTextSelection = CType(DTE.ActiveDocument.Selection(),  _
EnvDTE.TextSelection)
Dim cult As CultureInfo = _
System.Threading.Thread.CurrentThread.CurrentUICulture
objTextSelection.Text = Guid.NewGuid.ToString.ToUpper(cult)
End Sub

‘ My wrapper around the CommandWindow to make it easier to use.
Public Class CmdWindow
‘ The internal Command window.
Private m_CmdWnd As CommandWindow

‘ The constructor that simply gets the command window.
Public Sub New()
m_CmdWnd = CType(DTE.Windows.Item(EnvDTE.Constants.vsWindowKindCommandWindow).Object, CommandWindow)
End Sub

‘ Public method to write a line with a CRLF appended.
Public Sub WriteLine(Optional ByVal Str As String = “”)
m_CmdWnd.OutputString(Str + vbCrLf)
End Sub

‘ Public method to write a line.
Public Sub Write(ByVal Str As String)
m_CmdWnd.OutputString(Str)
End Sub

‘ Clears the command window.
Public Sub Clear()
m_CmdWnd.Clear()
End Sub

‘ Sends the input to the command window like you typed it.
Public Sub SendInput(ByVal Command As String, Optional ByVal Commit As Boolean = True)
m_CmdWnd.SendInput(Command, Commit)
End Sub

End Class
Public Class OutputPane
‘ The output pane this class wraps.
Private m_OutPane As OutputWindowPane
‘ The class constructor.
Public Sub New(ByVal Name As String, _
Optional ByVal ShowIt As Boolean = True)
‘ First, get the main output window itself.
Dim Win As Window = _
DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput)
‘ Show the window if I’m supposed to.
If (True = ShowIt) Then
Win.Visible = True
End If
‘ Convert the window to a real output window.
‘ The VB way of casting!
Dim OutWin As OutputWindow = CType(Win.Object, OutputWindow)
‘ Use exception handling to access this pane if it already exists.
Try
m_OutPane = OutWin.OutputWindowPanes.Item(Name)
Catch e As System.Exception
‘ This output tab doesn’t exist, so create it.
m_OutPane = OutWin.OutputWindowPanes.Add(Name)
End Try
‘ If it’s supposed to be visible, make it so.
If (True = ShowIt) Then
m_OutPane.Activate()
End If
End Sub
‘ Allows access to the value itself.
Public ReadOnly Property OutPane() As OutputWindowPane
Get
Return m_OutPane
End Get
End Property
‘ Wrapper.  Get the underlying object for all the rest
‘ of the OutputWindowPane methods.
Public Sub Clear()
m_OutPane.Clear()
End Sub
‘ The functions I wanted to add.
Public Sub WriteLine(ByVal Text As String)
m_OutPane.OutputString(Text + vbCrLf)
End Sub
Public Sub Write(ByVal Text As String)
m_OutPane.OutputString(Text)
End Sub
End Class

‘ SplitParameters – Splits up a parameter string passed to a macro
‘ into an array of parameters.
Function SplitParameters(ByVal parameterString As String) As String()

‘ No sense doing anything if no parameters.
If (0 = parameterString.Length) Then
Exit Function
End If

‘ Cool regex from Michal Ash:
‘ http://regexlib.com/REDetails.aspx?regexp_id=621
Dim initialArray As String()
initialArray = Regex.Split(parameterString, _
“,(?!(?<=(?:^|,)s*””(?:[^””]|””””|\””)*,)” & _
“(?:[^””]|””””|\””)*””s*(?:,|$))”, _
RegexOptions.Singleline)
‘ Go through and scrape off any extra whitespace on parameters.
Dim returnArray As List(Of String) = New List(Of String)
Dim i As Int32
For i = 0 To initialArray.Length – 1
returnArray.Add(initialArray(i).Trim())
Next
Return (returnArray.ToArray())
End Function

End Module

Â