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!

samplecode.zip


Comments (34)

  1. Santiago Gudino says:

    I still have the error: “HTTP status 401: Unauthorized”. I realized that you setup LoginWindows proxy but you dont use Login method(). I also tried using first this method before use the other web services(statusing) but i didnt work too. Could you please give some extra advices, i really need them. Thanks a lot !!

  2. Kamil Tarkowski says:

    In my case contextInfo.IsWindowsUser doesn’t return true, which is needed.

    Before I had "HTTP status 401: Unauthorized"..

  3. M Azeem says:

      I am having a problem while updating a custom field value of a specific

    project when OnPublishing event of project server raises. When I log on the

    Project Web Access with svc-emp user then it works fine and value of custom

    field updates, but when I log on on Project web access with different user it

    raises an exception that CICOCheckedOutToOtherUser. Following is my code,

    please any solution…

    using System;

    using System.Diagnostics;

    using Microsoft.Office.Project.Server.Events;

    using System.Collections.Generic;

    using System.Text;

    using System.Net;

    using System.Web.Services.Protocols;

    using System.Data;

    using System.Threading;

    using PSLibrary = Microsoft.Office.Project.Server.Library;

    namespace SaveProjectAttribute

    {

    public class AddAttributeOnSave : ProjectEventReceiver

    {

    public override void

    OnPublishing(Microsoft.Office.Project.Server.Library.PSContextInfo

    contextInfo, ProjectPrePublishEventArgs e)

    {

    base.OnPublishing(contextInfo, e);

    const string ls_projURL = "http://ruh-003-cr-001/pmo/";

    const string ls_CustomField = "TestCF";

    const string ls_LookupTableValue = "";

    bool verbose = true;

    const string PROJECT_SERVICE_PATH = "_vti_bin/psi/Project.asmx";

    const string LOGIN_SERVICE_PATH =

    "_vti_bin/psi/LoginWindows.asmx";

    bool isWindowsAccount = contextInfo.IsWindowsUser;

    Guid trackingGuid = new

    Guid(contextInfo.TrackingGuid.ToByteArray());

    string lcid = contextInfo.Lcid;

    string userNTAccount = contextInfo.UserName;

    Guid resourceGuid = new Guid(contextInfo.UserGuid.ToByteArray());

    Guid siteId = new Guid(contextInfo.SiteGuid.ToByteArray());

    ProjectDerived ws_Project = new ProjectDerived();

    LoginWindowsDerived loginWindows = new LoginWindowsDerived();

    LoginWindowsDerived.SetImpersonationContext(isWindowsAccount,

    userNTAccount, resourceGuid, trackingGuid, siteId, lcid);

    ProjectDerived.SetImpersonationContext(isWindowsAccount,

    userNTAccount, resourceGuid, trackingGuid, siteId, lcid);

    loginWindows.Url = ls_projURL + LOGIN_SERVICE_PATH;

    loginWindows.Credentials = CredentialCache.DefaultCredentials;

    ws_Project.Url = ls_projURL + PROJECT_SERVICE_PATH;

    ws_Project.Credentials = CredentialCache.DefaultCredentials;

    WSProject.ProjectDataSet lo_projs = null;

    WSProject.ProjectDataSet lo_projDS;

    Guid lo_projGUID;

    string ls_projName;

    CustomField cf = new CustomField(ls_projURL, ls_CustomField);

    LookupTable lt = new LookupTable(ls_projURL, cf.LookupTableGUID);

    try

    {

    // Read all the projects on the server

    lo_projs = ws_Project.ReadProjectList();

    DataRowCollection lo_projects =

    lo_projs.Tables[lo_projs.Project.TableName].Rows;

    lo_projDS = ws_Project.ReadProjectEntities(e.ProjectGuid,

    32, WSProject.DataStoreEnum.WorkingStore);

    foreach (WSProject.ProjectDataSet.ProjectCustomFieldsRow row

    in lo_projDS.ProjectCustomFields)

    {

    if (row.MD_PROP_UID == cf.CustomFieldGUID)

    {

    row.TEXT_VALUE = "Finally Got You!";

    Guid jobid = Guid.NewGuid();

    ws_Project.QueueUpdateProject(jobid,

    contextInfo.UserGuid, lo_projDS, false);

    }

    }

    }

    catch (SoapException lo_ex1)

    {

    if (verbose)

    System.Console.WriteLine("Error: " + lo_ex1.Message);

    }

    catch (WebException lo_ex)

    {

    if (verbose)

    System.Console.WriteLine("Error: " + lo_ex.Message);

    }

    catch (Exception lo_ex)

    {

    if (verbose)

    System.Console.WriteLine("Unknown Error: " +

    lo_ex.Message);

    }

    System.Console.WriteLine("Done!");

    }

    }

    class LoginWindowsDerived : WSLoginWindows.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 ProjectDerived : WSProject.Project

    {

    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/project.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;

    }

    }

    }

  4. Hi M Azeem,

    I’m not sure your impersonation is working correctly – as you do not appear to be referencing the web services through the SSP – but just within the site (unless I am missing something).  It works when the user is the service account as that is the one who has the project checked out in PWA – so other users fail as the event isn’t impersonating the other users correctly.  Also unless you have a reason to work with the publishing event (such as cancelling in somre circumstances) then working with the published might be more performant.

    Best regards,

    Brian.

  5. barney says:

    Thanks for the sample code; there is very little documentation out there on this topic.  Anyhow, I have a couple questions:

    1. Since ContextString is a static member of your derived classes, i.e., LoginWindowsDerived, wouldn’t calls to SetImpersonationContext from different users on a busy system result in the side effect of unintentionally overriding the static ContextString?  Static members do maintain their values between client requests.

    2. Any reason why the getter GetImpersonationContext is marked private?  I’m just trying to figure out the intent.

    Thanks again,

    barney

  6. Hi Barney,

    As this part of my sample cam direct from the SDK article Walkthrough: Develop an Impersonation Web Application, I went back to the source and got this response – thanks Jim!

    1. Calls from different concurrent users maintain their own contexts. ContextString is static because it keeps the value between client requests from any one user. If you make ContextString non-static, you’ll need to store or re-initialize ContextString for each new client request – which is certainly doable, depending on how you want to architect the solution.

    2. GetImpersonationContext is private in the sample code because it is called only from the public SetImpersonationContext.

    Let me know if you have any further questions,

    Best regards,

    Brian.

  7. Hi Barney, just following up on this.  

    The code I borrowed from was obviously a web app and in an event handler there is certainly the potential for the behavior you mention (although we are not sure it would really happen). You could try both approaches and see if you see any issues (but not in production of course).

    Best regards,

    Brian.

  8. KH says:

    Hi Brian,

    I’m writing a web part to display project information on the project workspace. The problem is when Team Members open the project workspace they get a 401 unauthorized message due to their lack of permission to call functions such as ReadProjectList and ReadProjectEntities. I tried using impersonation but it didn’t work due to the credentials in the project workspace being the user rather than SSP admin as in the case of a event handler, so calling the functions also returns a 401 unauthorized message.

    Is  there a way to accomplish what I want?

    Regards,

    KH

  9. Hi KH,

    As you are presenting information to the team members that they would not normally have access to I would suggest the best approach might be to give them access through the project security model – as otherwise you are by-passing this security.  The only other way would be to somehow elevate the user to the SSP admin – though I don’t have any exmples for this.

    Best regards,

    Brian.

  10. Winnipegguy2235 says:

    I’m not sure if this has anything to do with your code but is there a way to pass the username and password from the login screen to be used instead of you windowsidentity? Basically i want to logon as me but from another computer which isn’t mine. Now matter what i try it seems to log me in as the computer i’m currently using. Any help would be greatly appreciated.

  11. Hi Winnipegguy,

    Can you tell me a little more about your scenario?  Are you just wanting to log into PWA or Pro – or pass the credentials you have already logged in as (which are not the Windows logged in user) to some other code or event handler.

    Best regards,

    Brian.

  12. Winnipegguy2235 says:

    Let me try this. When i log onto my laptop my user i logon as MYDOMAINNAMEwinnipeg.guy2235 and enter my password.  Now when i run projtools i can just click on login and it’ll sign me in to my project server and bring up everything. Since i have clients i want to be able to go to there work sites with my laptop and type in say CLIENTDOMANNAMEclientsname and there password.  I’ve tried making the username and password enabled and entering data. When i debug it infacts puts the username and password into mysettings.username etc.. but when it brings up projectmain it still logs in with my windows login not what i specified. I’ve tried it with the windows radio button and the forms button clicked with no results. I’m trying to sign into http://servername/PWA.  Does that make sense or is that not the info you needed. I really appreciate the help.

  13. Tim Andrews says:

    Do you know what needs to be done to read timephased data for multiple users? I have impersonation working perfectly and I’m trying to build a list of how many hours a user (resource) is booked for the next week starting from the current date, so imagine a gridview with the resource name and 5 columns, one for each consecutive day, listing the hours assigned for each resource for each day. I can see my own hours just fine but every other resource gives GeneralSecurityAccessDenied error. Even the top-level administrator user gets the same error but can see his hours. Do I need to impersonate each resource one at a time when querying this data? I’m using the ReadStatusTimephasedData from the Statusing service. I wouldn’t mind being able to do this with just a SQL query, but I can’t figure out how the data is stored in SQL. Any suggestions? Thanks.

  14. Stephen Eyton-Jones says:

    Hi Brian,

    We continue to get 401 errors while trying to impersonate using the http://servername:56737/SharedServices1/PSI/Statusing.asmx and it does seem to be rights related.  Where do I set the correct permissions to allow the user to access this Web Service.  We will need to allow all users access to the Web Service.

    –As per your comments—

    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.

  15. Hi Stephen,

    Each web service described in the SDK should have a section headed Project Server permissions.  For example, the Statusing.ReadStatus method  section looks like this.

    Project Server Permissions

    No permissions are required. This method reads data for only the current logged-on resource.

    With no permissions required if this particular method fails then you probably need to check your impersonation setup.  Also adding the SSP account as a user might give clues if it then works – as it will try and read status for the SSP user.

    Hope this helps with troubleshooting.

    Best regards,

    Brian.

  16. jad says:

    Dear Brian,

    I need to get the project dataset from the draft database using PSI. i am using the method WSProject.ReadProject(e.ProjectUID,DataSource.WorkingStore). the main issue is… i want to read the dataset while the project is checked out. But it gave me an error indicating that the "project has write lock" and i cannot get the dataset from the working database. I want to read the project just from the draft database because i want to get the updated SessionUID. kindly provid me with the solution of this issue.

    Thank you in advance,

    Regards,

    Jad

  17. Hi Jad,

    Off the top of my head I can’t think of a way to do this.  Can you tell me why you need to get the updated SessionUID?  

    Best regards,

    Brian.

  18. Kannan says:

    hi Brian,

    I am also acing this 401 unauthorized error whenever i try to consume an asmx within the SSP in my application. I am using the code below to access the web service

    try

    {                              

       PrjServiceReference.Project prjClient = new WindowsFormsApplication1.PrjServiceReference.Project();                

       prjClient.Credentials = CredentialCache.DefaultCredentials;                    PrjServiceReference.ProjectDataSet  prjData = prjClient.ReadProjectList();

    }

    catch (Exception ex)

    {

      string strEx = "";

      strEx = ex.Message;

    }

    I installed the project server using a domain account. This account is the administrator in the PWA, SSP and is responsible for running all the application pools related to the project server. This acount is the dbo on all the databases related to project server. I am running visual studio under this account. But every time i try to consume the service in the SSP, i get

    401 – UNAUTHORIZED.

    Please help

  19. Nita says:

    Hi Brian,

    My impesonation using your code is doing great when I access that class from FORM Application, but it has different story when i call that class from my WebService Application. I got error The request failed with HTTP status 401: Unauthorized ..

    Please Advice. And Thanks a lot

  20. meliacooper says:

    I am attempting to create a timesheet for a user in project server.  I can create the timesheet but it is a surrogate timesheet.  I have have checked the headers generated from ImpersonateTSByResUID, however the timesheet is still created as a surrogate.  Am I missing anything here?  I have already looked at the examples on MSDN but am still not able to succesfully create the timesheet as the correct user.  Thanks in advance for any help!

                   TimesheetDerived timesheetDerived = null;                timesheetDerived = ImpersonateTSByResUID(resDS.Resources[0].RES_IS_WINDOWS_USER, resDS.Resources[0].RES_UID, resDS.Resources[0].WRES_ACCOUNT, siteID, "1033", tsWSUrl);

                   TimeSheetWS.TimesheetDataSet timesheetDs = new TimeSheetWS.TimesheetDataSet();                TimeSheetWS.TimesheetDataSet.HeadersRow headersRow = timesheetDs.Headers.NewHeadersRow();                headersRow.RES_UID = resourceGuid;                headersRow.TS_UID = Guid.NewGuid();                headersRow.WPRD_UID = periodUid;                //headersRow.TS_CREATOR_RES_UID = resourceGuid;                headersRow.TS_NAME = "TS – " + PSIAdmin.GetTSPeriodName(periodUid) + " – " + index.ToString();                headersRow.TS_COMMENTS = "Timesheet using PSI";                headersRow.TS_ENTRY_MODE_ENUM = (byte)PSLibrary.TimesheetEnum.EntryMode.Daily;                                timesheetDs.Headers.AddHeadersRow(headersRow);

                   // Create the timesheet with the default line types specified by the admin                timesheetDerived.CreateTimesheet(timesheetDs, TimeSheetWS.PreloadType.Default);                return headersRow.TS_UID;

          private static TimesheetDerived ImpersonateTSByResUID(bool isWindowsUser, Guid resUID, string userAcct, Guid siteID, string lcID, string url)        {            TimesheetDerived timesheetDerived = new TimesheetDerived();            timesheetDerived.Url = url;            timesheetDerived.Credentials = CredentialCache.DefaultCredentials;            TimesheetDerived.SetImpersonationContext(isWindowsUser, userAcct, resUID, Guid.NewGuid(), siteID, lcID);            return timesheetDerived;        }

           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;        }    }

  21. Rodrigo says:

    Hi Brian

    I am trying to make an update to a task using the statusing.updatestatus(xmlstring). I need to update my resources times however if I send an update which I am the owner there is no problem, but if I try to update my resources times I get an unaothorized error.

    What can I do??

    Thanks

    Rodrigo

  22. Hi Rodrigo,

    You would need to use impersonation to make changes to others status. See the SDK for examples of how to do this.  

    Best regards,

    Brian

  23. Rodrigo says:

    Hi Brian

    I tried to use the ProjTool example, however I still get the 401 Unauthorized.

    As well I used your example and the SDK examples but no sucess.

    If I dont use the impersonation and tried to update a task of another resource I get the error :

    System.Web.Services.Protocols.SoapException: ProjectServerError(s) LastError=GeneralSecurityAccessDenied Instructions: Pass this into PSClientError constructor to access all error information

      at Microsoft.Office.Project.Server.WebService.Statusing.UpdateStatus(String changexml)

    Is there

    Can you please provide me any help regarding this subject

    Thanks a lot for all of your help

    Regards

    Rodrigo

  24. Hi Rodrigo,

    To use impersonation, either with ProjTool or your own code you must be running as the account that runs the Shared Services Provider, and also referencing the PWA instance you wish to query by referencing the GUID. Are you doing both of these?

    Best regards,

    brian.

  25. Parth says:

    Hi,

    I am using Impersonation and it is running fine, but in case with some users it is giving me an error saying that "GeneralSecurityAccessDenied" and for other users it is working like a charm..!! how do i debug the app.!!??

    Thanks

    Parth

  26. Peci says:

    Hello Brian, I have problems with reading Generic Work Resource TimephasedData in Project Server 2007. Reading those data works fine for Work resources or MaterialResources, but for Generic Work Resources i'm getting GeneralSecurityAccessDenied exception.

    My code looks like this

    impersonatedService = _sspConnection.CreateStatusingSvc(resourceOwner.Resources[0].WRES_ACCOUNT,                            resourceOwner.Resources[0].RES_IS_WINDOWS_USER,

    resourceOwner.Resources[0].RES_UID);

    var incData = impersonatedService.ReadStatusTimephasedData(projectId,

    assignmentRow.ASSN_UID,

    assignmentRow.ASSN_START_DATE,

    assignmentRow.ASSN_FINISH_DATE,

    60 * 24);

  27. Mike P says:

    Hi Brian,

    Thank you. This examaple was great. I'm using timesheet impersonation in an event handler just fine in 2007 (adding lines to timesheet in timesheet created event), but I am unable to perform impersonation in PS 2010 using the timesheet service. While the 2010 sdk example on impersonation using the resource service works fine, I am unable to translate this to the timesheet. Are there any examples on how to use impersonation with the timesheet service, such as adding a timesheet line to the impersonated user's timesheet?

    Thanks,

    Mike

  28. Sachin says:

    Hi Brian,

    I am developing a event handler(using impersonation) on Task Submitted event to auto accept the changes.

    Where I need to fetch only tasks which are submitted by a resource, but when I am using ReadStatus() method, either getting all the task assigned to resource or particular task.

    Suppose Res A has five tasks assigned to him. Out which he submitted 2 tasks for approval from PWA.

    So I need to fetch only those 2 tasks only not complete set of 5 tasks assigned to him.

    Please help me in achieving the same.

    Thanks !!!

  29. Hi Sachin, can you use the Updateguids member from the eventargs (e.updateguids) msdn.microsoft.com/…/gg223402.aspx and then use this array of GUIDs to get to the precise updates?  Also I assume you are referring to 2007 – if you are using 2010 then there is an auto publish rule available since SP1 – see Approval Center – Manage Rules.

    Best regards,

    Brian.

  30. Sachin says:

    Thanks Brian for your kind help It worked !!!

  31. Sachin says:

    Hi Brian,

    Based on your above recommendation, I have completed the event handler and its successfully running for last 7-8 months.

    But this and last month few users noticed a problem with this event handler, which I explained in Proejct Cust Forum at following location "social.technet.microsoft.com/…/e8679552-3c0c-409e-bdbd-0a27f3951bcf" please have a look there and suggest me for my further actions in order to resolve this issue.

    Thanks !!!

  32. Todd Klostermann says:

    Hi, about your comment "To use impersonation, either with ProjTool or your own code you must be running as the account that runs the Shared Services Provider, and also referencing the PWA instance you wish to query by referencing the GUID. Are you doing both of these?"

    I'm using Project Server 2010 and I don't think the term SSP applies, where would I add my account to be a "SSP" in PS 2010?

  33. Hi Todd, for 2010 the better article to refer to would be msdn.microsoft.com/…/ff181538.aspx .  Also there are a few PSI calls that have changed that mean you can now pass in the RES_UID rather than needing to impersonate so worth reviewing the samples in the 2010 SDK to see if you really need to impersonate or not.

    Best regards,

    Brian.