How to: Run Code on All Web Servers

Applies to: SharePoint Foundation 2010

Many development scenarios, especially those that have administrative functionality, need the same code run on every front-end Web server in a farm. For example, all front-end Web servers on a Microsoft SharePoint Foundation farm must be identically configured and provisioned, at least regarding how they respond to HTTP requests. Thus, a change to a setting of a web.config file must be made on all the front-end Web servers. For another example, an assembly that is part of a farm solution must be deployed to the global assembly cache (GAC) of each front-end Web server.

For many kinds of configuration and deployment tasks, SharePoint Foundation comes with purpose-built APIs that ensure that the servers stay synchronized. For example, the SPWebConfigModification class provides functionality for modifying the settings of the stack of web.config files. For more information, see How to: Add and Remove Web.config Settings Programmatically. Similarly, when you deploy a farm solution, whether in the Central Administration application or the SharePoint Management Shell, any assembly that is part of the solution is deployed to the GAC of every front-end Web server. You can create customized farm solution deployment functionality with the SPSolution.Deploy() method.

All of these purpose-built APIs use SharePoint Foundation timer jobs to do their work. They are named timer jobs because each can be set to execute at a specified time in the future (or immediately upon creation). But they could also be called multi-server jobs because they can be set to run on multiple Web servers, including all front-end Web servers, or on a specific server. When you need a function to run on all front-end Web servers and there is no purpose-built API for the function, you can use the timer job APIs. This topic explains how.

Note

The distinction between a front-end Web server and an application server is more notional in SharePoint Foundation 2010 than in earlier versions of the product. With one exception, all of the SharePoint Foundation software is installed on all servers regardless of their status as application servers or front-end Web servers. (The exception is the server that hosts the Microsoft SQL Server database for the farm. Typically, SharePoint Foundation is not installed at all on this computer. Hereafter, throughout this topic, the phrase "all servers" is meant to exclude any such dedicated database server on which SharePoint Foundation is not installed.) As a general rule, a SharePoint Foundation server is a front-end Web server if the Microsoft SharePoint Foundation Web Application service is running on it; otherwise it is an application server.

To define a timer job

  1. In Visual Studio, start an Empty SharePoint Project. Make it a farm solution, not a sandboxed solution.

  2. Highlight the project name in Solution Explorer and be sure that Include Assembly in Package in the Properties pane is set to true.

  3. Add a C# or Visual Basic class file to the project.

  4. Open the class file and add using statements (Imports in Visual Basic) for the Microsoft.SharePoint and Microsoft.SharePoint.Administration namespaces. You may need to add other using statements depending on what namespaces are called by the code that you want to execute on all servers. For the running example in this topic, add using statements for System.Xml.Linq, System.Xml.XPath, System.IO, and System.Runtime.InteropServices.

  5. Change the namespace to conform to the guidelines in Namespace Naming Guidelines; for example, Contoso.SharePoint.Administration.

  6. Change the class declaration to specify that the class inherits from SPJobDefinition or from some class that derives from SPJobDefinition.

  7. Decorate the class declaration with a GuidAttribute attribute. This is a requirement for any class that derives directly or indirectly from SPPersistedObject.

  8. Add a default (parameterless) constructor to the class that simply calls the base constructor.

  9. Add a constructor with parameters of types String, SPWebApplication, SPServer, and SPJobLockType. Its implementation should call the base constructor SPJobDefinition(String, SPWebApplication, SPServer, SPJobLockType). The following shows what your code should look like in C# at this point.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.Administration;
    
    using System.Xml.Linq;
    using System.Xml.XPath;
    using System.IO; 
    using System.Runtime.InteropServices;
    
    namespace Contoso.SharePoint.Administration
    {
        [Guid("9573FAD9-ED89-45E8-BD8B-6A5034E03895")]
        public class MyTimerJob : SPJobDefinition
        {
            public MyTimerJob() : base() { }
    
            public MyTimerJob(String name, SPWebApplication wApp, SPServer server, SPJobLockType lockType)
                : base(name, wApp, server, lockType) { }
        }
    }
    

    Note

    If you wanted a job to run on all servers, including application servers, your class should derive from SPServiceJobDefinition. Pass the timer service (SPFarm.Local.TimerService) as the SPService parameter of the SPServiceJobDefinition(String, SPService) constructor.

  10. Add overrides of the DisplayName and Description properties. The DisplayName property is the friendly name of the job that appears in the job definitions, scheduled jobs, and job history lists in the Central Administration application. Description is a description of the job. For simplicity, in the example below these are assigned literal strings. In a more realistic scenario, consider assigning a localized string to each.

    public override string DisplayName
    {
        get
        {
            // TODO: return a localized name
            return "Add Contoso Mobile Adapter";
        }
    }
    
    public override string Description
    {
        get
        {
            // TODO: return a localized description
            return "Adds a mobile adapter for Contoso's voting Web Part.";
        }
    }
    
  11. Add an override of the Execute(Guid) method to the class.

    public override void Execute(Guid targetInstanceId)
    {
        // INSERT HERE CODE THAT SHOULD RUN ON ALL SERVERS.
    }
    
  12. The implementation of the method consists simply in the code that you want to run on all front-end Web servers. This code is called by the SharePoint 2010 Timer service, which should be running on all SharePoint Foundation servers in the farm. The Execute(Guid) method is called by the timer service when the job executes and it runs in the user context of the timer service. In a single-server SharePoint Foundation installation, this user is typically Network Service. But the interesting case is a multi-server farm, in which case the timer service runs in the context of the same domain user account that the farm uses to read and write to the content and configuration databases. This user is not a machine administrator on any server, but it is a member of the WSS_ADMIN_WPG user group on all servers. It has read, write, and execute rights to the folders in the %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\ tree and the c:\Inetpub\wwwroot\wss tree.

    In this topic, we suppose a scenario in which you have created a mobile Web Part adapter for use on the mobile version of Web Part pages. It is necessary to register the adapter in the compat.browser file for the Web application. This file is located in the C:\Inetpub\wwwroot\wss\VirtualDirectories\port_number\App_Browsers folder, where port_number is the port number of the Web application, such as "80". Each front-end Web server has its own copy of the file and all of them must be edited identically. The following code registers the adapter in the compat.browser file of every server on which the timer job executes. (How you ensure that the job executes on all servers is explained later in this topic.)

    Tip

    For simplicity, this example changes the compat.browser only for the default URL zone. In a more realistic scenario, consider looping through all of the members of IisSettings.

    public override void Execute(Guid targetInstanceId)
    {
        // Set the path to the file. The code that creates the MyTimerJob object associates
        // the job with a Web application.
        String pathToCompatBrowser = this.WebApplication.IisSettings[SPUrlZone.Default].Path 
                                   + @"\App_Browsers\compat.browser";
    
        XElement compatBrowser = XElement.Load(pathToCompatBrowser);
    
        // Get the node for the default browser.
        XElement controlAdapters = compatBrowser.XPathSelectElement("./browser[@refID = \"default\"]/controlAdapters");
    
        // Create and add the markup.
        XElement newAdapter = new XElement("adapter");
        newAdapter.SetAttributeValue("controlType", 
            "Contoso.SharePoint.WebPartPages.VotingWebPart, Contoso, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ffec2e2af2b4c675");
        newAdapter.SetAttributeValue("adapterType",
            "Contoso.SharePoint.WebPartPages.VotingWebPartMobileAdapter, Contoso, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ffec2e2af2b4c675");
        controlAdapters.Add(newAdapter);
    
        // Overwrite the old version of compat.browser with your new version.
        compatBrowser.Save(pathToCompatBrowser);
    }
    
  13. Select Deploy Solution from the Visual Studio Build menu. If the Site URL property of the Empty SharePoint Project in Visual Studio points to a single-server SharePoint Foundation farm, this action compiles the assembly, package it into a SharePoint Foundation Solution (wsp) file, upload the package to the farm's solution gallery, and deploy the assembly to the GAC. But for this topic, the interesting case is deployment to a multi-server farm. You can set the Site URL property to point to such a farm, but in that situation, clicking Deploy Solution does not do anything after uploading the solution to the farm's solution gallery. From there, you need to deploy it. One way is directly from the gallery in the Central Administration application. You could also use SharePoint Management Shell. Another option is to select Package from the Visual Studio Build menu. This action compiles the assembly and packages it. You can then install the package to the farm's solution gallery and deploy it by using SharePoint Management Shell. Regardless of the technique you use, on a multi-server farm, the deployment step installs the assembly to the GAC of all the front-end Web servers.

Creating an Instance of a Timer Job

After the assembly that defines your timer job type is deployed to all servers, you can programmatically create and schedule instances of it. The code that does this can be included in a wide variety of development contexts. If the custom timer job is part of a solution that you are deploying as a SharePoint Foundation Feature, you could include the job creation code in an override of the FeatureActivated(SPFeatureReceiverProperties) method in a feature receiver. (This Feature must be scoped to the farm or the Web application.) Another possibility is to extend the Central Administration application with a custom action that creates the job. You could also create a custom PowerShell cmdlet that can be run in SharePoint Management Shell. In this topic we use a simple console application. Regardless of where your job creation code is located, it must run in the user context of a farm administrator.

The main tasks the code must accomplish are to construct an object of your custom timer job type, associate it with a Web application, and set its Schedule property.

To create and schedule a timer job

  1. Start a console application project in Visual Studio. This can be a second project within the same Visual Studio solution as your timer job project or an entirely separate Visual Studio solution. There are advantages and disadvantages to both approaches. In this topic, we assume that an entirely separate Visual Studio solution is used.

  2. Right-click the project name in Solution Explorer and select Properties.

  3. On the Application tab, ensure that the Target framework is .NET Framework 3.5.

  4. On the Build tab, ensure that the Target platform is either x64 or Any CPU. For information about making the choice, see How to: Set the Correct Target Framework and CPU.

  5. Click the Save all files button on the menu.

  6. Right-click the project name in Solution Explorer and select Add Reference. Use the Projects or Browse tab to add a reference to the assembly you created in the procedure above.

  7. Add a reference to the Microsoft.SharePoint and Microsoft.SharePoint.Security assemblies.

  8. Open the code file and add using statements (Imports in Visual Basic) for the namespaces Microsoft.SharePoint and Microsoft.SharePoint.Administration.

  9. Either change the namespace to match the namespace you used for your custom timer job in the procedure above, or use a different namespace but add a using statement for the namespace in which your custom timer job is declared.

  10. Add the following code to the Main method.

    // Get a reference to the Web application for which you want to register the mobile Web Part adapter.
    SPWebApplication webApp = SPWebApplication.Lookup(new Uri("https://localhost/"));
    

    This is only one way to get a reference to a Web application. For more information, see Getting References to Sites, Web Applications, and Other Key Objects.

  11. Add the following code to the Main method.

    // Create the timer job.
    MyTimerJob myTJ = new MyTimerJob("contoso-job-add-mobile-adapter", webApp, null, SPJobLockType.None);
    

    Note the following about this code:

    • The first parameter of the constructor for MyTimerJob is the internal name of the job. By convention, internal job names are lowercase, hyphenated, and begin with the word "job". Consider adding your company name to the beginning of the internal job name as is done in this example.

    • The second parameter specifies the Web application to which the job should apply.

    • The third parameter can be used to specify a particular server on which the job should run. This is null when the job should run on all front-end Web servers.

    • The fourth parameter determines whether the job executes on all front-end Web servers. Passing SPJobLockType.None ensures that it runs on all servers on which the Microsoft SharePoint Foundation Web Application service is running. By contrast, passing SPJobLockType.Job ensures that it runs only on the first available server on which the Microsoft SharePoint Foundation Web Application service is running. (There is a third possible value. For more information, see SPJobDefinition and the topics for its constructors and other members.)

  12. Add the following code to the Main method.

    // Schedule the job.
    myTJ.Schedule = new SPOneTimeSchedule(DateTime.Now.AddSeconds(30.0));
    

    SPOneTimeSchedule is one of several classes that derive from the SPSchedule class and that can be used as the value of the Schedule property. You can set the job to run immediately by passing Now to the constructor. But adding a little time, in this case 30 seconds, may be instructive in the development stage because it gives you time to see the job appear on the job definitions and scheduled jobs lists in the Central Administration application. After it completes, it appears on the job history list. A recurring job remains indefinitely on the job definition list, but a one-time job—that is, a job with an SPOneTimeSchedule object as the value of its Schedule property—exists (and, thus, appears on the job definitions list) from the time it is created to the time it runs, which in this example is 30 seconds. It is automatically deleted after it runs.

  13. Add the following code to the Main method.

    // Save the scheduled job instance to the configuration database.
    myTJ.Update();
    

Compiling the Code

  1. Build the project in Visual Studio.

  2. Run the executable file on any server in the farm in the user context of a farm administrator.

    The job appears on both the job definitions and scheduled jobs lists in the Central Administration application until it is scheduled to execute. In the running example this is 30 seconds. At the scheduled time it executes on all servers. It then appears on the job history list in the Central Administration application, and it disappears from the scheduled jobs list. Because it is a one-time job, it also disappears from the job definitions list after successful completion.

  3. Verify that the job was executed on all servers. In the running example, do this by verifying that the controlAdapter element with its two attributes was added to the compat.browser file located in C:\Inetpub\wwwroot\wss\VirtualDirectories\port_number\App_Browsers folder, where port_number is the port number of the Web application, on each server.