- June
- 12
- 2009
I got tired of attaching debugger to w3wp.exe. And you?
Posted by pholpar
No Comments »
If you have done that yet, you know it is not an exciting task to select the correct process if you have dozens of instances. You can do that by trial and error, and checking if the red lights next to the breakpoints are turned on, or you should look for the next instance. It is one step better to run iisapps.vbs as described here, and use the process id it displays for the correct application pool, but it is still a cumbersome manual process, that a developer does not enjoy.
Developers enjoy development, and developing tools that help development. A good platform for those tools is the Visual Studio IDE and in that environment maybe the simplest way to create tools is writing macros.
To help my job I created a macro in Visual Studio 2005 that attaches the debugger to the w3wp.exe instance of the configured application pool. I hope it works for Visual Studio 2008 too, but I have not tested it with that yet.
Configuring the name of the application pool in Visual Studio
The configuration can be done using the project properties. These properties are stored in the csproj.user file of the project, so if you use version control and work in a team, the value can be different for teammates. I selected the Command line arguments text field on the Debug tab to store the name of the application pool we want to attach the debugger to.
There might be multiple projects in the solution, and each project may be multiple configuration (like debug or release), so I decided to use the debug configuration for the first startup project. It is important, because there may be multiple startup projects in a solution.
The macro
After this introduction, let’s see the code of the macro. We need the import the following namespaces:
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Management
Imports System.Text.RegularExpressions
The entry point for the macro is the AttachMacro sub. In that we first determine the projct whose configuration we will use. If there is only a single project in the solution we can go with that, if there are multiple projects then at least one of the must be set as startup project, but only the configuration of the first one will be used.
If we found the projects, we use the GetArgumentsForDebug function (see details later) to read the application pool name from the configuration.
Finally we call the AttachToAppPool function (see details later) to attach or debugger to the process of the application pool. Public Sub AttachMacro()
Try
' get the startup project first
Dim project As Project
Dim solutionBuild As SolutionBuild = DTE.Solution.SolutionBuild
Dim startUpProjs As Array = solutionBuild.StartupProjects
Dim projName As String
If startUpProjs.Length = 0 Then
If DTE.Solution.Projects.Count = 1 Then
project = DTE.Solution.Projects.Item(1)
Else
MsgBox("There is no startup project and solution contains multiple project!", MsgBoxStyle.Exclamation, "Alert")
Exit Sub
End If
Else
projName = solutionBuild.StartupProjects(0)
project = GetProjectByName(projName)
If project Is Nothing Then
MsgBox(String.Format("Startup project '{0}' not found by name!", projName), MsgBoxStyle.Exclamation, "Alert")
Exit Sub
End If
End If
Dim appPoolName As String = GetArgumentsForDebug(project)
If String.IsNullOrEmpty(appPoolName) Then
MsgBox(String.Format("Command line arguments property is not set for stratup project '{0}', debug mode!", projName), MsgBoxStyle.Exclamation, "Alert")
Exit Sub
End If
Dim processFound As Boolean = AttachToAppPool(appPoolName)
If Not processFound Then
MsgBox(String.Format("No worker process found for application pool called '{0}'!", appPoolName), MsgBoxStyle.Exclamation, "Alert")
Exit Sub
End If
Catch ex As System.Exception
MsgBox(ex.Message)
End Try
End Sub
The GetProjectByName function is a simple helper method to get the project object using the name of the startup project.
Private Function GetProjectByName(ByVal projectName As String) As Project
Dim result As Project = Nothing
For Each project As Project In DTE.Solution.Projects
If project.UniqueName = projectName Then
result = project
Exit For
End If
Next
GetProjectByName = result
End Function
In the GetArgumentsForDebug function we read the Command line arguments value that is stored in the StartArguments parameter from the debug config of the specifed project.
Private Function GetArgumentsForDebug(ByVal project As Project) As String
Dim configuration As EnvDTE.Configuration
GetArgumentsForDebug = String.Empty
For Each configuration In project.ConfigurationManager
If configuration.ConfigurationName = "Debug" Then
Dim startArgsObj As Object = configuration.Properties.Item("StartArguments")
If Not startArgsObj Is Nothing Then
GetArgumentsForDebug = CType(startArgsObj.Value, String)
End If
Exit Function
End If
Next
End Function
An interesting part of the macro is the GetProcessIdByAppPoolName function. In this function we use WMI (IMPORTANT: don’t forget to reference the System.Management assembly!) to get the list of all w3wp.exe processes, and select the one that belongs to the specified application pool. The ID of the process is returned.In the comparison we use the GetAppPoolNameFromCommandLine function (see later), that receives the CommandLine property of the process, that looks like this for a w3wp.exe process:
c:windowssystem32inetsrvw3wp.exe -a \.pipeiisipm5f3cda83-745a-423a-88b2-103a2f632200 -ap "MyAppPool"
You can see that the name of the application pool is at the end of the string.
Private Function GetProcessIdByAppPoolName(ByVal appPoolName As String) As Long
GetProcessIdByAppPoolName = -1
Dim scope As ManagementScope = New ManagementScope("\localhost
ootcimv2")
Dim searcher As ManagementObjectSearcher = New ManagementObjectSearcher("select * from Win32_Process where Name='w3wp.exe'")
searcher.Scope = scope
For Each process As ManagementObject In searcher.Get()
Dim commandLine As String = process.GetPropertyValue("CommandLine")
If GetAppPoolNameFromCommandLine(commandLine).ToUpper() = appPoolName.ToUpper() Then
GetProcessIdByAppPoolName = process.GetPropertyValue("ProcessId")
Exit For
End If
Next
End Function
In the GetAppPoolNameFromCommandLine function we use a simple regular expression to get the name of the application pool from the CommandLine string.Private Function GetAppPoolNameFromCommandLine(ByVal commandLine As String) As String
GetAppPoolNameFromCommandLine = String.Empty
Dim re As Regex = New Regex("-ap ""(.+)""", RegexOptions.IgnoreCase)
Dim matches As MatchCollection = re.Matches(commandLine)
If matches.Count = 1 Then
If matches.Item(0).Groups.Count > 1 Then
GetAppPoolNameFromCommandLine = matches.Item(0).Groups(1).Value
End If
End If
End Function
In the AttachToAppPool function we attach the debugger to the process having the same process ID that we determined earlier.Private Function AttachToAppPool(ByVal appPoolName As String) As Boolean
Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger
Dim trans As EnvDTE80.Transport = dbg2.Transports.Item("Default")
Dim dbgeng(2) As EnvDTE80.Engine
dbgeng(0) = trans.Engines.Item("T-SQL")
dbgeng(1) = trans.Engines.Item("Managed")
AttachToAppPool = False
For Each process As EnvDTE80.Process2 In dbg2.LocalProcesses
If process.ProcessID = GetProcessIdByAppPoolName(appPoolName) Then
process.Attach()
AttachToAppPool = True
Exit For
End If
Next
End Function
To make things even more comfortable it is the best to assign a keyboard shortcut to the macro using the Visual Studio Tools/Options… menu item as shown on the screenshot below: