How to Debug a Custom OWSTimer Job
Recently I had a requirement to provide the ability for files dropped in a document library by an automated process to be copied to a specific user's My Site in SharePoint 2007. The challenge was simple enough in concept. The approach was to write and deploy a custom Event Handler into MOSS that would copy the file that was just added to a specific source document library to a destination document library on a user's My Site. If the user did not have the document library, the event handler would take some time to provision one. The event handler would determine the destination My Site by looking at the name of the containing folder for the files in the source document library.
The challenge is that the legacy system writing to the document library would be writing through WebDav. MOSS event handlers don't catch WebDav events based on the fact that MOSS eventing is largely built on ASP.NET's model. Thus, when I attempted to add an item to the document library through the UI, the event handler caught the addition, and copied the file. However, when I attempted to do this through WebDav, the event handler did not fire.
I evaluated several alternatives, including a Windows Service to monitor the WebDav location with a .NET FileSystemWatcher (which I'm not sure if that would have even worked :)), and an Http handler that would have been placed in the SharePoint virtual directory that intercepted and decoded the complex and poorly documented WebDav operations to see if files were added and where. I also considered a custom OWSTimer job that would run, like a service, and work with the MOSS object model.
I chose the OWSTImer job because I could deploy into MOSS as a feature, have a OOB interface for deployment/retraction as well as an existing service with instrumenting, the ability to enable and disable the job, and I didn't have to write a service . I was able to take my event handler class and port it to a new project in which I created a new class library with a class that derived from SPJobDefinition. I also created a feature receiver for it in the same assembly and then deployed the assembly. The feature activation code cleaned up any existing instances of the job it found as well as installed a new definition. Thus, the job became visible in the Time Job Definitions page and I was able to enable/disable it from there.
However, the real challenge came when I tried to debug the job execution. The challenge is that instead of attaching to the w3wp.exe process, jobs run in the OWSTIMER process. However, merely attaching to that process didn't yield the same results. Thus, what I had to do was the following:
- In the Execute method of the job (the method you override to write your job execution logic) place the following code (Thanks Joe!): System.Diagnostics.Debugger.Break();
- Assuming you have already added your solution to the site and deployed it using Central Admin, simply uninstall the job assembly from the GAC and add the newly built one back in again
- What I did here is to stop the OWSTIMER service (to prevent my job from running a million times while I do these steps)
- Remove and copy the job assembly to the GAC
- Map the GAC folder so you can actually see the files to place the .pdb file in for debugging using subst Z C:\Windows\Assembly (this command run from the command prompt maps the file structure of the windows assembly folder to the z:\ drive so you can copy your assembly into the Z:\GAC_MSIL\MyAssembly\10000___StrongNameKey\ folder
- Reset IIS and restart the OWSTIMER service
- Quickly attach your VS.NET debugger to the OWSTIMER process and wait for your job to kick off inside OWSTIMER. The debugger will break on the System.Diagnostics.Debugger.Break(); allowing you to step through your job
Remember to remove the System.Diagnostics.Debugger.Break(); line before you go to production, or the OS will attempt to present you with a debugging option, even if VS.NET is closed. Be judicious about disabling your job if you are going to continue development.