Custom SPJobDefinition and “Access denied” Error

October 22 2010 15 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: 4% [?]

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

  1. Danny says:

    Hello,

    This line:

    SPWebService.ContentService.RemoteAdministratorAccessDenied = false;

    Also gives a access denied in my installation of SP2010.

    Am I forgetting something

  2. Juha Pitkänen says:

    Activate your feature with PowerShell or stsadm:

    Enable-SPFeature
    or
    stsadm -activatefeature

    as an administrator.

  3. Danny says:

    I am logged in in my site as a domain administrator. Is this not enough to run from site collection feature screen? Or is this just not possible?

  4. Juha Pitkänen says:

    See the second chapter of this post “…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…”.

    And the chapter after the second code clip: “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 post was only to get rid of setting RemoteAdministratorAccessDenied to false with powershell script before activating the feature that installs the timer job. The activation of the feature, unfortunately, has to be done with “Enable-SPFeature” or “stsadm -activatefeature”.

  5. Danny says:

    Will try. But all my sites and central admin all run under same app_pool account.This should not be a problem. Will get back to you

  6. Juha Pitkänen says:

    If you have the same app pool accounts for both your content web app and CA, you can log on to your content web app with the app pool account or use SPSecurity.RunWithElevatedPrivileges in your FeatureActivated and FeatureDeactivating. I just tested both scenarios and there is no problem activating/deactivating the feature from the button in your content web app’s feature management.

  7. Je trouve que votre blog est très bien fait, encore un post très intéressant. Continuez-le.

  8. Juha Pitkänen says:

    Danke schon ;)

  9. Andy Burns says:

    Yeah, the problem you’ve found here is that in Least Privileges configured farms the content app pool account doesn’t have any access to SharePoint’s Config database. Even if you RunWithElevatedPrivileges in your content web app you’ll only be running as the content app pool, and you won’t be able to access the config database.

    Sadly, that’s exactly what you need to be able to do to create a new timer job.

    The central admin app pool, however, must have access to the config database to work at all! Thus, code run within this app pool will create the timer job nicely.

    Options are:
    A) Make this happen from within Central Admin. I have seen a solution that created a “master” timer job which would check various web applications – and create “slave” Timer Jobs for those web applications if required. All the feature in the content app pool would do was flag that it wanted a timer job created for it.

    B) Grant access writes to the config db to your content app pool account.

  10. Juha Pitkänen says:

    Than you Andy for clarifying this out for us!

    Just out of curiosity, do you have an explanation to not being able to activate the feature from content app’s UI, when logged on to the content app with central admin’s app pool account? That to me was strange.

  11. “Danke schon”

  12. SaM says:

    HI
    I am having an issue with custom timer job webpart.
    I have two different Sharepoint 2010 envirionment with the service account as farm administrator with write privilage to the configuration database.The problem is if I have a custom timer job created with feature which works perfectly on both of the envirionment.But When I created a webpart to display the status of the timer job,and run and schedule the timer job.The webpart work perfectly in one envirionment but display the status but it doesnot run or could schedule job in other envirionment.I was suggested that I should enable remotedesktopadministor in the box which cannot run the timer job when in webpart.But its no help So could you please let me know if there is some other services I need to start before I set remoteAdministratorAccessDenied. I was trying to read more on the new Feature remoteAdministratorAccessDenied.Can somebody point me out how it works.
    Thanks
    Sam

  13. Allen Eick says:

    Heya i am for the primary time here. I came across this board and I in finding It really helpful & it helped me out much. I am hoping to give one thing again and help others like you aided me.

  14. sympa mais ce soir” il faut “a venir mon site registre des creations

  15. Thanks for sharing such a fastidious thought, post is nice, thats why i have read it fully

Leave a Reply