Auto Start SharePoint Timers

The default timer schedules in SharePoint can be set too high when you’re in a development environment or are wanting to demonstrate functionality to clients. The Workflow timer has a default schedule of 15 minutes for example. Also, if you work with state machine workflows and utilise delay activities you may have scenarios where the delay between various steps is too long, especially for a customer demonstration. Even setting the value to the lowest setting of a minute can cause issues. Imagine a scenario where a sales person is showing the life cycle of a document that contains multiple steps and sub workflows. Each state change of the state machine which either performs a custom action or starts another workflow may have to wait for the Workflow timer job to run before continuing. If the demonstration involves a lot of steps and workflows there will be a lot of space to fill, which although acceptable in a live environment may not be when talking to potential clients.

The code in this post shows how to give the Worklfow timer a kick, which should speed the whole process up. It’s not recommended to do this in a live environment as automatically starting timers all the time will just cause SharePoint to pull up the shutters and stop processing requests until it feels ready. Even on a non-production system with no real load, there are no guarantees that the timer will start when you ask it to. That’s exactly what you’re doing, asking it to execute and if SharePoint feels it under a heavy load it will ignore the request. Still, in most cases if you’re demonstrating some functionality it can be useful to at least try and speed the whole process up a little. Either that or get the sales people to bring along their juggling balls and joke book to fill the gaps 🙂

As mentioned above, the code below shows how to start the Workflow timer job but this could just as easily be any SharePoint timer job, Microsoft, third party or custom.

The first thing you should do is find out the internal name of the timer job you want to start. The Workflow timer job is called job-workflow and belongs to the spworkflowtimerv4 service.

An easy way of viewing the list of timers for a service is to write a simple console application to do the following:

foreach (SPService service in SPFarm.Local.Services)
{
    if (string.Compare(service.Name, "spworkflowtimerv4", true) == 0)
    {
        foreach (SPJobDefinition job in service.JobDefinitions)
        {
            Console.WriteLine("Job name:  " + job.Name);
        }
 
        break;
    }
}

Likewise, you can view the list of services by doing the following:

foreach (SPService service in SPFarm.Local.Services)
{                
    Console.WriteLine("Service name:  " + service.Name);
}

Now that you have the timer job name, in this case job-workflow, the following class will allow you to request SharePoint to execute it.

Start by adding the following namespaces:

using System;
using System.Collections.Generic;
using Microsoft.SharePoint.Administration;

Next, add the following public members:

public enum SPServiceTypes
{
    WorkflowTimer
}
 
public enum SPJobDefinitionTypes
{
    Workflow
}
 
public Dictionary<SPServiceTypes, string> SP_SERVICES = new Dictionary<SPServiceTypes, string>()
{
    { SPServiceTypes.WorkflowTimer, "spworkflowtimerv4"}
};
 
public Dictionary<SPJobDefinitionTypes, string> SP_JOB_DEFINITIONS = new Dictionary<SPJobDefinitionTypes, string>()
{
    { SPJobDefinitionTypes.Workflow, "job-workflow"}
};
 
public enum SPTimerStatuses
{
    AlreadyRunning,
    Running,
    Disabled
}

This gives us the ability to reference services and job definitions that we’re interested in auto starting in the rest of the class.

To start a job, you first need the corresponding SPJobDefinition object. The following methods add this functionality:

public SPService GetService(string name)
{
    foreach (SPService service in SPFarm.Local.Services)
    {
        if (string.Compare(service.Name, name, true) == 0)
        {
            return service;
        }
    }
 
    return null;
}
 
public SPService GetService(Guid id)
{
    return SPFarm.Local.Services[id];
}
 
public SPJobDefinition GetJobDefinition(SPService service, string name)
{
    foreach (SPJobDefinition job in service.JobDefinitions)
    {
        if (string.Compare(job.Name, name, true) == 0)
        {
            return job;
        }
    }
 
    return null;
}
 
public SPJobDefinition GetJobDefinition(SPService service, Guid id)
{
    return service.JobDefinitions[id];
}

You’ll notice the GetService and GetJobDefinition methods are overloaded. This is to allow calling classes to either find the service or job definition by name or it’s identifier.

The identifier methods are a lot simpler as the indexer is used to retrieve the desired object. You’d think the same would work by name and indeed there is an indexer for this, but for the life of me I couldn’t get this to work. The following code does not find the desired object for example:

foreach (SPJobDefinition job in service.JobDefinitions)
{
    SPJobDefinition aJob = service.JobDefinitions[job.Name];
}

aJob will be null even though you’d expect it to be the same object.

Finally we have a couple of methods that are used to request a timer job to start:

public SPTimerStatuses StartJob(SPJobDefinition job)
{
    return StartJob(job, 0);
}
 
public SPTimerStatuses StartJob(SPJobDefinition job, uint minutes)
{
    SPTimerStatuses status;
 
    if (job.IsDisabled)
    {
        status = SPTimerStatuses.Disabled;
    }
 
    if (minutes == 0 || job.LastRunTime < DateTime.Now.AddMinutes(minutes * -1))
    {
        try
        {
            job.RunNow();
            status = SPTimerStatuses.Running;
        }
        catch (Exception ex)
        {
            // log and handle error
        }
    }
    else
    {
        status = SPTimerStatuses.AlreadyRunning;
    }
 
    return status;
}

The first StartTimer method will attempt to start the job definition provided straight away, while the second method takes a parameter for specifying the number of minutes that should have passed since the job last ran before trying to start the job. Both methods return a status message for the job definition.

Leave a Reply

Your email address will not be published. Required fields are marked *

Solve the maths problem shown below before posting: *

Follow

Get every new post delivered to your Inbox

Join other followers: