Writing Timer Jobs in SharePoint 2010
Referred URL : https://www.simple-talk.com/dotnet/.net-tools/a-complete-guide-to-writing-timer-jobs-in-sharepoint-2010/
What is a Timer Job?
Timer Jobs are recurring background processes that are managed by SharePoint. If you navigate to the Central Administration site, click on the Monitoring link from the main page, and then choose the Review job definitionslink under the Timer Jobs section, then you’ll see a list of scheduled timer job instances. Notice that I did not say a list of timer jobs, but rather a list of scheduled timer job instances. Unfortunately, the term ‘Timer Job’ in SharePoint is a bit too general. A timer job really consists of three parts: Timer Job Definitions, Timer Job Instances, and Timer Job Schedules.
A timer job definition is a .NET class that inherits from the SPJobDefinition class and defines the execution logic of the Timer Job. Since it is just a .NET class, the Timer Job Definition has all of the things that you would expect a class to have: properties, methods, constructors, etc.
A timer job instance, as you may have guessed, is an object instance of the .NET Timer Job Definition class. You can have multiple instances of a timer job definition, which allows you to define a Timer Job Definition once, but vary the way it operates by specifying different property values for each Timer Job Instance. Whether you need one or many instances of a Timer Job Definition depends entirely on what you are trying to accomplish.
A timer job schedule is the last part of the puzzle. SharePoint exposes a series of pre-defined scheduling classes that derive from the SPSchedule class. A timer job instance must be associated with one of these schedules in order to run, even if you only want to run the timer job instance once.
Timer Jobs in the Central Administration UI
As seems standard with a number of constructs in SharePoint, Microsoft has made it a bit confusing for developers by using the term “Job Definition” in the SharePoint user interface that means something a bit different than what you would expect if you work them in code.
In Central Administration, you can review all of the “Job Definitions” by clicking on the Monitoring link from the main screen (1); then under the Timer Jobs heading clicking the Review Job Definitions (2) link. Since there is aSPJobDefinition class, you may be led to believe that this screen shows all of the timer job definitions that are available, but that is not the case. SharePoint does not have a mechanism to register a job definition by itself, so it does not have a list of the available job definitions. SharePoint only maintains a list of timer job instances that have been scheduled (i.e. SPJobDefinition instances with an associated SPSchedule). So the Review Job Definitionspage is really a list of timer job instances that have been scheduled.
Adding to the confusion, there is also a link from the Review Job Definitions page to Scheduled Jobs. Since a timer job instance needs to be scheduled before it can be used, you would think this page contains a list of timer job instances that have been scheduled. Technically-speaking, it does, but it’s basically the same list that you find on the Review Job Definitions page with a different view. The main difference is that this page displays, and is sorted by, the Next Start Time (which informs you when the job will run next) instead of by Title. Timer jobs with schedules that have been disabled will also not appear on this list, so it may have a fewer number of items listed than the Review Job Definitions page. Clicking on the Title from either page takes you to the same timer job instance configuration page that allows you to modify the schedule of the timer job instance.
Timer Job Associations
A timer job instance must be associated with either a SharePoint web application (SPWebApplication) or a SharePoint service application (SPService). It may also be associated with a server (SPServer) if you desire, but it is not a requirement. The reason behind the association requirements is that the SPJobDefinition class inherits from the SPPersistedObject class.
Without getting into many of the technical details, SharePoint automatically persists state information about SharePoint objects in the farm using a tree-like structure of SPPersistedObject instances. The root of this tree is the SharePoint Farm itself (an SPFarm object) and includes the various web applications, services, and a myriad of other SharePoint objects that reside in the farm. All SPPersistedObject instances must have a parent in order to reside in the tree, and Microsoft deemed the SPWebApplication and SPService objects appropriate places for timer job instances to live in that hierarchy.
What do Timer Job Associations Mean for a Developer?
What this means for you, the developer, is that there are two main constructors for the SPJobDefinition class. One of the constructors allows you to associate the timer job with a web application, and the other allows you to associate it with a service application. You will need to determine which one is best suited for your situation, a chore that should be relatively simple. If you are developing a service application, then it should be associated with that service application. Otherwise, it should be associated with a web application.
One question that may quickly arise is which web application should I associate my timer job with if my timer job really isn’t targeting a specific web application? I recommend associating it with the Central Administration web application, which will be demonstrated later on in this article.
You also have the option of associating a timer job with a specific server in the farm (SPServer). By doing so, it means that your timer job will only run on that one server. A server can be specified for either one of the constructors. In case you were curious, server association has absolutely nothing to do with the SPPersistedObjecthierarchy.
How Do I Associate My Timer Job with the Central Admin Web Application?
You can get a reference to the web application associated with the Central Admin site through theSPWebAdministration.Local property. Just pass this as the web application to the web application constructor and your timer job will be associated with the Central Admin Web Application.
Can I Associate My Timer Job with a Server and Skip the Other Entities?
No. It has to be associated with a web application or service application because it must have a parent in theSPPersistedObject hierarchy for the farm. As mentioned before, the server associated with a timer job instance has nothing to do with that persistence hierarchy.
Can I Just Pass A Null Association Into the Constructor?
Nice try, but no. If you attempt to get around the association by passing null into the constructor for either the web application or service application, it will result in a null object reference exception when that constructor is called in your code.
Using Associated Entities in Code
There are three properties on the SPJobDefinition that are important when it comes to the SharePoint entities associated with a timer job: WebApplication, Service, and Server. As you would hopefully expect, associating a timer job with any of these items results in these properties being populated with a reference to the associated item. Timer jobs don’t necessarily need to use these references, but if you do happen to need them they are available.
How Do I Write a Timer Job Definition?
Writing a timer job definition is extremely easy. All you have to do is create a class that inherits fromSPJobDefinition, implement a few constructors, and override the Execute method.
Required Assemblies
You will need to add a reference to the Microsoft.SharePoint.dll if you are starting from a blank project. If you created a SharePoint 2010 project, you should not need to manually add any references to your project to write a timer job. If you need to reference an assembly manually, most of the SharePoint assemblies are located in
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI
Recommended using Statements
Both the SPJobDefinition and SPSchedule classes reside in the Microsoft.SharePoint.Administrationnamespace. The execute method also accepts a GUID parameter, which lives in the System namespace. As such, you will need (at least) the following two using statements if you don’t want to fully qualify everything in your code:
using System;
using Microsoft.SharePoint.Administration;
Inherit from SPJobDefinition
All SharePoint timer job definitions ultimately inherit from SPJobDefinition, so our class will need to do the same. You can, of course, inherit from a different class as long as SPJobDefinition is somewhere in the inheritance chain.
public class MyTimerJob : SPJobDefinition
{
//Class Code
}
Write the Timer Job Definition’s ConstructorsThe SPJobDefinition class exposes three constructors: the default constructor, a web application association constructor, and a service application association constructor. You have two requirements when writing the constructor(s) for your timer job definition:
- Your timer job must implement a default (parameter-less) constructor.
- Your timer job must call either the web service association constructor or the service application association constructor from the SPJobDefinition class in one of its constructors.
You are required to implement a default (parameter-less) constructor for your timer job for deserialization purposes. The SPJobDefinition class inherits directly from the SPPersistedObject class, which means that SharePoint automatically stores the state of your timer job to a permanent store without you having to implement any of that storage logic. However, the deserialization mechanism expects a default parameter-less constructor to be present in order to operate correctly. Failure to implement the default constructor will result in the following message when you call .Update() from your timer job class:
<TimerJobClass> cannot be deserialized because it does not have a public default constructor.
You are also required to call either the web service association constructor or the service application association constructor from one of your timer job definition’s constructors. Remember, your timer job must be associated with one of these two entities. The only time that an association can be created is from the constructors on theSPJobDefinition class, so one of your constructors has to be associated with a call down to the appropriate base constructor.
There are four key pieces of information for which your timer job definition needs to account in the constructor:
- Web Application or Web Service Association Reference
- Server Association Reference (optional)
- Name of the timer job
- Lock Type of the Job
Please understand that the two constructor requirements listed previously are not mutually exclusive – in other words, you are not required to have 2 constructors. If you have static values for these four key pieces of information, then you can implement a single default constructor that calls the appropriate base constructor with your static values, as in the following example:
public MyTimerJob () : base(
/* NAME: */ "My Timer Job",
/* WEB APP: */ SPAdministrationWebApplication.Local,
/* SERVER: */ null,
/* LOCK TYPE:*/ SPJobLockType.Job)
{
//Constructor Code (if applicable)
}
Notice that the example above satisfies both requirements: the MyTimerJob class exposes a default constructor, that default constructor is calling down to the web application association constructor of the base class, and all four key pieces of information have been provided.
Many timer jobs will, however, require information to be passed in via a constructor. If this is the case, then you will need to implement two constructors: the default (parameter-less) constructor, and the constructor with the parameters that need to be passed.
If you are implementing a default constructor simply for the sake of having it for deserialization purposes, then you will find the following constructor helpful:
//Constructor for deserialization
public MyTimerJob () : base() { }
//...Your Other Constructor With Parameters...
Notice that you do not need to worry about passing any values into the base default constructor. Remember, SharePoint uses this constructor for deserialization, so all of the properties required by your timer job will be populated back into the timer job by SharePoint during that deserialization process.
Naming a Timer Job
One of the four key pieces of information that you must provide a timer job is the timer job name. Timer jobs have two properties related to naming: Name and DisplayName. The Name property is used to identify a timer job, so it must be unique for the timer job instances that exist under the parent SPService or SPWebApplication with which the timer job is associated. In other words, you can have two timer jobs with the same name, as long as they exist under different parents.
DisplayName should contain the name of the timer job as it will appear in Central Administration. If you do not provide an explicit DisplayName value, the value will default to the value in the Name property. Since this name is only used as a display value, it does not have to be unique. You should be aware, however, that the timer job instance lists in Central Administration do not display in a hierarchy – they appear as a flat list. As such, you should take care to distinguish the timer job DisplayName in some way for the sake of users.
For example, let’s say you have a timer job definition that cleans up files in a web application. You’ve got two timer job instances created, one of which is associated with Web Application A and one of which is Associated with Web Application B. Since the timer job instances reside under different web applications, they can both have the same Name. If you do not give them different display names, this is what users will see in the timer job instance list in Central Administration:
- Timer Job
- Timer Job
This can be a bit confusing because it looks like the same timer job is defined twice. By simply varying theDisplayName based on the associated web application’s Title, ID, or URL, you can clear up the confusion and display something far more meaningful like:
- Timer Job – Web App A
- Timer Job – Web App B
Later on we’ll discuss how to store custom properties in a timer job instance. Be aware that you can also use these custom properties to set both the Name and the DisplayName values to ensure that the name is unique under a given parent and that the user has some way to distinguish between timer jobs in the timer job instance list.
Specifying a Timer Job Lock Type
Another one of the four key pieces of information that you must provide the timer job instance constructor is theSPJobLockType value which helps dictate where and how many times the timer job runs. There are three options for this value:
Name
Value
None
Indicates that there are no locks on the job so it will run one time on each server in the farm, but only if the parent web application or service application with which the timer job is associated has been provisioned to that server. If you want a job to run on all the servers in the farm, then this is the option you will want to choose.
ContentDatabase
Indicates that the timer job will run once for each content database associated with the web application with which the timer job is associated. If there is one content database for the web application, it will run once. If there are twenty content databases for the web application, then it will run twenty times. When you specify this option, the targetInstanceId parameter on the Executemethod will be populated with the GUID of the content database for which the timer job is firing. Use this option when you have timer job operations that must interact with content databases.
Job
Indicates that the job will run only one time.
If you are wondering exactly where a timer job will run, know that lock types play a major role in defining that location. We will cover in more detail how to determine exactly which machine a timer job will run on later on in this article.
Override the Execute Method
The main reason you write a timer job is to run code on a periodic basis. All of the code you want to run when your timer job executes should be located in the conveniently named Execute method. Simply override the method and put in whatever code you want to run:
public override void Execute(Guid targetInstanceId)
{
//Code to execute when the timer job runs
}
As mentioned before, if your timer job has a lock type value of ContentDatabase, then the targetInstanceIdparameter is populated with the ID of the content database for which the timer job is being run. Otherwise, you can disregard this parameter. There is really no limit to what you can do inside of this method, so your timer job process can be as simple or as complicated as you like.
Writing the MyTimerJob Demo Timer Job Definition
In an effort to demonstrate what we’ve discussed, we’ll go ahead and build out a very simple timer job definition that writes out a single line of text containing the current date/time to a file each time it runs. Of course, you can implement a lot more complex timer jobs, but for the sake of demonstration I do not want to have an overly complex scenario that takes a lot of time to setup. Plus, it should make it easy to see that your timer job is actually working.
Following is the code for the MyTimerJob class:
public class MyTimerJob : SPJobDefinition
{
//Constructor
public MyTimerJob() : base() { }
//Constructor
public MyTimerJob(string name, SPWebApplication webApp,
SPServer server, SPJobLockType lockType)
: base(name, webApp, server, lockType) { }
//Timer Job Code
public override void Execute(Guid targetInstanceId)
{
string directory = "C:\\";
string fileName = string.Format("{0}.txt", this.Name);
FileInfo fi = new FileInfo(Path.Combine(directory, fileName));
if (!fi.Directory.Exists) fi.Directory.Create();
using (StreamWriter sw = new StreamWriter(fi.FullName, true))
{
sw.WriteLine(string.Format("{0}", DateTime.Now));
sw.Flush();
sw.Close();
}
}
}
The first thing to notice about this class is that it derives from SPJobDefinition class. As mentioned before, all timer job definitions ultimately derive from this class.
Next, we have two constructors. The first constructor is a default, parameter-less constructor that is required for serialization purposes. The second constructor mimics the base constructor from SPJobDefinition that associates a timer job with a SharePoint web application.
After the constructors, you will see the overridden Execute method. This is the method that contains any code that you want executed during the timer job. In our case, the code builds out a directory and file name, ensures the directory exists, and writes the current date/time to the file.
That’s all there is to it. Making a timer job definition is a pretty simple and straightforward process.
Scheduling a Timer Job Instance
Once you have a job definition, all that is left to do is create an instance of that definition, set any applicable properties on the instance, and associate instance with a schedule that defines how often the timer job runs. To do this, you have to create an appropriate SPSchedule object and assign it to the Schedule property on your timer job instance.
Since the SPSchedule class is an abstract base class that defines common functionality required for scheduling a timer job, you will not be creating instances of an SPSchedule object directly. SharePoint ships with a number of classes that derive from the SPSchedule class that offer a variety of scheduling options. Most of these are fairly self-explanatory, but here is the list none-the-less:
Class Name
Recurrence
SPYearlySchedule
Runs the timer job once per year
SPMonthlySchedule
Runs the timer job once per month on the specified day
For example, the 15 of the month
SPMonthlyByDaySchedule
Runs the timer job once per month on the specified day and week
For example, the 3rd Friday of the month
SPWeeklySchedule
Runs the timer job once per week
SPDailySchdedule
Runs the timer job once every day
SPHourlySchedule
Runs the timer job once every hour
SPMinuteSchedule
uns the timer job once every n minutes
where n is the value in the Interval property
SPOneTimeSchedule
Runs the timer job once
Scheduling a Job to Run Each Day
Following is an example demonstrating how to schedule the MyTimerJob timer job to run once per day between the hours of 11:05 a.m. and 2:15 p.m.
var timerJobInstance = new MyTimerJob("My Timer Job (Hourly)",
SPAdministrationWebApplication.Local, null, SPJobLockType.Job);
timerJobInstance.Schedule = new SPDailySchedule()
{
BeginHour = 11,
BeginMinute = 5,
EndHour = 13,
EndMinute = 15,
};
timerJobInstance.Update();
First, we create an instance of the timer job class. In this case we’re calling the timer job instance “My Timer Job (Hourly)”, associating it with the Central Administration web application, not associating it with any particular server in the farm, and specifying the lock type value as Job.
Next, we assign a new SPDailySchdedule instance to the Schedule property on the timer job instance. We’re using the object initialization syntax on the SPDailySchdedule constructor to define the window in which the timer job may run – the 11:05 start time is defined using the BeginHour and BeginMinute properties, and the 2:15 end time is defined using the EndHour and EndMinute properties. You can also get really specific and define the start and end time windows down to the second using the BeginMinute and EndMinute properties if you so desire.
Finally, we call the Update method on the timer job instance to save the timer job instance and starts it running on the schedule you have defined. If you fail to call update, then your timer job will not run and it will not show up in any of the timer job lists in Central Administration. If you fail to associate your timer job instance with a schedule, your timer job will not run and will not show up in the timer job lists in Central Administration (even if you do call theUpdate method).
What’s with the Begin an End Properties on Schedules?
All of the built-in SPSchedule classes allow you to define a window in which the timer job may run. The date/time when that window begins is defined by the properties prefixed with Begin like BeginDay, BeginHour, BeginMinute, etc. The end of the window is defined by the properties prefixed with End like EndDay, EndHour, and EndMinute.
An SPSchedule object communicates the next date and time when the timer job instance is supposed to run via the NextOccurrence method. For all of the built-in SharePoint SPSchedule objects, this method returns a randomized value that occurs somewhere in the window that has been defined. This is extremely beneficial for processor-intensive jobs that run on all servers in the farm because each machine in the farm calls theNextOccurrence method and receives a different start time than the other servers in the farm. Thus, the start-time for each server will be staggered to avoid slamming all of the servers in the farm with a processor-intensive timer job at the same time, allowing your farm to continuing processing requests.
Can SharePoint “Miss” a Timer Job if the Scheduled Window is Too Small?
No. Let’s say that you define a timer job instance with a schedule that has a two second window that starts at 10:00:01 and ends at 10:00:02. SharePoint uses the start and end times to calculate a random value that falls between those values. As such, the timer job will be scheduled to run either at 10:00:01 or 10:00:02 (because those are the only two possible values in this scenario). Although it will be random, it is a concrete value that the SharePoint timer service is aware of and will use to ensure that all timer jobs are run.
For example, let’s say that at 10:00:00 the SharePoint Timer Job Service “checks” to see if it should be running any jobs. Since it is not yet time to run your timer job instance, the job will not be started. Let’s say the next time the SharePoint Timer Service checks to see if it should be running a job is at 10:00:05, effectively missing the window when the timer job can start. Some people mistakenly believe that since the window was missed, SharePoint will simply not run the job. Rest assured, that is not the case. SharePoint has a concrete time that that timer job instance was supposed to start, and if the current time is past that start time then SharePoint is going to start that timer job.
Can I Make a Custom Schedule Class?
Writing a custom SPSchedule is outside of the scope of this article, but it is certainly possible. The question is how useful is it? Writing a custom schedule requires you to create a class that inherits from the SPSchedule class and overrides the NextOccurrence and ToString methods. The ToString method must return a valid recurrence string that defines how often the job recurs, and the syntax for this string has predefined recurrence intervals (e.g. you can’t create your own). As such, it appears that you’re stuck with the intervals that have already been defined, and those are effectively exposed via the SPSchedule objects outlined in the table above.
You can find more information about recurrence values from Mark Arend’s blog post on the subject.
Note: I have not done extensive research in this area, and I am not completely familiar with how the recurrence value from the ToString method and the DateTime returned from the NextOccurrence depend on one another. You may be able to “trick” SharePoint by having a valid recurrence value but returning aNextOccurrence value that matches the schedule you actually want to follow.
How Do I Update the Progress Bar for a Timer Job?
Timer jobs have the ability to communicate their overall progress to an end user, which is especially useful for long running processes. Seeing the progress bar update lets users know that the timer job is working and hasn’t hung for some reason. It also gives them a feeling for how long the timer job is going to run if they are waiting for it to complete.
Updating the progress for a timer job is extremely simple: you just call the UpdateProgress method and pass in a value from 0 to 100 to indicate the completion percentage of your timer job. The hardest part is probably figuring out the integer math to come up with the percentage:
public override void Execute(Guid targetInstanceId)
{
int total = 1000;
for (int processed = 1; processed <= total; processed++)
{
//Next line slows down loop so we can see the progress bar in the UI
System.Threading.Thread.Sleep(10);
int percentComplete = (processed * 100) / total;
UpdateProgress(percentComplete);
}
}
One mistake that people often make is calculating the percentage like you were taught in school – by calculating the number of items processed by the total and then multiplying by 100. Unfortunately, this always results in a value of 0 because a decimal value is truncated in integer math. So you need to multiply the dividend by 100 first so your value will have meaning after the decimal portion is truncated.
Divide First (Incorrect)
Multiply First (Correct)
(5 / 10) * 100 = 0
(5 * 100) / 10 = 50
How Do You Pass Parameters to a Timer Job?
You have two approaches for passing information to a timer job. One option is to have your timer job read from a defined location, like a SharePoint list or SQL database. To pass information to your list, you just write information to that designated location and your timer job will have access to it. This approach works well when you only plan on having one instance of your timer job definition.
You also have the option of storing key/value pairs along with your timer job instance using the PropertiesHashtable on the SPJobDefinition. The only requirement is that any values you place in the Hashtable must be serializable because they will be persisted to the SPPersistedObject hierarchy with your timer job instance. Since the properties are serialized with each timer job instance, this approach makes a lot of sense when you plan on having multiple instances of a timer job definition.
For an example of how to use the Properties Hashtable, see the next section.
Are Class-Level Timer Job Properties Stored Automatically?
No. Although a timer job instance is serialized automatically into the SPPersistedObject hierarchy, the public properties on the timer job are not automatically included in this process. If you want to expose a strongly-typed property that is serialized, back the property with the Properties Hashtable as demonstrated in the following example:
public string MyProperty
{
get { return (string)Properties["MyProperty"]; }
set { Properties["MyProperty"] = value; }
}
Deleting a Timer Job
Deleting a timer job is really simple. All you have to do is call the Delete method on the timer job instance. The real challenge is getting a reference to your timer job instance so you can call that Delete method. Both theSPWebApplication and SPService classes expose a property named JobDefinitions that contains aSPJobDefinitionCollection containing a collection of the timer job instances associated with the entity. Unfortunately, SPJobDefinitionCollection does not expose any helpful methods for locating your timer job instance, so you’ll have to manually iterate through the collection and check each item to see if it’s the one you want.
Following is a helpful extension method named DeleteJob that demonstrates how to look through the collection and find your timer job instance:
public static bool DeleteJob(
this SPJobDefinitionCollection jobsCollection,
string jobName)
{
var jobsList = jobsCollection.ToList();
for (int i = 0; i < jobsList.Count; i++)
{
if (jobsList[i].Name == jobName)
{
jobsList[i].Delete();
return true;
}
}
return false;
}
If you include this extension method, then you can call DeleteJob directly from the JobDefinitions property on both the SPWebApplication and SPService classes:
SPWebAdministration.Local.JobDefinitions.DeleteJob("Timer Job Name");
Which Server Does the Timer Job Run On?
One frequently asked question about timer jobs is which server do they run on? The answer depends on the following factors:
- Which SPJobLockType is associated with the timer job instance?
- Which server is the code that creates the timer job instance running on?
- Is the timer job instance associated with a specific server?
- Is the parent web application or service application associated with the timer job instance provisioned to the server?
Remember that timer jobs are associated with either a web application or a service application. In order for a server to be eligible to run a timer job instance, the server must have the associated web application or service application for the timer job provisioned to the server.
When the SPJobLockType of the timer job instance is set to Job or ContentDatabase, then the timer job is executed on a single server. By default, the server that called the code to create the timer job instance will also be the server that actually runs the timer job. However, if the server is ineligible to run the timer job because it does not have the associated web or service application provisioned, then the timer job will be run by the first server in the farm that does have the appropriate associated entity provisioned.
When the SPJobLockType of the timer job instance is set to None, then the timer job is executed on all of the servers in the farm on which the timer job instance’s associated web or service application has been provisioned.
When a specific server has been associated with a timer job instance, the timer job will only run on the specified server, even if the SPJobLockType is None. Furthermore, if the server is ineligible to run the timer job because it does not have the appropriate web or service application provisioned, then the job will simply not run.
What Account Does the Timer Job Run Under?
Timer jobs are executed by the SharePoint 2010 Timer Service (OWSTIMER.EXE), which you can find in the Services list in under the Administrative Tools item in the Windows Control Panel of your server. To determine the account the SharePoint 2010 Timer Service runs under, just look at the account located in the Log On As column:
By default, the Farm account is associated with the SharePoint 2010 Timer Job. However, there is nothing stopping an administrator from changing the service account through the Windows Services interface. If you are experiencing security or access issues, make sure to manually check the account on each server in the farm to ensure they are all using the same account, and that it is the account you expected.
0 comments