Customizing PWA

You can customize PWA in many ways from simple modifications to changes that must be made by advanced programmers. This section progresses from simple customizations that can be made without coding to the structure of the ASP code the website is composed of and how to add new pages to it.

Unless otherwise noted, any files referenced will be referred to relative to the IIS Virtual Root for the PWA instance you are dealing with. The default Project Server installation places these files at C:\Program Files\Microsoft Office Project Server 2003\IIS Virtual Root.

URL Parameters

Three URL parameters can be used to modify the look and function of any page: SimpleUI, NoSaveLinkButton, NoMenu. These parameters can be added to the URL in the address bar of the user's browser by any user to affect the page temporarily. Or they can be added to the URL for the page in the menu structure in the database by a DBA to affect the page permanently for all users.

The SimpleUI parameter should be set to an integer value between 0 and 127 that represents bitmapped instructions about which parts of the user interface should be shown. The seven options that can be set are Indicator Click Disabled, No Grid Buttons, No Title Elements, No Tabs, No Action Pane, Collapsed Action Pane, and No Menu. A table of integer values showing all the combinations of these options and a thorough description of their function can be found on Microsoft's MSDN website.

The NoSaveLinkButton and NoMenu parameters are left over from Project Server 2002 and are now deprecated because their function is contained in the SimpleUI functionality. However, when only one of these options is needed, it can be more intuitive to use one of these parameters rather than look up their SimpleUI equivalents.

The Home page supports the NoBanter parameter. Setting this parameter to 1 hides the pane on the right side of the page, which by default contains a graphic and a Work with Outlook link.

Pages that support views, such as the Project Center, can be modified with a number of parameters that determine what data is displayed and how the grid is formatted. The simplest way to view and modify these parameters is to customize the view from the PWA interface and then click the Save Link button. When you click on the saved link in the Actions pane, the URL of that link is displayed in your browser's address bar. More information about the particular parameters available can be found on Microsoft's MSDN website.

Information on how to modify the menus in PWA to include any desired URL parameters will be given later in this chapter.

Modifying the Stylesheet

Modifying the colors and fonts of the website is an easy way to give PWA a custom feel, or make it match a corporate intranet site. Most of the styles you want to customize are defined in the file STYLES\1033\MSPROJECT.css. Styles that contain a gradient background color or other background colors based on browser settings make calls to functions in SHELL\SHELL.asp, and these functions may need to be modified to make some color changes.

Modifying Menus

The menus in PWA are created with a combination of database queries and JScript from ASP files. This section focuses on changing the menu structure stored in the database, which can affect the menu items across the top of all pages and in the Actions panes. Editing database tables and script is necessary for some modifications because the administrator's interface in PWA allows you to modify only the names of existing pages and not their URLs.

The two database tables that control the menu structure are MSP_WEB_SECURITY_MENUS and MSP_WEB_SECURITY_PAGES. These two tables link to MSP_WEB_CONVERSTIONS to provide localized text values and MSP_WEB_SECURITY_FEATURES_ACTIONS to provide security restrictions. The schema of these tables appears in Figure 28.25.

Figure 28.25. Tables that need to be modified when customizing PWA menus.

An easy change to the menu structure is to add URL parameters to the existing pages to modify their look or function. For example, to remove the banter pane (right side of the page) from the Home page, find the entry for Home/homepage.asp in MSP_WEB_SECURITY_PAGES and change the WSEC_PAGE_URL field to Home/homepage.asp? NoBanter=1.

A more complicated change would be to add a new link to the Actions pane for an existing top-level menu item. As an introduction to the menu tables of the database, try adding a link to an existing PWA page. The same technique would be used to add a link to a new custom page.

For the sake of example, suppose that the administrators for your Project Server need to modify the settings for groups frequently and want to be able to access the group management page from the Home page. To enable this, add a link to the group management page to the top-level menu item for the Home section of PWA. This change requires adding an entry to MSP_WEB_CONVERSIONS for the link text, to MSP_WEB_SECURITY_MENUS so that the link appears under the Home top-level menu, and to MSP_WEB_SECURITY_PAGES. An entry for this page already exists in MSP_WEB_SECURITY_PAGES, but another entry is required to link it to the new entry in MSP_WEB_SECURITY_MENUS without removing the link from the Admin Actions menu.

MSP_WEB_CONVERSIONS contains string values related to both an ID and a language code to allow the user interface to be localized to different countries. The entries for menu items have a STRING_TYPE_ID of 5, and your new entry should follow this standard. CONV_VALUE is the ID used to refer to the new entry, so it must be unique among entries for the same language and string type. It should also be chosen to fit with the scheme used to identify other menu items. Make a new entry where STRING_TYPE_ID = 5, CONV_VALUE = 50107, LANG_ID = 1033, and CONV_STRING = "Manage Groups".

Add an entry to MSP_WEB_SECURITY_MENUS with the fields set as follows:

  • WSEC_MENU_ID = an identity (this will be 10001 if this is the first new entry)

  • WSEC_MENU_NAME_ID = the value used for CONV_VALUE for the link text entry in MSP_WEB_CONVERSIONS (50107 according to the previous instructions)


  • WSEC_MENU_PARENT_ID = the ID of the top-level menu item this item should fall under (100 because this link should appear on the Home menu)

  • WSEC_MENU_SEQ = the order of this item in the menu (6 so that this item appears at the end of the menu)

  • WSEC_MENU_DESC_ID = the value used for CONV_VALUE for the ToolTip entry in MSP_WEB_CONVERSIONS (leave this NULL because the link doesn't need a ToolTip)


  • WSEC_MENU_PAGE_ID = the value of WSEC_PAGE_ID for the page entry in MSP_WEB_SECURITY_PAGES (this entry links to a new entry that will get an identity value, so this should be set after the page entry is made)




Add an entry to MSP_WEB_SECURITY_PAGES with all fields set to the same values as the existing entry for the group management page. The existing entry has a WSEC_PAGE_ID of 913. The new entry gets an identity value for the WSEC_PAGE_ID, which will be 10001 if this is the first new page entry. Change the field WSEC_PAGE_MENU_ID to be the WSEC_MENU_ID of the entry you just made in MSP_WEB_SECURITY_MENUS (the value will be 10001 if the item was the first new entry). After the entry is made, update the WSEC_MENU_PAGE_ID of the entry in MSP_WEB_SECURITY_MENUS that you made in the previous step to be the WSEC_PAGE_ID of the entry you made in this step.

At this point, you should be able to log in to PWA as an administrator and see a new link in the Actions pane from the Home page. If you were already logged in, you will need to log out and back in before the change will be visible. This is because the menu structure is loaded at login and cached for quick access on future page requests.

In the previous example, you used an existing feature action (the WSEC_PAGE_ACT_ID field in MSP_WEB_SECURITY_PAGES) to secure the page. In the following section, you learn how to add a new feature action to put custom security on a new custom page.

Adding New Pages Using ASP

The PWA website is a collection of ASP pages written in JScript, so the most intuitive way to add a new page is to use the same technology and take advantage of the architecture used for the existing pages. If you are not familiar with ASP and JScript, or if you want to use another technology to take advantage of other features, that is possible, and a method for implementing it is explained in the next section. This section focuses on using the existing PWA structure and provides a sample page to be used as a starting point for your own custom pages.

With the exception of a few special pages (the login pages, for example) all the pages in PWA include the file SHELL.asp, which renders the common content for all the pages and provides functions for customizing certain areas of the page, such as the menus and title area. To be consistent with the rest of the pages, any custom page should also include this file. The simplest of custom pages then only needs to set the ID and title of the page for the shell object, tell the shell to write itself, and then render the main content of the page. The following code does this and is a starting point for all other examples in this section.

 <!-- #include virtual="/ProjectServerSAMS/Library/CommonIncTop.asp" --> <!-- #include virtual="/ProjectServerSAMS/Shell/Shell.asp" --> <% oShell.SetCurrentPage(10002); oShell.SetTitleText("Hello World Page"); oShell.Write(); %> <!-- Put your content here and it will appear in     the normal PWA shell inside a td --> <h1>Hello World!</h1> <!-- #include virtual="/ProjectServer/Shell/Bottom.asp" --> 

To add this page to PWA, follow these steps:


Make a new folder in IIS Virtual Root called CustomPages.


Copy the preceding code into a text file, name it HelloWorld.asp, and place it in the new folder.


Add an entry to the menu for the Home page by following the instructions from the previous section and use CustomPages/HelloWorld.asp as the WSEC_PAGE_URL and 101 as the WSEC_PAGE_ACT_ID. (This is the feature action ID for the Home page.)

Now that it is working, examine the code. The first two lines include files needed to access all the code common to other PWA files.

Line 5 tells the shell object the ID of the page (WSEC_PAGE_ID) that it is rendering. The ID must be set so that the shell can load the correct menus and verify that the user has permission to view the page. If the ID is not set, the page will still be displayed but without the menu items in the Actions pane.

Line 6 sets the title to be displayed on the page. When the title of the page is not set, the title displayed is the same as the text for the link to the page.

Line 7 has the shell object write its content to the output stream. This is where most of the hard work for rendering the page takes place, and you don't have to do any of it.

Following the script block is the HTML that is the main content of the page. This content appears inside a TD tag in the final page.

The last line includes a file that writes the remainder of the page to the output stream. This marks the end of just about the simplest custom page you could ever come up with.

The HelloWorld.asp page sets the title for the page, but some pages need to render more customized content in the title area. An example of this is the Project Center page, which places a drop-down list of views in the right side of the title area. This is accomplished by defining a function called Page_WriteHeader, which writes the HTML for that area. Adding the following code to the sample page (anywhere before the include of bottom.asp) renders a custom title area:

 function Page_WriteHeader() { %>   <!-- Put any content you want in the header here.       The following is a piece of what other pages use to write       the views drop down lists. -->   <TABLE       CELLSPACING="0" CELLPADDING="2" STYLE="width: 100%; height: 100%;     <%=(oPersist.bTitleElements ? '' : 'display: none;')%>">     <TR><TD COLSPAN="2" STYLE="font-size: 60%;">&nbsp;</TD></TR>     <TR>       <TD  VALIGN=BOTTOM          STYLE="visibility: <%=(oPersist.bTitle) ? "visible" : "hidden"%>;">           <%= "My Custom Page" %>       </TD>       <TD VALIGN="BOTTOM" ALIGN="RIGHT">         This is where the view drop down usually goes.       </TD>     </TR>   </TABLE> <% } 

Notice that when providing this function, you are now responsible for rendering the title of the page in addition to any other custom content.

Now add a submenu to the page. An example of a submenu can be found on most Admin pages. For example, when you click on Manage Users and Groups, the items Users and Groups are in a submenu in the Actions pane. Submenus are rendered from the ASP code for the page, not from the menu structure stored in the database. Submenu items can either be links to other pages or links that call JavaScript functions from their onclick event. The following code adds a submenu that contains one of each type of menu item:

 function FNThatWritesASubMenu(nPageID) {   //Special sub menu items are links that could be used   //either with a valid url, or an onclick event   oShell.AddNewSpecialSubMenu("idAdminManageScope", "Sub Menu For Something");   oShell.AddNewSpecialSubMenuItem("idAdminUsers",                                   "Sub Menu1",                                   "The tooltip for Sub Menu1",                                   "Admin/sec_users.asp",                                   true, //true for selected, false for unselected                                   "SubmenuAction1_OnClick()",                                   "MenuStyle");                                   //The last parameter should be either                                   //"subTitle" or "subItem" for different affects   oShell.AddNewSpecialSubMenuItem("idAdminGroups",                                   "Sub Menu2",                                   "The tooltip for Sub Menu2",                                   "Admin/sec_groups.asp",                                   false); } 

Now add a standalone menu. An example of a standalone menu is on the Project Center page, where the items Track Project Risks and Track Project Issues are in a standalone menu. Like submenus, standalone menus are rendered from the ASP code for the page, not from the menu structure in the database. Standalone menu items are links that call JavaScript functions from their onclick event. They cannot be links directly to other pages, but JavaScript could be used to redirect the browser. The code to add a standalone menu is as follows:

 function FNThatWritesAStandaloneMenu() {   //Standalone menu items are links with onclick events that call a function   oShell.bWriteStandaloneDivider = true;   oShell.sStandaloneSubhead = "Some actions related to My Page";   oShell.AddNewStandaloneMenu("idStandaloneAction1",                               "Standalone Action",                               "The tooltip for the standalone action.",                               "StandAloneAction1_OnClick();"); } 

To this point, the custom pages have contained only static content. Now you add code to render content gathered from a database query using helper functions provided by the PWA framework. The following code should replace the code that writes Hello World:

 <% oPJQuery.Connect(); var rsFields = oPJQuery.OpenRecordset(const_PjQuery_GetAllCategories); for (i = 0; i < rsFields.RecordCount; i++) {   %>   <tr>     <td><%= rsFields.GetColumn(const_dbWSEC_CAT_NAME) %></td>   </tr>   <%   rsFields.MoveNext(); } rsFields.Close(); %> 

Now that the page contains some sensitive information, you can change the permissions on it to use a user-defined feature action and make it so that only executives can see the link in the Actions pane for the home section. Feature actions are defined in MSP_WEB_SECURITY_FEATURES_ACTIONS, which links to MSP_WEB_CONVERSIONS enTRies with STRING_TYPE_ID of 9 for a localized name. Three feature actions are contained in the database out of the box that are intended to be used for defining custom permissions. Their names are User defined 1 through User defined 3, and they have IDs 150152. For the purposes of this example, you can change the name of one of these feature actions and set the WSEC_PAGE_ACT_ID for your custom page to the ID of that feature action. After you've created a lot of custom pages and run out of predefined feature actions, you can follow the template of these user-defined feature actions to add your own new ones.


When you create a new feature action, you must remember to add a reference to it to the MSP_WEB_SECURITY_ORG_PERMISSIONS table, or no users will ever really have access to that feature and your links will not appear.

Adding New Pages Using Another Language

If you want to add custom pages to PWA without using ASP, you will not be able to include SHELL.asp or any of the other files PWA provides you, so you will need another way to render the shell for the page. PWA lets you add links to external pages through the Admin interface, but out of the box this interface only supports absolute URLs, and it is often desirable to use a relative URL instead. This can be accomplished by making a small modification to SHELL/CUSTPAGE.asp as follows:

 <!-- #include virtual="/ProjectServer/Library/CommonIncTop.asp" --> <!-- #include virtual="/ProjectServer/Library/DataSpace.js" --> <!-- #include virtual="/ProjectServer/Shell/Shell.asp" --> <% var sCustPageURL = ""; var nPageID = Number(Request.QueryString("PageID").Item); if (!isNaN(nPageID)) {   oShell.SetCurrentPage(nPageID);   oPJQuery.Connect();   var rsPage = oPJQuery.OpenRecordset(     const_PjQuery_CustPageURLForPageID, nPageID);   if(rsPage.RecordCount > 0)   {     sCustPageURL = rsPage.GetColumn(const_dbWSEC_PAGE_CUSTOM_URL);     if (sCustPageURL == null) sCustPageURL = "";   }   rsPage.Close();   oPJQuery.Disconnect(); } if (sCustPageURL != "")   sCustPageURL += (sCustPageURL.indexOf('?') == -1)     ? "?InnerPage=1" : "&InnerPage=1"; //Added for customization if (sCustPageURL != "") {   //If :// and :\\ are not present in the URL, prepend the application path   if ( sCustPageURL.indexOf('://', 0) == -1 &&     sCustPageURL.indexOf(':\\', 0) == -1)     sCustPageURL = sAppPath + sCustPageURL; } //End of addition oShell.Write(); %> <A  STYLE="display:none;"></A> <DIV >   <IFRAME  FRAMEBORDER="0" NAME="idCustPageFrame"     SCROLLING="YES" STYLE="display: block; width: 100%; height: 100%;">   </IFRAME> </DIV> <SPAN  STYLE="display: none;"></SPAN> <SCRIPT LANGUAGE="JScript"> function Ultimate_OnLoad() {   InitIFrameSrcAttributes(); } function InitIFrameSrcAttributes() {   var e = new Error();   var bParentWindowHasSentryGUID = false;   try   {     bParentWindowHasSentryGUID =       (typeof(window.parent.D5D851A526ED41BFBB2D78C3625F3925) != "undefined");   }   catch (e)   {   }   if (window.self == || !bParentWindowHasSentryGUID)   {     var link = unescape("<%=escape(sCustPageURL)%>");     if (link == "")     {       return;     }     idLinkHelper2.href = link;     var sProt = sProtocol + "://";     if (idLinkHelper2.protocol != "")     {       sProt = "";     }     document.frames["idCustPageFrame"].window.location.href = sProt + link;   } } </SCRIPT> <!-- #include virtual="/ProjectServer/Shell/Bottom.asp" --> 

With this page in place, PWA still supports adding external links, but it also allows you to specify root relative links to your custom pages so that the PWA cookies are available for your pages to use. Having the cookies available is important so that you can obtain a session and access any needed session variables, such as the ID of the current user.

When using a technology other than ASP, it probably is not possible to get direct access to ASP session state even after obtaining the session ID from the PWA cookie. One way to access session variables from another technology is to write a simple ASP page that receives the session ID as a query parameter and returns the desired variable as its output. The following is a page used to determine the current user's ID:

 <%@ Language=JScript EnableSessionState=FALSE CODEPAGE=65001%> <% /* This file takes PjSessionId in the query string and writes the userID contained in the session it finds as a response.  It is intended to be used by pages that are added to PWA and need to know the logged in user's WRES_ID. */ var done = false; function ReturnResult( result ) {   Response.Write(result); } var session = null; try {   session = GetObject( "pcs://SessionMgr?" +     Request.QueryString("PjSessionID") ); } catch(e) {   ReturnResult("No session found");   done = true; } var userID; if( !done ) {   userID = session["userId"];   if (typeof(userID) == "undefined" || userID.length == 0)   {     ReturnResult("No userID in session");     done = true;   } } if( !done ) {   ReturnResult(userID); } %> 

To use this page, browse to it from within your code. The following is some VB.NET code that does this:

 Dim userIDUrl As String userIDUrl = FullAppPath & "userID.asp" Dim strUserID As String Try   If Request.Cookies("PjSessionId") Is Nothing Then     Throw New Exception("No session cookie found." & _       "  You must be logged in to PWA to view this page.")   Else     Dim userIDRequest As HttpWebRequest = HttpWebRequest.Create( _       userIDUrl & "?PjSessionPjSessionId").Value)     Dim userIDResponse As HttpWebResponse = userIDRequest.GetResponse()     Dim reader As New StreamReader(userIDResponse.GetResponseStream())     strUserID = reader.ReadToEnd()     Return Convert.ToInt32(strUserID)   End If Catch formatEx As FormatException   ReportError("Response from " & userIDUrl & ":<BR>" & strUserID) Catch webEx As WebException   ReportError("Error while trying to acces " & userIDUrl, webEx) Catch ex As Exception   ReportError("Error while trying to determine userID.", ex) End Try 

When using the preceding code, you will receive a 401 unauthorized error unless userID.asp is allowed anonymous access from within IIS.

Although this method allows you to write custom pages for PWA in the language of your choice, it does have some significant drawbacks. It does not allow you to customize the title area for the page. It does not allow you to add submenus or standalone menus to the Actions pane. It does not allow you to set custom permissions for the page, so access to the page must be secured by your own custom code. Some of these drawbacks can be overcome by writing an ASP page that customizes the PWA shell and contains an IFRAME to hold the content of your custom page written in your language of choice.

How to Customize One Instance and Not Others

Multiple instances of Project Server can be hosted from a single server by creating new instances using the Edit Site utility. These multiple instances use different databases and have different Virtual Roots in IIS, but they all use the same set of web files. This can cause a problem when you want to customize the pages of one instance of PWA and leave the other unchanged.

To customize a single instance, you need to make a copy of the IIS Virtual Root folder (located at C:\Program Files\Microsoft Office Project Server 2003 by default) and point the Virtual Root in IIS to use this new root folder. In addition to this, you need to register the copied file PJDBCOMM.DLL with IIS as a Web Service Extension. Without registering this file, Project Professional will not recognize that instance as valid. To register the file, perform the following steps:


In IIS Manager, expand the Web Service Extensions node.


Double-click the Microsoft Office Project Server 2003 ISAPI entry in the right pane.


On the Required Files tab, click the Add button.


Browse to the file PJDBCOMM.DLL, located in the ISAPI folder of the new IIS Virtual Root folder and click OK.

It is important to note that included files in all the PWA pages are referred to with root relative URLs. This means that the pages in your custom virtual root will refer to files in the standard virtual root unless you change all the include lines in every file. In most cases it isn't necessary to change all those lines, but keep in mind that if you customize an included file, you need to change every file that refers to that file, or your changes will not be used.

    QuantumPM - Microsoft Office Project Server 2003 Unleashed
    Microsoft Office Project Server 2003 Unleashed
    ISBN: 0672327430
    EAN: 2147483647
    Year: 2005
    Pages: 227
    Authors: QuantumPM LLC © 2008-2017.
    If you may any questions please contact us: