Project.CreateProjectFromTemplate method

Creates a project from a specified template. The new project has the specified project name.

Namespace:  WebSvcProject
Assembly:  ProjectServerServices (in ProjectServerServices.dll)

Syntax

'Declaration
<SoapDocumentMethodAttribute("https://schemas.microsoft.com/office/project/server/webservices/Project/CreateProjectFromTemplate", RequestNamespace := "https://schemas.microsoft.com/office/project/server/webservices/Project/",  _
    ResponseNamespace := "https://schemas.microsoft.com/office/project/server/webservices/Project/",  _
    Use := SoapBindingUse.Literal, ParameterStyle := SoapParameterStyle.Wrapped)> _
Public Function CreateProjectFromTemplate ( _
    templateUid As Guid, _
    projectName As String _
) As Guid
'Usage
Dim instance As Project
Dim templateUid As Guid
Dim projectName As String
Dim returnValue As Guid

returnValue = instance.CreateProjectFromTemplate(templateUid, _
    projectName)
[SoapDocumentMethodAttribute("https://schemas.microsoft.com/office/project/server/webservices/Project/CreateProjectFromTemplate", RequestNamespace = "https://schemas.microsoft.com/office/project/server/webservices/Project/", 
    ResponseNamespace = "https://schemas.microsoft.com/office/project/server/webservices/Project/", 
    Use = SoapBindingUse.Literal, ParameterStyle = SoapParameterStyle.Wrapped)]
public Guid CreateProjectFromTemplate(
    Guid templateUid,
    string projectName
)

Parameters

  • templateUid
    Type: System.Guid

    The GUID of the project template.

Return value

Type: System.Guid
The GUID of the created project.

Remarks

CreateProjectFromTemplate creates the new project in the Draft datatables. The current user must have both of the permissions specified in the Permissions table.

The CreateProjectFromTemplate method does persist any formatting information in a project template, such as timescale or font format. You can set formatting information for a project with Project Professional; formatting is not available in the public PSI methods or datasets. CreateProjectFromTemplate gets a ProjectDataSet from the project template in the Draft database, creates a new ProjectDataSet, and changes the project summary task name to the requested project name. It then adds all of the DataTable rows to the new ProjectDataSet, except assignment dates, and then creates a new project with a different GUID.

If the template includes tasks that have notes, the task notes do not show when you create a new project by using CreateProjectFromTemplate and then open the project in Microsoft Office Project Professional. You can use Project Professional to create a template that contains task notes, and publish the project template. The MSP_TASKS table in the Published database includes the TASK_RTF_NOTES column, which has data for the template. After you programmatically create and save a new project based on that template, the TASK_RTF_NOTES column contains text data for the task notes, not RTF (Rich Text Format) data.

The problem is that TASK_RTF_NOTES is of data type image for RTF data. The PSI web services in the Project Server service application cannot handle RTF data. To add task notes in projects that are programmatically created on Project Server from a template, you must directly access the MSP_TASKS table to do the following:

  • Add the RTF data to the TASK_RTF_NOTES column for the specific task.

  • Set the TASKS_HAS_NOTES column to 1 (true).

Note

Currently no method is available to programmatically add the task notes in a template to a project created from that template.

You cannot use the Project Server Interface (PSI) to create local custom fields in projects. However, the PSI does support editing local custom field values on tasks, resources, and assignments.

View settings, such as added fields, are not copied into the new project from the template.

Project Server Permissions

Permission

Description

NewProject

Allows a user to create a new project. Global permission.

OpenProjectTemplate

Allows a user to open project templates. Global permission.

Examples

The following example creates a template, finds that template by name, and checks the project name for characters that are not valid. The example creates a project based on that template, and then publishes the project. It is necessary to create a template because Project Server does not have any default templates that are guaranteed to be in place.

Typically, you would present a list of templates and their unique IDs, and use that to choose the desired template. In some cases, you might want to find a project or template by name. That is demonstrated here. You must have the exact name of the project to find it.

For critical information about running this code sample, see Prerequisites for WCF-based code samples in Project 2013.

using System;
using System.Text;
using System.Xml;
using System.ServiceModel;
using PSLibrary = Microsoft.Office.Project.Server.Library;

namespace CreateProjectFromTemplate
{
    class Program
    {
        private const string ENDPOINT_PROJECT = "basicHttp_Project";
        private const string ENDPOINT_QUEUESYSTEM = "basicHttp_QueueSystem";
        private static SvcProject.ProjectClient projectClient;
        private static SvcQueueSystem.QueueSystemClient queueSystemClient;

        static void Main(string[] args)
        {
            try
            {
                ConfigClientEndpoints(ENDPOINT_PROJECT);
                ConfigClientEndpoints(ENDPOINT_QUEUESYSTEM);

                // Create a template to use. Normally, you would already have a template 
                // stored in PWA that you would use, but this example creates a template.

                Console.WriteLine("Creating template");
                SvcProject.ProjectDataSet templateDs = new SvcProject.ProjectDataSet();
                SvcProject.ProjectDataSet.ProjectRow templateRow = templateDs.Project.NewProjectRow();
                templateRow.PROJ_UID = Guid.NewGuid();
                templateRow.PROJ_NAME = "Its a wonderful template! "
                    + DateTime.Now.ToShortTimeString().Replace(":", "-");
                templateRow.WPROJ_DESCRIPTION = "Temporary template for use in CreateProjectFromTemplate example.";
                templateRow.PROJ_TYPE = (int)PSLibrary.Project.ProjectType.Template;

                if (IsNameValid(templateRow.PROJ_NAME))
                {
                    templateDs.Project.AddProjectRow(templateRow);
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("\nThe project name contains characters that are not valid:" 
                                      + "\n\t{0}", templateRow.PROJ_NAME);
                    QuitTheApp();
                }

                // Add two tasks to the template.
                int numTasks = 2;
                SvcProject.ProjectDataSet.TaskRow task = null;

                for (int i = 0; i < numTasks; i++)
                {
                    task = templateDs.Task.NewTaskRow();
                    task.PROJ_UID = templateRow.PROJ_UID;
                    task.TASK_UID = Guid.NewGuid();
                    task.TASK_DUR_FMT = (int)PSLibrary.Task.DurationFormat.Day;
                    task.TASK_DUR = 4800; // The task duration is 8 hours.
                    task.TASK_NAME = "T" + (i + 1).ToString() + " in template";
                    task.TASK_START_DATE = System.DateTime.Now.AddDays(i + 1);
                    templateDs.Task.AddTaskRow(task);
                }

                // Write the new template information to the database.
                Console.WriteLine("\n\nSaving template to database");
                Guid jobId = Guid.NewGuid();
                DateTime startTime = DateTime.Now;

                projectClient.QueueCreateProject(jobId, templateDs, false);

                // Wait for the Project Server Queuing System to create the project.
                if (Helpers.WaitForQueue(SvcQueueSystem.QueueMsgType.ProjectCreate,
                    queueSystemClient, startTime))
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("\nTemplate created:\n\t{0}", templateRow.PROJ_NAME);
                    Console.ResetColor();
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("\nThe template was not created:\n\t{0}.\nThe queue wait time exceeded 30 seconds",
                        templateRow.PROJ_NAME);
                    Console.ResetColor();
                    QuitTheApp();
                }

                // Find the template by name.
                //    You could just use the GUID to create the project from a template, 
                //    but this example shows how to get the GUID from the template name.
                // Note: If you have a template on the enterprise server already, you can use
                // the ReadProjectStatus method to get a list of published templates.
                //    projectSvc.ReadProjectStatus(Guid.Empty, SvcProject.DataStoreEnum.PublishedStore, 
                //                                 String.Empty, (int) PSLibrary.Project.ProjectType.Template);
                Console.WriteLine("Finding the template by name");
                SvcProject.ProjectDataSet readTemplateDs = projectClient.ReadProjectStatus(Guid.Empty,
                    SvcProject.DataStoreEnum.WorkingStore, templateRow.PROJ_NAME,
                    (int)PSLibrary.Project.ProjectType.Template);

                // Name the project.
                string projectName = "Created from " + readTemplateDs.Project[0].PROJ_NAME
                    + " at " + DateTime.Now.ToShortTimeString().Replace(":", "-");

                // Create the new project on the server and get its GUID.
                Console.WriteLine("Create the new project from the template");
                Guid newProjectUid = projectClient.CreateProjectFromTemplate(readTemplateDs.Project[0].PROJ_UID,
                                                                             projectName);
                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("Project created:\n\t{0}\n\t{1}", newProjectUid.ToString(), projectName);
                Console.ResetColor();

                // Publish the project, to make it visible in PWA.
                jobId = Guid.NewGuid();
                projectClient.QueuePublish(jobId, newProjectUid, true, string.Empty);
                startTime = DateTime.Now;

                if (Helpers.WaitForQueue(SvcQueueSystem.QueueMsgType.ProjectPublish,
                                         queueSystemClient, startTime))
                {
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("Project published");
                    Console.ResetColor();
                }
                else
                {
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("\nThe project was not published.\nThe queue wait time exceeded 30 seconds");
                    Console.ResetColor();
                }

            }
            catch (FaultException fault)
            {
                // Use the WCF FaultException, because the ASMX SoapException does not 
                // exist in a WCF-based application.
                WriteFaultOutput(fault);
            }
            finally
            {
                QuitTheApp();
            }
        }

        // Check the project name for invalid characters.
        private static bool IsNameValid(string projName)
        {
            bool result = true;
            char[] badChars = PSLibrary.Project.InvalidCharacters();

            Console.WriteLine("Check project name for Project.InvalidCharacters:");
            Console.ForegroundColor = ConsoleColor.Yellow;
            foreach (char c in badChars)
            {
                Console.Write(c);
                Console.Write(" ");
            }
            Console.ResetColor();

            // The name is not valid if it is empty or contains leading or trailing white space.
            if (String.IsNullOrEmpty(projName) || String.Compare(projName, projName.Trim(), StringComparison.Ordinal) != 0)
            {
                return false;
            }

            // The name is also not valid if it contains one of the invalid characters.
            if (badChars != null)
            {
                if (projName.IndexOfAny(badChars) != -1)
                {
                    return false;
                }
            }
            return result;
        }

        // Extract a PSClientError object from the WCF FaultException object, and
        // then display the exception details and each error in the PSClientError stack.
        private static void WriteFaultOutput(FaultException fault)
        {
            string errAttributeName;
            string errAttribute;
            string errOut;
            string errMess = "".PadRight(30, '=') + "\r\n"
                + "Error details: " + "\r\n";

            PSLibrary.PSClientError error = Helpers.GetPSClientError(fault, out errOut);
            errMess += errOut;

            PSLibrary.PSErrorInfo[] errors = error.GetAllErrors();
            PSLibrary.PSErrorInfo thisError;

            for (int i = 0; i < errors.Length; i++)
            {
                thisError = errors[i];
                errMess += "\r\n".PadRight(30, '=') + "\r\nPSClientError output:\r\n";
                errMess += thisError.ErrId.ToString() + "\n";

                for (int j = 0; j < thisError.ErrorAttributes.Length; j++)
                {
                    errAttributeName = thisError.ErrorAttributeNames()[j];
                    errAttribute = thisError.ErrorAttributes[j];
                    errMess += "\r\n\t" + errAttributeName
                        + ": " + errAttribute;
                }
            }
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(errMess);
            Console.ResetColor();
        }


        // Use the endpoints defined in app.config to configure the client.
        private static void ConfigClientEndpoints(string endpt)
        {
            if (endpt == ENDPOINT_PROJECT)
                projectClient = new SvcProject.ProjectClient(endpt);
            else if (endpt == ENDPOINT_QUEUESYSTEM)
                queueSystemClient = new SvcQueueSystem.QueueSystemClient(endpt);
        }

        // Quit the application.
        private static void QuitTheApp()
        {
            Console.ResetColor();
            Console.WriteLine("\nPress any key to exit...");
            Console.ReadKey(true);
            Environment.Exit(1);
        }
    }

    // Helper methods: WaitForQueue and GetPSClientError.
    class Helpers
    {
        // Wait for the queue jobs to complete.
        public static bool WaitForQueue(SvcQueueSystem.QueueMsgType jobType,
            SvcQueueSystem.QueueSystemClient queueSystemClient,
            DateTime startTime)
        {
            const int MAX_WAIT = 30;    // Maximum wait time, in seconds.
            int numJobs = 1;            // Number of jobs in the queue.
            bool completed = false;     // Queue job completed.
            SvcQueueSystem.QueueStatusDataSet queueStatusDs =
                new SvcQueueSystem.QueueStatusDataSet();

            int timeout = 0;            // Number of seconds waited.
            Console.Write("Waiting for job: {0} ", jobType.ToString());

            SvcQueueSystem.QueueMsgType[] messageTypes = { jobType };
            SvcQueueSystem.JobState[] jobStates = { SvcQueueSystem.JobState.Success };

            while (timeout < MAX_WAIT)
            {
                System.Threading.Thread.Sleep(1000);    // Sleep one second.

                queueStatusDs = queueSystemClient.ReadMyJobStatus(
                    messageTypes,
                    jobStates,
                    startTime,
                    DateTime.Now,
                    numJobs,
                    true,
                    SvcQueueSystem.SortColumn.QueuePosition,
                    SvcQueueSystem.SortOrder.LastOrder);

                timeout++;
                Console.Write(".");
            }
            Console.WriteLine();

            if (queueStatusDs.Status.Count == numJobs)
                completed = true;
            return completed;
        }

        /// <summary>
        /// Extract a PSClientError object from the ServiceModel.FaultException,
        /// for use in output of the GetPSClientError stack of errors.
        /// </summary>
        /// <param name="e"></param>
        /// <param name="errOut">Shows that FaultException has more information 
        /// about the errors than PSClientError has. FaultException can also contain 
        /// other types of errors, such as failure to connect to the server.</param>
        /// <returns>PSClientError object, for enumerating errors.</returns>
        public static PSLibrary.PSClientError GetPSClientError(FaultException e,
                                                               out string errOut)
        {
            const string PREFIX = "GetPSClientError() returns null: ";
            errOut = string.Empty;
            PSLibrary.PSClientError psClientError = null;

            if (e == null)
            {
                errOut = PREFIX + "Null parameter (FaultException e) passed in.";
                psClientError = null;
            }
            else
            {
                // Get a ServiceModel.MessageFault object.
                var messageFault = e.CreateMessageFault();

                if (messageFault.HasDetail)
                {
                    using (var xmlReader = messageFault.GetReaderAtDetailContents())
                    {
                        var xml = new XmlDocument();
                        xml.Load(xmlReader);

                        var serverExecutionFault = xml["ServerExecutionFault"];
                        if (serverExecutionFault != null)
                        {
                            var exceptionDetails = serverExecutionFault["ExceptionDetails"];
                            if (exceptionDetails != null)
                            {
                                try
                                {
                                    errOut = exceptionDetails.InnerXml + "\r\n";
                                    psClientError =
                                        new PSLibrary.PSClientError(exceptionDetails.InnerXml);
                                }
                                catch (InvalidOperationException ex)
                                {
                                    errOut = PREFIX + "Unable to convert fault exception info ";
                                    errOut += "a valid Project Server error message. Message: \n\t";
                                    errOut += ex.Message;
                                    psClientError = null;
                                }
                            }
                            else
                            {
                                errOut = PREFIX + "The FaultException e is a ServerExecutionFault, "
                                    + "but does not have ExceptionDetails.";
                            }
                        }
                        else
                        {
                            errOut = PREFIX + "The FaultException e is not a ServerExecutionFault.";
                        }
                    }
                }
                else // No detail in the MessageFault.
                {
                    errOut = PREFIX + "The FaultException e does not have any detail.";
                }
            }
            errOut += "\r\n" + e.ToString() + "\r\n";
            return psClientError;
        }
    }
}

When you run the CreateProjectFromTemplate example, the console window shows the following output:

Creating template
Check project name for Project.InvalidCharacters:
' # : ; < > / | ? \ . * " ~ % & { } +

Saving template to database
Waiting for job: ProjectCreate ..............................

Template created:
        Its a wonderful template! 3-50 PM
Finding the template by name
Create the new project from the template
Project created:
        b9d0272e-62db-e111-b1ce-00155d146f2c
        Created from Its a wonderful template! 3-50 PM at 3-50 PM
Waiting for job: ProjectPublish ..............................
Project published

Press any key to exit...

To see the action of the FaultException handler, change the code that names the template and checks for invalid characters. The following code introduces the '+' character in the name, and comments out the call to the IsNameValid method:

                templateRow.PROJ_NAME = "Its a wonderful template! "
                    + DateTime.Now.ToShortTimeString().Replace(":","+");
                templateRow.WPROJ_DESCRIPTION = "Temporary template for use in CreateProjectFromTemplate example.";
                templateRow.PROJ_TYPE = (int)PSLibrary.Project.ProjectType.Template;

                templateDs.Project.AddProjectRow(templateRow);
                //if (IsNameValid(templateRow.PROJ_NAME))
                //{
                //    templateDs.Project.AddProjectRow(templateRow);
                //}
                //else
                //{
                //    Console.ForegroundColor = ConsoleColor.Red;
                //    Console.WriteLine("\nThe project name contains characters that are not valid:" 
                //                      + "\n\t{0}", templateRow.PROJ_NAME);
                //    QuitTheApp();
                //}

See also

Reference

Project class

Project members

WebSvcProject namespace