Applies To: SharePoint 2010
As mentioned in my previous post, I’ve recently put together a solution for automatically configuring your SharePoint servers to use the Adobe PDF icon for PDF files. You can download the solution as well as the source for free from CodePlex here: WireBear PDFdocIcon. I’m going to show some of the code as it currently exists below, but be sure to check out the CodePlex site to ensure you have the latest version.
In order to perform the necessary work on each server in the farm, the PDFdocIcon solution uses a custom Timer Job. This post will focus on the plumbing necessary to setup your own custom timer job that runs on every server in the farm. The actual code to change the DOCICON.XML file will be saved for later.
Choosing Your Job Definition Type
To make your own Timer Job you’ll want to subclass an exisiting Job Definition object and override the Execute method. There are several to choose from, here’s a helpful table:
Job Definitions you can inherit from in the Microsoft.SharePoint.Administration namespace:
SPAdministrationServiceJobDefinition | Invokes the SharePoint Administration Service |
SPAllSitesJobDefinition | Iterates through all sites in a Web Application |
SPContentDatabaseJobDefinition | Executed per Web Application and each Content Database is processed by individual jobs (Pausable) |
SPFirstAvailableServiceJobDefinition | Timer Job that runs on the first available server where the specified service exists (Pausable) |
SPJobDefinition | Base Class for Timer Jobs (Generally, this is the one to use) |
SPPausableJobDefinition | Timer Job that can be paused |
SPServerJobDefinition | Executed on a specific server (Pausable) |
SPServiceJobDefinition | Runs on every server in the farm where the service exists (Pausable) – This is the one I chose |
SPWorkItemJobDefinition | Works with the Timer Job to process work items (Pausable) |
For simple jobs the SPJobDefinition is the most flexible and is what you’ll generally want to use. For the PDFdocIcon solution, I needed the Timer Job to execute on every server in the farm. So I used the SPServiceJobDefinition and specified the Timer Service.
Storing Persistent Properties
You may not need properties, but if you’re doing anything even mildly complex you probably will. There are a couple of different alternatives here, but basically your properties need to serialize down to strings. You can look up a few examples of custom properties objects that do this, or you can just use my method of storing your properties in the JobDefinition’s Properties object (HashTable).
Here’s how I store the Boolean property _installing:
Private Const InstallingKey As String = "DocIconJob_InstallingKey" Private Property _installing() As Boolean Get If Properties.ContainsKey(InstallingKey) Then Return Convert.ToBoolean(Properties(InstallingKey)) Else Return True End If End Get Set(ByVal value As Boolean) If Properties.ContainsKey(InstallingKey) Then Properties(InstallingKey) = value.ToString Else Properties.Add(InstallingKey, value.ToString) End If End Set End Property
Basically, you have a String key for each property that you use to store/retrieve the value from the Properties HashTable. By wrapping those calls in a property you can treat it like a standard variable in the rest of your code and forget all about the specialized storage/retrieval required.
Constructors
You are required to have an empty (parameterless) constructor for serialization, so make sure you’ve got that:
Public Sub New() MyBase.New() End Sub
But you will probably need to implement at least a matching constructor with some custom properties. In my Timer Job, I wanted to pass three properties (which I then store using the method above), so I use this:
Public Sub New(JobName As String, service As SPService, Installing As Boolean, FileExtension As String, ImageFilename As String) MyBase.New(JobName, service) _installing = Installing _fileExtension = FileExtension _imageFilename = ImageFilename End Sub
Execution
Depending on the base Job Definition class you chose, the Execute method may have a slightly different signature, but either way this is the method to override to provide your own custom logic. In an SPServiceJobDefinition subclass the signature looks like this:
Public Overrides Sub Execute(jobState As Microsoft.SharePoint.Administration.SPJobState) 'Custom code here!! End Sub
Installing Your Job with a Solution
Using Visual Studio you can create a new Empty SharePoint Project and add your Timer Job class to it. To deploy it you’ll need to add a Feature (Right-click on Features and choose Add Feature). To install your job, you’ll need to add an Event Receiver (Right-click on your new Feature and choose Add Event Receiver).
Uncomment the FeatureActivated and FeatureDeactivating methods. Create a new method (Mine is named RunDocIconJob) with a Boolean and SPFeatureReceiverProperties parameters. This will be the method where we install or uninstall your custom job. In your FeatureActivated and FeatureDeactivating methods call this new method accordingly:
Public Overrides Sub FeatureActivated(properties As Microsoft.SharePoint.SPFeatureReceiverProperties) RunDocIconJob(True, properties) End Sub Public Overrides Sub FeatureDeActivating(properties As Microsoft.SharePoint.SPFeatureReceiverProperties) RunDocIconJob(False, properties) End Sub
Then your Job method will look something like this:
Private _fileExtension As String = "pdf" Private _iconFileName As String = "ICPDF.png" Public Sub RunDocIconJob(Installing As Boolean, properties As SPFeatureReceiverProperties) Dim JobName As String = String.Format("DocIconJob_{0}", _fileExtension) 'Ensure job doesn't already exist (delete if it does) Dim query = From job As SPJobDefinition In properties.Definition.Farm.TimerService.JobDefinitions Where job.Name.Equals(JobName) Select job Dim myJobDefinition As SPJobDefinition = query.FirstOrDefault() If myJobDefinition IsNot Nothing Then myJobDefinition.Delete() Dim myJob As New DocIconJob(JobName, SPFarm.Local.TimerService, Installing, _fileExtension, _iconFileName) 'Get that job going! myJob.Title = String.Format("{0} icon mapping for {1}", IIf(Installing, "Adding", "Removing"), _fileExtension) myJob.Update() myJob.RunNow() End Sub
This is the method I use for my SPServiceJobDefinition. I am not doing any kind of scheduling since this job just runs once on initial deployment and once when being removed. However, you may want to adjust your method to include a schedule (Just set the myJob.Schedule parameter before the Update() call).
Lines 5-10 are finding any existing job definitions that share the same name and deleting them since creating jobs with duplicate names will cause an error. The Title doesn’t have to be unique, but the name does.
Line 12 actually creates the job with my default parameters and then line 15 sets a Title. This is where you would introduce a schedule if you wanted the job to run more than once, but if not just call Update() to save your job. I want my job to run immediately, so in line 17 I call the RunNow() method to do exactly that.
That’s it! You now have a shell for setting up and installing a custom job – specifically one that runs on every server in the farm. My next post will cover what I’m actually doing in the Execution to ensure the DOCICON.xml file is updated appropriately.
[…] The Chris Kent Quick Fixes, Workarounds, & Neat Tricks I felt like sharing HomeAbout RSS ← Implementing a Custom SharePoint Timer Job […]
[…] using the same technique I use for PDF Icon Mapping entries, I’ve created a SharePoint solution to do this […]
[…] Job and assumes you know some basics about custom timer job creation (If not, check my other post Implementing a Custom SharePoint Timer Job). Either way, most of the code will be given right here […]
[…] that it can be very helpful to turn these back on when attempting to debug certain types of things (Custom Timer Jobs for instance), but be sure to bookmark this page because you will forget to turn it back off and […]