Programmatically reading SharePoint Designer Workflow XOML files at run time.

I’m working on a project where I need to read the actual XOML file for a SharePoint Designer workflow at runtime during a few of my custom activities I’m developing.  The reason, is that I have to enumerate and inspect all the workflow variables the end user has defined.  The only way to do this, is to actually peek back into the XOML file for the workflow.

After poking around and searching for a few days without any luck, I ran across this posting from Joe Zamora: “Query/enumerate all declarative workflow templates in an SPWeb and programmatically read the XOML files

Perfect! I thought.  This was exactly what I was looking for. I took his sample, cleaned it up a bit, and wrote a method to get to the XOML file. 

 

public static string 
  GetXOMLFromWorkflowInstance(SPListItem item, Guid wfInstanceId)
  {
    SPWorkflow wf = new SPWorkflow(item, wfInstanceId);
    SPWorkflowAssociation assoc = 
      item.ParentList.WorkflowAssociations[wf.AssociationId];

    XmlDocument doc = new XmlDocument();
    doc.Load(new StringReader(assoc.SoapXml));

    XmlNode node = doc.SelectSingleNode("/WorkflowTemplate");
    XmlAttribute attribute = node.Attributes["Name"];
    string wfName = attribute.Value.Replace(" ", "%20");            
    string webRelativeFolder = "Workflows/" + wfName;            
    string xomlFileName = wfName + ".xoml";

    SPFolder wfFolder = assoc.ParentWeb.GetFolder(webRelativeFolder);
    SPFile xomlFile = wfFolder.Files[xomlFileName];

    doc = new XmlDocument();
    using (Stream xomlStream = xomlFile.OpenBinaryStream())
    {
      doc.Load(xomlStream);
    }
    return doc.OuterXml;
  }

 

As you can see the workflow’s folder and file names, when initially saved, utilize the name you defined in SharePoint Designer. So, if I create a workflow and call it “My Cool Workflow”, then a folder get’s created in the Workflows folder of the site with the name “My Cool Workflow”, and it’s resulting .xoml file is stored within that folder with the name.  So it’s pretty easy to construct the path to the xoml file from that.

All was grand for about 1 hour.

And then….

While demonstrating the latest build to a customer, we renamed the workflow between a test, and everything broke.  After a bit of debugging, I figured out what the problem was.

The method above depends on the “Name” attribute within the WorkflowTemplate element to derive it’s path to the underlying XOML file in the site.  The problem, as I mentioned above, revealed itself when we renamed the workflow.  The name attribute could no longer be relied on as it possibly will NOT match the underlying workflow folder container, etc.  The workflow folder/.xoml etc will always keep the original name.  So, taking the example of the workflow originally being named “My Cool Workflow” and changing it to anything else doesn’t change the fact that the workflow will forever be stored in a folder called “My Cool Workflow”.

So, back to the drawing board for a few days trying once again to figure out HOW to get back to the XOML file itself.  As Joe found, the Object Model just doesn’t do a very good job of actually letting you determine that (that I know of), but then I noticed something in the SoapXml of the workflow association.  Aha!   It contains an Instantiation_FormURI element that has the path to the xsn of the instantiation form.  Well hey!  We can just use that, replace .xsn with .xoml and we’re back in the game.

 

public static string 
  GetXOMLFromWorkflowInstance(SPListItem item, Guid wfInstanceId)
  {
    SPWorkflow wf = new SPWorkflow(item, wfInstanceId);
    SPWorkflowAssociation assoc = 
      item.ParentList.WorkflowAssociations[wf.AssociationId];

    XmlDocument doc = new XmlDocument();
    doc.Load(new StringReader(assoc.SoapXml));

    XmlNode node = 
     doc.SelectSingleNode("/WorkflowTemplate/Metadata/Instantiation_FormURI/string");

    string xsnPath = node.InnerText;
    xsnPath = xsnPath.Replace(" ", "%20");

    string webRelativeFolder = 
      xsnPath.Substring(0, xsnPath.LastIndexOf('/'));
    string xomlFileName = 
      xsnPath.Substring(xsnPath.LastIndexOf('/') + 1);
    xomlFileName = 
      xomlFileName.Replace(".xsn", ".xoml");

    SPFolder wfFolder = assoc.ParentWeb.GetFolder(webRelativeFolder);
    SPFile xomlFile = wfFolder.Files[xomlFileName];

    doc = new XmlDocument();
    using (Stream xomlStream = xomlFile.OpenBinaryStream())
    {
      doc.Load(xomlStream);
    }
    return doc.OuterXml;
  }

The only concern I had with this approach, is what if there wasn’t an instantiation form?  Well, it turns out that SharePoint Designer won’t ALLOW you to not have one, even if it doesn’t contain anything.  If you delete the instantiation form, then save the workflow again, it will just generating it one more time.  So we’re guaranteed to have an Instantiation_FormURI element within SPWorkflowAssociation.SoapXml.

Hope this helps someone else!

[Update: 07/08/2011]

Well apparently the mandatory instantiation form does NOT exist by default in SharePoint Designer 2007 workflows, so it currently is NOT a reliable way for it.

[Update: 07/20/2011]

For SharePoint Designer 2007 workflows, it looks like you can depend on the “InitiationUrl” attribute.

The updated method I wrote for 2007 looks similar to this:

 

public static string 
  GetXOMLFromWorkflowInstance(SPListItem item, Guid wfInstanceId)
  {
    SPWorkflow wf = new SPWorkflow(item, wfInstanceId);
    SPWorkflowAssociation assoc = 
      item.ParentList.WorkflowAssociations[wf.AssociationId];

    XmlDocument doc = new XmlDocument();
    doc.Load(new StringReader(assoc.SoapXml));

    XmlNode node = doc.SelectSingleNode("/WorkflowTemplate");

    string xsnPath = node.Attributes["InstantiationUrl"].Value;
    xsnPath = xsnPath.Replace(" ", "%20");

    string webRelativeFolder = 
      xsnPath.Substring(0, xsnPath.LastIndexOf('/'));
    string xomlFileName = 
      xsnPath.Substring(xsnPath.LastIndexOf('/') + 1);
    xomlFileName = xomlFileName.Replace(".aspx", ".xoml");

    SPFolder wfFolder = assoc.ParentWeb.GetFolder(webRelativeFolder);
    SPFile xomlFile = wfFolder.Files[xomlFileName];

    doc = new XmlDocument();
    using (Stream xomlStream = xomlFile.OpenBinaryStream())
    {
      doc.Load(xomlStream);
    }
    return doc.OuterXml;
  }

– Keith

2 Replies to “Programmatically reading SharePoint Designer Workflow XOML files at run time.”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s