As we all know Project server workflow are sequential workflow rather than state machine hence we cannot push workflow a stage back when required, a typical scenario is project is in execution stage but for business reasons it needs to go on hold, now it could be handled multiple ways, the way i handle it by moving it to a new stage, now in project life cycle it needs to be again resumed in execution stage, we don’t know how many times a project can go on hold and then resume back, and since we cannot put all these stages in (as no of times is not definite) hence we use an alternate solution of restarting the workflow using PSI (WCF method instead of ASMX) which includes using of skip to stage feature as well 🙂 so here goes the code which will help restarting the workflow and setting a stage
Note: on restarting the workflow you loose workflow history data from audit perspective, hence you need to have measures in place to retain those 🙂
Add all the required references especially the ProjectServerServices.dll as described in technet documentation :: http://msdn.microsoft.com/en-us/library/ee767691.aspx
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Net; using System.Security.Principal; // Required for the TokenImpersonationLevel enumeration. using System.ServiceModel; using System.ServiceModel.Channels; //using Microsoft.IdentityModel.Protocols.WSTrust; using System.Xml; using System.ServiceModel.Security; //using Microsoft.IdentityModel.SecurityTokenService; using System.IdentityModel.Tokens; using System.ServiceModel.Web; using PSLibrary = Microsoft.Office.Project.Server.Library; using System.Web.Services.Protocols; using System.Xml.Serialization; namespace WebServiceWCFTest { public partial class Form1 : Form { public Form1() { InitializeComponent(); } static private Uri pwaUri; // URI of Project Web App. static private string pwaUrl; // URL of Project Web App. private void ChangeStage_Click(object sender, EventArgs e) { pwaUrl = @"https://ServerName/pwa/"; pwaUri = new Uri(pwaUrl); //SetClientEndpoints(pwaUri); //CreateWSSWorkspace(pwaUri); const int MAXSIZE = 500000000; const string svcRouter = "_vti_bin/PSI/ProjectServer.svc"; pwaUrl = pwaUri.Scheme + Uri.SchemeDelimiter + pwaUri.Host + ":" + pwaUri.Port + pwaUri.AbsolutePath; // Create a binding for HTTP. BasicHttpBinding binding = null; if (pwaUri.Scheme.Equals(Uri.UriSchemeHttps)) { // Create binding for HTTPS. binding = new BasicHttpBinding(BasicHttpSecurityMode.Transport); } else { // Create binding for HTTP. binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly); } binding.Name = "basicHttpConf"; binding.SendTimeout = TimeSpan.MaxValue; Console.WriteLine("SendTimeout value:\n\t{0} days,\n\t{1} hours,\n\t{2} minutes,\n\t{3} seconds", binding.SendTimeout.Days.ToString(), binding.SendTimeout.Hours.ToString(), binding.SendTimeout.Minutes.ToString(), binding.SendTimeout.Seconds.ToString()); binding.MaxReceivedMessageSize = MAXSIZE; binding.ReaderQuotas.MaxNameTableCharCount = MAXSIZE; binding.MessageEncoding = WSMessageEncoding.Text; binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm; // The endpoint address is the ProjectServer.svc router for all public PSI calls. EndpointAddress address = new EndpointAddress(pwaUrl + svcRouter); SvcWorkflow.WorkflowClient WrkflowClient = new SvcWorkflow.WorkflowClient(binding,address); #region UID ICredentials credentials = new NetworkCredential("Username", "Password", "Domain"); #endregion WrkflowClient.ClientCredentials.Windows.ClientCredential = (NetworkCredential)credentials; WrkflowClient.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation; WrkflowClient.ChannelFactory.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; WrkflowClient.ChannelFactory.Credentials.Windows.AllowNtlm = true; WrkflowClient.Endpoint.Address = new EndpointAddress(pwaUrl + "_vti_bin/PSI/ProjectServer.svc"); using (OperationContextScope scope = new OperationContextScope(WrkflowClient.InnerChannel)) { // Disable Forms authentication, to enable Windows authentication. WebOperationContext.Current.OutgoingRequest.Headers.Remove("X-FORMS_BASED_AUTH_ACCEPTED"); WebOperationContext.Current.OutgoingRequest.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f"); try { SvcWorkflow.WorkflowDataSet WfDS = new SvcWorkflow.WorkflowDataSet(); Guid PrjUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX "); Guid JobID = Guid.NewGuid(); Guid EptUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX "); Guid NewStageUID = new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"); //New Stage ID // WfDS = WrkflowClient.ReadWorkflowStatus(PrjUID,true); WfDS.UpdateProjectWorkflows.AddUpdateProjectWorkflowsRow(JobID, PrjUID, EptUID, false, NewStageUID); WrkflowClient.QueueUpdateProjectWorkflows(WfDS); WrkflowClient.Close(); WrkflowClient = null; } catch (System.ServiceModel.FaultException fault) { string errAttributeName; string errAttribute; string errOut; string errMess = "".PadRight(30, '=') + "\r\n" + "Error details: " + "\r\n"; PSLibrary.PSClientError error = 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; } } MessageBox.Show(errMess); } } } } }