Create a Custom Timer Job to Enforce Changes to PWA Permission Levels
posted April 14th, 2008 by Stephen SanderlinIn 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:
- Download the ZIP and extract it
- Build the project
- Copy the EPMFAQ.StephenSanderlin.PwaRootRoleSyncJob.wsp file from bin\Debug\SpPackage to a location on your Project Server
- Browse to the BIN directory of the 12 Hive and run stsadm -o addsolution -filename PathToWspFile\EPMFAQ.StephenSanderlin.PwaRootRoleSyncJob.wsp
- Run stsadm -o deploysolution -immediate -allowgacdeployment -name EPMFAQ.StephenSanderlin.PwaRootRoleSyncJob.wsp
- 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:
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
- Part 1: Adjust the Default Project Web Access Permission Levels
- Part 2: Create a Custom Timer Job to Enforce Changes to PWA Permission Levels
Discuss this post on the EPMFAQ Blog Posts Forum.







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