Friday, May 6, 2011

Creating Custom SharePoint Timer Job

When I started looking for some materials on SharePoint Timer Job, I was overwhelmed to see the contents available, however I felt to share my part of SharePoint Timer Job experience. This being my first effort to create the timer job, I was bit confused with the windows scheduler. Though, the SharePoint timer job is very simple and straight forward.

Let me give the topic a totality by outlining the points I would wish to cover

1.    When do we need to create a custom SharePoint timer job?

2.    How to create it?

3.    How to deploy it in Development Environment?

4.    How to debug it?

5.    How to deploy it in Test/Production Environment?

6.    Errors you might face

 

1.    When do we need to create a custom SharePoint timer job?

When we want SharePoint to act on a scheduled time without any user intervention, such as sending mails, fetch external data, synchronizing lists etc. on a periodical basis.

2.    How to create it?

The timer job would need only two classes (i) The timer job and (ii) Feature Receiver class

a)    Need to create a class that will inherit from the SPJobdefintion class, i.e. from "Microsoft.SharePoint.Administration.SPJobdefinition".  Add the reference of the namespace using Microsoft.SharePoint.Administration; the class would look like public class MySPTimerJobActivation : SPJobDefinition

b)    Constructor is very important in SP Timer Jobs. In this code the constructor would be called from the feature receiver class while instantiating the MySPTimerJobActivation. There are two overloaded constructors apart from the default contractor available, in my case the one I have used is sufficient.

#region Overloaded Contructors

public MySPTimerJobActivation(): base()

{

}

public MySPTimerJobActivation(string jobName, SPWebApplication webApp, SPServer server, SPJobLockType joblock)

            : base(jobName, webApp, server, joblock)

{

   this.Title = jobName;

}

#endregion

 

c)     Implement the desired code in Execute() method. Whenever the timer job would be activated, this Execute() method would be called. Another important thing worth mentioning is from within the Timer Jobs class we will not be able to get the reference to the site, since the context is different. Thus we need to put the site url in the properties bag of the timer job class while activating the feature in feature receiver class and use it here

public override void Execute(Guid targetInstanceId)

{

string strUrl = this.Properties["SiteURL"].ToString();

SPSite oSiteColl = new SPSite(strUrl)

////Desired Code to execute here

 

}

The Timer Job Class would look something like this.

public class MySPTimerJobActivation : SPJobDefinition

{

  #region Overloaded Contructors

  public MySPTimerJobActivation()

  : base()

  {

  }

 

  public MySPTimerJobActivation(string jobName, SPWebApplication webApp, SPServer server, SPJobLockType joblock)

  : base(jobName, webApp, server, joblock)

  {

    this.Title = jobName;

  }

  #endregion

 

  #region Execute Method

  public override void Execute(Guid targetInstanceId)

  {

    string strUrl = this.Properties["SiteURL"].ToString();

    string strListName = "TimerList";

    try

    {

      using (SPSite oSiteColl = new SPSite(strUrl))

      {

        using (SPWeb oWeb = oSiteColl.OpenWeb())

          {

            ////Desired Code to execute here

            ////Get the list item

       SPListItemCollection oItemColl = oWeb.Lists[strListName].Items;

            ////Add new item

            SPListItem oListItem = oItemColl.Add();

            ////Logging the data

            oListItem["Title"] = "From MySP Timer Job";

            oListItem["DateTimeFired"] = DateTime.Now;

            oListItem.Update();

          }

        }

      }

      catch (Exception ex)

      {

        PortalLog.LogString("Exception occured in Notification Update Module – {0} – {1}", ex.Message, ex.StackTrace);

      }

    }

   #endregion

}

The timer job class is complete now. The feature receiver class would be the next stop to look into

d)    The Feature Receiver Class would be similar to any other receiver class inheriting from SPFeatureReceiver. There are few very important and interesting things to remember at this point.

i)              Pass the parameter in Constructor – “SPServer” as null (the constructor would expect this parameter)

ii)             SPJobLockType.Job – Options are ContentDatabase,Job,None

Consider the different available Job Locks:

·         SPJobLockType.ContentDatabase - Locks the content database. A timer job runs one time for each content database that is associated with the Web Application; therefore your job will run as many times for each content database associated with the Web Application that exists

·         SPJobLockType.Job - Locks the timer job. This prevents multiple instances of the job from running on a single server (Recommended).

·         SPJobLockType.None - No locks

 

iii)            oTimerJob.Properties["SiteURL"]=SiteCol.Url; as mentioned earlier we would need this site url in the Execute Method of Timer Job class

iv)            Good practice to delete any previous instance of the job

////Deleting the Job on deactivation of the feature

    foreach (SPJobDefinition objMyJob in jobCol)

    {

      if (objMyJob.Name == My_Timer_Job)

      jobToDelete = objMyJob;

    }

    if (jobToDelete != null)

    jobToDelete.Delete();

 

Do not delete the job within the enumeration rather get the job and delete it outside the enumeration.

v)             Timer Job Running Intervals



If you want to run it at 11:05 PM every day, use SPDailySchedule class to configure:

SPDailySchedule tempSchedule = new SPDailySchedule();

tempSchedule.BeginHour = 23;

tempSchedule.BeginMinute=5;

tempSchedule.BeginSecond = 0;

tempSchedule.EndSecond = 15;

tempSchedule.EndMinute = 5;

tempSchedule.EndHour = 23;

oTimerJob.Schedule = tempSchedule;                oTimerJob.Update();

If you want to run it on the 1st of every month between 1:15am to 1:30am, use SPMonthlySchedule

SPMonthlySchedule schedule = new SPMonthlySchedule();  


schedule.BeginDay = 1;  


schedule.EndDay = 1;  


schedule.BeginHour = 1;  


schedule.EndHour = 1;  


schedule.BeginMinute = 15;  


schedule.EndMinute = 30; 


tempJob.Schedule = schedule;


tempJob.Update();


 


If you want to run on Monday every week between 2:01:00 am to 2:01:05 am, use SPWeeklySchedule 


 


SPWeeklySchedule schedule = new SPWeeklySchedule();


schedule.BeginDayOfWeek = DayOfWeek.Monday;


schedule.BeginHour = 2;


schedule.BeginMinute = 1;


schedule.BeginSecond = 0;


schedule.EndSecond = 5;


schedule.EndMinute = 1;


schedule.EndHour = 2;


schedule.EndDayOfWeek = DayOfWeek.Monday;


tempJob.Schedule = schedule;


tempJob.Update();


If every year on Jan 23 at 9:05AM, use SPYearlySchedule



SPYearlySchedule JobSchedule = new SPYearlySchedule();


JobSchedule.BeginMonth = 1;


JobSchedule.EndMonth = 1;


JobSchedule.BeginDay = 23;


JobSchedule.EndDay = 23;


JobSchedule.BeginHour = 9;


JobSchedule.EndHour = 9;


JobSchedule.BeginMinute = 5;


JobSchedule.EndMinute = 5;


JobSchedule.BeginSecond = 0;


JobSchedule.EndSecond = 5;


tempJob.Schedule = schedule;


tempJob.Update();



 



At the end the class would look like below. As said earlier this class will inherit from the Microsoft.SharePoint.SPFeatureReceiver class and implement the FeatureActivated & FeatureDeactivated event handlers:








public class MySPTimerJobActivationFeatureReceiver : SPFeatureReceiver



{



public const string My_Timer_Job = "Notification Timer Job";



#region Feature Activated



public override void FeatureActivated(SPFeatureReceiverProperties properties)



{



 try



  {



    ////Get the Site collection



    SPSite SiteCol = (SPSite)properties.Feature.Parent;



    SPWebApplication webApp = SiteCol.WebApplication;



    SPJobDefinitionCollection jobCol = webApp.JobDefinitions;



    ////Deleting older vrsion of Job if exists               



    SPJobDefinition jobToDelete = null;



    ////Deleting the Job on deactivation of the feature



    foreach (SPJobDefinition objMyJob in jobCol)



    {



      if (objMyJob.Name == My_Timer_Job)



      jobToDelete = objMyJob;



    }



    if (jobToDelete != null)



    jobToDelete.Delete();



  ////Install the Job Definition



  MySPTimerJobActivation oTimerJob = new      MySPTimerJobActivation(My_Timer_Job, webApp, null, SPJobLockType..Job);



 ////Set the Site Url.. will be used later from the Timer Job Class



  oTimerJob.Properties["SiteURL"] = SiteCol.Url;



  ////Run the Scheduler every 2 minutes



  SPMinuteSchedule minSchedule = new SPMinuteSchedule();



  minSchedule.BeginSecond = 0;



  minSchedule.EndSecond = 30;



  minSchedule.Interval = 2;



  oTimerJob.Schedule = minSchedule;



  oTimerJob.Update();



  }



  catch (Exception ex)



  {



  Microsoft.Office.Server.Diagnostics.PortalLog.LogString("Exception  occured in fetch query module– {0} – {1}", ex.Message, ex.StackTrace);



  }



}



#endregion



 



#region Feature Deactivating



public override void FeatureDeactivating(SPFeatureReceiverProperties properties)



{



            try



            {



                SPSite SiteCol = (SPSite)properties.Feature.Parent;



                SPWebApplication webApp = SiteCol.WebApplication;



                SPJobDefinitionCollection jobCol = webApp.JobDefinitions;



                ////Deleting older vrsion of Job if exists               



                SPJobDefinition jobToDelete = null;



                ////Deleting the Job on deactivation of the feature



                foreach (SPJobDefinition objMyJob in jobCol)



                {



                    if (objMyJob.Name == My_Timer_Job)



                        jobToDelete = objMyJob;



                }



                if (jobToDelete != null)



                    jobToDelete.Delete();



            }



            catch (Exception ex)



            {



                Microsoft.Office.Server.Diagnostics.PortalLog.LogString("Exception occured in fetch query module– {0} – {1}", ex.Message, ex.StackTrace);



            }



}



#endregion




 



3.    How to deploy in Dev Environment?



Now... to get it working, all you need to do is:



     I.        Deploy the strongly named assembly to the GAC.



    II.        Reset IIS (required for SharePoint to "see" the new timer job in the GAC) or AppPool



   III.        Create a feature specifying the receiver class and assembly that contains the event receivers.



   IV.        Install the feature.



    V.        Activate the feature.



If the WSPBuilder is installed, these all task could be performed from Visual Studio à WSPBuilder deploy option will do everything except the Activate feature.



Now the Timer Job is ready and we have to install it on the farm and deploy to our web application. The "recommended way" for doing this would be to create a Feature Receiver and implement the FeatureActivated event. In this event, can instantiate the job, set the job schedule and update the Job. Below is the code snippet of the Feature.xml



 








<?xml version="1.0" encoding="utf-8"?>



<Feature  Id="E4922BE5-5F1E-448b-9BD9-4CA4B1C652CD"



          Title="MySPTimerJob"



          Description="This feature will create a MySp Timer Job"



          Version="1.0.0.0"



          Hidden="FALSE"



          Scope="Site"



          ImageUrl ="DECISION.GIF"



          DefaultResourceFile="core"



          ReceiverAssembly="MySPTimerJob, Version=1.0.0.0, Culture=neutral, PublicKeyToken=86244771e654f5a1"



         ReceiverClass="MySPTimerJob.MySPTimerJobActivationFeatureReceiver"



          xmlns="http://schemas.microsoft.com/sharepoint/">



</Feature>




 



The Feature Receiver Class has already been discussed earlier with few examples on schedule timer to run hourly, daily, yearly, monthly, weekly or minute basis.



Depending on the requirement, sometimes the feature could be deployed at WebApplication level keeping scope as webapplication i.e, Scope="WebApplication" then we can activate the feature from Central Admin as well.



Activating this feature will deploy this Timer Job on the web application. Once the solution is deployed, can either activate the feature using stsadm -o activatefeature command or go to Central Administration -> Application Management -> Manage Web Application Features -> select web application -> Activate your Feature as shown in the snapshot below







 



Once the feature is activated, 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.



OK L, I know it is giving a typical WSS_Config database error once trying to activate the feature or it is not showing in the Timer Job Definitions. There is nothing wrong with the code rather it is about the permission. The feature needs firm admin credential to activate it. See below section on Error you might face - for solution or visit by blog on “The EXECUTE permission was denied on the object 'proc_putObject', database 'SharePoint_Config'”. Testing the timer job in dev environment, we can temporarily change the application pool account to the application pool account being used for Central Administration. Once that is done, try activating.



4.    How to debug it?



Debugging a timer job application is not simple as compared to some other custom developed components of SharePoint. The SharePoint Timer jobs runs with the SharePoint Firm Admin credentials since, the information get into the SharePoint Config Database. Thus the application pool will not have the access. I guess, is that the timer service is supposed to run under NT AUTHORITY\NetworkService windows account which has SHAREPOINT\System SharePoint privileges, and thus there's no need to elevate privileges for a timer job.



SharePoint Jobs do not have a current context, they are executed by another windows process called “Windows SharePoint Services Timer”. The executable name of this process is “OWSTIMER.EXE”. This is the process that should be attached to the code within Execute() method to be able to debug the job



 



Set Breakpoints in your code especially the Execute() method. Click on "Attach to Process..." from the menu bar as shown below:





 



Check the box at the bottom "Show Processes from All Users".

Select OWSTIMER.EXE



 





Click the Attach button and a breakpoint should display.



 



 



5.    How to deploy it in Test/Production Environment?



Create the solution package of the timer job and deploy in production environment by automated stsadm.exe scripts.



ScriptDeployTimerJob.bat



@echo Off



echo **************************** Deployment Starts ******************************



@Set STSADM="C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\stsadm.exe"



echo -----------------------------------------------------------------------------



%STSADM% -o addsolution -filename MySPTimerJobr.wsp



echo -----------------------------------------------------------------------------



%STSADM% -o deploysolution -name MySPTimerJobr.wsp -allowgacdeployment -immediate



Rem %STSADM% -o execadmsvcjobs



echo -----------------------------------------------------------------------------



echo **************************** Deployment ends ******************************



 



6.    Errors you might face












1



The EXECUTE permission was denied on the object 'proc_putObject', database 'SharePoint_Config', schema 'dbo'.



The SharePoint Timer jobs runs with the SharePoint Firm Admin credentials since, the information get into the SharePoint Config Database. Thus the application pool will not have the access.



1. Can run the stsadm with firm admin credential and activate the feature.



2. You can create a hidden feature and activate it, making sure you are logged in as a farm administrator




 



Please post a comment if you have other ways of doing this same thing… I’ll be happy to hear.







If this helps...... then please share......



HaPpY CoDiNg... (Aurum)



 

No comments:

Post a Comment