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.
It seems that somehow I messed up and the post "Scalable Issues & Risks Report" ended up showing up in the past. So in case you missed it:
http://blogs.msdn.com/project_programmability/archive/2007/09/28/scalable-issues-risks-report.aspx
Chris
Microsoft Office Enterprise Project Management University (EPMU) is offering a range of new courses in Project Server and Project Portfolio Sever 2007 including Developer Training.
Office Project Server 2007 Developer Training
For full details of all courses visit www.msepmu.com
Back on May 17th I posted code that showed how to update the workspace data with the RDB:
http://blogs.msdn.com/project_programmability/archive/2007/05/17/syncing-project-workspaces-with-the-rdb.aspx
Here is a similar program to synchronize project workspace membership. The interesting twist is that I show how to be kinder to the Project Queue by adding a few jobs at a time and waiting for them to complete.
Here is the code:
using System;using System.Collections.Generic;using System.Text;using System.Net;using System.Data;using System.Web.Services.Protocols;using System.Diagnostics;using PSLibrary = Microsoft.Office.Project.Server.Library;namespace WorkspaceUpdate{ class Program { static void Main(string[] args) { int count = 0; bool verbose = false; // Verbose Output switch Guid job = Guid.Empty; // The latest job submitted to the queue. int timeOut = 60; // Default timeout before terminating the queue job string ls_projURL = ""; const string PROJECT_SERVICE_PATH = "_vti_bin/psi/Project.asmx"; const string WSSINTEROP_SERVICE_PATH = "_vti_bin/PSI/WSSInterop.asmx"; const string WSQUEUESYSTEM_SERVICE_PATH = "_vti_bin/PSI/QueueSystem.asmx"; if (args.Length == 0 || args.Length > 3) { Message(); } else if (args[0] == "/?") { Message(); } else { ls_projURL = args[0]; if (args.Length > 2 && args[2].ToLower() == "verbose") { verbose = true; } try { timeOut = Convert.ToInt32(args[1]); } catch { Event("Warning: Invalid timeout, defaulting to 60 seconds.", EventLogEntryType.Warning, verbose); } WSProject.Project ws_Project = new WSProject.Project(); WSSInterop.WssInterop ws_WssInterop = new WSSInterop.WssInterop(); WSQueueSystem.QueueSystem qsWS = new WSQueueSystem.QueueSystem(); if (!ls_projURL.EndsWith("/")) { ls_projURL += "/"; } try { ws_Project.Url = ls_projURL + PROJECT_SERVICE_PATH; ws_Project.Credentials = CredentialCache.DefaultCredentials; ws_WssInterop.Url = ls_projURL + WSSINTEROP_SERVICE_PATH; ws_WssInterop.Credentials = CredentialCache.DefaultCredentials; qsWS.Url = ls_projURL + WSQUEUESYSTEM_SERVICE_PATH; qsWS.Credentials = CredentialCache.DefaultCredentials; Guid lo_projGUID; string ls_projName; WSProject.ProjectDataSet lo_projs = null; WSProject.ProjectDataSet lo_projDS; try { lo_projs = ws_Project.ReadProjectList(); DataRowCollection lo_projects = lo_projs.Tables[lo_projs.Project.TableName].Rows; for (int i = 0; i < lo_projects.Count; i++) { lo_projGUID = new Guid(lo_projects[i][0].ToString()); ls_projName = lo_projects[i][1].ToString(); try { lo_projDS = ws_Project.ReadProjectEntities(lo_projGUID, 1, WSProject.DataStoreEnum.PublishedStore); // Check if the Project has a Workspace if (lo_projDS.Tables[lo_projDS.Project.TableName].Rows[0][lo_projDS.Project.WSTS_SERVER_UIDColumn.ColumnName] != null && lo_projDS.Tables[lo_projDS.Project.TableName].Rows[0][lo_projDS.Project.WSTS_SERVER_UIDColumn.ColumnName].ToString() != "") { Message("Synchronizing Workspace for Project" + ls_projName, verbose); //Wait to let the server process the work if (count % 4 == 3) { if (WaitForQueue(timeOut, job, qsWS) == false) { Event("Warning: Queue Job not Processed for Project:" + ls_projName + " Check Project Server Queue.", EventLogEntryType.Warning, verbose); } } job = Guid.NewGuid(); ws_WssInterop.QueueSynchronizeMembershipForWssSite(lo_projGUID, job); count++; } else { Message("Notice: Project" + ls_projName + " does not have a workspace.", verbose); } } catch (SoapException lo_ex) { PSLibrary.PSClientError psiError = new PSLibrary.PSClientError(lo_ex); PSLibrary.PSErrorInfo[] psiErrors = psiError.GetAllErrors(); if (psiErrors.Length == 1) { if (psiErrors[0].ToString() == "ProjectNotFound") { Message("Notice: Project" + ls_projName + " is not published.", verbose); } } } } Event("Successfully Synchronized Membership for Workspaces", EventLogEntryType.Information, verbose); } catch (WebException lo_ex) { Event("Error:" + lo_ex.Message, EventLogEntryType.Error, verbose); } catch (Exception lo_ex) { Event("Unknown Error:" + lo_ex.Message, EventLogEntryType.Error, verbose); } } catch (UriFormatException lo_ex) { Event("Unknown Error:" + lo_ex.Message, EventLogEntryType.Error, verbose); ; } } } private static bool WaitForQueue(int timeOut, Guid jobId, WSQueueSystem.QueueSystem qsWS) { int sleep = 3; int timeSlept = 0; // Total time slept (seconds) bool jobSuccess = false; string xmlError; WSQueueSystem.JobState jobState; // Status of the queue job timeOut = timeOut * 1000; while (true) { jobState = qsWS.GetJobCompletionState(jobId, out xmlError); if (jobState == WSQueueSystem.JobState.Success) { jobSuccess = true; break; } else if (jobState == WSQueueSystem.JobState.Unknown || jobState == WSQueueSystem.JobState.Failed || jobState == WSQueueSystem.JobState.FailedNotBlocking || jobState == WSQueueSystem.JobState.CorrelationBlocked || jobState == WSQueueSystem.JobState.Canceled) { jobSuccess = false; break; } else if (timeSlept > timeOut) { jobSuccess = true; //qsWS.CancelJobSimple(jobId); break; } System.Threading.Thread.Sleep(sleep * 1000); timeSlept = +sleep * 1000; } return jobSuccess; } static private void Message() { System.Console.WriteLine(""); System.Console.WriteLine("WorkspaceUpdate url timeout [verbose]"); System.Console.WriteLine(" url - The URL to the project server."); System.Console.WriteLine(" timeout - Seconds to wait for the queue job to process the User Sync"); System.Console.WriteLine(" verbose - An optional parameter that outputs progress."); } static private string Message(string as_msg, bool verbose) { as_msg = DateTime.Now.ToString() + ":" + as_msg; if (verbose) System.Console.WriteLine(as_msg); return as_msg; } static private void Event(string as_msg, EventLogEntryType eventType, bool verbose) { EventLog lo_eventLog = new EventLog(); lo_eventLog.Source = "WorkspaceUpdate Sync Job"; as_msg = Message(as_msg, verbose); lo_eventLog.WriteEntry(as_msg, eventType, 3652); } }}
Another Visual How-to for Project development is published on MSDN: Writing and Debugging Event Handlers for Project Server 2007
In less than 12 minutes, the video shows the development, testing, and debugging of a simple event handler for publishing projects. The OnPublishing event handler cancels publishing if the project name does not satisfy a specified condition, and then logs an application event on the server. The video shows the use of Visual Basic code; the related article includes both Visual Basic and C# code.
The Visual How-to is based on the Project SDK article, How to: Write and Debug a Project Server Event Handler.