Calling SharePoint workflow instances programmatically from other SharePoint workflows (Justin Joyce)

by Justin Joyce

Overview:

Our client has a main list of items that require workflow. Some of these items will have to move along further in the workflow than others depending on certain field values and other conditions. Certain other types of items will only have to take a few steps, and then will exit the flow never to return.

Due to this requirement we decided to use two workflows on this list. The main workflow will always fire on item creation and will perform all the common actions required to initialize the new list items, including things like setting the unique proprietary item ID code, and collecting a few initial approvals on the item. The second workflow is to be called from the first workflow only when certain field values exist and the item is of a certain content type.

This presented a few interesting questions as to how we would accomplish this. One of our main areas of concern was finding the correct workflow to start on the item. We couldn’t be sure the instance name would always be what we had set it to, since the client could come in and change this if they chose, and since there were already two workflow templates attached to the list, we couldn’t use the index property either.

Implementation:

So what does identify a workflow template in a meaningful and unique way? Each workflow template has a base template ID number, in the form of a GUID, which gives it a distinct identity. The workflow templates attached to a particular list preserves this base template ID through .BaseID property. Ah ha! So now we can identify our target. What’s the easiest way to find the GUID with which to match to the BaseID property? In my case since I developed both the workflows and therefore have access to all the code I was simply able to go in to the project for the target workflow, and open up the Elements.XML file (If you don’t have the source you can still get this value from the workflow’s Elements.xml file by finding it in the hive : C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\FEATURES\FeatureName\WorkflowName\Elements.xml). Here you will see the Id property on the workflow node which serves as your base template Id.

clip_image001

So now we know what we’re looking, but how do we go about finding it? By using the best thing since sliced bread, obviously, LINQ.

The parents list we are running under has a property called WorkflowAssociations, as you have probably already guessed this is a collection of SPWorkflowAssociation objects that you can query with LINQ.

https://msdn.microsoft.com/EN-US/library/ms462802

Now one more little monkey wrench to watch out for here, it could be the case that the target workflow needs to be upgraded or changed at some point while still allowing for current instances of the workflow to live out their lives blissfully ignorant to the fact that they are being superseded by the latest and greatest. In this case you would go in to the workflow settings for the list and set the old workflow to “No New Instances” and then attach its new and improved baby brother. This presents a potential issue that we will have to account for in our LINQ query. Since these workflow instances would both have the same base template Id, they would both be considered valid to fulfill the criteria of the following query:

 Guid svpwfid = new Guid("4e126fc9-6b12-1212-8c21-21120f264bad");
var wfa = (from SPWorkflowAssociation spwfa in workflowProperties.Item.ParentList.WorkflowAssociations
           where spwfa.BaseId == svpwfid
           select spwfa).FirstOrDefault();

Now looking at the members for the SPWorkflowAssociation class, nothing jumps out at you with a property name like NoNewInstances, or something to that effect.

https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.workflow.spworkflowassociation_members.aspx

However there is the Enabled property. When you set a workflow attached to a list to No New Instances, the Enabled property will be False. So with a minor tweak to our LINQ query, we can make sure that the only result returned by it is a workflow association template that both matches our base template Id and is allowed to start new instances on that particular list.

 Guid svpwfid = new Guid("4e126fc9-6b12-1212-8c21-21120f264bad");
var wfa = (from SPWorkflowAssociation spwfa in workflowProperties.Item.ParentList.WorkflowAssociations
           where spwfa.BaseId == svpwfid && spwfa.Enabled == true
           select spwfa).FirstOrDefault();

Now that we have our query in place to get the proper workflow association template, we can instantiate the SPWorkflowManager object for the site, and pass it along our workflow association, the association data, our list item, and the bool flag for the auto start property through its StartWorkflow() method.

Full implementation would look like this:

 Guid svpwfid = new Guid("4e126fc9-6b12-1212-8c21-21120f264bad");
var wfa = (from SPWorkflowAssociation spwfa in workflowProperties.Item.ParentList.WorkflowAssociations
           where spwfa.BaseId == svpwfid && spwfa.Enabled == true
           select spwfa).FirstOrDefault();

if (wfa != null)
{
    SPWorkflowManager wfMan = workflowProperties.Item.ParentList.ParentWeb.Site.WorkflowManager;
    wfMan.StartWorkflow(workflowProperties.Item, wfa, wfa.AssociationData, true);
}
else
{
    // could not find workflow template.
}

Summary:

And that’s how you find and start a workflow, from another workflow. This code could be easily tweaked to run in any other type of application related to SharePoint, it just might take a little more work to get the list, list item, and site, since you will not have access to the workflow context.

Justin Joyce