Custom SPJobDefinition and “Access denied” Error

October 22 2010 52 comments

I found Stef Van Hooijdonk’s post when trying to install a custom timer job and having the “Access denied” -issue. I tried Stef’s workaround by running the powershell -script he provided and got my custom timer job to install via web scoped feature’s feature receiver.

I think there are issues to consider though. Do we want to permanently set the RemoteAdministratorAccessDenied false or do we want to run one script to set RemoteAdministratorAccessDenied false before feature activation/deactivation and after that run another script to set it back true again? Installing custom timer job evidently is an operation where Farm Admin privileges are needed and if the activator is web- or site-scoped feature, the activation dialog (the Activate/Deactivate-buttons in ManageFeatures.aspx) is available also for users with inadequate privileges. After further investigation it seems that to activate a feature from content application’s feature management UI, it is necessary to have the same application pool accounts for the content application and central admin – it seems not to be enough to be logged on to your content web app with farm admin account or with the central admin’s application pool account. The behavior is a little weird but at least when I tested different scenarios, this is how it appears to be. To have same application pool account in Central Admin and content web app, however, is not recommended.

Nevertheless, I needed a custom timer job to be installed from a web scoped feature because the timer job handles information per web. Therefore the name of the installed timer job is per web and properties to determine which web should be handled when timer job ticks are also added to the timer job’s properties.

So, I took the idea from Stef’s post and used the same idea in my feature receiver and by doing so I don’t have to run the powershell-script per environment before feature activation:

public class MyFeatureReceiver : SPFeatureReceiver
{
    private const string MyCustomTimerJobName =
        "Custom Timer Job for web: {0}";

    public override void FeatureActivated(SPFeatureReceiverProperties
        properties)
    {
        using (var web = properties.Feature.Parent as SPWeb)
        {
            if (web == null) return;

            // THE ORIGINAL VALUE OF REMOTE ADMINISTRATOR
            var remoteAdministratorAccessDenied =
                SPWebService.ContentService.
                RemoteAdministratorAccessDenied;
            try
            {

                // SET THE REMOTE ADMINISTATOR ACCESS DENIED FALSE
                SPWebService.ContentService.
                    RemoteAdministratorAccessDenied = false;
                // delete the custom timer job if it exists
                var app = web.Site.WebApplication;
                foreach (var job in
                    app.JobDefinitions.Where(job =>
                        job.Name == string.Format(MyCustomTimerJobName,
                        web.Url)))
                {
                    job.Delete();
                }
                // install the custom timer job
                var schedule = new SPMinuteSchedule
                {
                    BeginSecond = 0,
                    EndSecond = 59,
                    Interval = 5
                };
                var myTimerJob =
                    new MyTimerJob(
                        string.Format(MyCustomTimerJobName,
                        web.Url), web.Site.WebApplication,
                        null, SPJobLockType.Job)
                        { Schedule = schedule };
                // add properties to determine which site and web
                // the timer job handles
                myTimerJob.Properties.Add("site-id", web.Site.ID);
                myTimerJob.Properties.Add("web-id", web.ID);
                myTimerJob.Update();

            }
            finally
            {
                // SET THE REMOTE ADMINISTATOR ACCESS DENIED BACK WHAT
                // IT WAS
                SPWebService.ContentService.
                    RemoteAdministratorAccessDenied =
                    remoteAdministratorAccessDenied;
            }
        }
    }

    public override void FeatureDeactivating(SPFeatureReceiverProperties
        properties)
    {
        using (var web = properties.Feature.Parent as SPWeb)
        {
            if (web == null) return;

            // THE ORIGINAL VALUE OF REMOTE ADMINISTRATOR
            var remoteAdministratorAccessDenied =
                SPWebService.ContentService.
                RemoteAdministratorAccessDenied;

            try
            {
                // SET THE REMOTE ADMINISTATOR ACCESS DENIED FALSE
                SPWebService.ContentService.
                    RemoteAdministratorAccessDenied = false;
                // delete the custom timer job if it exists
                var app = web.Site.WebApplication;
                foreach (var job in
                    app.JobDefinitions.Where(job =>
                        job.Name == string.Format(MyCustomTimerJobName,
                        web.Url)))
                {
                    job.Delete();
                }
            }
            finally
            {
                // SET THE REMOTE ADMINISTATOR ACCESS DENIED BACK WHAT
                // IT WAS
                SPWebService.ContentService.
                    RemoteAdministratorAccessDenied =
                    remoteAdministratorAccessDenied;
            }
        }
    }
}

The idea is that remote administration is allowed (SPWebService.ContentService.RemoteAdministratorAccessDenied = false) for the period of time the custom timer job is either installed (activated) or deleted (deactivated) and it’s set back to what it was at the end.

Below is a custom timer job stub just to make the point complete:

public class MyTimerJob : SPJobDefinition
{
    public MyTimerJob()
    {
    }

    public MyTimerJob(string name, SPService service, SPServer server,
        SPJobLockType lockType)
        : base(name, service, server, lockType){}

    public MyTimerJob(string name, SPWebApplication webApplication,
        SPServer server, SPJobLockType lockType)
        : base(name, webApplication, server, lockType){}

    public override void Execute(Guid targetInstanceId)
    {
        if (!Properties.ContainsKey("site-id") ||
            !Properties.ContainsKey("web-id"))
            return;

        var siteId = (Guid)Properties["site-id"];
        var webId = (Guid)Properties["web-id"];

        using (var site = new SPSite(siteId))
        {
            using (var web = site.OpenWeb(webId))
            {
                // do your deeds
            }
        }
    }
}

I made my feature hidden only to be activated via scripting to be sure no one would try to activate the feature from my content web application’s UI.

The sample about creating custom timer jobs in msdn (also linked in Stef’s post) article installs the custom timer job via WebApplication-scoped feature. I think timer jobs are only meant to be installed in Central Admin’s context but then how could you fluently develop timer jobs that handle something in your content applications if you can’t install timer jobs per site or web.

The workaround is however something I would’t have wanted to find out, nor use, so use it with your own risk.

Popularity: 5% [?]

52 comments to “Custom SPJobDefinition and “Access denied” Error”

  1. Il en de meme pour les differents accessoires pour au lave vaisselle that is regulierement to babycook comme spatules, bols et petits:
    mieux vaudra laver a la utilisant savonneuse plutot que de les trop qui aurait pour de les abimer plus rapidement.

  2. Hi! I know this is somewhat offf topic but I was wondering if
    you kne where I could fiond a captcha plugin for my comment form?
    I’m using the samee blog platform as yours and I’m having problems finding one?
    Thanks a lot!

Leave a Reply