Thursday, August 5, 2010

Create a Sample Timer Job

Create a Sample Timer Job
Create your own timer JobMOSS Timer JobsIn previous versions of SharePoint (or other platforms), if you had some task you wanted to perform on a scheduled basis, you'd have to either create a console EXE and schedule it to run via Windows Task Scheduler (ala AT.EXE) or create a Windows Service that went to sleep for a period of time. In order to install (and maintain) these tasks, you had to have console access to your production SharePoint (or other app) servers... something IT or admins wouldn't easily hand out.Addressing this issue, Microsoft has added something called timer jobs to Microsoft Office SharePoint Server (MOSS) 2007. Microsoft uses timer jobs to do things like dead web cleanup (purging unused sites from site collections) among others. To see what other timer jobs are out there, from Central Administration, click Operations and then Timer Job Definitions. Not only does Microsoft use timer jobs in MOSS, but you can create your own custom timer jobs to do your own scheduled tasks. What's nice about this is once your timer job has been installed (something you can easily do with a solution & a feature), you can view it's status through Central Administration and even disable/enable it... all without console access to your production servers! Another cool thing is that when your job runs, MOSS passes it the GUID of the content database for the site the job is registered with. You can use this GUID to obtain a reference to a content database, then a site collection, and finally a site within the collection (SPWeb).How do you build one? Well, unfortunately the documentation is lacking here... there isn't a single article in the SDK talking about creating custom timer jobs and the necessary objects aren't well documented either.Everything surrounds the Microsoft.SharePoint.Administration.SPJobDefinition object. Create a new class that inherits from SPJobDefinition, implement a few constructors and a single method: Execute(Guid). I created a simple timer job that (assuming it's associated with a WSS site created using the Team Site template and there's a Tasks list in the root of the site) creates a new task every 5 minutes when the job is enabled. Here's what the constructors look like ([update 1/11 12p] added a default empty constructor[/]):
1: public TaskLoggerJob ()2: : base(){3: }4: public TaskLoggerJob (string jobName, SPService service, SPServer server, SPJobLockType targetType)5: : base(jobName, service, server, targetType) {6: }7: public TaskLoggerJob (string jobName, SPWebApplication webApplication)8: : base(jobName, webApplication, null, SPJobLockType.ContentDatabase) {9: this.Title = "Task Logger";10: }
Now, after you build the assembly containing your custom timer job and add it to the GAC (a requirement), you need to associate it with a specific site, site collection, web application, or farm and specify a schedule for when it should run. Ideally I'd like to do this with STSADM.EXE, but no such command exists (custom command opportunity out there!). Another way is to do this through the object model. Because I want to minimize any console access requirements, I'll do this in a feature by implementing the FeatureActivated event. To do this, you create an instance of your job, set the schedule, and call update like so:1: // get a reference to our job in the GAC2: TaskLoggerJob taskLoggerJob = new TaskLoggerJob("Task Logger", site.WebApplication);3: // set the execution schedule4: SPMinuteSchedule schedule = new SPMinuteSchedule();5: schedule.BeginSecond = 0;6: schedule.EndSecond = 59;7: schedule.Interval = 5;8: taskLoggerJob.Schedule = schedule;9: //update the job10: taskLoggerJob.Update();
That's it! Course you should take this a step further by packaging all this up in a solution that will deploy the assembly to the GAC and install a feature. Then, upon activating the feature, it will associate the web with the timer job. Conversely, upon deactivating the feature, it removes the timer job. Very slick!Creating custom sharepoint timer jobsA few weeks back I posted about how you can create custom timer jobs for use in the latest release of SharePoint to perform scheduled tasks. However, considering there have been almost 40 comments to the post in the last three weeks, I figured the post needed some clarification (duh, ya think?).The first thing you need to do is create a class that inherits from the Microsoft.SharePoint.Administration.SPJobDefinition class. To implement this class, you need to create a few constructors and override the Execute() method, like so:
1: namespace AndrewConnell.TaskLogger {2: public class TaskLoggerJob : SPJobDefinition{3: 4: public TaskLoggerJob ()5: : base(){6: }7: 8: public TaskLoggerJob (string jobName, SPService service, SPServer server, SPJobLockType targetType)9: : base (jobName, service, server, targetType) {10: }11: 12: public TaskLoggerJob (string jobName, SPWebApplication webApplication)13: : base (jobName, webApplication, null, SPJobLockType.ContentDatabase) {14: this.Title = "Task Logger";15: }16: 17: public override void Execute (Guid contentDbId) {18: // get a reference to the current site collection's content database19: SPWebApplication webApplication = this.Parent as SPWebApplication;20: SPContentDatabase contentDb = webApplication.ContentDatabases[contentDbId];21: 22: // get a reference to the "Tasks" list in the RootWeb of the first site collection in the content database23: SPList taskList = contentDb.Sites[0].RootWeb.Lists["Tasks"];24: 25: // create a new task, set the Title to the current day/time, and update the item26: SPListItem newTask = taskList.Items.Add();27: newTask["Title"] = DateTime.Now.ToString();28: newTask.Update();29: }30: }31: }
As you can see, this job does nothing important but add a new item to a Task list every time it's executed. Now that you have the job built, you need to get it registered. Unfortunately the only way to do this is through code, but it sure would be one heck of a candidate for a custom STSADM command. Anyway, since we don't have that today, I like to use the feature activated & deactivated events to install/uninstall my custom jobs.To do this, you have to create another class that inherits from the Microsoft.SharePoint.SPFeatureReceiver class and implement the FeatureActivated & FeatureDeactivated event handlers like so:
1: namespace AndrewConnell.TaskLogger {2: class TaskLoggerJobInstaller : SPFeatureReceiver {3: conststring TASK_LOGGER_JOB_NAME = "TaskLogger";4: publicoverridevoid FeatureInstalled (SPFeatureReceiverProperties properties) {5: }6: 7: publicoverridevoid FeatureUninstalling (SPFeatureReceiverProperties properties) {8: }9: 10: publicoverridevoid FeatureActivated (SPFeatureReceiverProperties properties) {11: SPSite site = properties.Feature.Parent as SPSite;12: 13: // make sure the job isn't already registered14: foreach (SPJobDefinition job in site.WebApplication.JobDefinitions) {15: if (job.Name == TASK_LOGGER_JOB_NAME)16: job.Delete();17: }18: 19: // install the job20: TaskLoggerJob taskLoggerJob = new TaskLoggerJob(TASK_LOGGER_JOB_NAME, site.WebApplication);21: 22: SPMinuteSchedule schedule = new SPMinuteSchedule();23: schedule.BeginSecond = 0;24: schedule.EndSecond = 59;25: schedule.Interval = 5;26: taskLoggerJob.Schedule = schedule;27: 28: taskLoggerJob.Update();29: }30: 31: publicoverridevoid FeatureDeactivating (SPFeatureReceiverProperties properties) {32: SPSite site = properties.Feature.Parent as SPSite;33: 34: // delete the job35: foreach (SPJobDefinition job in site.WebApplication.JobDefinitions) {36: if (job.Name == TASK_LOGGER_JOB_NAME)37: job.Delete();38: }39: }40: }41: }
Now... to get it working, all you need to do is:1. Deploy the strongly named assembly to the GAC.2. Reset IIS (required for SharePoint to "see" the new timer job in the GAC).3. Create a feature specifying the receiver class and assembly that contains the event receivers.4. Install the feature.5. Activate the feature.This sample timer job assumes it's running within the context of a WSS v3 site that has a list created with the Tasks list template in the root web within the site collection (or, just create a Team Site at the root of your site collection).Once you activate the feature, it should show up on the Timer Job Definitions page in Central Administration / Operations. It won't appear in the Timer Job Status page until it's executed at least one time. Wait for five minutes or so (the schedule this sample is using) to see if it's running. You should start to see items showing up in the Tasks list in the root site in your site collection.Here's a Visual Studio 2005 solution that includes everything you need to create a custom timer job. Note that I used the technique I described in this article to modify the Visual Studio project to create the WSP file for me:» TaskLogger_CustomTimerJob.zipOr, you can use this Windows SharePoint Solution Package (*.WSP) to deploy the prebuilt timer job used in this article (handles steps 1-4 above):» AndrewConnell.TaskLoggerJob.zip (unpack the ZIP to get the WSP)To deploy the WSP file, simply add it to the solution store using STSADM (using the following command) and then deploy it to the desired site:stsadm –o addsolution –filename AndrewConnell.TaskLoggerJob.wspUsing locks in Custom MOSS Timer JobsMicrosoft Office SharePoint Server(MOSS) 2007 provides both out of box timer jobs as well as the option to create your own custom timer.The MOSS OWSTimer.exe process controls and executes all timer definition jobs for SharePoint.Custom timer jobs can be created using either a feature or using a solution.Microsoft.SharePoint.Administration.SPJobDefinition class contains the definition for the MOSS timer jobs. MOSS passes the GUID of the content database for the site the job is registered with. You can use this GUID to obtain a reference to a content database, then a site collection, and finally a site within the collection (SPWeb).The class containing the invocation code for the timer jobs must inherit from the Microsoft.SharePoint.Administration.SPJobDefinition class. The constructors need to be created and the Execute() method must be overridden as mentioned below. The Execute() method is overridden for performing the custom activity that the user has planned to do using the timer jobs. The Execute() is invoked by the MOSS timer services but if there is no lock applied on the code then concurrency issues arise if different users are planning to trigger the timer job at the same time. To overcome concurrency issues the Execute() code must be locked by the process that is executing and the other concurrent process must wait till the lock is removed.Scenario: Users in an organization are planning to synchronize lists(which they have access on) in a MOSS web application. They are performing the activity by using a UI (ASP.Net web application running under MOSS context) and creating timer jobs that in turn run and complete the process. Users who are requesting for the sync activity are notified with a mail in the end on the success or failure of the job. In our current scenario we are planning to use the included property bag (SPJobDefinintion.Properties) to store your custom properties. The property bag stores the requestor alias(UserAlias) and the SiteUrl. namespace Infosys.Blogs
{
public class TimerJobClass : SPJobDefinition{
/// /// Base class constructor
/// public TimerJobClass() : base(){
}
public TimerJobClass(string jobName, SPService service, SPServer server, SPJobLockType targetType) : base (jobName, service, server, targetType)
{
}
/// /// Constructor that accepts the JobName,SPWebapplication
/// public TimerJobClass(string jobName, SPWebApplication webApplication) : base (jobName, webApplication, null, SPJobLockType.ContentDatabase) { } /// /// Called by MOSS when the Job is kicked off for execution
/// /// public override void Execute(Guid contentDbId){lock (timerLock)
{
//Fetch the value from the Job PropertyBagif (this.Properties.ContainsKey(JobPBValue)){fetchJobPB = this.Properties[timerJobName].ToString();} string[] jobPropertyBag = fetchJobPB.Split(','); string siteUrl = jobPropertyBag[jobPropertyBag.Length - 0];string userAlias = jobPropertyBag[jobPropertyBag.Length - 1];//Queue at the Root site collection level of the web applicationcurrentSite = new SPSite(siteUrl);currentSite = new SPSite(currentSite.Url);currentWeb = currentSite.OpenWeb();
//Body of code - writes to a list..//End of code - writes to a listSPUtility.SendEmail(currentWeb, false, false, userAlias, "Status of Timer Job" + this.Name, “The Job is Completed”);
}

No comments:

Post a Comment