As a SharePoint developer, I spend most of my days developing and testing web parts, custom pages, fields and event receivers. Part of this activity is debugging, where one should attach the debugger to the w3wp.exe process instance that belongs to the application pool of the web site we developing on.
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
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 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
- 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
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:\windows\system32\inetsrv\w3wp.exe -a \\.\pipe\iisipm5f3cda83-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\root\cimv2")
- 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
- 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
- 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(3) As EnvDTE80.Engine
- dbgeng(0) = trans.Engines.Item("T-SQL")
- dbgeng(1) = trans.Engines.Item("T-SQL")
- dbgeng(2) = 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:
Tags: Debugging, SharePoint
February 15, 2010 at 01:00 |
[...] method and attaching the debugger to the worker process, so you can catch the event if it fired. One of my former posts shows you how to attach the debugger [...]