FIX: Custom timer job triggered multiple times

Scenario:
In a SharePoint farm, your custom timer job fires on each server of your farm. You only want it to fire once.

Fix:
You will find countless sites offering solutions for this problems and most of them do not work.

You are probably implementing a class inheriting from SPJobDefinition:

   class TimerJob : SPJobDefinition  { ... }  

SPJobDefinition has two public constructors (and one reserved for internal use):
     // Parameters:  
     //  name: Specifies the name of the job definition.  
     //  service: Specifies an Microsoft.SharePoint.Administration.SPService object instance.  
     //  server: Specifies an Microsoft.SharePoint.Administration.SPServer object instance.  
     //  lockType: Specifies an enumeration value from the Miccrosoft.SharePoint.Administration.SPJobLockType enum.  
     protected SPJobDefinition(string name, SPService service, SPServer server, SPJobLockType lockType);  

     // Parameters:  
     //  name: Specifies the name of the job definition.  
     //  webApplication: Specifies an Microsoft.SharePoint.Administration.SPWebApplication  
     //  server: Specifies an Microsoft.SharePoint.Administration.SPServer object instance.  
     //  lockType: Specifies an enumeration value from the  
 object instance.  
     protected SPJobDefinition(string name, SPWebApplication webApplication, SPServer server, SPJobLockType lockType);  
 pe);  

It would seem that setting lockType to SPJobLockType.Job solves the problem. The job should only run once per job instance. In fact, unless you also specify an application server, the job will still run on ever server in the farm, therefore multiple times.

Suggested implementation:

Timer class:
 class TimerJob : SPJobDefinition {  
     public TimerJob(SPWebApplication webApp, SPServer server)  
       : base(JobTitle, webApp, server, SPJobLockType.Job)  
     {  
       Title = JobTitle;  
     }  
 ...  
 }  

Create the timer (in a SPFeatureReceiver for example):
   public class OrbisTaskMessengerEventReceiver : SPFeatureReceiver  
   {  
     public override void FeatureActivated(SPFeatureReceiverProperties properties)  
     {  
       CreateJob(properties.Feature.Parent as SPSite);  
     }  
     //create the timer job  
     private static void CreateJob(SPSite site) {  
       SPServer server = RandomApplicationServer(site);  
       if (server != null) {  
         TimerJob job = new TimerJob(site.WebApplication, server);  
         SPDailySchedule schedule = new SPDailySchedule { BeginHour = 7, EndHour = 8 };  
         job.Schedule = schedule;  
         job.Update();  
       }  
       else {  
         throw new System.Exception("Failed to create job; cannot determine an Application server in the farm.");  
       }  
     }  
     //find a random application server in the farm  
     private static SPServer RandomApplicationServer(SPSite site) {  
       foreach (SPServer server in site.WebApplication.Farm.Servers) {  
         if (server.Role == SPServerRole.Application)  
           return server;  
       }  
       return null;  
     }  

Activate or deactivate timer job feature using PowerShell

Scenario:

  • You want to activate your newly deployed feature to install a timer job
  • You want to deactivate the feature and remove the job
  • You want to deploy an update to the existing timer job feature
Message:
The application attempted to perform an operation not allowed by the security policy. To grant this application the required permission please contact your system administrator or change the application's trust level in the configuration file.

Note:
I was able to Deactivate the feature from the SharePoint UI, as a farm admin, but the Activate still failed for me.

Solution:
Use the following PowerShell commands:

 Disable-SPFeature -Identity "TimerJobName" -url http://site:port  
or
 Enable-SPFeature -Identity "TimerJobName" -url http://site:port  

To determine your feature's name you can use (which can be tricky sometimes) use this command:
 Get-SPFeature | where-object {$_.DisplayName -match "MyPattern"} | Select DisplayName