Welcome to MSDN Blogs Sign in | Join | Help

Brian Smith's Project Support WebLog

What's new and upcoming in the world of Microsoft Office Project Support (PSS).
How to ensure your accepted updates get published in Project Server 2007

In Project Server 2003 updates needed to be made with Project Professional , and then when Project was closed an auto publish would happen – if you wanted it to or not.  Some people wanted it, some didn’t.  In 2007 as we don’t need to use Project Professional and this can happen purely on the server then we leave the choice of when to publish up to you. 

One way you can simulate the 2003 behavior is to use a server-side event handler to act on the Statusing.OnApplied method, and carry out a publish of the project via the PSI.  This would then ensure that all accepted updates were reflected in PWA.  The following code samples is based on the SDK TestEventHandler, and apart from the code, would also require web references to the Project and LoginWindows web services.  I am also not using impersonation, so my SSP Administrator (the account that would be running the Event services) does need to be a user in PWA with the right permissions to publish the projects.  You could use the same techniques as I recently published for impersonation if you did not want to give any PWA permissions to this account.  If multiple publish events hit the queue at one time then most will get skipped for optimization – but if you are processing a great deal of updates you should consider the load this extra publish work will put on your server.  I use the “post” rather than the “pre” event so that even if the event handler fails it will not block or cancel the event.  Obviously my writing to the event log is optional too – just helped me to see it was working.

As usual with MSDN blog postings the code is supplied “as-is”, with no warranties or support and could probably do with some better exception handling – but hopefully for any customers wanting to get auto publishing this will be a help.  You will need to replace servername and pwa with your own servername and pwa instance names.

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

namespace TestEventHandler
{
    public class MyPublishingEventHandler : StatusingEventReceiver 
    {
        const string LOGINWINDOWS = "_vti_bin/PSI/LoginWindows.asmx";
        const string PROJECT = "_vti_bin/PSI/Project.asmx";
        private static WebSvcLoginWindows.LoginWindows 
            loginWindows = new 
                WebSvcLoginWindows.LoginWindows();
        private static WebSvcProject.Project project =
            new WebSvcProject.Project();
        private string baseUrl = "http://servername/pwa/";
 
        public override void  OnApplied(PSContextInfo contextInfo, 
            StatusingPostApplyEventArgs e)

        {
             base.OnApplied(contextInfo, e);
            
            loginWindows.Url = baseUrl + LOGINWINDOWS;
            loginWindows.Credentials = 
                CredentialCache.DefaultCredentials;
            project.Url = baseUrl + PROJECT;
            project.Credentials = 
                CredentialCache.DefaultCredentials;

            // I don't do a full publish 
            // You could change the third parameter to true if you wanted to
            Guid jobUid = Guid.NewGuid();
            project.QueuePublish(jobUid, e.ProjectID, false, "");

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

            // Get information from the event arguments, and 
            // write an entry to the Application event log. 
            string userName = contextInfo.UserName.ToString();
            string projectGuid = e.ProjectID.ToString();
            int eventId = 3945;
            string logEntry;

            logEntry = "User: " + userName + "\nProject UID: "
                + projectGuid + " has been queued for publish.";
            myLog.WriteEntry(logEntry, 
                EventLogEntryType.Information, eventId);

            }
        
    }
}

The SDK description of the Event Handlers, and links to the SDK samples can be found here.

New Project Server content posted over the last couple of months

Thanks to our great UA team we have had a lot of new content posted over the last couple of months, including some great videos “how-to’s”, and some articles describing the Project Resource Kits tools.  Grouped into the different content types they are:

Office Online content:

The PWA Role Guides

These role guides present broad overviews of Office Project Web Access functionality from the perspective of the different roles your organization might have.

Available fields

This article lists all the fields available to users of Microsoft Office Project 2007.

Set working times, vacations, and holidays for your project

Creating resource and task calendars article now includes two videos to help with project scheduling.

Project Demo videos:

Watch this: Use lag and lead time

This demo shows how to use lag and lead time to create gaps and overlaps between tasks in a project.

Watch this: Create a project

This demo shows how to create a project, set project properties, and set file properties.

Watch this: Set up a recurring task

This demo shows how to create a task that repeats on a set schedule throughout a project.

Watch this: Split a task

This demo shows how to interrupt a task, creating a gap between two portions of the task. It also shows how to move the entire split task, adjust the length of the gap created by the split, and rejoin the split portions of the task to remove the gap.

Watch this: Insert a task

This demo shows how to insert a new task between two existing tasks in a project.

Watch this: Group tasks or resources

This demo shows how to group tasks, remove the grouping, and create a new resource group using multiple criteria.

Watch this: Create a cross-project link

This demo shows how to create task dependencies across separate Project 2007 files.

Watch this: Link tasks in your project

This demo shows how to create task dependencies within a single project, and how to adjust the link type for the dependency.

TechNet content

Install Project Server 2007 in Windows Server 2008 (single-server installation)

This article discusses the requirements and steps for installing Microsoft Office Project Server 2007 in a stand-alone Windows Server 2008 environment.

Manage Active Directory synchronization in Project Server 2007

These articles describe how to configure and manage Active Directory synchronization of the Enterprise Resource Pool and Project Server security groups in Office Project Server 2007.

Back up Project Server 2007 by using SQL Server tools

Use this procedure to back up the databases associated with Office Project Server 2007.

Migrate Project Server 2007 by using SQL Server tools

Use this procedure to migrate the databases associated with Office Project Server 2007 from one computer to another.

Restore Project Server 2007 by using SQL Server tools

Use this procedure to restore the databases associated with Office Project Server 2007.

Back up Project Server 2007 by using the Stsadm command-line tool

Use these procedures to back up a server farm, Web application, database, site collection, site, or subsite by using the Stsadm command-line tool.

Restore Project Server 2007 by using the Stsadm command-line tool

Use these procedures to restore a server farm, Web application, database, site collection, site, or subsite by using the Stsadm command-line tool.

Portfolio Analyzer Views Migration tool

This article describes how to use the Portfolio Analyzer Views Migration tool. This tool allows Project Server administrators to bulk edit the Microsoft SQL Server Analysis Services settings in Data Analysis views.

Project Server Data Populator tool

This article describes how to use the Project Server Data Populator tool. This tool allows Project Server administrators to generate custom field, resource, project, task, and assignment data in bulk using the Project Server interface. Customers planning new or expanding existing Enterprise Project Management (EPM) implementations can use this tool to validate performance and storage-related metrics and determine hardware/architectural requirements.

Project Workspace Site Relinker tool

This article describes how to use the Project Workspace Site Relinker tool. This tool can be used to relink Project Workspace Sites that have become disconnected from Project Server 2007. These sites can become disconnected when a Project Server database is restored.

Server Settings Backup/Restore tool

This article describes how to use the Server Settings Backup/Restore tool. This tool allows Project Server administrators to back up server settings from a selected Project Server instance to an XML file. The tool can be run against another Project Server instance to which you can restore the server settings.

View Effective Rights tool

This article describes how to use the View Effective Rights tool. This tool can be used by Project Server administrators to troubleshoot issues regarding security settings and access control.

Technorati Tags: ,
Sorry – another non-Project post – but take a look at Popfly Game Creator!

Going Live in Alpha today is the new Game Creator stuff on Popfly.  Seriously cool!   There is a Video demo up at Soapbox too.

More information follows…

The Popfly family just got a little bigger

Popfly Game Creator | May 2nd 2008 | 12:01PM | 7lbs, 4oz | 20 inches | Proud Parents- The Popfly Team

Starting today, game creation is now officially accessible to everyone.  You don’t have to be a physics major or hard-core coder to create your own personalized game using the Popfly Game Creator.  In fact, you can create your first game in three easy steps through Popfly’s intuitive interface.

1. Select your actors:  Choose from nearly 400 pre-built actors in categories like people, vehicles, power-ups, and more.  Can’t find what you’re looking for? Import and create your own.

clip_image001

2. Set the scene:  Pick a background, select some sounds, and position your actors through a visual designer.

clip_image002

3. Bring your game to life:  This is where the magic happens.  Using Popfly, you can specify behaviors on your actors and scenes that define how they should interact. Use Popfly’s intuitive user interface to define events, set properties, and specify actions without writing a single line of code!  If you want more control, define a custom behavior in JavaScript.

clip_image003

Creating Games has never been easier.

To learn more and build your first game head over to www.popfly.com

Project Server 2003 and Multilingual User Interface Packs

A rare Project Server 2003 posting from me to clarify some issue we see with customers applying the MUIs to Project Server 2003.

Firstly if you are interested in using MUIs then 2007 language packs address some of the issues that 2003 suffers from.  2007 is unicode and you can mix and match languages without the restrictions in 2003.  But if you must still use 2003…

Unlike the language packs for Project Server 2007 the MUI itself does not contain a Project Workspace template for the MUI language.  In KB articles we do say how this can be added by running WSSWIZ for the appropriate language – but then don’t give any details of where you might find this!  Add to this the fact that for any language the file has the same name it is not surprising customers get confused!

The WSSWIZ you need to run can be found on the native language CD of Project Server 2003 for the language you want to add.  The next problem is therefore that you are unlikely to have this CD.  For instance, if you are loading French MUI on the US English version you will have the US CD and the downloaded MUI – but you will not have the French CD!  I will let someone else jump in if the following appears to be a licensing issue – but from reading the public material my take on this is we intend people with MUI to also be able to use foreign language workspaces – but didn’t think through distribution of the WSSWIZ.  If you have an account manager then I would liaise with them to obtain the right media.  You could also find this on MSDN media – and I am not talking about using the full install – but just using the WSSWIZ stuff from the \support\wsswiz directory.  You may also find this on your volume licensing media – but I have seen one issue with the French CD where onet.xml file was empty – so if you use MSVL and don’t appear to get the French template then check if you are hitting this issue.

On a very related issue another file used during MUI installation is INSDEFLP.sql which adds the language strings to the database.  Since SP2a we do actually install this file correctly – but you still need to be sure you are running the version for the right language.

From  http://support.microsoft.com/kb/906429/ where this issue is fixed… 

You cannot update databases when you install Microsoft Multilingual User Interface Pack (MUI) on Project Server 2003
When you install Microsoft Multilingual User Interface Pack (MUI) on Project Server 2003, you receive a warning message that states that only the default database will be updated if the server contains more than one database. The message also states that the Insdeflp.sql file will have to be run against the other databases. However, the message does not indicate the location of the Insdeflp.sql file. The Insdeflp.sql file is never installed to the system. Therefore, you cannot run the script against the other Project Server databases.

You need to look in the directory with the appropriate LCID – such as C:\Program Files\Microsoft Office Project Server 2003\SCRIPTS.SQL\1036 for French.  Also remember that MUI has service packs to match the base product service packs.

Technorati Tags: ,
Which master pages can you edit in Project Server 2007?

A recent support incident showed that some of our documentation didn’t make it clear just what you can and can’t do with master pages.  There are two different types of sites in Project – PWA (the Project Web Access sites) and PWS (the project workspace sites).  For the PWA sites you are limited to making use of the default.master we already supply to give any extra pages the same look and feel, but you are not able to edit these default.master pages.  We also block SharePoint Designer from opening these sites – so you cannot use this tool against a PWA site.  You can however use SharePoint Designer against the PWA workspace sites – and edit the default.master.

The reason for this difference is that it would be easy to break our application by editing our pages – and this would not make life good for a support engineer (and indeed for the customers who manage to break things!).  Also remember that even though the workspaces can be modified you shouldn’t add extra instances lists of the specific project type lists of issues, risks, dependencies and documents.  Details from these default lists feed through to the reporting database – if there are duplicates then this will break the workspace reporting feature.

This topic is covered at http://msdn.microsoft.com/en-us/library/ms504195.aspx and this highlights another great way you can add to the sum of human knowledge on MSDN.  BruceVB added some community content based on his experience – and our own Jim Corbin has added a further comment.  Although I wouldn’t necessarily agree that our original document is incorrect – it is certainly not complete in telling you what you can and cannot do.  But even being able to use our default.master on new pages does make it easier to build custom Project Web Access solutions – even if you cannot edit the default.master.

If you do try and update the PWA default.master through Site Actions, Site Settings then you may see the error message "The enablesessionstate attribute on the page directive is not allowed in this page." 

You would need to reset to the site definition to get the site working again. 

Please do use the community content features on MSDN – it certainly does help us to understand how we can improve our content – and thanks again BruceVB for bringing this particular problem to our attention.

100 Posts Today!

According to my blog dashboard this is my 100th post!  So to celebrate I thought I would be totally self-indulgent (so nothing new there) and ignore Project and talk about something else.  It has been fun keeping the blog going and also great to have communication with so many of you – either through the blog (600+ comments, and rising) or on support cases or at the Project conference.  Keep the comments coming – and the answers too, as I will sometimes post questions I don’t have the answers for.

If you didn’t already know I am from the UK, but moved to the Seattle area about 5 1/2 years ago and live on the Snoqualmie Valley.  My journey to and from the office gives me the chance to see some of the great beauty that the Pacific North West has to offer – and even this time of year when it has been known to rain we still get great mountain views at some point on most days.  One of my other interests is photography – and in fact my first career was in the research labs formulating developers and fixers when photography was still “wet”.  I knew that wouldn’t last – so on to computers and digital!  I was recently very fortunate to visit Hawaii for the first time – so took lots of photos – but it is testament to the beauty of the local countryside that for my desktop wallpaper I have chosen recent local pictures I have taken over the Hawaiian paradise.

Mountains_24

This first one is looking East from my house just before sunset.  The second one was last Friday evening – when a tree that will regularly have Bald Eagles perched in it had 7!  So back out with my camera to get some pictures – including this one of an immature bald eagle in flight.

Eagles_11Crop

I know we have other keen and very capable photographers in the Project community and Jack Dahlgren has posted some spectacular ones too http://zo-d.com/blog/archives/architecture/bay-bridge-at-sunrise.html.

So posting 101 will be back on topic, but I might just post the occasional photo too…

Impersonation Within a Project Server 2007 Server-Side Event Handler

One topic that our customers and partners seem to be having a hard time with is impersonation.  One reason is that the term we are using – impersonation – may already mean something to developers, and so the specific steps required to get impersonation working for Project Server 2007 and the PSI aren’t taken into consideration.  The SDK covers this in a couple of topics, but to show a couple more examples I’ve prepared a couple of event handlers that show what happens when you use and don’t use impersonation.

What does impersonation achieve with event handlers?  It allows a an event handler to take action as a particular user – and this is useful for a few different reasons.

1.  Whatever transaction that is carried out will be running as a chosen user – so it will look as though this user made any changes – and this will be reflected in any user accounts associated with the transaction.

2.  The account running the event service (the SSP administrator) does not need to be a user in PWA and/or have any specific permissions.

3.  For certain web services, particularly statusing, the call returns data based on the user making the request – so you need to pretend to be the user in question – rather than the SSP Admin.

If you don’t impersonate and the SSP Admin is not a user in PWA you will get errors like The request failed with HTTP status 401: Unauthorized.  If this exception is not handled it will also mean the event handler crashes and if it is a “pre” event it will be canceled. Depending on where the event comes from the user may or may not get any notification that the cancellation occurred – so you need to code for this .  If they are a user, then they will need the right permissions for whichever web service they are calling – and if they call a statusing web service such as ReadStatus then the result will be the tasks for the SSP Admin – and not for the user who triggered the request.

If you do impersonate then you don’t need to be a user in PWA, and a request to ReadStatus gets the tasks for the user who triggered the request.

My code sample below shows the same code both with and without impersonation and catches the timesheet OnCreated event, and also the statusing  OnStatusUpdate.  The event handlers don’t do anything useful – just write to the event log both some data from the event payload and also make a request to a web service and write some details of the response – just to illustrate the differences seen when impersonating.

As usual, my samples don’t include the level of exception handling you should include – and also include some hard coded values that you may wish to either use in application settings or to resolve at run-time using reflection.  Remember that with event handlers, and particularly the “pre” events, you want your code to be very light weight.  Also remember that in “pre” events that although you have access to more of the payload than post events you cannot modify it.

For my sample responding the the timesheet OnCreated  event that does not use impersonation then if the SSP Admin is not a user we get the expected 401 error – whoever creates a timesheet, because the call to the ReadTimesheet fails.  If the SSP Admin is a user but is just a team member then the call to ReadTimesheet will work ONLY when the SSP Admin creates a timesheet – but will fail with a GeneralSecurityAccessDenied error from the event handler – as the SSP Admin cannot read anyone else’s timesheet.  If they are an administrator (or explicitly have the View Resource Timesheet global permission)  then they can successfully call the ReadTimesheet and the event handler will work.  If the same is done using impersonation then it will work regardless of the SSP Admin permissions – and they do not even need to be a user in Project Server.

For the sample responding to the statusing OnStatusUpdate event that does not use impersonation we see slightly different behavior.  We still see failure if the SSP is not a user (401), but if they are a user then the call to ReadStatus will work – but does not return the number of tasks for the user who triggered the event – but always the number of tasks for the SSP Admin (so only correct if the user triggering the event was SSP Admin) and we also see that the name of the SSP Admin is the one returned from this call.

Basically the steps needed for impersonation are that the code is running as the SSP Admin (which the event service always does – so no problem here) and that the call to web services goes to the SSP location of the web service and not the PWA location (see the code below for examples).  This also needs context setting for the resource and site uid – both of which are available from the contextInfo of the event itself.  The final point is that you need to create web services derived from the Project Server web services that has an override of the WebRequest to add a couple of items to the header.  I used classes already included in the ProjTool sample (in the Utils directory) with very minor changes. 

Here is the code - and the only other thing you need to know is that you will need to add web references to the LoginWindows, TimeSheet and Statusing web services - and references to the Microsoft.Office.Project.Schema, Microsoft.Office.Project.Server.Library and Microsoft.Office.Project.Server.Events.Receivers.

I’ve also attached a zip of the two .cs files as I noticed there is some trimming of the longer lines.

First without impersonation…

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

namespace EventHandlerWithNoImpersonation
{
    public class MyEventHandler:TimesheetEventReceiver 
    {
        const string LOGINWINDOWS = "_vti_bin/PSI/LoginWindows.asmx";
        const string TIMESHEET = "_vti_bin/PSI/TimeSheet.asmx";
        private static WebSvcLoginWindows.LoginWindows loginWindows =
            new WebSvcLoginWindows.LoginWindows();
        private static WebSvcTimeSheet.TimeSheet timeSheet =
            new EventHandlerWithNoImpersonation.WebSvcTimeSheet.TimeSheet();
        private string baseUrl = "http://servername/cal/"; 

        public override void OnCreated(PSLibrary.PSContextInfo contextInfo, 
            TimesheetPostEventArgs e)
        {
            base.OnCreated(contextInfo, e);

            loginWindows.Url = baseUrl + LOGINWINDOWS;
            loginWindows.Credentials = CredentialCache.DefaultCredentials;
            timeSheet.Url = baseUrl + TIMESHEET;
            timeSheet.Credentials = CredentialCache.DefaultCredentials;

            WebSvcTimeSheet.TimesheetDataSet dsTimeSheet =
                new EventHandlerWithNoImpersonation.WebSvcTimeSheet.TimesheetDataSet();
            //  As this call is using a specific UID it doesn't require 
            dsTimeSheet = timeSheet.ReadTimesheet(e.TsUID);
            WebSvcTimeSheet.TimesheetDataSet.HeadersRow headersRow = 
                (WebSvcTimeSheet.TimesheetDataSet.HeadersRow)dsTimeSheet.Headers.Rows[0];

            string tsName = headersRow.TS_NAME;

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

            // Get information from the event arguments, and 
            // write an entry to the Application event log. 
            string userName = contextInfo.UserName.ToString();
            string timesheetGuid = e.TsUID.ToString();
            int eventId = 3937;
            string logEntry;

            logEntry = "User: " + userName + "\nTimesheet UID: "
                + timesheetGuid + "\nThis timesheet is called: " + tsName;
            myLog.WriteEntry(logEntry, EventLogEntryType.Information, eventId);


        }
        
    }
    public class MyEventHandler2 : StatusingEventReceiver
    {
        const string LOGINWINDOWS = "_vti_bin/PSI/LoginWindows.asmx";
        const string STATUSING = "_vti_bin/PSI/Statusing.asmx";
        private static WebSvcLoginWindows.LoginWindows loginWindows =
            new WebSvcLoginWindows.LoginWindows();
        private static WebSvcStatusing.Statusing statusing =
            new EventHandlerWithNoImpersonation.WebSvcStatusing.Statusing();
        private string baseUrl = "http://servername/PWA/";

        public override void OnStatusUpdating(Microsoft.Office.Project.Server.Library.PSContextInfo contextInfo, StatusUpdatePreEventArgs e)
        {
            base.OnStatusUpdating(contextInfo, e);

            loginWindows.Url = baseUrl + LOGINWINDOWS;
            loginWindows.Credentials = CredentialCache.DefaultCredentials;
            statusing.Url = baseUrl + STATUSING;
            statusing.Credentials = CredentialCache.DefaultCredentials;

            WebSvcStatusing.StatusingDataSet dsStatusing =
                new EventHandlerWithNoImpersonation.WebSvcStatusing.StatusingDataSet();
            dsStatusing = statusing.ReadStatus(Guid.Empty,DateTime.MinValue,DateTime.MaxValue);
            WebSvcStatusing.StatusingDataSet.ResourcesRow resourceRow =
                (WebSvcStatusing.StatusingDataSet.ResourcesRow)dsStatusing.Resources.Rows[0];


            int taskCount = dsStatusing.Tasks.Count;
            string statusOwner = resourceRow.RES_NAME;

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

            // Get information from the event arguments, and 
            // write an entry to the Application event log. 
            string userName = contextInfo.UserName.ToString();
            string statusingXml = e.ChangeXml;
            int eventId = 3938;
            string logEntry;

            logEntry = "User: " + userName + "\nChangeXML: "
                + statusingXml + "\nThis statusing dataset has " + taskCount
                + " tasks, and they belong to " + statusOwner;
            myLog.WriteEntry(logEntry, EventLogEntryType.Information, eventId);


        }
    }
}
and then with impersonation…
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;
using Microsoft.Office.Project.Server.Events;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace EventHandlerWithImpersonation
{
    public class MyEventHandlerImp :TimesheetEventReceiver 
    {
        const string LOGINWINDOWS = "LoginWindows.asmx";
        const string TIMESHEET = "TimeSheet.asmx";
       
        
        private string baseUrl = "http://servername:56737/SharedServices1/PSI/";

        private static LoginWindowsDerived loginWindows = new LoginWindowsDerived();
        private static TimesheetDerived timeSheet = new TimesheetDerived();
       
        public override void OnCreated(PSLibrary.PSContextInfo contextInfo, TimesheetPostEventArgs e)
        {
            base.OnCreated(contextInfo, e);
            bool isWindowsAccount = contextInfo.IsWindowsUser;
            Guid trackingGuid = Guid.NewGuid();
            string lcid = "1033";
            string userNTAccount = contextInfo.UserName;
            Guid resourceGuid = new Guid(contextInfo.UserGuid.ToByteArray());
            Guid siteId = new Guid(contextInfo.SiteGuid.ToByteArray());

            LoginWindowsDerived.SetImpersonationContext(isWindowsAccount, userNTAccount, resourceGuid, trackingGuid, siteId, lcid);
            TimesheetDerived.SetImpersonationContext(isWindowsAccount, userNTAccount, resourceGuid, trackingGuid, siteId, lcid);

            
            loginWindows.Url = baseUrl + LOGINWINDOWS;
            loginWindows.Credentials = CredentialCache.DefaultCredentials;
            timeSheet.Url = baseUrl + TIMESHEET;
            timeSheet.Credentials = CredentialCache.DefaultCredentials;

            WebSvcTimeSheet.TimesheetDataSet dsTimeSheet =
                new EventHandlerWithImpersonation.WebSvcTimeSheet.TimesheetDataSet();
            dsTimeSheet = timeSheet.ReadTimesheet(e.TsUID);
            WebSvcTimeSheet.TimesheetDataSet.HeadersRow headersRow = 
                (WebSvcTimeSheet.TimesheetDataSet.HeadersRow)dsTimeSheet.Headers.Rows[0];

            string tsName = headersRow.TS_NAME;

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

            // Get information from the event arguments, and 
            // write an entry to the Application event log. 
            string userName = contextInfo.UserName.ToString();
            string timesheetGuid = e.TsUID.ToString();
            int eventId = 3939;
            string logEntry;

            logEntry = "User: " + userName + "\nTimesheet UID: "
                + timesheetGuid + "\nThis timesheet is called: " + tsName;
            myLog.WriteEntry(logEntry, EventLogEntryType.Information, eventId);


        }
        

        
    }
    public class MyEventHandlerImp2 :StatusingEventReceiver
    {
        const string LOGINWINDOWS = "LoginWindows.asmx";
        const string STATUSING = "Statusing.asmx";

        private string baseUrl = "http://servername:56737/SharedServices1/PSI/";

        private static LoginWindowsDerived loginWindows = new LoginWindowsDerived();
        private static StatusingDerived statusing = new StatusingDerived();

        public override void OnStatusUpdating(PSLibrary.PSContextInfo contextInfo, StatusUpdatePreEventArgs e)
        {
            base.OnStatusUpdating(contextInfo, e);

            bool isWindowsAccount = contextInfo.IsWindowsUser;
            Guid trackingGuid = Guid.NewGuid();
            string lcid = "1033";
            string userNTAccount = contextInfo.UserName;
            Guid resourceGuid = new Guid(contextInfo.UserGuid.ToByteArray());
            Guid siteId = new Guid(contextInfo.SiteGuid.ToByteArray());

            LoginWindowsDerived.SetImpersonationContext(isWindowsAccount, userNTAccount, resourceGuid, trackingGuid, siteId, lcid);
            StatusingDerived.SetImpersonationContext(isWindowsAccount, userNTAccount, resourceGuid, trackingGuid, siteId, lcid);


            loginWindows.Url = baseUrl + LOGINWINDOWS;
            loginWindows.Credentials = CredentialCache.DefaultCredentials;
            statusing.Url = baseUrl + STATUSING;
            statusing.Credentials = CredentialCache.DefaultCredentials;

            WebSvcStatusing.StatusingDataSet dsStatusing =
                new EventHandlerWithImpersonation.WebSvcStatusing.StatusingDataSet();
            dsStatusing = statusing.ReadStatus(Guid.Empty, DateTime.MinValue, DateTime.MaxValue);
            WebSvcStatusing.StatusingDataSet.ResourcesRow resourceRow = 
                (WebSvcStatusing.StatusingDataSet.ResourcesRow)dsStatusing.Resources.Rows[0];


            int taskCount = dsStatusing.Tasks.Count;
            string statusOwner = resourceRow.RES_NAME;

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

            // Get information from the event arguments, and 
            // write an entry to the Application event log. 
            string userName = contextInfo.UserName.ToString();
            string statusingXml = e.ChangeXml;
            int eventId = 3940;
            string logEntry;

            logEntry = "User: " + userName + "\nChangeXML: "
                + statusingXml + "\nThis statusing dataset has " + taskCount 
                + " tasks, and they belong to " + statusOwner;
            myLog.WriteEntry(logEntry, EventLogEntryType.Information, eventId);

        }
    }
    class LoginWindowsDerived : WebSvcLoginWindows.LoginWindows
    {
        private static String ContextString = String.Empty;

        protected override WebRequest GetWebRequest(Uri uri)
        {
            //here we are overriding the GetWebRequest method and adding the 2 web request headers
            WebRequest webRequest = base.GetWebRequest(uri);
            if (ContextString != String.Empty)
            {
                webRequest.UseDefaultCredentials = true;

                bool isImpersonating = (System.Security.Principal.WindowsIdentity.GetCurrent(true) != null);
                webRequest.Credentials = CredentialCache.DefaultNetworkCredentials;

                webRequest.Headers.Add("PjAuth", ContextString);
                webRequest.Headers.Add("ForwardFrom", "/_vti_bin/psi/LoginWindows.asmx");

                webRequest.PreAuthenticate = true;
            }
            return webRequest;
        }

        public static void SetImpersonationContext(bool isWindowsUser, String userNTAccount, Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
        {
            ContextString = GetImpersonationContext(isWindowsUser, userNTAccount, userGuid, trackingGuid, siteId, lcid);
        }

        private static String GetImpersonationContext(bool isWindowsUser, String userNTAccount, Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
        {
            PSLibrary.PSContextInfo contextInfo = new PSLibrary.PSContextInfo(isWindowsUser, userNTAccount, userGuid, trackingGuid, siteId, lcid);
            String contextString = PSLibrary.PSContextInfo.SerializeToString(contextInfo);
            return contextString;
        }
    }
    class TimesheetDerived : WebSvcTimeSheet.TimeSheet 
    {
        private static String ContextString = String.Empty;

        protected override WebRequest GetWebRequest(Uri uri)
        {
            //here we are overriding the GetWebRequest method and adding the 2 web request headers
            WebRequest webRequest = base.GetWebRequest(uri);
            if (ContextString != String.Empty)
            {
                webRequest.UseDefaultCredentials = true;

                bool isImpersonating = (System.Security.Principal.WindowsIdentity.GetCurrent(true) != null);
                webRequest.Credentials = CredentialCache.DefaultNetworkCredentials;

                webRequest.Headers.Add("PjAuth", ContextString);
                webRequest.Headers.Add("ForwardFrom", "/_vti_bin/psi/timesheet.asmx");

                webRequest.PreAuthenticate = true;
            }
            return webRequest;
        }

        public static void SetImpersonationContext(bool isWindowsUser, String userNTAccount, Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
        {
            ContextString = GetImpersonationContext(isWindowsUser, userNTAccount, userGuid, trackingGuid, siteId, lcid);
        }

        private static String GetImpersonationContext(bool isWindowsUser, String userNTAccount, Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
        {
            PSLibrary.PSContextInfo contextInfo = new PSLibrary.PSContextInfo(isWindowsUser, userNTAccount, userGuid, trackingGuid, siteId, lcid);
            String contextString = PSLibrary.PSContextInfo.SerializeToString(contextInfo);
            return contextString;
        }
    }
    class StatusingDerived : WebSvcStatusing.Statusing
    {
        private static String ContextString = String.Empty;

        protected override WebRequest GetWebRequest(Uri uri)
        {
            //here we are overriding the GetWebRequest method and adding the 2 web request headers
            WebRequest webRequest = base.GetWebRequest(uri);
            if (ContextString != String.Empty)
            {
                webRequest.UseDefaultCredentials = true;

                bool isImpersonating = (System.Security.Principal.WindowsIdentity.GetCurrent(true) != null);
                webRequest.Credentials = CredentialCache.DefaultNetworkCredentials;

                webRequest.Headers.Add("PjAuth", ContextString);
                webRequest.Headers.Add("ForwardFrom", "/_vti_bin/psi/statusing.asmx");

                webRequest.PreAuthenticate = true;
            }
            return webRequest;
        }

        public static void SetImpersonationContext(bool isWindowsUser, String userNTAccount, Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
        {
            ContextString = GetImpersonationContext(isWindowsUser, userNTAccount, userGuid, trackingGuid, siteId, lcid);
        }

        private static String GetImpersonationContext(bool isWindowsUser, String userNTAccount, Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
        {
            PSLibrary.PSContextInfo contextInfo = new PSLibrary.PSContextInfo(isWindowsUser, userNTAccount, userGuid, trackingGuid, siteId, lcid);
            String contextString = PSLibrary.PSContextInfo.SerializeToString(contextInfo);
            return contextString;
        }
    }
    
}
Enjoy!
Project Server 2007 server-side event handling

Christophe has just posted on the Codeplex solution that allows easy handling of event handlers http://blogs.msdn.com/chrisfie/archive/2008/04/15/deploying-a-custom-event-handler-has-never-been-easier.aspx and I had my first chance to play with this yesterday.  It made life so much easier!  Please do take a look if you are working with server -side event handlers. 

On a similar topic I have been working on event handlers that use impersonation to ensure that the event can get at the Project Server data in the right context - or use a specific user account to have the right permissions.  I'll be posting that in the next few days.  But for now - take a look at the Codeplex stuff.

Technorati Tags: ,
And here is the PowerPoint for the WebCast...
Seemed to hit some limit with everything in a zip - so here is the PowerPoint.
Project Server Developer Webcast Samples

Thanks to all of you who joined the Webcast today.  I have attached a zip with the PowerPoint and some samples that were presented. For those who couldn't make it a brief summary so the samples make sense.

First I re-hashed an old sample showing creation of a list item and linking to a task (LoginDemoWithIssues).  I also covered the challenges you might run into in a multi language environment.  So here is an automatically created "Issue" linked to Task 1 in my Project.

OLPDemo

As I have created the Object Link Provider entries this is also exposed in the Project Center view of the project (highlighted).

OLPDemo2

I then showed an example of getting the SSP context programmatically.  A better example can be found here.  Thanks Martin!  My lame example is in EnumerateItems.cs and requires reference to Microsoft.SharePoint and Microsoft.Office.Server.

The best bit (in my opinion) was around the Business Data Catalog.  Thanks for Christophe for the work he did on BDC search in his blog and posted at CodePlex- which guided me to get this done,  Here are a couple of screenshots with the addition of a Project "Data Column" to an appointment,

image

and the addition of a Business Data Catalog List and Item which are connected Web Parts and the adding of a data column to a WSS list (which shows extra related information - pulled in this case from the Project Server reporting database).  And all without code!

BDCDEmo

In the zip file is the XML used for the application definition.  Hopefully you can understand what you need to change to get this working for you. 

Thanks to Joyce too for the logistics - look out for the recording soon!

More Role Guides for Project Server 2007 Now Available!

You may have already picked this up from some or all of the other Project blogs - but worth repeating as I know you have all been waiting for these!

They are now available (similar to EPM 2003) here: Learn about available roles for Project Web Access

Depending on your role in your organization and what security permissions are assigned to you, you will have access to different features of Microsoft Office Project Web Access. The following role guides present broad overviews of Office Project Web Access functionality from the perspective of the different roles you might have.

Role Guide/Description

  • Administrator role guide
    Use this guide if you are an administrator of Office Project Web Access. You can also use it as a guide to help evaluate Office Project Web Access for your organization.
  • Project manager role guide
    Use this guide if you are responsible for day-to-day project management tasks such as creating, maintaining, and updating schedules, and coordinating with other project managers, resource managers, and team members.
  • Executive role guide
    Use this guide if you are responsible for a portfolio of projects, such as all the projects for an entire department.
  • Resource manager role guide
    Use this guide if you are responsible for managing resources and their skills and capabilities.
  • Team member role guide
    Use this guide if you are responsible for any of the day-to-day activities in one or more projects.
Technorati Tags:
What were you doing in 1984? Hopefully not updating tasks in your current projects!

If this blog had a sub-title it would be "Task updates never get to Project Professional - even when they (eventually) say 100% Success".  And the error message you might find in the ULS logs would be:

Microsoft.Office.Project.Scheduling.SchedulingException: The engine's event horizon was extended beyond the maximum length of 36500 days. This may be caused by invalid task or assignment data. You can also change the value of the InitialProjectDays scheduling parameter.

This error led us to review the project timescale, and at first glance the Project Information was showing reasonable start and finish dates - from late 2007 to mid 2008.  But when looking at the plan the project summary task went back, and back, and back...  There was a milestone task 100% complete for 1/4/1984 driving the start date of the project summary.  Not sure at this stage how it got in that state - but this makes the scheduling engine work very hard processing status updates (you may see the queue service using LOTS of RAM) and eventually it gives the above error (in my case after 30-40 minutes) but the job appears to have completed OK.  However no updates appear in the plan.

The current workaround is just to correct the date, save and publish and then re-submit the time.

Technorati Tags:
Developer Webcast this week!

If you remember my blog about the Developer Community from a few weeks ago - and joined up, then you should have seen an invite to a Webcast this week.  I have been invited to talk about extending development from Project Server to Microsoft Office SharePoint Server.  The topics I am going to cover are:

  • The programmatic creation of list items in the WSS lists project uses, and the use of the Object Link Provider to expose these item in PWA
  • Finding your way around the parts of a SharePoint farm - such as identifying the Shared Service Providers in the farm
  • Using the Business Data Catalog as a way to get Project Server data exposed in other parts of SharePoint.

This final topic has been the most interesting to work on and is related to the work Christophe blogged about on searching Project data - but this time using Project data in SharePoint lists.  I will be showing how you could add a column to your SharePoint lists that shows Project names - and other related information.  Other uses of this might be to show Resource names, or the RBS as a Business Data column.  While I am on the topic of Christophe - he has been very busy lately and has some great postings on TechEd 2008, SQL Server 2008 and general performance improvement tips. And finally a very useful link to the Project Server 2007 hot fixes RSS feed

We might be building cube now... But not if you scheduled it for half-past!

In support we frequently have those moments that make us smile.  Often they aren't things I would choose to blog about - but I will share this one.  I certainly don't mean to trivialize bugs - and I do appreciate that they can cause a great deal of inconvenience to our customers (One particular customer I am thinking of in this case) but they can have their amusing moments!

For those that just want the short story - always schedule your cube builds on the hour.  If you choose 12:30, 1:30 etc it will not build when you want it.  If you want the long version then read on...

For this particular support incident we were troubleshooting a cube building issue where the customer was scheduling a cube build and it wasn't happening on time - but sometime later - that seemed variable but in some cases looked like it was using Universal Coordinated Time (UCT or GMT).  I set my cube build for 4:30pm that afternoon, set my ULS logs to give verbose output for the Analysis Cube Build and started SQL Profiler traces to see what was going on in the database.  My usual steps for "following the data".  I saw that the cube build didn't happen at 4:30pm so thought I might have a repro so left it all running overnight.

The following morning I could see the cube had built at 2:30am - which fitted the pattern as I am in PST (GMT-8), so I started through the logs and traces to see what was going on.  Only 3 million rows in my SQL traces so shouldn't take long...  I also set another cube build for 8:00 while I worked through the logs - and this worked.  Then set one for 8:30 - which failed, 9:00 worked.  I could see the pattern, and having this information and the logs I managed to find the bug.

The amusing part was looking in the ULS logs for the previous afternoon and at a few milliseconds past 4:30 seeing this entry.

Cube building timer job invoked and we might be building cube now

It was as if the SharePoint timer service (which was the process that gave this message) wasn't going to take any accountability for what happened next!  Or more correctly the developer coding the message!  If you are a regular reader you will know that the cube build can fail for a number of reasons, so perhaps I shouldn't have been surprised that the message was a little evasive.  It did however make me dig deeper to understand this part of the cube build process which I hadn't investigated in depth before.

It follows a similar path to the provisioning of a PWA site, and for similar reasons.  It is going to use a WSS Timer job, but the SSP account does not have permissions to create one - so this is what happens.  When you click save on the cube build settings page for a periodical update it saves the information to the MSP_WEB_CUBE_ADMIN table in the _Published database and also puts a row into the SharedServices1_DB (your name may vary) in the MIPScheduledJob table.  This is the timer job request. This is read by the SharePoint timer service after a minute or so and it gets put into the Objects table of the SharePoint_Config database - and is seen as a timer job in timer job definitions.  At the appointed time it will run and you will see a job in the TimerRunningJobs table and this is when the message to the ULS logs gets posted.  If all works well this would be followed by another couple of messages saying the cube building job has started.  In my case I didn't see these messages in my log until 2:30am - even though they referred back to the requested time of 4:30pm.

Cube building job has started for scheduled time: 3/25/2008 4:30 PM

[CBS] Status message: Cube build request message has been added to the Project Server queue

In fact you will see the "might be building cube" message at every 30 minutes past - even after it builds the cube - once the timer job is in place.  So the real reason it says "might" is that only once during the day (if you have daily builds) will it actually be building when that message appears!

A couple of other interesting points about this process is that the times in the SharePoint databases are held in UCT (which is by design and not the reason for the strange build times).  However the requested build time in the MSP_WEB_CUBE_ADMIN table is held as an integer defining at how many half hours past midnight the cube should be built.  12:30 would be 1, 3:00am would be 6 etc.  It is the decoding of this for the odd number that appears to be at the root of this bug.

For the main issue though - that cubes scheduled for the half hours do not get built on time - we do have a hotfix in the pipeline.  We might release this in April :).

Slow OLAP Cube Builds and Large TempDB - Revisited

After quite a bit of work on this one it appears that SP1 (and a slightly earlier hotfix) were almost coincidental in this problem rather than being the cause - and it can occur even without SP1 if you add enough dimensions to your cubes.  The best resolution is a combination of the first 2 or all 3 of the following items:

1.  Keep you statistics up to date!  In minor cases updating the statistics on your reporting database can help - but in all cases it is required to get the full benefit.  You should set up a maintenance plan or initial for testing you could use sp_updatestats on your reporting database.  See Christophe's recent blog for some guidance here - database maintenance is one of the keys to better queue performance.

2.  if you see a "No Join Predicate" warning in the execution plan of the large query causing the problem then apply SQL Server 2005 SP2 Cumulative Update 6 http://support.microsoft.com/kb/946608 .  The SQL bug is resolved by Hotfix 942444 which is in that roll up.  Please also read http://support.microsoft.com/kb/942444 carefully, particularly the details of setting Trace Flags 4101 and 4121 - which are required to be set to active this hotfix.  See the remarks section at http://msdn2.microsoft.com/en-us/library/ms188396.aspx for more details of trace flag setting.  Use the command line option -T4101 and -T4121, and set these in the SQL Server Configuration Manager for the instance of SQL Server 2005 used for your reporting database.

3.  If this still isn't giving you the speed of cube build you require then it might be time to consider upgrading your server hardware.  In our tests with very large cube builds you can improve the build times with more RAM - and also x64 hardware (which also allows you to support even more RAM).

If you are not sure about step 2 - then see my previous posting which will help you identify the bad query - and then paste this into a SQL Query window against you reporting database and click to see the estimated query plan.  In the execution plan - which will be quite large - you are looking for something like this:

image

And if you hover over the warning triangle you should see a warning of "No Join Predicate".  Also note the expense (74% of the cost of the query) attributed to the Table Spool.  This is where tempdb is getting heavily used.

As an example of potential improvements after following these steps (without any hardware upgrades) we have seen cube builds of 20+ hours complete in 45 minutes, and a 1 hour 15 minute build come down to less than 1 minute.  Your mileage may vary :).

You may still see quite heavy tempdb usage and this is to be expected with such a large query - and should not be a concern.  If Your server doesn't have the space for the tempdb SQL is trying to use after following these steps then time to invest in more disk space!

More Posts Next page »
Page view tracker