How to: Write a Simple Impersonation Application

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

To learn how to write an impersonation application for Microsoft Office Project Server 2007, it helps to start with a simple application. The console application in this article shows how to use the Project Web Access URL of the Project Server Interface (PSI) as well as how to use the Shared Services Provider (SSP) URL for impersonation. (This article is based on code contributed by KC Vootkuri Krishna, Microsoft Corporation.)

The ImpersonationConsoleApp application reads the account name of a Project Server user to impersonate. The application uses the Project Web Access URL of the PSI to call ReadResources and get the RES_UID (resource GUID) of the user to impersonate from Project Server. The application then impersonates the specified user and calls ReadResources through the SSP URL of the PSI Resources Web service to get the RES_UID.

An impersonation application requires the site ID (GUID) of the Project Web Access site in Windows SharePoint Services. If you develop the application on a different computer from the one where Project Web Access is installed, you must hard-code the SharePoint site ID of Project Web Access. If you develop or install the application on the same computer where Project Web Access is installed, you can use the Windows SharePoint Services API to determine the site ID. The sample uses a hard-coded value for the site ID, but also shows how to use the Windows SharePoint Services SPSite.ID class property.

Instead of setting a Web reference to a PSI Web service, you generate proxy source code for each Web service you need. You can then create a class such as ResourceDerived that inherits from the Resource Web service proxy. The ResourceDerived class overrides the GetWebRequest method for SOAP calls. The new GetWebRequest method adds the necessary user credentials and headers in the Web request to allow impersonation. The ResourceDerived class also includes methods to get and set the impersonation context for the specified user.

For a larger application that includes impersonation, see Using the ProjTool Test Application. The Project Server 2007 SDK Samples download includes the complete source code for ImpersonationConsoleApp and ProjTool. For a link to the download, see Welcome to the Microsoft Office Project 2007 SDK.

Developing the Impersonation Application

The following procedures show the essential parts of the console application for impersonation. They do not show all of the detail in comments or try - catch statements. For the full code in the Program and ResourceDerived classes, see the Example section.

Procedure 1. To create the basic console application:

  1. In Microsoft Visual Studio 2005, create a new console application and name it ImpersonationConsoleApp.

  2. If you want, change the namespace. The sample namespace is Microsoft.SDK.Project.Samples.Impersonation.

  3. Copy the Microsoft.Office.Project.Server.Library.dll assembly from C:\Program Files\Microsoft Office Servers\12.0\Bin on the Project Server computer to your development computer.

  4. Add references to System.Data, System.Web.Services, System.XML, and the Microsoft.Office.Project.Server.Library.dll.

  5. Add using statements to complete the basic application outline, as follows:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Web.Services;
    using System.Net;
    using PSLibrary = Microsoft.Office.Project.Server.Library;
    
    namespace Microsoft.SDK.Project.Samples.Impersonation 
    {
        class Program
        {
            static void Main(string[] args)
            {
            }
        }
    }
    

Procedure 2. To generate the PSI proxy file:

  1. In a Visual Studio 2005 Command Prompt window, run a wsdl.exe command that specifies the namespace you are using, language, output file, and Web service URL. For example, run the following command (all on one line):

    wsdl /n:Microsoft.SDK.Project.Samples.Impersonation /language:C# 
    /out:ResProxy.cs http://ServerName/PWA/_vti_bin/psi/resource.asmx?WSDL
    
    NoteNote

    To be sure you are using the correct version of the PSI, generate new PSI proxy files for each Web service whenever you install an update or service pack for Project Server. Copy the proxy files to the Visual Studio solution and recompile the application.

  2. Add the ResProxy.cs file to the Visual Studio solution. The file defines the class Resource, which inherits from System.Web.Services.Protocols.SoapHttpClientProtocol.

Procedure 3. To create the derived class for the PSI proxy:

  1. Add a class named ResourceDerived to the solution that inherits from Resource. Add a class variable to hold the impersonation context string.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Web.Services;
    using System.Net;
    using PSLibrary = Microsoft.Office.Project.Server.Library;
    
    namespace Microsoft.SDK.Project.Samples.Impersonation
    {
        class ResourceDerived : Resource
        {
            private static String contextString = String.Empty;
            //  . . . Add methods here
        }
    }
    
  2. Override the GetWebRequest method and add the two Web request headers.

    protected override WebRequest GetWebRequest(Uri uri)
    {
        WebRequest webRequest = base.GetWebRequest(uri);
        if (contextString != String.Empty)
        {
            webRequest.UseDefaultCredentials = true;
    
            bool isImpersonating = 
                (System.Security.Principal.WindowsIdentity.GetCurrent(true) != null);
            webRequest.Credentials = CredentialCache.DefaultCredentials;
    
            webRequest.Headers.Add("PjAuth", contextString);
            webRequest.Headers.Add("ForwardedFrom", "/_vti_bin/psi/resource.asmx");
    
            webRequest.PreAuthenticate = true;
        }
        return webRequest;
    }
    
  3. Create methods to get and set the impersonation context. When you run the application, the userAccount value must be a valid Project Server user, of the form domain\username or aspnetsqlmembershipprovider:username. If the latter, isWindowsUser must be false. The siteId parameter is the Project Web Access site ID. The lcid parameter contains the Locale ID, for example 1033 for English.

    public static void SetImpersonationContext(bool isWindowsUser, String userAccount, 
        Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
    {
        contextString = GetImpersonationContext(isWindowsUser, userAccount, 
            userGuid, trackingGuid, siteId, lcid);
    }
    
    private static String GetImpersonationContext(bool isWindowsUser, String userAccount, 
        Guid userGuid, Guid trackingGuid, Guid siteId, String lcid)
    {
        PSLibrary.PSContextInfo contextInfo =
            new PSLibrary.PSContextInfo(isWindowsUser, userAccount, 
            userGuid, trackingGuid, siteId, lcid);
        String contextString = PSLibrary.PSContextInfo.SerializeToString(contextInfo);
        return contextString;
    }
    

Procedure 4. To complete the application:

  1. Add constants for hard-coded values in the Program class. Change the values for your server name, SSP name, and Project Web Access site ID.

    private const string PSI_RESOURCE_SSP = 
        "http://ServerName:56737/SSPName/PSI/Resource.asmx";
    private const string PROJECT_SERVER_URI = "http://ServerName/ProjectServerName";
    private const string RESOURCE_SERVICE_PATH = "/_vti_bin/psi/resource.asmx";
    
    // GUID of the Project Web Access site in Windows SharePoint Services. 
    private const string PWA_SITE_GUID = "44c4ae03-16c2-4618-bf9a-12643006c5be ";
    

    To find the SSP name, open the Internet Information Services (IIS) Manager window and expand the Office Server Web Services node. The default SSP name is typically SharedServices1.

    To find the Project Web Access site ID, use one of the following methods:

    1. Open the SharePoint ContentDB for the SSP. Open the Webs table and get the SiteId, where the FullUrl value is the name of the Project Web Access instance, for example, ProjectServerName.

    2. Open the Manage Project Web Access Sites page in the SSP Administration site, click the drop-down list for the site, and then click Edit. The site ID is the GUID of the ID option in the URL. For example, the following URL has a site ID of 44c4ae03-16c2-4618-bf9a-12643006c5be.

      http://ServerName:19466/ssp/admin/_layouts/createpwa.aspx?task=Edit&id=44c4ae03-16c2-4618-bf9a-12643006c5be

    3. Use the Windows SharePoint Services SPSite.ID property in the Windows SharePoint Services API. Set a reference to both Microsoft.SharePoint and Microsoft.SharePoint.Search namespaces. The commented-out code is shown in Step 5.

    4. add a Web reference to http://ServerName/ProjectServerName/_vti_bin/sitedata.asmx. If you name the reference WebSvcSiteData, for example, use the following code to get the site ID of Project Web Access.

      WebSvcSiteData.SiteData siteData = new WebSvcSiteData.SiteData();
      siteData.Url = "http://ServerName/ProjectServerName/_vti_bin/sitedata.asmx";
      siteData.Credentials = CredentialCache.DefaultCredentials;
      
      string url = "http://ServerName/ProjectServerName";
      string pwaUrl;
      string pwaId;
      
      siteData.GetSiteUrl(url, out pwaUrl, out pwaId);
      
      NoteNote

      If you develop or install the application on the computer where Project Web Access is hosted, you can use the SPSite.ID property to get the site ID. If you plan to install the application on a remote site, use the SiteData Web service instead of the SPSite.ID property. For more information, see Windows SharePoint Services Infrastructure for Project Server.

  2. Add a class variable for the ResourceDerived object.

    static ResourceDerived resProxyBySSP;
    
  3. Add a method to the Program class that gets the resource ID from the account name of the user to impersonate. The GetResourceUid method uses the PSI through the Project Web Access URL. There is no impersonation yet when the application calls GetResourceUid. To successfully complete the call, the application user must be a valid Project Server user.

    private static Guid GetResourceUid(String accountName)
    {
        Resource resProxyByPWA = new Resource();
        resProxyByPWA.Credentials = CredentialCache.DefaultCredentials;
        // Before impersonation, use the Project Web Access URL.
        resProxyByPWA.Url = PROJECT_SERVER_URI + RESOURCE_SERVICE_PATH;
    
        ResourceDataSet rds = new ResourceDataSet();
    
        // Filter for the account name, which can be a Windows or Project Server account.
        PSLibrary.Filter filter = new PSLibrary.Filter();
        filter.FilterTableName = rds.Resources.TableName;
    
        PSLibrary.Filter.Field accountField =
            new PSLibrary.Filter.Field(rds.Resources.TableName, 
                rds.Resources.WRES_ACCOUNTColumn.ColumnName);
        filter.Fields.Add(accountField);
    
        PSLibrary.Filter.FieldOperator op =
            new PSLibrary.Filter.FieldOperator(
                PSLibrary.Filter.FieldOperationType.Equal, 
                rds.Resources.WRES_ACCOUNTColumn.ColumnName, accountName);
        filter.Criteria = op;
    
        Console.WriteLine("\nNot impersonating:\n\tResourceProxy.Url:\n\t" + resProxyByPWA.Url);
        rds = (ResourceDataSet)(resProxyByPWA.ReadResources(filter.GetXml(), false));
    
        // Return the account GUID 
        return (Guid)rds.Resources.Rows[0]["RES_UID"];
    }
    
  4. Add code in the Main method to input a user account name.

    Console.WriteLine("Enter a valid Project Server user account, such as");
    Console.WriteLine(@"domain\username or aspnetsqlmembershipprovider:username:");
    
    string userAccount = Console.ReadLine();
    
  5. Finish the Main method with code that does the following jobs:

    1. Create a ResourceDerived object that uses the SSP URL for the Resource Web service.

    2. Call GetResourceUid to get the resource GUID of the user to impersonate.

    3. Set the parameter values, and then call SetImpersonationContext.

    4. Call GetCurrentUserUid in the Resource Web service through the SSP.

    5. Write the data to the console window.

    try
    {
        resProxyBySSP = new ResourceDerived();
    
        resProxyBySSP.Url = PSI_RESOURCE_SSP;
        resProxyBySSP.Credentials = System.Net.CredentialCache.DefaultCredentials;
    
        // Get the GUID of the user to impersonate. 
        Guid userGuid = GetResourceUid(userAccount);
        Console.WriteLine("User GUID to impersonate: " + userGuid.ToString());
    
        // To use the following code, the application must run on the Project Web 
        // Access computer. Set references to the Microsoft.SharePoint and 
        // Microsoft.SharePoint.Search assemblies on the same computer.
        // Microsoft.SharePoint.SPSite site = new SPSite(PROJECT_SERVER_URI);
        // Guid siteId = site.ID; 
    
        //Otherwise, hard-code the site ID.
        Guid siteId = new Guid(PWA_SITE_GUID);
    
        bool isWindowsUser = true;
        if (userAccount.Contains("aspnetsqlmembershipprovider")) 
            isWindowsUser = false;
    
        ResourceDerived.SetImpersonationContext(isWindowsUser, userAccount, 
            userGuid, Guid.Empty, siteId, "1033");
    
        // To get the GUID of the user we are impersonating, 
        // call GetCurrentUserUid in the Resource Web service.
        Guid impersonatedUserGuid = resProxyBySSP.GetCurrentUserUid();
    
        Console.WriteLine(string.Format("\nImpersonating '{0}':\n\tResourceProxy.Url:\n\t{1}", 
            userAccount, resProxyBySSP.Url));
    
        Console.Write("\nImpersonated user GUID: ");
        Console.WriteLine(impersonatedUserGuid.ToString());
    }
    catch (SoapException ex)
    }
       . . .
    }
    finally
    {
       . . .
    }
    Console.Write("Press any key...");
    Console.ReadKey();
    

Following is an example of input and output when you run the application in the Command Prompt window. The input value aspnetsqlmembershipprovider:joe is a valid Project Server user for Forms authentication.

C:\Project\ImpersonationConsoleApp\bin\Debug>ImpersonationConsoleApp

Enter a valid Project Server user account, such as

domain\username or aspnetsqlmembershipprovider:username:

aspnetsqlmembershipprovider:joe

Not impersonating:

ResourceProxy.Url:

http://ServerName/pwa/_vti_bin/psi/resource.asmx

User GUID to impersonate: 189350da-5669-44ad-886f-43ff3c9595dc

Impersonating 'aspnetsqlmembershipprovider:joe':

ResourceProxy.Url:

http://ServerName:56737/SharedServices1/PSI/Resource.asmx

Impersonated user Guid: 189350da-5669-44ad-886f-43ff3c9595dc

Press any key...

C:\Project\ImpersonationConsoleApp\bin\Debug>

The GUID obtained by the impersonated user is the same as that obtained by the application user. This shows that the impersonated user successfully called GetCurrentUserUid through the SSP path to the PSI.

Example

The following code includes content of the Program.cs and ResourceDerived.cs files in the download. You must generate the ResProxy.cs file using wsdl.exe, as previously described in Procedure 2.

/* Code for Program.cs */

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Web.Services.Protocols;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace Microsoft.SDK.Project.Samples.Impersonation
{
    class Program
    {
        // During impersonation, use URLs for the PSI Web services in the Shared Services Provider.
        // The Shared Services Provider URLs go directly to the PSI, not through Project Web Access.
        private const string PSI_RESOURCE_SSP = 
            "http://ServerName:56737/SSPName/PSI/Resource.asmx";

        // Before impersonation, use the PSI through Project Web Access.
        private const string PROJECT_SERVER_URI = "http://ServerName/ProjectServerName";
        private const string RESOURCE_SERVICE_PATH = "/_vti_bin/psi/resource.asmx";

        // GUID of the Project Web Access site in Windows SharePoint Services.  **Change to your value**
        private const string PWA_SITE_GUID = "44c4ae03-16c2-4618-bf9a-12643006c5be";

        static ResourceDerived resProxyBySSP;

        static void Main(string[] args)
        {
            Console.WriteLine("Enter a valid Project Server user account, such as");
            Console.WriteLine(@"domain\username or aspnetsqlmembershipprovider:username:");
            
            string userAccount = Console.ReadLine();

            try
            {
                resProxyBySSP = new ResourceDerived();

                // To use the following code, the application must run on the Project Web 
                // Access computer. Set references to the Microsoft.SharePoint and 
                // Microsoft.SharePoint.Search assemblies on the same computer.
                // Microsoft.SharePoint.SPSite site = new SPSite(PROJECT_SERVER_URI);
                // Guid siteId = site.ID; 

                // Otherwise, hard-code the site ID.
                Guid siteId = new Guid(PWA_SITE_GUID);
                resProxyBySSP.Credentials = System.Net.CredentialCache.DefaultCredentials;

                // Get the GUID of the user to impersonate. 
                Guid userGuid = GetResourceUid(userAccount);
                Console.WriteLine("User GUID to impersonate: " + userGuid.ToString());

                // You can also hard-code the user's GUID. Get the user's GUID from the Published   
                // database or from the System Identification Data section on the Manage User page  
                // in Project Web Access. For example:
                //    Guid userGuid = new Guid("0154dd59-13fc-4f50-908e-5f24fa6ef785");

                // To use the following code, the application must run on the Project Server computer 
                // where Windows SharePoint Services is installed.
                //
                // Microsoft.SharePoint.SPSite site = new SPSite(PROJECT_SERVER_URI);
                // Guid siteId = site.ID;

                // You can hard-code the siteId. Get the siteID in one of the following two ways:
                //    a. Open the SharePoint ContentDB for the SSP. Open the Webs table and  
                //       get the SiteId where the FullUrl value is the name of the 
                //       Project Web Access instance, for example, ProjectServerName. 
                //    b. Open the Manage Project Web Access Sites page in the SSP Admin site,
                //       click the drop-down list for the site and then click Edit. The siteID 
                //       is the GUID of the ID option in the URL, for example:
                //       http://ServerName:19466/ssp/admin/_layouts/createpwa.aspx?task=Edit&id=44c4ae03-16c2-4618-bf9a-12643006c5be
                // 
                Guid siteId = new Guid(PWA_SITE_GUID);

                bool isWindowsUser = true;
                if (userAccount.Contains("aspnetsqlmembershipprovider")) 
                    isWindowsUser = false;

                ResourceDerived.SetImpersonationContext(isWindowsUser, userAccount, 
                    userGuid, Guid.Empty, siteId, "1033");
                
                // To get the GUID of the user we are impersonating, 
                // call GetCurrentUserUid in the Resource Web service.
                Guid impersonatedUserGuid = resProxyBySSP.GetCurrentUserUid();

                Console.WriteLine(string.Format("\nImpersonating '{0}':\n\tResourceProxy.Url:\n\t{1}", 
                    userAccount, resProxyBySSP.Url));

                Console.Write("\nImpersonated user GUID: ");
                Console.WriteLine(impersonatedUserGuid.ToString());
            }
            catch (SoapException ex)
            {
                ExceptionHandlers.HandleSoapException(ex);
            }
            catch (WebException ex)
            {
                ExceptionHandlers.HandleWebException(ex);
            }
            catch (Exception ex)
            {
                ExceptionHandlers.HandleException(ex);
            }
            finally
            {
                ExceptionHandlers.ResetConsole();
            }
            Console.Write("Press any key...");
            Console.ReadKey();
        }


        /// <summary>
        /// Returns the GUID for a Project Server account name. 
        /// </summary>
        /// <param name="ntAccount"></param>
        /// <returns></returns>
        private static Guid GetResourceUid(String accountName)
        {
            Resource resProxyByPWA = new Resource();
            resProxyByPWA.Credentials = CredentialCache.DefaultCredentials;
            // Before impersonation, use the Project Web Access URL.
            resProxyByPWA.Url = PROJECT_SERVER_URI + RESOURCE_SERVICE_PATH;

            ResourceDataSet rds = new ResourceDataSet();

            // Filter for the account name, which can be a Windows or Project Server account.
            PSLibrary.Filter filter = new PSLibrary.Filter();
            filter.FilterTableName = rds.Resources.TableName;

            PSLibrary.Filter.Field accountField =
                new PSLibrary.Filter.Field(rds.Resources.TableName, 
                    rds.Resources.WRES_ACCOUNTColumn.ColumnName);
            filter.Fields.Add(accountField);

            PSLibrary.Filter.FieldOperator op =
                new PSLibrary.Filter.FieldOperator(
                    PSLibrary.Filter.FieldOperationType.Equal, 
                    rds.Resources.WRES_ACCOUNTColumn.ColumnName, accountName);
            filter.Criteria = op;

            Console.WriteLine("\nNot impersonating:\n\tResourceProxy.Url:\n\t" + resProxyByPWA.Url);
            rds = (ResourceDataSet)(resProxyByPWA.ReadResources(filter.GetXml(), false));

            // Return the account GUID 
            return (Guid)rds.Resources.Rows[0]["RES_UID"];
        }
    }

    class ExceptionHandlers
    {

        public static void HandleSoapException(SoapException ex)
        {
            PSLibrary.PSClientError error = new PSLibrary.PSClientError(ex);
            PSLibrary.PSErrorInfo[] errors = error.GetAllErrors();
            string errMess = "==============================\r\nError: \r\n";
            for (int i = 0; i < errors.Length; i++)
            {
                errMess += "\n" + ex.Message.ToString() + "\r\n";
                errMess += "".PadRight(30, '=') + "\r\nPSCLientError Output:\r\n \r\n";
                errMess += errors[i].ErrId.ToString() + "\n";

                for (int j = 0; j < errors[i].ErrorAttributes.Length; j++)
                {
                    errMess += "\r\n\t" + errors[i].ErrorAttributeNames()[j] + ": "
                       + errors[i].ErrorAttributes[j];
                }
                errMess += "\r\n".PadRight(30, '=');
            }
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("SOAP Exception: " + errMess);
        }

        public static void HandleWebException(WebException ex)
        {
            string errMess = ex.Message.ToString() +
               "\n\nLog on, or check that Project Server is running.";
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Web Exception: " + errMess);
        }

        public static void HandleException(Exception ex)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("System Exception: " + ex.Message);
            Console.WriteLine(ex.StackTrace);
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("Check the user account name.");
        }

        public static void ResetConsole()
        {
            Console.ResetColor();
        }
    }
}

/* Code for ResourceDerived.cs */
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Services;
using System.Net;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace Microsoft.SDK.Project.Samples.Impersonation
{
    class ResourceDerived:Resource
    {
        private static String contextString = String.Empty;

        protected override WebRequest GetWebRequest(Uri uri)
        {
            //Here we are overriding the GetWebRequest method and adding the two webRequest 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.DefaultCredentials;

                webRequest.Headers.Add("PjAuth", contextString);
                webRequest.Headers.Add("ForwardedFrom", "/_vti_bin/psi/resource.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;
        }
    }
}

The complete code sample and Visual Studio solution files are in the Project 2007 SDK Samples download.

Compiling the Code

The sample code in ImpersonationConsoleApp uses a hard-coded value for PWA_SITE_GUID, PSI_RESOURCE_SSP, and other Program class constants. Change the constants to match the values for your instance of Project Web Access and Project Server installation. Before you compile the application, generate a new ResProxy.cs file as described in Procedure 2.

See Also

Tasks

Walkthrough: Develop an Impersonation Web Application

Concepts

Using the ProjTool Test Application

Windows SharePoint Services Infrastructure for Project Server

Other Resources

Locale ID (LCID) Chart