TechEd Preview – Part 3 – Project Server 2007 Provisioning Project Specific Workspaces automatically

First day of TechEd today and despite the 3hr time difference from Seattle I still woke up early.  This posting will be part of the subject of the chalk talk I am delivering with Boris Scholl tomorrow (6/4) morning.  If any of you remember the Solution Accelerator for Six Sigma this is part inspired by some custom code that was in that solution – and is designed to allow a Project Manager to select a workspace type from a list in a custom project field – and then when she publishes that type of site will be provisioned from a pre-built template.

The first part of this is to create some templates for new workspaces.  This is covered in the SDK, but briefly you must base any new template on an unlinked site, then you save as a template, then from the template gallery save to a file (*.stp) and then you use stsadm –o addtemplate to bring the template back in so it can be used for provisioning (and a final IIS reset).  You will also need to use the command stsadm –o enumtemplates to get the internal name (something like _GLOBAL_#2) for each of your templates for later use.

The next step is creating a lookup table to hold the template details – and it should look something like this:-



I am using the description field to hold the internal name of the template as that is the one used when requesting a site to be provisioned.  You could resolve this in code somewhere – it just seemed easier to me to put it here. You also need to create a Project Level custom field that references this lookup table – and in my example I use the “NotYet” value to mean don’t create a site for me.  The names of the field/table aren’t important – but you will need to know the Guid associated with them.  Also you will need to turn off automatic site creation – as we will be controlling this via an event handler.  I am using Red/Green/Blue for the visual recognition for the demo – obviously ISO 9001, Six Sigma, Administration m- would be more useful types of templates to use.

Now the event handler.  I’ve thrown a few comments in and also it logs to the application event log the different actions it can take.  It could certainly do with more (read some) exception handling… but it does the job.  Based on the custom field requested it will get the template name from the lookup table description.  If a site already exists it will stop – and if the requested site is already in use it will stop.

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

namespace TechEdEventHandler
    public class MyPublishedEventHandler : ProjectEventReceiver
        private const string URLPREFIX = "http://";
        private const string LOGINWINDOWSWEBSERVICE = "_vti_bin/PSI/LoginWindows.asmx";
        private const string PROJECTWEBSERVICE = "_vti_bin/PSI/Project.asmx";
        private const string WSSINTEROPWEBSERVICE = "_vti_bin/PSI/WSSInterop.asmx";
        private const string LOOKUPTABLEWEBSERVICE = "_vti_bin/PSI/LookupTable.asmx";
        private string baseUrl = "http://brismithwfe/cal/";
        private const int EVENTID = 9191;
        private string webTemplateName;
        private Guid projectGuid;
        private Guid codeValue;
        // We need the Guids for the lookup table and custom field - better practice would be to use a settings file
        private Guid workspaceTemplateLUTGuid = new Guid("b0fc9d01-b716-43d3-aa74-335a3297214b");
        private Guid workspaceTemplateCFGuid = new Guid("98c9444e-1fa2-4f12-ab2b-4cb7e1818738");

        private static WebSvcLoginWindows.LoginWindows loginWindows =
            new WebSvcLoginWindows.LoginWindows();
        private static WebSvcProject.Project project =
            new WebSvcProject.Project();
        private static WebSvcWssInterop.WssInterop wssInterop =
            new WebSvcWssInterop.WssInterop();
        private static WebSvcLookupTable.LookupTable lookupTable =
            new WebSvcLookupTable.LookupTable();

        public override void OnPublished(Microsoft.Office.Project.Server.Library.PSContextInfo contextInfo, ProjectPostPublishEventArgs e)
            string eventName = "Auto Workspace Creation - OnPublished";
            loginWindows.Url = baseUrl + LOGINWINDOWSWEBSERVICE;
            loginWindows.Credentials = CredentialCache.DefaultCredentials;
            project.Url = baseUrl + PROJECTWEBSERVICE;
            project.Credentials = CredentialCache.DefaultCredentials;
            lookupTable.Url = baseUrl + LOOKUPTABLEWEBSERVICE;
            lookupTable.Credentials = CredentialCache.DefaultCredentials;
            wssInterop.Url = baseUrl + WSSINTEROPWEBSERVICE;
            wssInterop.Credentials = CredentialCache.DefaultCredentials;

            projectGuid = e.ProjectGuid;

            WebSvcWssInterop.WssServersDataSet dsWssServersDataSet = 
                new WebSvcWssInterop.WssServersDataSet();

            WebSvcWssInterop.WssSettingsDataSet dsWssSettingsDataSet =
                new WebSvcWssInterop.WssSettingsDataSet();

            WebSvcWssInterop.ProjectWSSInfoDataSet dsProjectWSSInfoDataSet =
                new WebSvcWssInterop.ProjectWSSInfoDataSet();

            // If rowcount is greater than zero then a site already exists for the project - so exit
            dsProjectWSSInfoDataSet = wssInterop.ReadWssData(projectGuid);
            int rowCount = dsProjectWSSInfoDataSet.ProjWssInfo.Rows.Count;
            if (rowCount == 0)

                WebSvcProject.ProjectDataSet dsProject =
                    new WebSvcProject.ProjectDataSet();
                // We are getting the project dataset - but only for the entity type 32 - custom fields
                dsProject = project.ReadProjectEntities(projectGuid, PROJECT_CUSTOM_FIELDS_ENTITY_TYPE, WebSvcProject.DataStoreEnum.WorkingStore);
                // The we look for the workspace template field - and get the code value
                foreach (WebSvcProject.ProjectDataSet.ProjectCustomFieldsRow rowProjectCF in dsProject.ProjectCustomFields)
                    if (rowProjectCF.MD_PROP_UID == workspaceTemplateCFGuid)
                        codeValue = rowProjectCF.CODE_VALUE;
                // Using the Guid for the lookup table we search for the code value 
                Guid[] ltUidList = new Guid[] { workspaceTemplateLUTGuid };
                WebSvcLookupTable.LookupTableDataSet dsLookupTable =
                    new WebSvcLookupTable.LookupTableDataSet();
                dsLookupTable = lookupTable.ReadLookupTablesByUids(ltUidList, false, 1033);
                // the code value gets us to the description which we use as the webtemplatename
                foreach (WebSvcLookupTable.LookupTableDataSet.LookupTableTreesRow rowLookupTable in dsLookupTable.LookupTableTrees)
                    if (rowLookupTable.LT_STRUCT_UID == codeValue)
                        webTemplateName = rowLookupTable.LT_VALUE_DESC;
                dsWssServersDataSet = wssInterop.ReadWssServerInfo();
                dsWssSettingsDataSet = wssInterop.ReadWssSettings();

                Guid wssServerUid = dsWssSettingsDataSet.WssAdmin[0].WADMIN_CURRENT_STS_SERVER_UID;
                string wssWebFullUrl = dsWssServersDataSet.WssServers.FindByWSTS_SERVER_UID(wssServerUid).WSS_SERVER_URL
                    + "/" + dsWssSettingsDataSet.WssAdmin[0].WADMIN_DEFAULT_SITE_COLLECTION
                    + "/" + e.ProjectName.ToString();

                int webTemplateLcid = 1033;
                // If site exists then we cannot create it for this project...
                bool SiteExists = wssInterop.WSSWebExists(wssWebFullUrl);
                if (!SiteExists)
                    //NotYet means we will create it later
                    if (webTemplateName != "NotYet")
                        // This is where we create a site if we need one
                        wssInterop.CreateWssSite(projectGuid, wssServerUid, wssWebFullUrl, webTemplateLcid, webTemplateName);
                        string msg = string.Format("{0}: Site {1} created for Project {2}", eventName, wssWebFullUrl, e.ProjectName);
                        WriteEvent(msg, EventLogEntryType.SuccessAudit , EVENTID);

                        string msg = string.Format("{0}: No site required at this time for project {1}", eventName, e.ProjectName);
                        WriteEvent(msg, EventLogEntryType.Information, EVENTID);

                    string msg = string.Format("{0}: Site {1) already exists", eventName, wssWebFullUrl);
                    WriteEvent(msg, EventLogEntryType.Error, EVENTID);

                string msg = string.Format("{0}: Workspace already created", eventName);
                WriteEvent(msg, EventLogEntryType.Information, EVENTID);
    private void WriteEvent(string msg, EventLogEntryType logEntryType, int eventId)
            EventLog myLog = new EventLog();
            myLog.Source = "Project Event Handler";

            string message = msg;
            myLog.WriteEntry(msg, logEntryType, eventId);


Comments (10)

  1. Ray Letts says:

    Good post Brian. A related question if I may…

    How do you know when the site creation has been done?  I believe the job is placed in a queue, but unlike the create and publish project calls we don’t have a jobID with which to check the queue and see when it is done.

    The reason I ask is that I am getting the error "401 unauthorized. Logon or check the project queuing service" when I try to use SPNavigation to add a link to the newly created document library.

    If I step through the code or sleep for some time the call to SPNav (a web svc I created) works most of the time but not always.

    Any other work arounds such as to have a loop checking for the existence of a document library to know when createwsssite is done?

    Just trying to brainstorm some I D ‘ers..

    thank you


  2. Great question Ray.  We have asked the product group for some guidance on this but had nothing back yet.  Possibly tracking the correaltion ID of the queue jobs will work, as all the jobs relating to the initial publish which then leads to the site creation should share the same correlation ID.  If this doesn’t work for you (as there may still be a timing issue) then looking for a library may be the best bet.

    Best regards,


  3. Alok Pagaria says:


    Did you got any workaround for this? (How do you know when the site creation has been done?)

  4. Filipe says:

    Hi Brian,

    Im trying to override the onpublished method but im getting this message:

    Error 1 ‘ClassLibrary1.Class1.OnPublished(Microsoft.Office.Project.Server.Library.PSContextInfo, Microsoft.Office.Project.Server.Events.ProjectPostPublishEventArgs)’: no suitable method found to override

    Can you help me?

    Thank you!


  5. No workaround yet Alok.  Sorry.

  6. Hi Filipe,

    I’d suggest following the example from the SDK – that should guide you through event handle creation –

    Best regards,


  7. Don Landry says:

    Hi Brian,

    Is there a webcast on this?  Since I'm new to C# I'm not sure what type of Visual Studio project this should be set too and I since I haven't done a deployment I'm not sure where the app gets deployed to.  I'm going through the SDK now so sorry for the basic questions.  


  8. Hi Don,  The best place to start would be the SDK, and also a step by step video at…/bb802729(office.12).aspx. The video isn't exactly the use case from my blog, but should get you in the right direction. The SDK can be found at…/bb290998(office.12).aspx.  This is for 2007 and ypou should be able to find the 2010 vbersion there too if you need that.

    Best regards,


  9. Bob P says:

    Brian – I'm using this in 2010 and it works as described. I know this is a very old post but I have one question. As there is no ootb way in from Project Pro 2010 to control which template is selected (Or which Ent Project Type to set) we are using this approach. But when the User selects to Publish in Pro, they can alter the name of the site to be created and they can select the site to be provisioned under another site in the Publish Project window. These selections are not picked up by the code and the site is always created under the default PWA site using the project name. Is there any way to capture the alteration to use in the code when creating the site?


  10. Hi Bob, in 2010, with the ability to set a site template for a specific EPT then you get this behavior for free when creating plans in PWA>  For Project Pro however, you will always get the site type for the default EPT.  Not sure you can get hold of the changes the user makes in code like above – but I did wonder if you could use similar code to swap the default EPT (i.e. make the one you want the current default, then swap it back later) – which might get you what you are looking for.  Let me know how it goes.

    Best regards,


Skip to main content