Friday, February 15, 2013

Timer Job to turn on branding feature for MySites

One fine morning, I was given the task of branding all SharePoint sites of my company. So, I rolled up my sleeves, and did what any other beginner would do, GOOGLE! I found some really good tutorials which helped me build my master pages and style sheets, as per the requirements given to me. I was asked to deploy the branding solution as a farm feature but not enable automatic activation as we had site collections we wanted to keep clean for testing purposes. So, to deploy and activate the solution, we ran a powershell script that was built to activate a feature on a specific list of URLs.
But then, a few months later, came the dreaded day when MySites were implemented! It would have been a hell of a process to write a script that monitored the creation of a MySite site collection and turn on the branding feature for it. So, enter the idea of creating a timer job which is to check for every site collection under a specified web application, if the branding feature was turned on, if no, activate, if yes, skip.
So here is the code which I have put together from the references mentioned at the end of the post.


1. Create an empty SharePoint Project
Start Visual Studio 2010, and then create a new Empty SharePoint Project, as shown in the following illustration. Name the project BrandingTimerJob

In the SharePoint Customization Wizard, specify a valid local site for debugging, select Deploy as a farm solution, and then click Finish.

2. Create a Job Definition Class
To create a job definition class, right click on the project name in solution explorer and add new item
Name the class JobDefinition.cs
The following code encapsulates your timer job logic.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// Add the following using statement
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint;

namespace BrandingTimerJob
{
    //Change the visibility of the class to public, and make the class inherit from the SPJobDefinition class.
    public class JobDefinition : SPJobDefinition
    {
        public const string jobname = "/*Give a name to your timer Job Eg: BrandingTimerJob*/";
         
        //Add a default constructor to the class that inherits from the default constructor of the base class
        public JobDefinition() : base() { }
         
        //Add a constructor that accepts an argument of type SPWebApplication
        //This enables the base SPJobDefinition class to instantiate your timer job within a specific Web application
       /*When you create your constructor, you must pass the values for the four parameters 
Name: The name of the job.
webApplication: An instance of the SPWebApplication class that owns this job.
Server: An instance of the SPServer class associated with this job. Pass null if this job is not associated with a specific server.
lockType: An SPJobLockType value that indicates the circumstances under which multiple instances of the job can be run simultaneously. */


        public JobDefinition(string jobname, SPWebApplication webApp)
            : base(jobname, webApp, null, SPJobLockType.Job)
        {
            this.Title = jobname; 
        }
         
        public override void Execute(Guid targetInstanceId) // Include your timer job logic here
        {
            // Store the Guid of the branding feature(or any other you want the timer job to activate)
            Guid featureGuid = new Guid("/*Guid*/");
            
           // Get a reference to parent webapplication which is running the timer job
            SPWebApplication webApplication = this.Parent as SPWebApplication;
           
            //Optional Code
            /* When you create a timer job as a farm solution and set the scope to "Web Application", by default it creates a timer job for each web application under the farm. If you want the timer job logic to be executed only for one specific web application, follow this approach
SPWebApplication webApplication = SPWebApplication.Lookup(new Uri("/*URL of webaplication*/"));
*/
            // Get all the features for all site collections under current webapplication
            foreach(SPSite site in WebApplication.Sites)
            {
                var siteFeatures = site.Features;
                SPFeature feature = siteFeatures[featureGuid];
                // Check if the feature is activated
                if (feature == null)
                {
                    siteFeatures.Add(featureGuid, false);
                }
site.Dispose();
            }
        }
    }
}

3. Create a Feature to Register the Job
To register a Web application-scoped timer job, in the VS2010 project solution explorer, right click on feature and add new feature
Rename the feature to an appropriate name eg: BrandingTimerJob Feature

Double-click the BrandingTimerJobFeature node to open the feature designer window, and then set the scope of the feature to WebApplication.

Add an event receiver to the BrandingTimerJobFeature. To do this, right-click BrandingTimerJobFeature in Solution Explorer, and then click Add Event Receiver.

In the BrandingTimerJobFeatureEventReceiver class, add the following code

using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
// add the following using statement
using Microsoft.SharePoint.Administration;

namespace BrandingTimerJob.Features.BrandingTimerJobFeature
{
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// </summary>
    /// <remarks>
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// </remarks>
    [Guid("73b26965-5360-43c0-a57c-1b898254e5c8")] // This is the GUID of your timerjob feature
public class BrandingTimerJobFeatureEventReceiver : SPFeatureReceiver
    {
/*Add a method named DeleteJob that accepts an argument of type SPJobDefinitionCollection. The method should iterate through the job definition collection and delete any instances of JobDefinition. */
private void DeleteJob(SPJobDefinitionCollection jobs)
        {
            foreach (SPJobDefinition job in jobs)
            {
                if (job.Name == JobDefinition.jobname)
                {
                    job.Delete();
                }
            }
        }

public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
/*Optional Code
If in your job definition you have looked up a specific URL, please include the same here
SPWebApplication webApp = SPWebApplication.Lookup(new Uri("/*URL of your webapplication*/"));
*/

            // Delete the job if exists            DeleteJob(webApp.JobDefinitions);

            //Create a timerjob for teach webapplication
            JobDefinition timerJob = new JobDefinition(JobDefinition.jobname, webApp);
           
/*Schedule the interval for timerjob to run. To change the schedule you can use any of the following types
SPOneTimeSchedule
SPHourlySchedule
SPDailySchedule
SPMonthlySchedule
SPWeeklySchedule
SPYearlySchedule */

            SPMinuteSchedule schedule = new SPMinuteSchedule();
            schedule.BeginSecond = 0;
            schedule.EndSecond = 59;
            schedule.Interval = 5;
            timerJob.Schedule = schedule;
            timerJob.Update();
           
        }

 public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
            DeleteJob(webApp.JobDefinitions);
/*Optional Code
If in your job definition you have looked up a specific URL, please include the same here
SPWebApplication webApp = SPWebApplication.Lookup(new Uri("/*URL of your webapplication*/"));
*/         }

Save your project, build and deploy.
Now, if you check in your central admin, you will find the timerjob created for either each webapplication or the specific webapplication URL specified

Potential Errors:
1. The timer job may fail with the error "Object reference not set to an instance of an object"
Solution: Check if you have elevated permissions to the environment you are trying to deploy or run the timer job.

Sometimes, retracting the solution and redeploying also helps.

2. Once you deploy the solution, and you make changes to your code after that and redeploy, sometimes the new code changes do not reflect in your timer job.
Solution: Restart the timer service.

I hope my solution is helpful in building yours! Thanks.

References
1. Google.com :) :)

No comments:

Post a Comment