Second Life of a Hungarian SharePoint Geek

May 20, 2010

Visual Studio 2010 extension for helping SharePoint 2010 debugging

Filed under: Reflection, SP 2010, VSX — Tags: , , — Peter Holpar @ 01:03

Visual Studio 2010 has – of course, among others – a great new feature that is VSIX and MEF-based extensions. Built on that technology you can extend the out of the box SharePoint toolset in Visual Studio.

There is a lot of useful resources on the theory and practice of VS SharePoint tools extensions, so I will not cover it now. If you need the background, you can  easily pick that up from these sites:

SharePoint Development in Visual Studio 2010 (Visual Studio 2010 Walkthroughs)
SharePoint Development in Visual Studio
Visual Studio Software Development Kit (SDK)
Several posts from Waldek Mastykarz on VS 2010
Visual Studio 2010: My very first SharePoint project extension from Johan Leino
Code and walkthrough for my Visual Studio Extensibility session from Wouter van Vugt

In this post I would like to introduce an extension I’ve created to make my life a bit easier. These tools are always “under construction”, but I’ve tried to create a consistent release to be able to share it with fellow developers.

I have published the project on CodePlex here. If you want to build it yourself, don’t forget to install the Visual Studio 2010 SDK.

The extension adds six new menu items to the SharePoint project menu:

Attach debugger to OWSTIMER: If you would like to debug timer jobs, e-mail event receivers or other code that runs in the context of the SharePoint Timer Service, you can click this menu item instead of bothering with the Attach to Process dialog box (Debug / Attach to Process…).

Attach debugger to VSSPHOST4: The vssphost4.exe process is responsible for running SharePoint commands started by Visual Studio 2010. If your code runs in this context, that is the case for example when you create feature event receivers, and would like to debug that code, you can use this menu. In the current version the tool assumes that the process is already running. If this assumption is false, the action will fail.

Attach debugger to W3WP: Although Visual Studio attaches the debugger to the current web application pool process if you start and deploy the project, but deployment sometimes takes extra time (for example, the code did not change and already deployed) and would be easier just to simply attach to the worker process.

Recycle W3WP: Yes, there is a similar deployment step that recycle the current application pool process, but as I wrote, I don’t want to always deploy my project. This menu item helps me in this case.

Restart OWSTIMER: The reason for that is similar than the former one.

Start timer jobs: If you want to debug your timer jobs, you should start them first. You can wait for the jobs to start by their schedule or start them manually one-by-one in Central Admin, but it would be the best if you were able to start the from the Visual Studio IDE, where you want to debug them. So I extended the SharePoint projects with a custom property where you can specify the jobs you want to restart frequently when working on the project. This menu item will try to restart the jobs for you.

image

The following is an output of the job restart. You can see that there is a job that does not exist in my system. In this case you get an alert. Similarly, if the job is already in the running state, you get another kind of warning.

image

In the project extension I add new menu items and properties to the project:

  1. public void Initialize(ISharePointProjectService projectService)
  2. {
  3.     _ps = projectService;
  4.     _ps.ProjectMenuItemsRequested += new EventHandler<SharePointProjectMenuItemsRequestedEventArgs>(ProjectMenuItemsRequested);
  5.     _ps.ProjectPropertiesRequested += new EventHandler<SharePointProjectPropertiesRequestedEventArgs>(ProjectPropertiesRequested);
  6. }
  7.  
  8. // adding custom properties
  9. void ProjectPropertiesRequested(object sender, SharePointProjectPropertiesRequestedEventArgs e)
  10. {
  11.     SPVSProjectProps propertiesObject;
  12.  
  13.     // If the properties object already exists, get it from the project's annotations.
  14.     if (!e.Project.Annotations.TryGetValue<SPVSProjectProps>(out propertiesObject))
  15.     {
  16.         // Otherwise, create a new properties object and add it to the annotations.
  17.         propertiesObject = new SPVSProjectProps(e.Project);
  18.         e.Project.Annotations.Add(propertiesObject);
  19.     }
  20.  
  21.     e.PropertySources.Add(propertiesObject);
  22. }
  23.  
  24. // adding custom project menus
  25. void ProjectMenuItemsRequested(object sender, SharePointProjectMenuItemsRequestedEventArgs e)
  26. {
  27.     IMenuItem miAttachToW3wp = e.ActionMenuItems.Add("Attach debugger to W3WP");
  28.     miAttachToW3wp.Click += new EventHandler<MenuItemEventArgs>(miAttachToW3wp_Click);
  29.     IMenuItem miAttachToOwsTimer = e.ActionMenuItems.Add("Attach debugger to OWSTIMER");
  30.     miAttachToOwsTimer.Click += new EventHandler<MenuItemEventArgs>(miAttachToOwsTimer_Click);
  31.     IMenuItem miRestartVsspHost = e.ActionMenuItems.Add("Attach debugger to VSSPHOST4");
  32.     miRestartVsspHost.Click += new EventHandler<MenuItemEventArgs>(miAttachToVsspHost_Click);
  33.     IMenuItem miRecycleW3wp = e.ActionMenuItems.Add("Recycle W3WP");
  34.     miRecycleW3wp.Click += new EventHandler<MenuItemEventArgs>(miRecycleW3wp_Click);
  35.     IMenuItem miRestartOwsTimer = e.ActionMenuItems.Add("Restart OWSTIMER");
  36.     miRestartOwsTimer.Click += new EventHandler<MenuItemEventArgs>(miRestartOwsTimer_Click);
  37.     IMenuItem miRestartTimerJob = e.ActionMenuItems.Add("Start timer jobs");
  38.     miRestartTimerJob.Click += new EventHandler<MenuItemEventArgs>(miRestartTimerJob_Click);
  39. }

As you can see, the menu items call forward into methods defined in helper classes:

  1. void miRestartTimerJob_Click(object sender, MenuItemEventArgs e)
  2. {
  3.     ISharePointProject project = ((ISharePointProject)e.Owner);
  4.  
  5.     ExtensionHelper.RestartTimerJobs(project);
  6. }
  7.  
  8. void miRestartOwsTimer_Click(object sender, MenuItemEventArgs e)
  9. {
  10.     LogToOutputWindows("Restarting OWSTIMER service…");
  11.     if (ExtensionHelper.RestartService("SPTimerV4", 10))
  12.     {
  13.         LogToOutputWindows("OWSTIMER service restarted");
  14.     }
  15.     else
  16.     {
  17.         LogToOutputWindows("Restarting OWSTIMER service failed");
  18.     }
  19. }
  20.        
  21. void miAttachToW3wp_Click(object sender, MenuItemEventArgs e)
  22. {
  23.     ISharePointProject project = ((ISharePointProject)e.Owner);
  24.     ISharePointConnection connection = project.SharePointConnection;
  25.     EnvDTE.Project dteProj = _ps.Convert<ISharePointProject, EnvDTE.Project>(project);
  26.     AttachProcessHelper apHelpers = new AttachProcessHelper(dteProj.DTE);
  27.  
  28.     // get the URL of the project SharePoint site just for logging
  29.     String siteUrl = project.SiteUrl.AbsoluteUri;
  30.     LogToOutputWindows("Site URL: " + siteUrl);
  31.     // get the application pool name from the current context
  32.     String appPoolName = connection.ExecuteCommand<String>("PHolpar.GetAppPoolName");
  33.     LogToOutputWindows("Application pool name: " + appPoolName);
  34.     // get the process ID of the application pool
  35.     uint appPoolProcId = apHelpers.GetProcessIdByAppPoolName(appPoolName);
  36.     LogToOutputWindows("Application pool process ID: " + appPoolProcId);
  37.     // try to attach to the process
  38.     LogToOutputWindows("Trying to attach to W3WP process");
  39.     if (apHelpers.AttachToProcessById(appPoolProcId))
  40.     {
  41.         LogToOutputWindows("Debugger attached to W3WP process");
  42.     }
  43.     else
  44.     {
  45.         LogToOutputWindows("Failed to attach to W3WP process");
  46.     }
  47. }
  48.  
  49. void miAttachToOwsTimer_Click(object sender, MenuItemEventArgs e)
  50. {
  51.     ISharePointProject project = ((ISharePointProject)e.Owner);
  52.     ExtensionHelper.AttachToProcessByName(project, "owstimer.exe");
  53. }
  54.  
  55. void miAttachToVsspHost_Click(object sender, MenuItemEventArgs e)
  56. {
  57.     ISharePointProject project = ((ISharePointProject)e.Owner);
  58.     ExtensionHelper.AttachToProcessByName(project, "vssphost4.exe");
  59. }
  60.  
  61. void miRecycleW3wp_Click(object sender, MenuItemEventArgs e)
  62. {
  63.     ISharePointProject project = ((ISharePointProject)e.Owner);
  64.     ExtensionHelper.RecycleW3wp(project);            
  65. }

We use DTE to attach processes. The AttachProcessHelper class encapsulates the bulk of the functionality. We can attach to the process by the ID or by the name of the process.

  1. internal class AttachProcessHelper
  2. {
  3.     private DTE _dte;
  4.  
  5.     public AttachProcessHelper(DTE dte)
  6.     {
  7.         _dte = dte;
  8.     }
  9.  
  10.     internal bool AttachToProcessByName(String processName)
  11.     {
  12.         Debugger dbg = _dte.Debugger;
  13.         foreach (EnvDTE80.Process2 process in dbg.LocalProcesses)
  14.         {
  15.             if (process.Name.ToUpper().LastIndexOf(processName.ToUpper()) == (process.Name.Length – processName.Length))
  16.             {
  17.                 process.Attach();
  18.                 return true;
  19.             }
  20.         }
  21.         return false;
  22.     }
  23.  
  24.     internal bool AttachToProcessById(long processId)
  25.     {
  26.         Debugger dbg = _dte.Debugger;
  27.  
  28.         foreach (EnvDTE80.Process2 process in dbg.LocalProcesses)
  29.         {
  30.             if (process.ProcessID == processId)
  31.             {
  32.                 process.Attach();
  33.                 return true;
  34.             }
  35.         }
  36.         return false;
  37.     }
  38.  
  39.     internal uint GetProcessIdByAppPoolName(String appPoolName)
  40.     {
  41.         uint result = 0;
  42.         ManagementScope scope = new ManagementScope(@"\\localhost\root\cimv2");
  43.         ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from Win32_Process where Name='w3wp.exe'");
  44.         searcher.Scope = scope;
  45.         foreach (ManagementObject process in searcher.Get())
  46.         {
  47.             String commandLine = (String)process.GetPropertyValue("CommandLine");
  48.             String appPoolNameFromCommandLine = GetAppPoolNameFromCommandLine(commandLine);
  49.             if (appPoolNameFromCommandLine.ToUpper() == appPoolName.ToUpper())
  50.             {
  51.                 result = (uint)process.GetPropertyValue("ProcessId");
  52.                 break;
  53.             }
  54.         }
  55.         return result;
  56.     }
  57.  
  58.     internal String GetAppPoolNameFromCommandLine(string commandLine)
  59.     {
  60.         String result = String.Empty;
  61.         Regex re = new Regex(@"-ap ""(.+)"" -v", RegexOptions.IgnoreCase);
  62.         MatchCollection matches = re.Matches(commandLine);
  63.         if (matches.Count == 1)
  64.         {
  65.             if (matches[0].Groups.Count > 1)
  66.             {
  67.                 result = matches[0].Groups[1].Value;
  68.             }
  69.         }
  70.         return result;
  71.     }
  72.  
  73. }

There is an additional layer of abstraction in the static ExtensionHelper class:

  1. internal static void AttachToProcessByName(ISharePointProject project, String processName)
  2. {
  3.     EnvDTE.Project dteProj = project.ProjectService.Convert<ISharePointProject, EnvDTE.Project>(project);
  4.     LogToOutputWindows(project, String.Format("Trying to attach to {0} process", processName));
  5.     AttachProcessHelper apHelpers = new AttachProcessHelper(dteProj.DTE);
  6.     if (apHelpers.AttachToProcessByName(processName))
  7.     {
  8.         LogToOutputWindows(project, String.Format("Debugger attached to {0} process", processName));
  9.     }
  10.     else
  11.     {
  12.         LogToOutputWindows(project, String.Format("Failed to attach to {0} process", processName));
  13.     }
  14. }

We get the name of the current application pool by the following SharePoint command:

  1. [SharePointCommand("PHolpar.GetAppPoolName")]
  2. public static String GetAppPoolName(ISharePointCommandContext context)
  3. {
  4.     SPSite site = context.Site;
  5.     SPWebApplication webApp = site.WebApplication;
  6.     SPApplicationPool appPool = webApp.ApplicationPool;                
  7.     return appPool.Name;
  8. }

The RestartService method is used to restart the specified service, in this case the SharePoint timer. You can see from the miRestartOwsTimer_Click introduced above that this method is called with the “SPTimerV4” as service name (that is the internal name of the timer service in SharePoint v4) and 10 seconds as the hard-coded timeout to service stop and start.

  1. internal static bool RestartService(String serviceName, int timeOut)
  2. {
  3.     try
  4.     {
  5.         TimeSpan timeOutSpan = TimeSpan.FromSeconds(timeOut);
  6.         ServiceController sc = new ServiceController(serviceName);
  7.         if (sc.Status == ServiceControllerStatus.Running)
  8.         {
  9.             sc.Stop();
  10.             sc.WaitForStatus(ServiceControllerStatus.Stopped, timeOutSpan);
  11.         }
  12.         sc.Start();
  13.         sc.WaitForStatus(ServiceControllerStatus.Running, timeOutSpan);
  14.         return true;
  15.     }
  16.     catch
  17.     {
  18.         return false;
  19.     }
  20. }

The RecycleW3wp method is a bit more tricky. Since there is already a built-in method in a deployment step to recycle the application pool for a project, I re-used that internal method using Reflection.

  1. internal static void RecycleW3wp(ISharePointProject project)
  2. {
  3.     try
  4.     {
  5.         // hack to get the private Microsoft.VisualStudio.SharePoint.Project assembly
  6.         Assembly privateSPProjAssembly = typeof(VSProjectPackage).Assembly;
  7.         // DeploymentUtils is an internal static classes,
  8.         // so you cannot get them directly from code            
  9.         Type deploymentUtilsType = privateSPProjAssembly.GetType("Microsoft.VisualStudio.SharePoint.Deployment.DeploymentUtils");
  10.  
  11.         // let's call the
  12.         // internal static void RecycleAppPool(ISharePointProject project)
  13.         // method of DeploymentUtils
  14.         MethodInfo mi_RecycleAppPool = deploymentUtilsType.GetMethod("RecycleAppPool",
  15.                 BindingFlags.NonPublic | BindingFlags.Static);
  16.         if (mi_RecycleAppPool != null)
  17.         {
  18.             // call the static method with the project parameter
  19.             mi_RecycleAppPool.Invoke(null, new object[1] { project });
  20.             LogToOutputWindows(project, "IIS application pool recycled");
  21.         }
  22.         else
  23.         {
  24.             LogToOutputWindows(project, "Recycling IIS application pool failed.");
  25.         }
  26.     }
  27.     catch (Exception ex)
  28.     {
  29.         LogToOutputWindows(project, String.Format("Recycling IIS application pool failed. Exception: {0}", ex.Message));
  30.     }
  31. }

Timer jobs are restarted by the RestartTimerJobs method. This method gets the name of the related jobs using the custom project properties we will discuss soon.

  1. internal static void RestartTimerJobs(ISharePointProject project)
  2. {
  3.     LogToOutputWindows(project, "Restarting timer jobs");
  4.     ISharePointConnection connection = project.SharePointConnection;
  5.     SPVSProjectProps propertiesObject;
  6.  
  7.     if (project.Annotations.TryGetValue<SPVSProjectProps>(out propertiesObject))
  8.     {
  9.         List<String> relatedTimerJobs = new List<String>(propertiesObject.RelatedTimerJobs);
  10.         foreach (String relatedTimerJob in relatedTimerJobs)
  11.         {
  12.             LogToOutputWindows(project, String.Format("Restarting timer job: {0}", relatedTimerJob));
  13.             StartTimerJobResult result = connection.ExecuteCommand<String, StartTimerJobResult>("PHolpar.StartTimerJob", relatedTimerJob);
  14.             if (result.Succeeded)
  15.             {
  16.                 LogToOutputWindows(project, "Job started");
  17.             }
  18.             else
  19.             {
  20.                 LogToOutputWindows(project, result.ResponseMessage);
  21.             }
  22.         }
  23.     }

The restart takes place through the StartTimerJob command defined in a separate 64-bit project in the solution.

  1. [SharePointCommand("PHolpar.StartTimerJob")]
  2. public static StartTimerJobResult StartServiceJob(ISharePointCommandContext context, String jobName)
  3. {
  4.     SPSite site = context.Site;
  5.     return CommandHelper.StartServiceJob(site, jobName);
  6. }

It calls forward into static helper methods again.

  1. public static StartTimerJobResult StartServiceJob(SPSite site, String serviceTypeName, String jobName)
  2.         {
  3.             try
  4.             {
  5.                 foreach (SPJobDefinition jobDefinition in site.WebApplication.JobDefinitions)
  6.                 {
  7.                     Trace.TraceInformation("Job name: '{0}, title: {1}'", jobDefinition.Name, jobDefinition.Title);
  8.                     if ((jobDefinition.Name == jobName) || (jobDefinition.Title == jobName))
  9.                     {
  10.                         Trace.TraceInformation("Job found");
  11.                         if (!JobIsRunning(site.WebApplication.RunningJobs, jobDefinition))
  12.                         {
  13.                             Trace.TraceInformation("Job starting");
  14.                             jobDefinition.RunNow();
  15.                             return new StartTimerJobResult
  16.                                 {
  17.                                     Succeeded = true
  18.                                 };
  19.                         }
  20.                         else
  21.                         {
  22.                             Trace.TraceInformation("Job is already running, cannot be started");
  23.                             return new StartTimerJobResult
  24.                             {
  25.                                 ResponseMessage = "Job is already running, cannot be started",
  26.                                 Succeeded = false
  27.                             };
  28.                         }                      
  29.                     }
  30.                 }
  31.             }
  32.             catch (Exception ex)
  33.             {
  34.                 Trace.TraceError(String.Format("Error in StartServiceJob: '{0}'", ex.Message));
  35.                 return new StartTimerJobResult
  36.                 {
  37.                     ResponseMessage = String.Format("Error in StartServiceJob: '{0}'", ex.Message),
  38.                     Succeeded = false
  39.                 };
  40.             }
  41.             Trace.TraceInformation("Job not found");
  42.             return new StartTimerJobResult
  43.             {
  44.                 ResponseMessage = String.Format("Job not found"),
  45.                 Succeeded = false
  46.             };
  47.         }
  48.  
  49.         public static StartTimerJobResult StartServiceJob(SPSite site, String jobName)
  50.         {
  51.             return StartServiceJob(site, null, jobName);
  52.         }
  53.         
  54.         private static bool JobIsRunning(SPRunningJobCollection runningJobs, SPJobDefinition jobDefinition)
  55.         {
  56.             foreach (SPRunningJob runningJob in runningJobs)
  57.             {
  58.                 if (jobDefinition.Id == runningJob.JobDefinitionId)
  59.                 {
  60.                     return true;
  61.                 }
  62.             }
  63.             return false;
  64.         }

The above methods accepts the name or the title of the job to be started and returns the result using the following  class:

  1. [Serializable()]
  2. public class StartTimerJobResult
  3. {
  4.     public String ResponseMessage { get; set; }
  5.     public bool Succeeded { get; set; }
  6. }

The class is decorated with the Serializable attribute. I discussed that issue in my former post.

The following table shows some of the possible values for timer jobs probably installed on your system and scheduled frequently so consume not much resources and ideal for testing:

Job title

Job name

Immediate Alerts

job-immediate-alerts

Scheduled Unpublish

SchedulingUnpublish

Scheduled Approval 

SchedulingApproval

But let’s back to the above-mentioned custom project properties.

The following class define the custom property, stored in the project file:

  1. public class SPVSProjectProps
  2. {
  3. private ISharePointProject sharePointProject;
  4. private IVsBuildPropertyStorage projectStorage;
  5. private const string ProjectFilePropertyId = "RelatedTimerJobs";
  6. private const string ProjectFilePropertyDefaultValue = "";
  7.  
  8. public SPVSProjectProps(ISharePointProject myProject)
  9. {
  10.     sharePointProject = myProject;
  11.     projectStorage = sharePointProject.ProjectService.Convert<ISharePointProject, IVsBuildPropertyStorage>(sharePointProject);
  12. }
  13.  
  14. [DisplayName("Related timer jobs")]
  15. [Description("Name of the related timer jobs")]
  16. [DefaultValue(ProjectFilePropertyDefaultValue)]
  17. // we want our custom property to show up in the SharePoint section in the project properties property grid
  18. [Category("SharePoint")]
  19. // use custom property editor to avoid "Constructor on type 'System.String' not found." error in design mode
  20. [Editor("System.Windows.Forms.Design.StringArrayEditor, System.Design", typeof(UITypeEditor))]
  21. [TypeConverter(typeof(CsvArrayConverter))]
  22. public String[] RelatedTimerJobs
  23. {
  24.     get
  25.     {
  26.         String propertyValue;
  27.         int hr = projectStorage.GetPropertyValue(ProjectFilePropertyId, string.Empty,
  28.             (uint)_PersistStorageType.PST_PROJECT_FILE, out propertyValue);
  29.  
  30.         // Try to get the current value from the project file; if it does not yet exist, return a default value.
  31.         if (!ErrorHandler.Succeeded(hr) || String.IsNullOrEmpty(propertyValue))
  32.         {
  33.             propertyValue = ProjectFilePropertyDefaultValue;
  34.         }
  35.  
  36.         // remove accidental whitespaces
  37.         String[] timerJobNames = propertyValue.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
  38.         timerJobNames = Array.ConvertAll<String, String>(timerJobNames, timerJobName => timerJobName.Trim());
  39.         return timerJobNames;
  40.     }
  41.  
  42.     set
  43.     {
  44.         String propertyValue =
  45.             (value == null) ?
  46.             String.Empty :
  47.             // remove accidental whitespaces
  48.             String.Join("|", Array.ConvertAll<String, String>(value, timerJobName => timerJobName.Trim()));
  49.         // Do not save the default value.
  50.         if (!String.IsNullOrEmpty(propertyValue))
  51.         {
  52.             projectStorage.SetPropertyValue(ProjectFilePropertyId, string.Empty,
  53.                 (uint)_PersistStorageType.PST_PROJECT_FILE, propertyValue);
  54.         }
  55.     }
  56. }

It is based on a String array and utilize a custom editor, displayed below.

image

We eliminate empty values and trim whitespace from the beginning and end of the strings.

We use a converter class to make the property display value more user friendly.

  1. class CsvArrayConverter : ArrayConverter
  2. {
  3.     public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
  4.     {
  5.         Array array = value as Array;
  6.         if (destinationType == typeof(string) && array != null)
  7.         {
  8.             StringBuilder sb = new StringBuilder();
  9.             for (int i = 0; i < array.Length; i++)
  10.             {
  11.                 if (sb.Length > 0)
  12.                 {
  13.                     sb.Append(", ");
  14.                 }
  15.                 sb.Append(array.GetValue(i));
  16.             }
  17.             return sb.ToString();
  18.  
  19.         }
  20.         return base.ConvertTo(context, culture, value, destinationType);
  21.     }
  22. }

image

The next figure shows the persisted settings in the project file:

image

The extension also adds three new deployment steps to the existing ones. These new steps are three of the above mentioned menu items. I feel the Attach debugger to VSSPHOST4 step useful when you work with feature event handlers and would like to capture the first activation of the feature that happens when the project is deployed by Visual Studio. In this case you should include this step in your custom deployment configuration before the Activate Features step.

image 

Since these steps utilize the same helper methods as the menu items do, there is nothing new in their code. I just show one of them to illustrate the structure of the deployment steps for those of you not very familiar with that:

  1. [DeploymentStep("PHolpar.AttachToVsspHost4")]
  2. [Export(typeof(IDeploymentStep))]
  3. internal class AttachDeployStep : IDeploymentStep
  4. {
  5.     public bool CanExecute(IDeploymentContext context)
  6.     {
  7.         return true;
  8.     }
  9.  
  10.     public void Execute(IDeploymentContext context)
  11.     {
  12.         ISharePointProject project = context.Project;
  13.         ExtensionHelper.AttachToProcessByName(project, "vssphost4.exe");
  14.     }
  15.  
  16.     public void Initialize(IDeploymentStepInfo stepInfo)
  17.     {
  18.         stepInfo.Name = "Attach debugger to VSSPHOST4";
  19.         stepInfo.Description = "This step attaches the debugger to the vssphost4 (if already running) to enable debugging of elements involved in the deployment process, for example, feature receivers.";
  20.         stepInfo.StatusBarMessage = "Attaching to vssphost4…";
  21.     }
  22. }

To get the full code and play with that visit the CodePlex site of the project.

About these ads

8 Comments »

  1. I dont know why but it’s retrieving 0 for the AppPool process id, thus attaching to w3 failed:
    Message 26 Application pool name: SharePoint – 80
    Message 27 Application pool process ID: 0
    Message 28 Trying to attach to W3WP process
    Message 29 Failed to attach to W3WP process

    Comment by Half — June 3, 2010 @ 09:45

    • Hi Half

      Getting 0 as the process ID means the code does not find the process by name. You should debug the code to see the reason for that.

      Peter

      Comment by pholpar — June 4, 2010 @ 21:48

  2. [...] you can debug the timer job. Don’t forget to attach the debugger to OWSTIMER.EXE. You may find my Visual Studio extensions useful in this [...]

    Pingback by Creating a timer job for Active Directory – SharePoint profile user image synchronization « Second Life of a Hungarian SharePoint Geek — November 17, 2010 @ 02:40

  3. PLEASE make a Moss 2007 veersion?

    Comment by fred — March 17, 2011 @ 20:00

    • Hi Fred,

      Sorry to say, but my Visual Studio 2010 SharePoint extensions are created for SP 2010 only, and I don’t think I have time to implement it for WSS 3.0 / MOSS 2007 even if MEX and VS add-in infrastructure would make that theoretically possible. Anyway, the source code is available for you to use for such modifications as well.

      Peter

      Comment by Peter Holpar — March 18, 2011 @ 08:27

  4. “There is a lot of useful resources” error “invalid grammar”.

    Change to “There are a lot of useful resources”

    Comment by Peter — September 14, 2011 @ 21:30

    • Peter,

      Thanks for your comment, you might be right. There are almost 3.000.000 hits for “there are a lot of resources” when you search the web, but there are also > 500.000 for “there’s a lot of resources”, so it may be not so wrong. Unfortunately, I’m sure you can find even more serious grammar errors on my blog. I just can hope it was not the most important thing you learnt from this post. If you would like to discuss this question further, I suggest to do that on forums like this. In case you have a more technical question regarding VS 2010 extensions for SharePoint I try to answer that.

      Comment by Peter Holpar — September 14, 2011 @ 22:23

  5. Loving the grammar discussion Peter… awsome post by the way.

    Comment by Rich — December 2, 2011 @ 15:39


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Shocking Blue Green Theme Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 50 other followers

%d bloggers like this: