EPMFAQ
Project Server Help, FAQs, Blogs, and HowTos
This is The Header Then

Create a Custom Timer Job to Enforce Changes to PWA Permission Levels

posted April 14th, 2008 by Stephen Sanderlin
1 Star2 Stars3 Stars4 Stars5 Stars (3 votes, average: 5 out of 5)
Loading ... Loading ...

In our previous article, we discussed Permission Levels for Project Web Access sites. We talked about how they were too liberal for most organizations and how to change them.

Unfortunately for us, the fact of the matter is that any changes you make to the default permission levels (in PWA or in a PWS) are not permanent, since the two Membership Synchronization processes overwrite them.

The PSI Methods for these two processes (QueueSynchronizeMembershipForWssSite and SynchronizeMembershipForPwaAppRootSite) can be found in the WssInterop service, which resides at http://ServerName/ProjectServerInstanceName/_vti_bin/psi/WssInterop.asmx. As previously discussed, both of them will delete and recreate the permission levels (or roles, depending which part of what document/interface/article/SDK you read) whenever triggered either by you or by Project Server.

So, how do we go about enforcing our own permissions for these permission levels (or roles)? There are a number of ways we can go about it (e.g. create a Windows service, create a command line application and kick it off using the Windows Scheduler), but the one we will discuss in this article is the creation of a custom timer job to handle this for us. I can hear some of you asking “What about the Custom Event Handlers?”

Unfortunately, Microsoft did not create a way for us to override this behavior with a custom event handler, so we are forced into using the less desirable alternative of a custom timer job.

Custom timer jobs are actually pretty simple — it’s surprising there aren’t more articles floating about. The technique I will demonstrate in this post is a derivative of one posted by Andrew Connell a couple months ago. I also used his guidance on automating the creation of a WSP file by using the MS Cabinet SDK. Thanks for the great articles, Andrew!

As discussed in Andrew’s article, first we create a class that inherits from Microsoft.SharePoint.Administration.SPJobDefinition:

    1 namespace EPMFAQ.StephenSanderlin.PwaRootRoleSync

    2 {

    3     public class PwaRootRoleSyncJob

    4         :SPJobDefinition

    5     {

    6         /// <summary>

    7         /// Initializes a new instance of the PwaRootRoleSyncJob class.

    8         /// </summary>

    9         public PwaRootRoleSyncJob ()

   10             :base()

   11         {

   12         }

   13 

   14         /// <summary>

   15         /// Initializes a new instance of the PwaRootRoleSyncJob class.

   16         /// </summary>

   17         /// <param name=”jobName”>Name of the job.</param>

   18         /// <param name=”webApplication”>A

   19         /// <see cref=”T:Microsoft.SharePoint.SPWebApplication”></see>

   20         /// object that represents the web application we wish to

   21         /// associate the job with.</param>

   22         public PwaRootRoleSyncJob

   23             (string jobName, SPWebApplication webApplication)

   24             : base (jobName, webApplication, null,

   25             SPJobLockType.ContentDatabase)

   26         {

   27             this.Title = “EPMFAQ.com Custom PwaRootRoleSyncJob”;

   28         }

   29 

   30         /// <summary>

   31         /// Executes this job against the specified content db id.

   32         /// </summary>

   33         /// <param name=”contentDbId”>The content db id.</param>

   34         public override void Execute (Guid contentDbId)

   35         {         

   36             // Using the Trace Log example in the SDK,

   37             // write an event to the trace

   38             TraceProvider.RegisterTraceProvider();

   39             TraceProvider.WriteTrace(TraceProvider.TagFromString(“PRRS”),

   40                 TraceProvider.TraceSeverity.Monitorable, Guid.NewGuid(),

   41                 “EPMFAQ.com Custom PwaRootRoleSyncJob”,

   42                 “EPMFAQ.com Custom PwaRootRoleSyncJob”,

   43                 “Timer Job”,

   44                 “Executing EPMFAQ.com Custom PwaRootRoleSyncJob”);

   45 

   46             // Get the appropriate references

   47             SPWebApplication webApplication =

   48                 this.Parent as SPWebApplication;

   49             SPWeb pwaWeb = webApplication.Sites[“PWA”].RootWeb;

   50 

   51             // Get the role definition collection

   52             SPRoleDefinitionCollection roles = pwaWeb.RoleDefinitions;

   53 

   54             // Create a list of valid roles

   55             StringBuilder roleNames = new StringBuilder();

   56 

   57             foreach (SPRoleDefinition role in roles)

   58             {

   59                 roleNames.Append(“||” + role.Name);

   60             }

   61 

   62             // Output our list to the trace

   63             TraceProvider.WriteTrace(TraceProvider.TagFromString(“PRRS”),

   64                 TraceProvider.TraceSeverity.Monitorable, Guid.NewGuid(),

   65                 “EPMFAQ.com Custom PwaRootRoleSyncJob”,

   66                 “EPMFAQ.com Custom PwaRootRoleSyncJob”,

   67                 “Timer Job”, “Roles Found: “ + roleNames);

   68 

   69             // Since we want to copy the permissions of the

   70             // Readers (Microsoft Office Project Server)

   71             // role, let’s make a copy of it.

   72             SPRoleDefinition readerRole =

   73                 roles[“Readers (Microsoft Office Project Server)”];

   74 

   75             // Pull out the two roles we are interested in

   76             SPRoleDefinition pmRole =

   77                 roles[

   78                 “Project Managers (Microsoft Office Project Server)”];

   79             SPRoleDefinition tmRole =

   80                 roles[“Team members (Microsoft Office Project Server)”];

   81 

   82             // Copy over the base permissions to the

   83             // Project Managers and Team members roles

   84             // from the readerRole

   85             pmRole.BasePermissions = readerRole.BasePermissions;

   86             tmRole.BasePermissions = readerRole.BasePermissions;

   87 

   88             // Output status to the trace

   89             TraceProvider.WriteTrace(TraceProvider.TagFromString(“PRRS”),

   90                 TraceProvider.TraceSeverity.Monitorable, Guid.NewGuid(),

   91                 “EPMFAQ.com Custom PwaRootRoleSyncJob”,

   92                 “EPMFAQ.com Custom PwaRootRoleSyncJob”,

   93                 “Timer Job”, “Updating PM and TM Roles”);

   94             // Update the role

   95             pmRole.Update();

   96             tmRole.Update();

   97 

   98             pwaWeb.Close();

   99 

  100             // Output status to the trace

  101             TraceProvider.WriteTrace(TraceProvider.TagFromString(“PRRS”),

  102                 TraceProvider.TraceSeverity.Monitorable, Guid.NewGuid(),

  103                 “EPMFAQ.com Custom PwaRootRoleSyncJob”,

  104                 “EPMFAQ.com Custom PwaRootRoleSyncJob”,

  105                 “Timer Job”, “PwaRootRoleSyncJob Complete!”);

  106             TraceProvider.UnregisterTraceProvider();

  107         }

  108     }

  109 }

Whew! Although that looks like a lot of code, it’s relatively simple — plus, there’s at least 10-20 lines added by the reformatting I had to do in order to make it fit on the blog.

Essentially, all we are doing is getting a couple Role Definitions (another name for Permission Level) from the PWA site. We then copy the permissions for the Readers role over top of those for Project Managers and Team Members. We then save the change — it’s just that easy!

You also may have noticed that I’m performing some writes out to the Tracing log… I’m using the stock code from the WSS/MOSS SDK to do this — nothing fancy, just wanted to throw up some information as the job was executed.

To install the feature, the current best practice is to create a class that inherits from Microsoft.SharePoint.SPFeatureReciever:

    1 namespace EPMFAQ.StephenSanderlin.PwaRootRoleSync
    2 {
    3     class PwaRootRoleSyncJobInstaller : SPFeatureReceiver
    4     {
    5         private const string _jobName = “PwaRootRoleSync”;
    6 
    7         /// <summary>
    8         /// Occurs after a Feature is installed.
    9         /// </summary>
   10         /// <param name=”properties”>An 
   11         /// <see cref=”T:Microsoft.SharePoint.SPFeatureReceiverProperties”>
   12         /// </see> object that represents the properties of the 
   13         /// event.</param>
   14         public override void FeatureInstalled
   15             (SPFeatureReceiverProperties properties)
   16         {
   17         }
   18 
   19         /// <summary>
   20         /// Occurs when a Feature is uninstalled.
   21         /// </summary>
   22         /// <param name=”properties”>An 
   23         /// <see cref=”T:Microsoft.SharePoint.SPFeatureReceiverProperties”>
   24         /// </see> object that represents the properties of the 
   25         /// event.</param>
   26         public override void FeatureUninstalling
   27             (SPFeatureReceiverProperties properties)
   28         {
   29         }
   30 
   31         /// <summary>
   32         /// Occurs after a Feature is activated.
   33         /// </summary>
   34         /// <param name=”properties”>An 
   35         /// <see cref=”T:Microsoft.SharePoint.SPFeatureReceiverProperties”>
   36         /// </see> object that represents the properties of the 
   37         /// event.</param>
   38         public override void FeatureActivated
   39             (SPFeatureReceiverProperties properties)
   40         {
   41             // register the the current web
   42             SPSite site = properties.Feature.Parent as SPSite;
   43 
   44             // make sure the job isn’t already registered
   45             foreach
   46                 (SPJobDefinition job in site.WebApplication.JobDefinitions)
   47             {
   48                 if (job.Name == _jobName)
   49                     job.Delete();
   50             }
   51 
   52             // install the job
   53             PwaRootRoleSyncJob taskLoggerJob =
   54                 new PwaRootRoleSyncJob(_jobName, site.WebApplication);
   55 
   56             SPMinuteSchedule schedule = new SPMinuteSchedule();
   57             schedule.BeginSecond = 0;
   58             schedule.EndSecond = 59;
   59             schedule.Interval = 5;
   60             taskLoggerJob.Schedule = schedule;
   61 
   62             taskLoggerJob.Update();
   63         }
   64 
   65         /// <summary>
   66         /// Occurs when a Feature is deactivated.
   67         /// </summary>
   68         /// <param name=”properties”>An 
   69         /// <see cref=”T:Microsoft.SharePoint.SPFeatureReceiverProperties”>
   70         /// </see> object that represents the properties of the 
   71         /// event.</param>
   72         public override void FeatureDeactivating
   73             (SPFeatureReceiverProperties properties)
   74         {
   75             SPSite site = properties.Feature.Parent as SPSite;
   76 
   77             // delete the job
   78             foreach 
   79                 (SPJobDefinition job in site.WebApplication.JobDefinitions)
   80             {
   81                 if (job.Name == _jobName)
   82                     job.Delete();
   83             }
   84         }
   85     }
   86 }

That’s it!

Obviously, I took a few shortcuts in my code simply because this is a simple proof-of-concept. I would recommend that you implement some code to control the settings for each role — along with the path — using settings or properties, rather than hardcoding them as I have done.

To deploy this code:

  1. Download the ZIP and extract it
  2. Build the project
  3. Copy the EPMFAQ.StephenSanderlin.PwaRootRoleSyncJob.wsp file from bin\Debug\SpPackage to a location on your Project Server
  4. Browse to the BIN directory of the 12 Hive and run stsadm -o addsolution -filename PathToWspFile\EPMFAQ.StephenSanderlin.PwaRootRoleSyncJob.wsp
  5. Run stsadm -o deploysolution -immediate -allowgacdeployment -name EPMFAQ.StephenSanderlin.PwaRootRoleSyncJob.wsp
  6. Wait a few minutes, then run stsadm -o activatefeature -name PwaRootRoleSyncJob -url “http://ServerName/ProjectServerInstanceName/”

If your deployment is successful, you will see a line similar to this in your Central Administration > Operations > Timer Job Definitions screen:

Timer Job Definition

If it fails for some reason, an error should show up in the Application event log.

You can download all the relevant code for this article using this link.

Note that this code was built in Visual Studio 2008… I’ve included only the code, the SNK, and the csproj, so you should be able to open it in Visual Studio 2005 without any trouble. Let me know if you discover otherwise.

Please post your feedback, questions, and problems in the EPMFAQ Blog Posts Forum!


Adjust the Default Project Web Access Permission Levels Series


Stephen Sanderlin is Vice President of Technology for MSProjectExperts and a Microsoft Project MVP. His earlier writings on Project Management and Microsoft Project can be read at EPMFAQ.
He is actively posting new content at ProjectServerHelp.



Discuss this post on the EPMFAQ Blog Posts Forum.


Related Posts



« Previous Entry Next Entry »

Please discuss this post on the EPMFAQ Blog Posts Forum.

One Response to “Create a Custom Timer Job to Enforce Changes to PWA Permission Levels”


  1. […] Create a Custom Timer Job to Enforce Changes to PWA Permission Levels […]


The opinions expressed on this site by the authors and those providing comments are theirs alone, and do not reflect the opinions of the respective employers of the authors or any employee thereof. Our respective employers are not responsible for the accuracy of any of the information supplied by on this site.

Terms of Use | Privacy Policy | Publishers

All content on this website is covered by the
Creative Commons Attribution-Share Alike 3.0 United States License.
Theme ©2007 The Heckerped WordPress Theme created by JTk of Doc5 under the Creative Commons Attribution License.