Friday, May 1, 2009

Creating and Working with SharePoint Timer Job Definitions

Hi All,

Many times we require creating a timer job definition because of requirement logic. Let’s say, we have some requirement something like whenever list gets added to the site, site administrator should get email saying that this list is being added by so and so person at this time.

Second example, you are supposed to write a job which starts the workflow every night for an approval process of some data calculations.

Now we do not have anything like ListAdding and ListAdded event that gives the answer of our question or any other event that solves some requirement like second example.

In these cases, we can create custom timer job and specify when to run and what to do when it runs.

So let us go ahead and create a custom timer job.

For this, I suggest creating a feature class which installs the job definition when activated and removes the job definition when feature is deactivated or other way is to go to timer job definitions under Operations in central administration and disable that specific job definition.

Before starting up the example, I also would like to explain you few classes for scheduling. Depending upon the schedule that you want to set, you need to take the classes. For example we have SPWeeklySchedule, SPDailySchedule, SPMonthlySchedule, SPYearlySchedule, SPHourlySchedule, SPMinuteSchedule.

You must have got the idea by the name of these classes when to use them. If you want to run your job every hour, you have SPHourlySchedule; if you want it to run every minute then you have SPMinuteSchedule. You can get the idea about the properties that needs to be set when using respective classes from searching MSDN.

Here I am taking an example of SPMinuteSchedule to explain you the example. Do keep in mind that different classes have different properties to be set.

First thing, create a Feature class.

namespace xyz
{
public class ListAddedFeature : SPFeatureReceiver
{
const string LIST_ADDED_JOB_NAME = "ListJobNameAddingCheck";

public override void FeatureInstalled (SPFeatureReceiverProperties properties) {
}

public override void FeatureUninstalling (SPFeatureReceiverProperties properties) {
}

public override void FeatureActivated (SPFeatureReceiverProperties properties) {
SPSite site = properties.Feature.Parent as SPSite;

// make sure the job isn't already registered
foreach (SPJobDefinition job in site.WebApplication.JobDefinitions) {
if (job.Name == LIST_ADDED_JOB_NAME)
job.Delete();
}

// install the job
ListAddingLatestJob taskLoggerJob =new ListAddingLatestJob(LIST_ADDED_JOB_NAME, site.WebApplication);

SPMinuteSchedule schedule = new SPMinuteSchedule();
schedule.BeginSecond = 0;
schedule.EndSecond = 59;
schedule.Interval = 5;
taskLoggerJob.Schedule = schedule;

taskLoggerJob.Update();
}

public override void FeatureDeactivating (SPFeatureReceiverProperties properties) {
SPSite site = properties.Feature.Parent as SPSite;

// delete the job
foreach (SPJobDefinition job in site.WebApplication.JobDefinitions) {
if (job.Name == LIST_ADDED_JOB_NAME)
job.Delete();
}
}

}
}



This gives you an idea that when feature will be activated at that time job will be scheduled, if exists then removed and then scheduled and at the time of deactivating job will be removed. When you will activate the feature you will find the Job name in the Timer Job Definitions in the operations section of central administration under global configuration section and you can also see the timer job status but you won’t find your custom timer job there untill it executes atleast once. This will run every 5 minutes.

Here I also would like to share is what is the significance of BeginSecond and EndSecond and any such propoerty for Begin and End for any above class is that timer job starts at any random time between sub specified time durations. This is because if you have many timer jobs running at the same time, then it could put a heavy load on server.

Here is actual job definition class.

namespace xyz
{
public class ListAddingLatestJob : SPJobDefinition
{


public ListAddingLatestJob()
: base(){
}


public ListAddingLatestJob (string jobName, SPService service, SPServer server, SPJobLockType targetType)
: base (jobName, service, server, targetType) {
}

public ListAddingLatestJob(string jobName, SPWebApplication webApplication)
: base (jobName, webApplication, null, SPJobLockType.ContentDatabase) {
this.Title = "ListAdding Check";
}


public override void Execute (Guid contentDbId) {
// get a reference to the current site collection's content database

SPWebApplication webApplication = this.Parent as SPWebApplication;

SPContentDatabase contentDb = webApplication.ContentDatabases[contentDbId];

SPWeb objWeb = contentDb.Sites[0].RootWeb;

DateTime objTISiteCreationtime = objWeb.Created;

SPList taskList = contentDb.Sites[0].RootWeb.Lists["Tasks"];


SPQuery objQuery = new SPQuery();

objQuery.Query = "<Where><Gt><FieldRef Name=\"ID\"/><Value Type=\"Counter\">0</Value></Gt></Where>";

SPListItemCollection objCollection = taskList.GetItems(objQuery);

DataTable dtAllLists = objCollection.GetDataTable();

ArrayList objArrayList = new ArrayList();

if (dtAllLists != null)
{
if (dtAllLists.Rows.Count > 0)
{

for (int iCnt = 0; iCnt <= dtAllLists.Rows.Count - 1; iCnt++)
{

objArrayList.Add(Convert.ToString(dtAllLists.Rows[iCnt]["Title"]));
}
}
}


for (int iCnt = 0; iCnt <= objWeb.Lists.Count - 1; iCnt++)
{
if (!objArrayList.Contains(objWeb.Lists[iCnt].Title))
{
if (objWeb.Lists[iCnt].Created.ToShortDateString()
!= objTISiteCreationtime.ToShortDateString())
{

SPListItem newTask = taskList.Items.Add();

newTask["Title"] = objWeb.Lists[iCnt].Title;

newTask.Update();
//Write a logic to send a mail to admin

}
}
}


}

}
}


Do not go deep into the logic, as this is just to give an idea about the creation of timer job and along with that simple logic which checks for the lists created by default at the time of site creation which will have the created time same as time created for site.

What I am doing here is first I am storing List Names in the Tasks List (Just to demonstrate you, you can change the logic suiting to your need). I get all items from the tasks list and compare stored Title (List Name) with the each list name of the site, if not there that means it is newly created, send mail to admin and then add the list name again to tasks list title column. So this will keep on working and every five mintes, it will keep checking if newly list has been added or what and sends mail.

Please note that logic is not perfact, just to demonstrate you and give you an idear about Timer job definition and at the same time idea about how to implement List added logic. You can share the best idea with us.

IMP : How to debug the timer job definitions.

Other very important thing is debugging the timer job definitions. Debugging timer job definition is something different because its not about attaching w3wp process because this doesn’t run under web. So we do have other process here to attach to get this thing debug. Name of the process is OWStimer.exe. When you attach this process, then just wait for your timer to get to the right defined time by you and then it will automatically come for debugging. Means if you have set it to 2 minutes, then attach the OWStimer.exe process and just wait for 2 minutes. Every 2 minutes you will get the breakpoint hitting the location.

Ok, one more thing. If you have observed the Timer job definition in central administration, then it gives you an option to disable the job definition but it doesn’t allow to execute it at force.

One way to go about this is to use the stsadm –o execadmsvcjobs but the problem with this is it will execute all the administrative timer jobs without waiting for any job to run in schedule because it doesn’t take any parameter.

So solution is again code. Here is that one liner code that does stuff for us.

foreach (SPJobDefinition job in site.WebApplication.JobDefinitions) {
if (job.Name == LIST_ADDED_JOB_NAME)
job.Execute(site.WebApplication.ContentDatabases[0]);
}


That;s it. Your job is done.

5 comments:

Sandeep said...

Adding a configuration file to fetch values from Timer Job.

Its important to fetch certain values from from config files inside timer job. There are two ways to achieve this

To get reference of the associated web.config file

Get the reference of the SPWeb or SPSite (depending upon feature scope) during feature activation through

SPWeb objWeb = properties.Feature.Parent as SPWeb.
InvoiceLogger objInvoiceLoggerJob = new InvoiceLogger(CONSTANTS.JOB_NAME, objWeb);


2. Add a new constructor on your job definition feature

public InvoiceLogger(string strJobName, SPWebApplication objWebAppToAssociate)
: base(strJobName, objWebAppToAssociate, null, SPJobLockType.ContentDatabase)
{
this.Title = strJobName;

this._spweburlPersisted = strJobName;
}


and there should be one variable holding persisted information

[Persisted]
public string _spweburlPersisted;

Now in the execute method, get the reference of the web url through persisted string information, content database.

public void Execute(Guid _contentDBID)
{

///Get the reference to contentDB

SPWebApplication objWebApp = this.Parent as SPWebApplication;

SPContentDatabase objContentDb = objWebApp .ContentDatabases[contentDbId];

using (SPSite objSite = new SPSite(objContentDb .Sites[0].RootWeb.Url))
{
using (SPWeb objWeb= objSite .OpenWeb())
{

Configuration webconfig = WebConfigurationManager.OpenWebConfiguration("/", objWebApp.Name);

string value = webconfig.AppSettings.Settings["eUserName"].value;

string constringVal = WebConfigurationManager.OpenWebConfiguration("/",objWebApp.Name).ConnectionStrings["constring"].Value;
}
}


To get values from app.config.

In the class library of Timer Jobs, add a new OWSTIMER.exe.config file and add configuration Keys like ..connectionstring or ..appsetting in it.

Deploy the feature using stsadm commond.

Place the OWSTIMER.exe.config file in
C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN

or add the configuration tags into existing OWSTIMER.exe.config file

===================================

restart the timer service....

Bill said...

Every example I've seen of an SPMinuteSchedule uses some factor of 60 as its Interval. If you set it to a value like 45, you'll see that it resets every hour. Does anybody know how to set a timer for every 45 or 90 minutes that truly fires every 45 or 90 minutes?

SharePoint Kings said...

Bill,

default value of interval is 5 as per MSDN

please check this link

so that should work and can be able to reset after 45 or 90 as per your need.
can you please explain your scenario exactly?

Bill said...

Have you actually tried setting it to 45 or 90? If I set it to 45, it fires at xx:45, then when it resets at xx:00. If I set it to 90, it never fires because it resets every hour.

SharePoint Kings said...

yes bill,
SPMinuteSchedule will reset after 60 minute so for 90 minute you have to go for SPHourlySchedule as 90 minute is more then an hour.

you can also check SPDailySchedule which may also work for you.

and FYI...

there are other classes are also available for sp timer job just have a look

SPMinuteSchedule


SPHourlySchedule


SPDailySchedule


SPWeeklySchedule


SPMonthlySchedule


SPYearlySchedule




Share your SharePoint Experiences with us...
As good as the SharePointKings is, we want to make it even better. One of our most valuable sources of input for our Blog Posts comes from ever enthusiastic Visitors/Readers. We welcome every Visitor/Reader to contribute their experiences with SharePoint. It may be in the form of a code stub, snippet, any tips and trick or any crazy thing you have tried with SharePoint.
Send your Articles to sharepointkings@gmail.com with your Profile Summary. We will Post them. The idea is to act as a bridge between you Readers!!!

If anyone would like to have their advertisement posted on this blog, please send us the requirement details to sharepointkings@gmail.com