Project Programmability

This blog focuses on customizations and programming for Project Web App, Project Server, Project Professional and Project Standard. Includes User Interface (UI) customizations, Project Server Interface (PSI) and Visual Basic for Applications (VBA) Programming. It also covers Business Intelligence.

  • Project Programmability and Business Intelligence

    Getting a Project GUID on the Cheap


    Many people have requested for a complementary method to GetProjectNameFromProjectUid where they can pass in a project name and it returns the project's GUID. Unfortunately it did not make it into the Project API. Since it has been requested many times, I figured that it would be useful to post a method, GetProjectUidFromProjectName, which did just that:

    public static Guid GetProjectUidFromProjectName(string projectName)
      Guid projectGUID;

      WSProject.ProjectDataSet readProjDs = projWS.ReadProjectStatus(

      if (readProjDs.Project.Rows.Count == 1)
        projectGUID = new Guid(readProjDs.Project[0].PROJ_UID.ToString());
        throw new Exception("No Project by the name: " + projectName + " Found");

      return projectGUID;


    First thing to note is that projWS is defined elsewhere and it is a connection to the Project Web Service. See my earlier post on Getting Started with the PSI on how to make the connection to the Project Web Service.

    To get the GUID we are going to use the ReadProjectStatus method, as it is the cheapest call to make to get the project's GUID. As you will see, this method takes 4 parameters. The first parameter is the project GUID. In this case, we pass in an empty GUID because that is what we are looking for. Passing in an empty GUID tells Project Server not to search for a project by the GUID.  

    The second parameter is the store to get the project GUID from. Here we have the option of getting the GUID from the working, published or archived store. In this example, I am getting it from the working store, since I want to get the GUID for projects that are actively being worked on and may or may not have been published. Note that this will also get projects that have been published, since they exist in both stores with the same GUID. You may want to change the store based on your requirements.

    The third parameter is the name of the project. This is passed into our GetProjectUidFromProjectName method.

    The last parameter is the type of project. There a number of different project types:

    In most cases 0, which represents standard projects, will be the correct project type.

    The ReadProjectStatus returns a project dataset. If the number of rows returned for the project table is equal to 1, than we have found a project with the given name and we can return the GUID for the project. If the number of rows is not equal to one, than no project by the name was found and we throw an exception.

    Hope this helps,

    Chris Boyd

  • Project Programmability and Business Intelligence

    Adding a Project to a Category


    Brian Smith from PSS has passed along this sample that we thought might be helpful:

    The scenario here is that you have a lookup table that shows the categories you want users to select from when creating a project, and then the GUID for the "real" security category is held in the description for the lookup table value.  You make the CF that feeds from the Lookup Table a required Project Level text field.  The Project.Created event fires and the dataset is read - the custom field identified and the GUID of the security category is then used to add the project to the security category.

    No error checking or exception handling is shown - you can do this bit. You would also need to set the categories to the rule "only projects...".  I've hardcoded my lookup table and a reference required to the Microsoft.Office.Project.Server.Library and Events. A Web References to LookupTable, Project, Security and LoginWindows is also required.

    The code will run as the user running the services - so you will either need that account to have PWA permissions or to change to use impersonation.

    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Diagnostics;
    using System.Text;
    using Microsoft.Office.Project.Server.Events;
    using Microsoft.Office.Project.Server.Library;

    namespace TestEventHandler

        public class AutoCategory:ProjectEventReceiver

            public override void OnCreated(PSContextInfo contextInfo, ProjectPostEventArgs e)

                // cfGuid holds CF for Project Category
                Guid cfGuid = new Guid("9bbc698f-5c1d-4f8d-a3d0-163006416bf2");

                // ltGuid holds LT for Categories
                Guid ltGuid = new Guid("625bab60-4427-4f0b-941b-9860d1293338");

                // lt_Struct_Uid gets the id for the selected LT value
                Guid lt_Struct_Uid = new Guid("00000000-0000-0000-0000-000000000000");

                // securityCategoryGuid gets the Security Categorty Guid from the Descriptio field in the lookup table
                Guid securityCategoryGuid = new Guid("00000000-0000-0000-0000-000000000000");

                // 32 is used to just get the CF entities from the readProjectEntities
                const int PROJECT_ENTITY_TYPE_PROJECTCUSTOMFIELD = 32;

                Guid SECURITY_CATEGORY_OBJECT_TYPE_PROJECT = new Guid("1771B1C0-6E26-4FB3-A480-C798AB506E82");

                WebSvcLoginWindows.LoginWindows loginWindows = new TestEventHandler.WebSvcLoginWindows.LoginWindows();
                WebSvcProject.Project project = new TestEventHandler.WebSvcProject.Project();
                WebSvcSecurity.Security security = new TestEventHandler.WebSvcSecurity.Security();
                WebSvcLookupTable.LookupTable lookupTable = new TestEventHandler.WebSvcLookupTable.LookupTable();


                //login to Project Server - this assumes the event service has a login with permissions

                // Impersonation would be better
                loginWindows.Url = @"https://SERVER_NAME/ProjectServer/_vti_bin/PSI/LoginWindows.asmx";
                loginWindows.Credentials = CredentialCache.DefaultCredentials;

                // Get the dataset
                project.Url = @"https://SERVER_NAME/ProjectServer/_vti_bin/PSI/Project.asmx";
                project.Credentials = CredentialCache.DefaultCredentials;

                lookupTable.Url = @"https://SERVER_NAME/ProjectServer/_vti_bin/PSI/LookupTable.asmx";
                lookupTable.Credentials = CredentialCache.DefaultCredentials;

                security.Url = @"https://SERVER_NAME/ProjectServer/_vti_bin/PSI/security.asmx";
                security.Credentials = CredentialCache.DefaultCredentials;

                WebSvcProject.ProjectDataSet dsProjectDataSet = new TestEventHandler.WebSvcProject.ProjectDataSet();

                dsProjectDataSet = project.ReadProjectEntities(e.ProjectGuid, PROJECT_ENTITY_TYPE_PROJECTCUSTOMFIELD, TestEventHandler.WebSvcProject.DataStoreEnum.WorkingStore);

                for (int i = 0; i < dsProjectDataSet.ProjectCustomFields.Count; i++)
                    if (dsProjectDataSet.ProjectCustomFields[i].MD_PROP_UID == cfGuid)
                        lt_Struct_Uid = dsProjectDataSet.ProjectCustomFields[i].CODE_VALUE;

                Guid[] arrayLtUid = new Guid[1]{ltGuid};
                WebSvcLookupTable.LookupTableDataSet dsLookupTable = new TestEventHandler.WebSvcLookupTable.LookupTableDataSet();
                dsLookupTable = lookupTable.ReadLookupTablesByUids(arrayLtUid, false, 1033);

                for (int i = 0; i < dsLookupTable.LookupTableTrees.Count; i++)
                    if (dsLookupTable.LookupTableTrees[i].LT_STRUCT_UID == lt_Struct_Uid)
                        securityCategoryGuid = new Guid(dsLookupTable.LookupTableTrees[i].LT_VALUE_DESC.ToString());

    WebSvcSecurity.SecurityCategoriesDataSet dsSecurityCategories
       = new TestEventHandler.WebSvcSecurity.SecurityCategoriesDataSet();

    // Read the existing values for the security category into the dataset
    dsSecurityCategories = security.ReadCategory(securityCategoryGuid);

                // Get a new objects row to put the created project into

                WebSvcSecurity.SecurityCategoriesDataSet.SecurityCategoryObjectsRow dsSecurityCategoryObjectsRow
    = dsSecurityCategories.SecurityCategoryObjects.NewSecurityCategoryObjectsRow();

                //Set the values
                dsSecurityCategoryObjectsRow.WSEC_OBJ_TYPE_UID = SECURITY_CATEGORY_OBJECT_TYPE_PROJECT;
                dsSecurityCategoryObjectsRow.WSEC_CAT_UID = securityCategoryGuid;
                dsSecurityCategoryObjectsRow.WSEC_OBJ_UID = e.ProjectGuid;

                // Add the row to the dataset and then pass to SetCategories to update

                // Create an EventLog instance and assign its source.
                EventLog myLog = new EventLog();
                myLog.Source = "Project Event Handler";

                // Get information from the event arguments, and

                // write an entry to the Application event log.
                string userName = contextInfo.UserName.ToString();
                string projectName = e.ProjectName.ToString();
                string secCatUid = securityCategoryGuid.ToString();
                int eventId = 3652;
                string logEntry;


                logEntry = "User: " + userName +

                        "\nProject: " + projectName +

                        "\nSecurity Category Uid: " + secCatUid;

                    myLog.WriteEntry(logEntry, EventLogEntryType.Information, eventId);




  • Project Programmability and Business Intelligence

    Forms Authentication in SQL Reporting Services


    Phil passed along a blog entry from one of our SQL colleagues which mentions how to support Forms Authentication in SQL Reporting services. It isn’t enabled out of the box but it can be supported. This is cool news for all forms users who want to access reports. We haven’t tried it out yet but let us know if you get it working:


  • Project Programmability and Business Intelligence

    Getting Started with the PSI


    Many people have asked, “How do I get started working with the PSI?” So I figured I would blog about creating a very simple application that interacts with the PSI. For this example, I will create a simple Windows Application that connects to Project Server and retrieves a list of resources for a given project.  

    Before we begin, it is important to realize that the PSI is made up of a number of Web Services. You can find a list of all the Project Server Web Service here: These Web Services are logically separated by business objects. For this example, we will be using both the Project and Resource Web Service.

    To get started, open visual studio and create a Windows Application. The first step will be to add web references to the Project and Resource Web Services:

    1.       In the Solution Explorer, right click on References

    2.       Click on Add Web Reference.

    3.       Type in the URL to the Project Web Service.

    The URL for the web service is:


    Where SERVER_NAME is the name of the server Project Server is hosted on and PWA_INSTANCE is the name of the Project Web Access instance you want to connect to. _vti_bin/psi is where all the Project Server PSI Web Services reside. project.asmx is specific to the Project Web Service.

    4.       Give the Web Reference a name, such as WSProject

    5.       Click Add Reference

    This will add a reference to the Project Web Service. Repeat the same steps again, except this time, on step 3 specify resource.asmx instead of project.asmx and in step for name the Web Reference WSResource.

    Now that the Web References are step up, we can start to program! When I develop against the PSI, I always create a connection object to handle the various connections to the PSI. This allows me to reuse the connection class in a number of applications. Below is the source code of my connection class:

    using System;

    using System.Collections.Generic;

    using System.Text;

    using System.Data;

    using System.Net;

    using System.Resources;

    using System.Globalization;

    using System.Web.Services.Protocols;

    using System.Reflection;


    namespace PSIDemo


        public class Connection


            public const string Resource = "Resource";

            public const string Project = "Project";


            private static Dictionary<string, SoapHttpClientProtocol> WSDictionary;


            private string ms_ProjServURL;


            public Connection(string as_ProjServURL)


                ms_ProjServURL = as_ProjServURL + "/_vti_bin/psi/";


                WSDictionary = new Dictionary<string, SoapHttpClientProtocol>();



            public SoapHttpClientProtocol GetWebService(string as_WSName)


                SoapHttpClientProtocol lo_WS;


                if (WSDictionary.TryGetValue(as_WSName.ToString(), out lo_WS) == false)




                        case Resource:

                            Auth(Resource, new WSResource.Resource());


                        case Project:

                            Auth(Project, new WSProject.Project());




                    lo_WS = WSDictionary[as_WSName];



                return lo_WS;



            public static void Reset()





            private void Auth(string as_WSName, SoapHttpClientProtocol as_WS)




                    object [] parameters = new object [1];


                    parameters[0] = ms_ProjServURL + as_WSName + ".asmx";

                    MethodInfo setUrlMethod = as_WS.GetType().GetProperty("Url").GetSetMethod();

                    setUrlMethod.Invoke(as_WS, parameters);


                    parameters[0] = CredentialCache.DefaultCredentials;

                    MethodInfo setCredentialsMethod = as_WS.GetType().GetProperty("Credentials").GetSetMethod();

                    setCredentialsMethod.Invoke(as_WS, parameters);


                    WSDictionary.Add(as_WSName, as_WS);


                catch (Exception ex)


                    throw ex;







    This connection class has a dictionary of all the available PSI Web Services. With this implementation we are only concerned with the Project and Resource Web Service, so I have not included any other web services, but it would not be difficult to add additional PSI Web Services. It would only require a couple more lines of code for each Web Service. I will save that for another post.

    The method that handles the setup to the Web Service is Auth. For each Web Service, we need to set the URL for the server that we want to connect to at run time. The URL was passed in with the constructor and this can be a different URL then the one used for the Web Reference.  The second step is to set the credentials. For this example, we will only do NT authentication, but if there is interest, I can post an extension for Forms Authentication.  Once that is done, we add the Web Service to the dictionary and it is ready to be used.

    Next, I am going to add three controls, plus a few labels to the Windows form. The first control is a text box for the URL to the Project Server (txtURL), the second control is a drop down which will be populated with all the Projects the user has access to (cboProjects) and the third control will be a list box which will contain the names of resources that belong to the selected project (lstResources). Below is a screen shot of the form:

    This form has two methods that contain all the calls to the Web Services. The first method is the Leave event for the URL textbox:



    private void txtURL_Leave(object sender, EventArgs e)




          conn = new Connection(txtURL.Text);


          projWS = (WSProject.Project)conn.GetWebService(Connection.Project);


          DataTable projList = projWS.ReadProjectList().Tables[0];


          foreach (DataRow dr in projList.Rows)


                cboProjects.Items.Add(new ProjListItem(dr["Proj_Name"].ToString(), new Guid(dr[0].ToString())));



          if (cboProjects.Items.Count > 0)


                cboProjects.SelectedItem = cboProjects.Items[0];




    In this method, we instantiate the Connection object and pass in the URL for the Project Server. This is the URL that will be used at run time. Next, we get the Project and Resource Web Services from the connection object. This allows us to read the projects and populate the drop down with all the project names.  I have created a basic object, ProjListItem, which contains the GUID and name of the project so that we can easily retrieve the GUID later on to get the list of resources. It is important to note here, that we are working with datasets. The majority of our Web Services have datasets that can be manipulated and sent back to the server to update the data.

    The second method is the select index changed for the drop down list of project names:



    private void cboProjects_SelectedIndexChanged(object sender, EventArgs e)




    WSProject.ProjectTeamDataSet pds;


    ProjListItem projItem = (ProjListItem)cboProjects.SelectedItem;


    pds = projWS.ReadProjectTeam(projItem.getGuid());

    DataTable dt = pds.Tables["ProjectTeam"];


    foreach (DataRow dr in dt.Rows)







    Here we retrieve the GUID for the selected project from the ProjListItem object that we populated the drop down list in the first method and we get the resources on the team by calling ReadProjectTeam method and passing the selected project GUID.  ReadProjectTeam returns a dataset that contains a data table “ProjectTeam” that lists all the resources that are team members on the project.

    So, now we have a little application that is able to connect to the server and retrieve data,

    Chris Boyd

  • Project Programmability and Business Intelligence

    Welcome to the Project Programmability blog!


    As we are on the verge of releasing Microsoft Office Project 2007 we want to help our customer and partner community by providing a forum for learning how to develop applications that communicate with both Project Server and Project Client.

    There will be three of us manning this blog, Patrick Conlan, Phil Smail and myself Chris Boyd. We are all Program Managers on the Project Team. The plan is to post samples that are requested and cool things we want to try out. So if you need some sample code or explanations on how to develop with the upcoming release of Project, post a comment!

    To get started, here are some helpful links:

    Chris Boyd
    Program Manager


    This posting is provided "AS IS" with no warranties, and confers no rights.


Page 11 of 11 (255 items) «7891011